【问题标题】:Count occurrence of two elements in column of list计算列表列中两个元素的出现次数
【发布时间】:2018-11-20 23:03:46
【问题描述】:

我已经为此苦苦挣扎了几天。我在网上阅读了很多,发现了一些类似的问题,例如: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()

【问题讨论】:

  • 您的预期输出是什么?您能否在您的问题中包含dfdf2 的输出(为了便于阅读)?
  • 您的示例中有一些错误。请尝试运行您提供的示例代码。第一行应该是df1 = pd.DataFrame({"subject": ["a", "b", "c"]}),但其他行也有问题。
  • 这个问题是正确的,因为很高兴看到您尝试过的一些事情,但我不确定您在寻找什么,并且您的样本中有简单的错误。
  • 首先抱歉设计和编码混乱!

标签: python list pandas lambda count


【解决方案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.defaultdictitertools.combinations 的非 Pandas 解决方案。逻辑有两部分:

        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})
        

        【讨论】:

          【解决方案5】:

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

          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
          

          【讨论】:

            猜你喜欢
            • 2020-06-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多