在数据框的每一行中计算元素出现的次数

3

我有一个看起来像这样的数据框

enter image description here

数据框有11列,每一列都有一个等级。对于每个记录,我需要计算其中A、B和C的数量。
这是我的预期输出结果。

enter image description here

我尝试使用apply函数来实现。目前为止,我的代码如下:

import pandas as pd
# sample data
df_dict = {'level_1': {0: 'C', 1: 'A', 2: 'C', 3: 'B', 4: 'A', 5: 'C', 6: 'A', 7: 'B', 8: 'B'},
           'level_2': {0: 'B', 1: 'B', 2: 'C', 3: 'A', 4: 'A', 5: 'C', 6: 'B', 7: 'C', 8: 'A'},
           'level_3': {0: 'B', 1: 'A', 2: 'B', 3: 'A', 4: 'B', 5: 'B', 6: 'C', 7: 'B', 8: 'C'},
           'level_4': {0: 'A', 1: 'C', 2: 'B', 3: 'C', 4: 'B', 5: 'C', 6: 'A', 7: 'B', 8: 'C'},
           'level_5': {0: 'B', 1: 'B', 2: 'B', 3: 'A', 4: 'A', 5: 'A', 6: 'B', 7: 'B', 8: 'A'},
           'level_6': {0: 'C', 1: 'C', 2: 'C', 3: 'B', 4: 'B', 5: 'B', 6: 'C', 7: 'A', 8: 'C'},
           'level_7': {0: 'C', 1: 'A', 2: 'C', 3: 'C', 4: 'C', 5: 'C', 6: 'C', 7: 'A', 8: 'A'},
           'level_8': {0: 'B', 1: 'A', 2: 'B', 3: 'B', 4: 'B', 5: 'A', 6: 'A', 7: 'A', 8: 'C'},
           'level_9': {0: 'A', 1: 'B', 2: 'A', 3: 'C', 4: 'C', 5: 'B', 6: 'A', 7: 'C', 8: 'B'},
           'level_10': {0: 'B', 1: 'C', 2: 'A', 3: 'A', 4: 'A', 5: 'A', 6: 'A', 7: 'A', 8: 'C'},
           'level_11': {0: 'C', 1: 'B', 2: 'C', 3: 'B', 4: 'C', 5: 'B', 6: 'B', 7: 'C', 8: 'B'}
           }
sample_df = pd.DataFrame(df_dict)

# function to count the values of A, B, C
def count_in_df(series):
    _ = series.value_counts()
    _ = _[['A', 'B', 'C']]
    return _.tolist()

count_df = pd.DataFrame(sample_df.apply(count_in_df, axis=1).tolist(),
                       columns=['counts_of_A', 'counts_of_B', 'counts_of_C'])

# add count information back 
sample_df = sample_df.join(count_df)

这提供了我需要的信息,但问题在于代码太慢了。我有大约70万条记录和66个列(而不是11个),需要执行此操作,这花费了我约30分钟才得到结果。
有没有办法可以加速代码?有哪些优化方法可以尝试?
3个回答

2

stack + groupby + value_counts 可以用于简单的操作。重命名列后再添加回去。

最初的回答:使用堆栈(stack)、分组(groupby)和计数(value_counts)来进行简单的操作。然后重命名列并将其添加回去。
df = (sample_df
      .stack()
      .groupby(level=0)
      .value_counts()
      .unstack(1)
      .add_prefix('counts_of_')
     )

df = pd.concat([sample_df, df], axis=1)

Output: df

   count_of_A  count_of_B  count_of_C
0           2           5           4
1           4           4           3
2           2           4           5
3           4           4           3
4           4           4           3
5           3           4           4
6           5           3           3
7           4           4           3
8           3           3           5

不错!根据您的建议,我使用了add_prefix和assign方法添加了一个解决方案。+1 - Anton vBR
1
啊,说得好。虽然它允许你链式操作,但我的记忆是它会复制整个DataFrame来重命名列,这可能会很慢。 - ALollz
这很漂亮。一分钟内运行。谢谢。 - Clock Slave
@ALollz,如果您不介意的话,能否解释一下为什么这样会快那么多? - Clock Slave
1
@ALollz 你说得对。我删除了解决方案,并将 add_prefix 集成到你的代码中,这使速度略有提升(非常微小)。 - Anton vBR

2

I use str.get_dummies

sample_df.stack().str.get_dummies().sum(level=0)

Out[142]:
   A  B  C
0  2  5  4
1  4  4  3
2  2  4  5
3  4  4  3
4  4  4  3
5  3  4  4
6  5  3  3
7  4  4  3
8  3  3  5

1
太棒了!疯狂的一行代码。 - Vishnudev Krishnadas

1

@ALollz的回答很好。但是我的方法会是这样的。

>>> dummy_df = pd.get_dummies(sample_df)
>>> sample_df['count_of_A'] = dummy_df.filter(regex='level_(\d+)_A').sum(axis=1)
>>> sample_df['count_of_A']
0    2
1    4
2    2
3    4
4    4
5    3
6    5
7    4
8    3

同样地,如果您有多个 grades
>>> grades = list('ABC')
>>> for grade in grades:
...     sample_df[f'count_of_{grade}'] = dummy_df.filter(regex=f'level_(\d+)_{grade}').sum(axis=1)
... 
>>> sample_df.filter(regex='count_of_')
   count_of_A  count_of_B  count_of_C
0           2           5           4
1           4           4           3
2           2           4           5
3           4           4           3
4           4           4           3
5           3           4           4
6           5           3           3
7           4           4           3
8           3           3           5

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