计算列表

时间:2018-06-11 16:18:02

标签: python list pandas lambda count

我几天来一直在努力解决这个问题。我在网上看了很多,发现了一些类似的问题: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]))

此代码计算一个" list"的出现次数。但是,我无法弄清楚如何更改它以便为每个值组合执行此操作。

df1["list_a_b"] = df2["subject_list"].apply(lambda x: x.count(df1.subject[0] and df1.subject[1]))
df1.list_a_b.sum()

5 个答案:

答案 0 :(得分: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,因为这样可以保证只计算存在两个值的实例。< / p>

最后df_combinations是:

  combination  number of occurrences
0      (a, b)                    2.0
1      (a, c)                    1.0
2      (b, c)                    3.0

答案 1 :(得分: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

答案 2 :(得分: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'

答案 3 :(得分:0)

以下是使用collections.defaultdictitertools.combinations的非熊猫式解决方案。逻辑分为两部分:

  1. 计算df1['subject']
  2. 的所有组合
  3. 迭代df2['subject_list']并增加字典数。
  4. 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})
    

答案 4 :(得分:0)

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

  1. 我们可以首先将df2中的每个列表与值df1相交。这样我们就可以避免考虑每行df2的冗余子集。

  2. 在步骤1之后,df2可能包含重复的集合。收集重复的内容可能会加快剩余的计算速度。

  3. 剩下的任务是考虑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