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

时间:2019-01-08 19:57:01

标签: python string pandas dataframe

我有一个名为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的新列,该列计算xxxmat_deliv_1mat_deliv_2mat_deliv_3和{{1 }}。值应如下所示:

mat_deliv_4

我尝试了以下代码:

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四个xxx列。

注意:这是对以下问题的跟进问题:Creating a column based on the presence of part of a string in multiple other columns

3 个答案:

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

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