4
votes

Compter les occurrences d'une chaîne dans plusieurs colonnes de chaînes

J'ai un dataframe appelé df qui ressemble à ceci (sauf que le nombre de colonnes 'mat_deliv' monte à mat_deliv_8, il y a plusieurs centaines de clients et un certain nombre d'autres colonnes entre Client_ID et mat_deliv_1 - Je l'ai simplifié ici).

df = df.assign(xxx_count=df.loc[:, "mat_deliv_1":"mat_deliv_4"].\
               apply(lambda col: col.str.count('xxx')).fillna(0).astype(int))

Je souhaite créer une nouvelle colonne appelée xxx_count qui compte le nombre de fois où xxx apparaît dans mat_deliv_1 , mat_deliv_2 , mat_deliv_3 et mat_deliv_4 . Les valeurs doivent ressembler à ceci:

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

J'ai essayé le code suivant:

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

Mais il ne produit pas de count, uniquement une variable binaire où 0 = aucun cas de xxx et 1 = la présence de xxx dans à au moins une des quatre colonnes mat_deliv .

NB: il s'agit d'une question complémentaire à celle posée ici: Création d'une colonne basée sur la présence d'une partie d'une chaîne dans plusieurs autres colonnes


0 commentaires

3 Réponses :


3
votes

Essayez de les joindre horizontalement avant de compter?

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

Cela fonctionnera en supposant que "xxx" ne se produise qu'une seule fois par colonne. S'il se produit plus d'une fois, il comptera chaque occurrence.


Une autre option implique stack:

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

Cela peut facilement être modifié pour ne compter que la première occurrence, en utilisant str.contains:

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

S'il est possible que "xxx" soit un sous-chaîne, d'abord diviser puis compter:

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

Pour les performances, utilisez une compréhension de liste:

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

Pourquoi une boucle est-elle plus rapide que d'utiliser les méthodes str ou apply ? Voir Pour les boucles avec les pandas - Quand dois-je m'en soucier? .


2 commentaires

Cela a parfaitement fonctionné - je suis allé pour la suggestion finale, en utilisant la compréhension de liste. Merci beaucoup pour votre aide


Un point à noter avec count est qu'il inclura une sous-chaîne correspondante dans une chaîne plus grande, par exemple xxx et xxxx compteront tous les deux. Si c'est OK, alors très bien. Sinon, alors il faut tester l'égalité, par exemple [sum (1 pour mot dans ','. join (row) .split (',') if word == 'xxx') pour ligne dans df.loc [:, "mat_deliv_1": "mat_deliv_4"] .fillna (''). valeurs]



2
votes

Utilisation de 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


1 commentaires

Merci pour la réponse - cependant, j'ai reçu un message d'erreur après l'avoir exécuté sur mon df que .str ne pouvait pas être exécuté sur un objet dtype, j'ai donc opté pour la réponse ci-dessous



0
votes

Vous pouvez utiliser la division par , , puis utiliser un lambda dans un lambda . L'avantage de cette solution est que vous ne voyez pas de résultats incorrects si xxx existe en tant que sous-chaîne d'un yyy.

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)

Ou, mieux, utilisez une fonction:

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


2 commentaires

"puis utiliser un lambda dans un lambda" ... crie en interne


@coldspeed, Haha, j'allais aussi mettre à jour avec une fonction! ... Terminé.