在多个字符串列中计算一个字符串出现的次数

4

我有一个叫做df的数据框,它看起来类似于这个例子(除了'mat_deliv'列的数量会增加到mat_deliv_8,有数百个客户和一些在Client_ID和mat_deliv_1之间的其他列 - 这里我简化了它)。

Client_ID  mat_deliv_1  mat_deliv_2  mat_deliv_3  mat_deliv_4
C1019876   xxx,yyy,zzz  aaa,bbb,xxx  xxx          ddd
C1018765   yyy,zzz      xxx          xxx          None
C1017654   yyy,xxx      aaa,bbb      ccc          ddd
C1016543   aaa,bbb      ccc          None         None
C1019876   yyy          None         None         None

我想创建一个名为xxx_count的新列,它统计在mat_deliv_1mat_deliv_2mat_deliv_3mat_deliv_4中出现xxx的次数。值应该如下所示:
Client_ID  mat_deliv_1  mat_deliv_2  mat_deliv_3  mat_deliv_4  xxx_count
C1019876   xxx,yyy,zzz  aaa,xxx,bbb  xxx          ddd          3
C1018765   yyy,zzz      xxx          xxx          None         2
C1017654   yyy,xxx      aaa,bbb      ccc          ddd          1
C1016543   aaa,bbb      ccc          None         None         0
C1015432   yyy          None         None         None         0

我尝试了以下代码:
df = df.assign(xxx_count=df.loc[:, "mat_deliv_1":"mat_deliv_4"].\
               apply(lambda col: col.str.count('xxx')).fillna(0).astype(int))

但它并不产生计数,只有一个二进制变量,其中0表示没有xxx的情况,1表示在四个mat_deliv列中至少存在一个xxx
注:这是对此处提出的跟进问题:Creating a column based on the presence of part of a string in multiple other columns
3个回答

3
尝试在计数之前将它们水平连接起来?
df['counts'] = (df.loc[:, "mat_deliv_1":"mat_deliv_4"]
                  .fillna('')
                  .agg(','.join, 1)
                  .str.count('xxx'))
df
  Client_ID  mat_deliv_1  mat_deliv_2 mat_deliv_3 mat_deliv_4  counts
0  C1019876  xxx,yyy,zzz  aaa,bbb,xxx         xxx         ddd       3
1  C1018765      yyy,zzz          xxx         xxx         NaN       2
2  C1017654      yyy,xxx      aaa,bbb         ccc         ddd       1
3  C1016543      aaa,bbb          ccc         NaN         NaN       0
4  C1019876          yyy          NaN         NaN         NaN       0

假设每列中只出现一次"xxx",这将有效。如果它出现多次,它将计算每个出现的次数。


另一个选项涉及stack

df['counts'] = (
    df.loc[:, "mat_deliv_1":"mat_deliv_4"].stack().str.count('xxx').sum(level=0))
df
  Client_ID  mat_deliv_1  mat_deliv_2 mat_deliv_3 mat_deliv_4  counts
0  C1019876  xxx,yyy,zzz  aaa,bbb,xxx         xxx         ddd       3
1  C1018765      yyy,zzz          xxx         xxx         NaN       2
2  C1017654      yyy,xxx      aaa,bbb         ccc         ddd       1
3  C1016543      aaa,bbb          ccc         NaN         NaN       0
4  C1019876          yyy          NaN         NaN         NaN       0

使用 str.contains 可以轻松修改此代码,仅计算第一次出现的情况:

df['counts'] = (
    df.loc[:, "mat_deliv_1":"mat_deliv_4"].stack().str.contains('xxx').sum(level=0))

如果“xxx”可能是一个子字符串,首先要进行分割,然后再进行计数:
df['counts'] = (df.loc[:, "mat_deliv_1":"mat_deliv_4"]
                  .stack()
                  .str.split(',', expand=True)
                  .eq('xxx')
                  .any(1)  # change to `.sum(1)` to count all occurrences
                  .sum(level=0))

为了提高性能,使用列表推导式:
df['counts'] = [
    ','.join(x).count('xxx') 
    for x in df.loc[:, "mat_deliv_1":"mat_deliv_4"].fillna('').values
]
df
  Client_ID  mat_deliv_1  mat_deliv_2 mat_deliv_3 mat_deliv_4  counts
0  C1019876  xxx,yyy,zzz  aaa,bbb,xxx         xxx         ddd       3
1  C1018765      yyy,zzz          xxx         xxx         NaN       2
2  C1017654      yyy,xxx      aaa,bbb         ccc         ddd       1
3  C1016543      aaa,bbb          ccc         NaN         NaN       0
4  C1019876          yyy          NaN         NaN         NaN       0

为什么使用循环比使用str方法或apply更快?请参见For loops with pandas - When should I care?


1
这个完美地运行了 - 我采用了最终的建议,使用列表推导式。非常感谢您的帮助。 - FGreen
1
需要注意的是,count 函数将包含一个较大字符串内的匹配子字符串,例如 xxxxxxx 都会被计数。如果这样可以接受,那么就没问题。如果不可接受,那么就需要进行相等性测试,例如:[sum(1 for word in ','.join(row).split(',') if word == 'xxx') for row in df.loc[:, "mat_deliv_1":"mat_deliv_4"].fillna('').values] - Alexander

2
使用 str.findall
df.iloc[:,1:].apply(lambda x : x.str.findall('xxx')).sum(1).str.len()
Out[433]: 
0    3
1    2
2    1
3    0
4    0
dtype: int64

谢谢您的回复 - 但是,在我的数据框上运行后,我收到了一个错误消息,指出无法在对象dtype上运行.str,因此我选择了下面的答案。 - FGreen

0

你可以使用 , 进行分割,然后在 lambda 中再使用一个 lambda。这种解决方案的优点是,如果 xxx 作为 yyy 的子字符串存在,你不会看到错误的结果。

df['xxx_count'] = df.filter(like='mat_deliv').apply(lambda x: x.str.split(',')\
                                                    .apply(lambda x: 'xxx' in x)).sum(1)

print(df)

  Client_ID  mat_deliv_1  mat_deliv_2 mat_deliv_3 mat_deliv_4  xxx_count
0  C1019876  xxx,yyy,zzz  aaa,bbb,xxx         xxx         ddd          3
1  C1018765      yyy,zzz          xxx         xxx        None          2
2  C1017654      yyy,xxx      aaa,bbb         ccc         ddd          1
3  C1016543      aaa,bbb          ccc        None        None          0
4  C1019876          yyy         None        None        None          0

或者更好的方法是使用一个函数:

def sum_counts(series, value):
    def finder(item, value):
        return value in item
    return series.str.split(',').apply(finder, value=value)

df['xxx_count'] = df.filter(like='mat_deliv').apply(sum_counts, value='xxx').sum(1)

1
然后在lambda内部使用另一个lambda...内心哭泣。 - cs95
1
@coldspeed,哈哈,我也打算用一个函数来更新!...完成。 - jpp

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