在数组列上查找常见的 friends/interactions
Find common friends/interactions on array columns
我有一个结构如下的 DF:
test = pd.DataFrame({
'person_1': ['Frodo', 'Frodo', 'Gandalf']
,'person_2': ['Sam', 'Legolas', 'Legolas']
,'relations_person_1': [
['Gandalf', 'Sam', 'Legolas', 'Gollum', 'Sauron'],
['Gandalf', 'Sam', 'Legolas', 'Gollum', 'Sauron'],
['Bilbo', 'Frodo', 'Sauron', 'Sam']
]
,'relations_person_2': [
['Gandalf', 'Frodo', 'Gimli', 'Gollum'],
['Galadriel', 'Arwen', 'Gimli', 'Frodo'],
['Galadriel', 'Arwen', 'Gimli', 'Frodo'],
]
})
其中relations_person_1
和relations_person_2
分别是person_1
和person_2
的关系。
我需要找到“person_1”和“person_2”之间关系的通用名称。
我设法用下面的代码解决了这个问题
test['common_friends'] = test.apply(
lambda x: np.intersect1d(x.relations_person_1 ,x.relations_person_2)
,axis = 1)
test.head()
#output:
person_1 person_2 relations_person_1 relations_person_2 common_friends
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [Gandalf, Gollum]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [Frodo]
我的解决方案的问题是使用 apply
,在整个 DF 中使用它时速度非常慢。我想知道是否有更优化的方法来获得结果,也许避免使用 apply
或在数据中使用一些图形结构。
apply
在 pandas
中本质上较慢,而不是使用 apply
我们可以 zip
列然后在 [=17 的成员资格的列表理解测试中=] 在 relations_person_2
中使用 set
交集
test['common_friends'] = [list(set(x).intersection(y)) for x, y in
zip(test['relations_person_1'], test['relations_person_2'])]
结果
person_1 person_2 relations_person_1 relations_person_2 common_friends
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [Gollum, Gandalf]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [Frodo]
时间安排供参考
# Sample dataframe with 300000 rows generated for testing purpose
test = pd.concat([test] * 100000, ignore_index=True)
%%timeit
_ = [list(set(x).intersection(y)) for x, y in zip(test['relations_person_1'], test['relations_person_2'])]
# 262 ms ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
_ = test.apply(
lambda x: np.intersect1d(x.relations_person_1 ,x.relations_person_2)
,axis = 1)
# 19.3 s ± 1.51 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
简单地避免 apply
并使用列表理解和 set
交集,我们可以获得大约 73x
的性能提升
我会走不同的路线,以不同的方式表示关系。
我假设这是您最初使用的数据框:
>>> df
person relations_person
0 Frodo [Gandalf, Sam, Legolas, Gollum, Sauron]
1 Gandalf [Bilbo, Frodo, Sauron, Sam]
2 Sam [Gandalf, Frodo, Gimli, Gollum]
3 Legolas [Galadriel, Arwen, Gimli, Frodo]
如果没有,您可以随时使用:
>>> df = pd.concat([
... test[['person' + suffix, 'relations_person' + suffix]]\
... .rename(columns=lambda c: c.replace(suffix, ''))
... for suffix in ('_1', '_2')
... ]).drop_duplicates('person').reset_index(drop=True)
然后使用 explode
创建所有交互对:
>>> relations = df.explode('relations_person')
>>> relations
person relations_person
0 Frodo Gandalf
1 Frodo Sam
2 Frodo Legolas
3 Frodo Gollum
4 Frodo Sauron
5 Gandalf Bilbo
6 Gandalf Frodo
7 Gandalf Sauron
8 Gandalf Sam
9 Sam Gandalf
10 Sam Frodo
11 Sam Gimli
12 Sam Gollum
13 Legolas Galadriel
14 Legolas Arwen
15 Legolas Gimli
16 Legolas Frodo
为了获得类似格式的关系以便我们可以进行简单的合并,我们 stack
列:
>>> lookup = test[['person_1', 'person_2']].stack().to_frame('person').rename_axis(['row', 'col'])
>>> lookup
person
row col
0 person_1 Frodo
person_2 Sam
1 person_1 Frodo
person_2 Legolas
2 person_1 Gandalf
person_2 Legolas
现在很容易生成所有的关系:
>>> common = lookup.reset_index().merge(relations, how='left', on='person')
>>> common
row col person relations_person
0 0 person_1 Frodo Gandalf
1 0 person_1 Frodo Sam
2 0 person_1 Frodo Legolas
3 0 person_1 Frodo Gollum
4 0 person_1 Frodo Sauron
5 0 person_2 Sam Gandalf
6 0 person_2 Sam Frodo
7 0 person_2 Sam Gimli
8 0 person_2 Sam Gollum
9 1 person_1 Frodo Gandalf
10 1 person_1 Frodo Sam
11 1 person_1 Frodo Legolas
12 1 person_1 Frodo Gollum
13 1 person_1 Frodo Sauron
14 1 person_2 Legolas Galadriel
15 1 person_2 Legolas Arwen
16 1 person_2 Legolas Gimli
17 1 person_2 Legolas Frodo
18 2 person_1 Gandalf Bilbo
19 2 person_1 Gandalf Frodo
20 2 person_1 Gandalf Sauron
21 2 person_1 Gandalf Sam
22 2 person_2 Legolas Galadriel
23 2 person_2 Legolas Arwen
24 2 person_2 Legolas Gimli
25 2 person_2 Legolas Frodo
从那里每个关系出现在 test
数据框的一行中的次数:
>>> relation_counts = common.groupby('row')['relations_person'].value_counts()
>>> relation_counts
row relations_person
0 Gandalf 2
Gollum 2
Frodo 1
Gimli 1
Legolas 1
Sam 1
Sauron 1
1 Arwen 1
Frodo 1
Galadriel 1
Gandalf 1
Gimli 1
Gollum 1
Legolas 1
Sam 1
Sauron 1
2 Frodo 2
Arwen 1
Bilbo 1
Galadriel 1
Gimli 1
Sam 1
Sauron 1
最后根据每行至少出现两次的条目制作列表:
>>> test.join(relation_counts[relation_counts >= 2].groupby('row').agg(list).reindex(test.index, fill_value=[]))
person_1 person_2 relations_person_1 relations_person_2 relations_person
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [2, 2]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [2]
所以在一个浓缩形式中:
relcounts = test[['person_1', 'person_2']].stack().rename('person').reset_index()\
.merge(df.explode('relations_person'), on='person', how='left')\
.groupby('level_0')['relations_person'].value_counts()
test.join(relcounts[relcounts > 1].groupby(level=0).agg(list))
我有一个结构如下的 DF:
test = pd.DataFrame({
'person_1': ['Frodo', 'Frodo', 'Gandalf']
,'person_2': ['Sam', 'Legolas', 'Legolas']
,'relations_person_1': [
['Gandalf', 'Sam', 'Legolas', 'Gollum', 'Sauron'],
['Gandalf', 'Sam', 'Legolas', 'Gollum', 'Sauron'],
['Bilbo', 'Frodo', 'Sauron', 'Sam']
]
,'relations_person_2': [
['Gandalf', 'Frodo', 'Gimli', 'Gollum'],
['Galadriel', 'Arwen', 'Gimli', 'Frodo'],
['Galadriel', 'Arwen', 'Gimli', 'Frodo'],
]
})
其中relations_person_1
和relations_person_2
分别是person_1
和person_2
的关系。
我需要找到“person_1”和“person_2”之间关系的通用名称。
我设法用下面的代码解决了这个问题
test['common_friends'] = test.apply(
lambda x: np.intersect1d(x.relations_person_1 ,x.relations_person_2)
,axis = 1)
test.head()
#output:
person_1 person_2 relations_person_1 relations_person_2 common_friends
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [Gandalf, Gollum]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [Frodo]
我的解决方案的问题是使用 apply
,在整个 DF 中使用它时速度非常慢。我想知道是否有更优化的方法来获得结果,也许避免使用 apply
或在数据中使用一些图形结构。
apply
在 pandas
中本质上较慢,而不是使用 apply
我们可以 zip
列然后在 [=17 的成员资格的列表理解测试中=] 在 relations_person_2
中使用 set
交集
test['common_friends'] = [list(set(x).intersection(y)) for x, y in
zip(test['relations_person_1'], test['relations_person_2'])]
结果
person_1 person_2 relations_person_1 relations_person_2 common_friends
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [Gollum, Gandalf]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [Frodo]
时间安排供参考
# Sample dataframe with 300000 rows generated for testing purpose
test = pd.concat([test] * 100000, ignore_index=True)
%%timeit
_ = [list(set(x).intersection(y)) for x, y in zip(test['relations_person_1'], test['relations_person_2'])]
# 262 ms ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
_ = test.apply(
lambda x: np.intersect1d(x.relations_person_1 ,x.relations_person_2)
,axis = 1)
# 19.3 s ± 1.51 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
简单地避免 apply
并使用列表理解和 set
交集,我们可以获得大约 73x
我会走不同的路线,以不同的方式表示关系。 我假设这是您最初使用的数据框:
>>> df
person relations_person
0 Frodo [Gandalf, Sam, Legolas, Gollum, Sauron]
1 Gandalf [Bilbo, Frodo, Sauron, Sam]
2 Sam [Gandalf, Frodo, Gimli, Gollum]
3 Legolas [Galadriel, Arwen, Gimli, Frodo]
如果没有,您可以随时使用:
>>> df = pd.concat([
... test[['person' + suffix, 'relations_person' + suffix]]\
... .rename(columns=lambda c: c.replace(suffix, ''))
... for suffix in ('_1', '_2')
... ]).drop_duplicates('person').reset_index(drop=True)
然后使用 explode
创建所有交互对:
>>> relations = df.explode('relations_person')
>>> relations
person relations_person
0 Frodo Gandalf
1 Frodo Sam
2 Frodo Legolas
3 Frodo Gollum
4 Frodo Sauron
5 Gandalf Bilbo
6 Gandalf Frodo
7 Gandalf Sauron
8 Gandalf Sam
9 Sam Gandalf
10 Sam Frodo
11 Sam Gimli
12 Sam Gollum
13 Legolas Galadriel
14 Legolas Arwen
15 Legolas Gimli
16 Legolas Frodo
为了获得类似格式的关系以便我们可以进行简单的合并,我们 stack
列:
>>> lookup = test[['person_1', 'person_2']].stack().to_frame('person').rename_axis(['row', 'col'])
>>> lookup
person
row col
0 person_1 Frodo
person_2 Sam
1 person_1 Frodo
person_2 Legolas
2 person_1 Gandalf
person_2 Legolas
现在很容易生成所有的关系:
>>> common = lookup.reset_index().merge(relations, how='left', on='person')
>>> common
row col person relations_person
0 0 person_1 Frodo Gandalf
1 0 person_1 Frodo Sam
2 0 person_1 Frodo Legolas
3 0 person_1 Frodo Gollum
4 0 person_1 Frodo Sauron
5 0 person_2 Sam Gandalf
6 0 person_2 Sam Frodo
7 0 person_2 Sam Gimli
8 0 person_2 Sam Gollum
9 1 person_1 Frodo Gandalf
10 1 person_1 Frodo Sam
11 1 person_1 Frodo Legolas
12 1 person_1 Frodo Gollum
13 1 person_1 Frodo Sauron
14 1 person_2 Legolas Galadriel
15 1 person_2 Legolas Arwen
16 1 person_2 Legolas Gimli
17 1 person_2 Legolas Frodo
18 2 person_1 Gandalf Bilbo
19 2 person_1 Gandalf Frodo
20 2 person_1 Gandalf Sauron
21 2 person_1 Gandalf Sam
22 2 person_2 Legolas Galadriel
23 2 person_2 Legolas Arwen
24 2 person_2 Legolas Gimli
25 2 person_2 Legolas Frodo
从那里每个关系出现在 test
数据框的一行中的次数:
>>> relation_counts = common.groupby('row')['relations_person'].value_counts()
>>> relation_counts
row relations_person
0 Gandalf 2
Gollum 2
Frodo 1
Gimli 1
Legolas 1
Sam 1
Sauron 1
1 Arwen 1
Frodo 1
Galadriel 1
Gandalf 1
Gimli 1
Gollum 1
Legolas 1
Sam 1
Sauron 1
2 Frodo 2
Arwen 1
Bilbo 1
Galadriel 1
Gimli 1
Sam 1
Sauron 1
最后根据每行至少出现两次的条目制作列表:
>>> test.join(relation_counts[relation_counts >= 2].groupby('row').agg(list).reindex(test.index, fill_value=[]))
person_1 person_2 relations_person_1 relations_person_2 relations_person
0 Frodo Sam [Gandalf, Sam, Legolas, Gollum, Sauron] [Gandalf, Frodo, Gimli, Gollum] [2, 2]
1 Frodo Legolas [Gandalf, Sam, Legolas, Gollum, Sauron] [Galadriel, Arwen, Gimli, Frodo] []
2 Gandalf Legolas [Bilbo, Frodo, Sauron, Sam] [Galadriel, Arwen, Gimli, Frodo] [2]
所以在一个浓缩形式中:
relcounts = test[['person_1', 'person_2']].stack().rename('person').reset_index()\
.merge(df.explode('relations_person'), on='person', how='left')\
.groupby('level_0')['relations_person'].value_counts()
test.join(relcounts[relcounts > 1].groupby(level=0).agg(list))