我有关于数据框合并的问题。 我有两个数据框如下,
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
答案 0 :(得分:1)
查看可运行代码的帖子结尾。建议的解决方案在函数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)
是下面解决方案的主要思想。它之所以能提高性能
是因为测试两列之间的相等性要快得多
测试一列字符串是否是另一列字符串的子串,或者
是包含字符串列表的列的成员。
我上面说“差不多”的原因是因为还有另一个困难 -
在df1
上合并df2
和ID
后,某些行是无领的,例如bob,david
行:
ID name-group Leader location
1 bob,david
由于我们只想保留这些行,并且我们不想测试条件#1在这种情况下是否成立,我们需要区别对待这些行 - 不要扩展它们。 我们可以通过将无领导行与具有潜在领导者的行分开来处理这个问题(见下文)。
通过在df1
上合并df2
和ID
,可以轻松实施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)
会为lambda函数调用一次
行。如果dft
中有很多行,这可能会非常慢。
如果dft
中有很多行,我们可以先将dft
转换为更好
整齐的格式(PDF) - 放置每个
dft['name-group']
中的成员在其自己的行上。但首先,让我们将dft
分成2
sub-DataFrames,有领导者的那些,以及没有领导者的那些:
has_leader = pd.notnull(dft['leader'])
leaderless, leaders = dft.loc[~has_leader, :], dft.loc[has_leader, :]
现在将leaders
置于整齐的格式(每行一个成员):
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来确定做额外工作的成本是否会得到回报。
以下是df1
和df2
使用1000行较大数据帧的基准:
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
超过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()
答案 1 :(得分:0)
为什么不使用
Dft = pd.merge(df1,df2,how=‘left’,left_on = [‘ID’],right_on =[‘ID’])