データフレームのマージについて問題があります。次の2つのデータフレームがあります。
df1:
ID name-group status
1 bob,david good
2 CC,robben good
3 jack bad
df2:
ID leader location
2 robben JAPAN
3 jack USA
4 bob UK
結果をフローとして取得したい。
dft
ID name-group Leader location
1 bob,david
2 CC,robben Robben JAPAN
3 jack Jack USA
[リーダー]と[場所]は次の場合にマージされます
[leader] in df2 **IN** [name-group] of df1
&
[ID] of df2 **=** [ID] of df1
forループを試しましたが、時間コストが非常に高くなっています。この問題について何かアイデアはありますか?
ありがとう
実行可能なコードについては、投稿の最後を参照してください。提案された解決策は、関数にありusing_tidy
ます。
ここでの主な問題は、name-group
コンマで区切られた複数の名前が含まれていると、メンバーシップの検索が困難になることです。代わりに、のdf1
各メンバーがname-group
独自の行にある場合、メンバーシップのテストは簡単です。つまり、次のdf1
ようになっているとします。
ID name-group status
0 1 bob good
0 1 david good
1 2 CC good
1 2 robben good
2 3 jack bad
次に、単純にマージdf1
してdf2
オンにID
し、leader
等しい かどうかをテストできname-group
ます...ほぼ(以下の「ほぼ」理由を参照)。
置くdf1
きちんとフォーマット(でPDFは)以下の溶液中での主なアイデアです。パフォーマンスが向上する理由は、2つの列間の同等性のテストは、文字列の列が文字列の別の列のサブ文字列であるか、文字列のリストを含む列のメンバーであるかをテストするよりもはるかに高速であるためです。
上記で「ほぼ」と言った理由は、別の問題があるためです。マージしdf1
てdf2
からID
、次のような一部の行にはリーダーがありませんbob,david
。
ID name-group Leader location
1 bob,david
これらの行を保持したいだけで、この場合は基準#1が当てはまるかどうかをテストしたくないので、これらの行を別の方法で処理する必要があります。展開しないでください。この問題は、リーダーのない行を潜在的なリーダーのある行から分離することで処理できます(以下を参照)。
IDが併合することによって強制することは容易で一致していることを第二の基準、df1
及びdf2
上ID
:
dft = pd.merge(df1, df2, on='ID', how='left')
最初の基準は、にあることdft['leader']
ですdft['name-group']
。この基準は次のように表すことができます
In [293]: dft.apply(lambda x: pd.isnull(x['leader']) or (x['leader'] in x['name-group'].split(',')), axis=1)
Out[293]:
0 True
1 True
2 True
dtype: bool
ただし、を使用するdft.apply(..., axis=1)
と、行ごとに1回ラムダ関数が呼び出されます。に多くの行がある場合、これは非常に遅くなる可能性がありますdft
。
に多くの行がある場合はdft
、最初dft
に整頓された形式(PDF)に変換して、各メンバーをdft['name-group']
独自の行に配置することで、より良い結果を得ることができます。ただし、最初に、dft
リーダーのあるサブデータフレームとリーダーのないサブデータフレームの2つに分割しましょう。
has_leader = pd.notnull(dft['leader'])
leaderless, leaders = dft.loc[~has_leader, :], dft.loc[has_leader, :]
次にleaders
、整頓された形式(行ごとに1つのメンバー)を配置します。
member = leaders['name-group'].str.split(',', expand=True)
member = member.stack()
member.index = member.index.droplevel(1)
member.name = 'member'
leaders = pd.concat([member, leaders], axis=1)
このすべての作業の見返りは、基準#1を高速計算で表現できるようになったことです。
# this enforces criteria #1 (leader of df2 is in name-group of df1)
mask = (leaders['leader'] == leaders['member'])
leaders = leaders.loc[mask, :]
leaders = leaders.drop('member', axis=1)
望ましい結果は次のとおりです。
dft = pd.concat([leaderless, leaders], axis=0)
df1
きちんとしたフォーマットにするために、私たちはいくつかの作業をしなければなりませんでした。基準#1をより速く計算できるようにすることで、その余分な作業を行うコストが報われるかどうかを判断するためにベンチマークを行う必要があります。
ここでは1000行の大きめのデータフレーム使用したベンチマークであるdf1
とはdf2
:
In [356]: %timeit using_tidy(df1, df2)
100 loops, best of 3: 17.8 ms per loop
In [357]: %timeit using_apply(df1, df2)
10 loops, best of 3: 98.2 ms per loop
の行数が増えると、using_tidy
overの速度の利点が増えます。using_apply
pd.merge(df1, df2, on='ID', how='left')
ベンチマークの設定は次のとおりです。
import string
import numpy as np
import pandas as pd
df1 = pd.DataFrame({'name-group':['bob,david', 'CC,robben', 'jack'],
'status':['good','good','bad'],
'ID':[1,2,3]})
df2 = pd.DataFrame({'leader':['robben','jack','bob'],
'location':['JAPAN','USA','UK'],
'ID':[2,3,4]})
def using_apply(df1, df2):
dft = pd.merge(df1, df2, on='ID', how='left')
mask = dft.apply(lambda x: pd.isnull(x['leader']) or (x['leader'] in x['name-group'].split(',')), axis=1)
return dft.loc[mask, :]
def using_tidy(df1, df2):
# this enforces criteria #2 (the IDs are the same)
dft = pd.merge(df1, df2, on='ID', how='left')
# split dft into 2 sub-DataFrames, based on rows which have a leader and those which do not.
has_leader = pd.notnull(dft['leader'])
leaderless, leaders = dft.loc[~has_leader, :], dft.loc[has_leader, :]
# expand leaders so each member in name-group has its own row
member = leaders['name-group'].str.split(',', expand=True)
member = member.stack()
member.index = member.index.droplevel(1)
member.name = 'member'
leaders = pd.concat([member, leaders], axis=1)
# this enforces criteria #1 (leader of df2 is in name-group of df1)
mask = (leaders['leader'] == leaders['member'])
leaders = leaders.loc[mask, :]
leaders = leaders.drop('member', axis=1)
dft = pd.concat([leaderless, leaders], axis=0)
return dft
def make_random_str_array(letters=string.ascii_uppercase, strlen=10, size=100):
return (np.random.choice(list(letters), size*strlen)
.view('|U{}'.format(strlen)))
def make_dfs(N=1000):
names = make_random_str_array(strlen=4, size=10)
df1 = pd.DataFrame({
'name-group':[','.join(np.random.choice(names, size=np.random.randint(1,10), replace=False)) for i in range(N)],
'status':np.random.choice(['good','bad'], size=N),
'ID':np.random.randint(4, size=N)})
df2 = pd.DataFrame({
'leader':np.random.choice(names, size=N),
'location':np.random.randint(10, size=N),
'ID':np.random.randint(4, size=N)})
return df1, df2
df1, df2 = make_dfs()
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加