统计列表列中两个元素的出现次数

3

我已经为此苦苦挣扎了几天。我在网上阅读了很多文章,发现了一些类似的问题,例如:Pandas counting occurrence of list contained in column of listspandas: 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()

2
你期望的输出是什么? 你能否在问题中同时包含dfdf2的输出结果(为了易读性)? - cs95
你的示例中有一些错误。请尝试运行你提供的示例代码。第一行应该是 df1 = pd.DataFrame({"subject": ["a", "b", "c"]}),但其他行也存在问题。 - Zev
这个问题总体来说还算是在正确的轨道上,因为很高兴看到你已经尝试了几件事情。但是我不确定你在寻找什么,而且你的示例中有一些简单的错误。 - Zev
对于一开始的混乱设计和编码,非常抱歉! - Hannah
5个回答

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

0

这个问题有点难,因为根据你有多少值,可能会有很多成对比较。我认为你可能想创建一个带有每个值的虚拟 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

0

这是我尝试解决您的问题的方法。

主要有两个步骤:

  • 从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'的任何组合。

0

这里提供了一个非Pandas解决方案,使用collections.defaultdictitertools.combinations。逻辑分为两部分:

  1. 计算df1['subject']的所有组合。
  2. 迭代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})

0

这里有另一种方法。两个主要的见解如下:

  1. 我们可以从交集每个列表在 df2 中与 df1 的值开始。这样我们可以避免考虑 df2 的每行冗余子集。

  2. 在步骤 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接