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
3 Réponses :
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? .
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]
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
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
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
"puis utiliser un lambda dans un lambda" ... crie en interne
@coldspeed, Haha, j'allais aussi mettre à jour avec une fonction! ... Terminé.