计算列表列中两个元素的出现次数
Posted
技术标签:
【中文标题】计算列表列中两个元素的出现次数【英文标题】:Count occurrence of two elements in column of list 【发布时间】:2018-11-20 23:03:46 【问题描述】:我已经为此苦苦挣扎了几天。我在网上阅读了很多,发现了一些类似的问题,例如:Pandas counting occurrence of list contained in column of lists 或 pandas: count string criteria across down rows 但在这种情况下都不能完全工作。
我有两个数据框:df1 由一列字符串组成。 df2 由一列列表组成(这些列表是 df1 中字符串的组合,一个列表中的每个元素都是唯一的)。
我想知道每个字符串组合中有多少个 df2 列表。那么,有多少列表有“a”和“b”作为元素?有多少列表以“a”和“c”作为元素等等。
这是 df1 的样子(简化):
df1 = pd.DataFrame("subject": ["a", "b", "c"])
df1
subject
0 a
1 b
3 c
这就是 df2 的样子(简化)。
df2 = pd.DataFrame("subject_list": [["a", "b" ,"c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]])
df2
subject_list
0 ["a", "b" ,"c"]
1 ["a", "b"]
2 ["b", "c"]
3 ["c"]
4 ["b", "c"]
我有两个代码都可以工作但不太正确:
此代码在 df1 中查找两行的组合(根据需要)。但是,df1 比 df2 包含更多的行,因此它在 df2 的最后一行停止。但仍有一些“字符串组合”需要测试。
df1["combination_0"] = df2["subject_list"].apply(lambda x: x.count(x and df.subject[0]))
此代码计算一个“列表”的出现次数。但是,我不知道如何更改它,以便它为每个值组合执行此操作。
df1["list_a_b"] = df2["subject_list"].apply(lambda x: x.count(df1.subject[0] and df1.subject[1]))
df1.list_a_b.sum()
【问题讨论】:
您的预期输出是什么?您能否在您的问题中包含df
和df2
的输出(为了便于阅读)?
您的示例中有一些错误。请尝试运行您提供的示例代码。第一行应该是df1 = pd.DataFrame("subject": ["a", "b", "c"])
,但其他行也有问题。
这个问题是正确的,因为很高兴看到您尝试过的一些事情,但我不确定您在寻找什么,并且您的样本中有简单的错误。
首先抱歉设计和编码混乱!
【参考方案1】:
这是我尝试的解决方案。
从你拥有的两个数据框开始,你可以使用 itertools 两两得到df1
元素的所有可能组合:
import itertools
df1 = pd.DataFrame("subject": ["a", "b", "c"])
df2 = pd.DataFrame("subject_list": [["a", "b", "c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]])
# Create a new dataframe with one column that has the possible two by two combinations from `df1`
df_combinations = pd.DataFrame('combination': list(itertools.combinations(df1.subject, 2)))
然后循环遍历新的数据帧,在本例中为 df_combinations
,以找出每个组合在 df2
中出现的次数:
for index, row in df_combinations.iterrows():
df_combinations.at[index, "number of occurrences"] = df2["subject_list"].apply(lambda x: all(i in x for i in row['combination'])).sum()
这一步与您的原始解决方案的主要区别在于,我没有使用x.count
,而是使用all
,因为这保证只有同时存在两个值的实例才会被计算在内。
最后df_combinations
是:
combination number of occurrences
0 (a, b) 2.0
1 (a, c) 1.0
2 (b, c) 3.0
【讨论】:
【参考方案2】:这个问题有点困难,因为根据你有多少值,可能会有很多成对的比较。我认为您可能想要为每个值创建一个虚拟df
,然后您可以使用.all
轻松查询您想要的任何成对组合。如果您想要任意数量的元素的组合,也很容易概括。
首先创建df_dummy
,它指示该值是否包含在列表中。
df_dummy = df2.subject_list.str.join(sep='?').str.get_dummies(sep='?')
# a b c
#0 1 1 1
#1 0 1 1
#2 1 1 0
#3 0 1 1
#4 0 0 1
然后创建您需要进行的所有成对组合的列表(忽略顺序)和相同的值
vals = df1.subject.unique()
combos = list((vals[j], vals[i]) for i in range(len(vals)) for j in range(len(vals)) if i>j)
print(combos)
#[('a', 'b'), ('a', 'c'), ('b', 'c')]
现在检查所有成对组合:
for x, y in combos:
df2[x+'_and_'+y]=df_dummy[[x, y]].all(axis=1)
df2
是:
subject_list a_and_b a_and_c b_and_c
0 [a, b, c] True True True
1 [b, c] False False True
2 [a, b] True False False
3 [b, c] False False True
4 [c] False False False
如果要统计总数,则直接使用sum
,忽略第一列
df2[df2.columns[1:]].sum()
#a_and_b 2
#a_and_c 1
#b_and_c 3
#dtype: int64
【讨论】:
【参考方案3】:这是我为解决您的问题所做的尝试。
主要有两个步骤:
生成所有可能的列表以检查 df1 的值 计算 df2 中包含每个组合的行数代码:
import itertools
def all_in(elements, a_list):
# Check if all values in the list elements are present in a_list
return all(el in a_list for el in elements)
# All the (unique) values in df1
all_values = sorted(set(df1.sum()['subject']))
result = pd.Series()
# For each sequence length (1, 2, 3)
for length in range(1, len(all_values)+1):
# For each sequence of fixed length
for comb in itertools.combinations(all_values, length):
# Count how many rows of df2 contains the sequence
result["_".join(comb)] = df2.squeeze().apply(lambda x: all_in(comb, x)).sum()
给出:
result
a 2
b 4
c 4
a_b 2
a_c 1
b_c 3
a_b_c 1
根据实际数据的大小和您的要求,您可以让事情变得更智能。例如,如果您知道 'a'
不在一行中,那么您将自动将 False 分配给任何组合,包括 'a'
【讨论】:
【参考方案4】:这是一个使用 collections.defaultdict
和 itertools.combinations
的非 Pandas 解决方案。逻辑有两部分:
-
计算来自
df1['subject']
的所有组合。
迭代 df2['subject_list']
并增加字典计数。
frozenset
是故意使用的,因为它们是可散列的,并且在您的问题中表明该顺序不相关。
from collections import defaultdict
from itertools import combinations
df1 = pd.DataFrame("subject": ["a", "b", "c"])
df2 = pd.DataFrame("subject_list": [["a", "b" ,"c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]])
# calculate all combinations
combs = (frozenset(c) for i in range(1, len(df1.index)+1) \
for c in combinations(df1['subject'], i))
# initialise defaultdict
d = defaultdict(int)
# iterate combinations and lists
for comb in combs:
for lst in df2['subject_list']:
if set(lst) >= comb:
d[comb] += 1
print(d)
defaultdict(int,
frozenset('a'): 2,
frozenset('b'): 4,
frozenset('c'): 4,
frozenset('a', 'b'): 2,
frozenset('a', 'c'): 1,
frozenset('b', 'c'): 3,
frozenset('a', 'b', 'c'): 1)
【讨论】:
【参考方案5】:这是另一种方法。两个主要见解如下:
我们可以首先将df2
中的每个列表与df1
的值相交。这样我们就可以避免考虑df2
每一行的冗余子集。
在步骤1
之后,df2
可能包含重复集。收集重复的可能会加快剩余的计算。
剩下的任务是考虑df1
的每个子集并计算出现次数。
import pandas as pd
import numpy as np
from itertools import combinations
from collections import Counter
df1 = pd.DataFrame("subject": ["a", "b", "c"])
df2 = pd.DataFrame(
"subject_list": [
["a", "b", "c", "x", "y", "z", "1", "2", "3"],
["b", "c"],
["a", "b"],
["b", "c"],
["c"],
]
)
s1 = set(df1.subject.values)
def all_combs(xs):
for k in range(1, len(xs) + 1):
yield from combinations(xs, k)
def count_combs(xs):
return Counter(all_combs(xs))
res = (
df2.subject_list.apply(s1.intersection)
.apply(frozenset)
.value_counts()
.reset_index()
)
# (b, c) 2
# (c, b, a) 1
# (c) 1
# (b, a) 1
res2 = res["index"].apply(df1.subject.isin).mul(res.subject_list, axis=0)
res2.columns = df1.subject
# subject a b c
# 0 0 2 2
# 1 1 1 1
# 2 0 0 1
# 3 1 1 0
res3 = pd.Series(
"_".join(comb): res2[comb][(res2[comb] > 0).all(1)].sum(0).iloc[0]
for comb in map(list, all_combs(df1.subject.values))
)
# a 2
# b 4
# c 4
# a_b 2
# a_c 1
# b_c 3
# a_b_c 1
# dtype: int64
【讨论】:
以上是关于计算列表列中两个元素的出现次数的主要内容,如果未能解决你的问题,请参考以下文章