Je souhaite remplir la colonne "Category" de df1 dataframe avec les valeurs correctes de la colonne "Category" de df2 dataframe.
df1 Receiver Category 0 Insurance company Insurances 1 Shop Groceries 2 Pizza place Fastfood 3 Library 4 Gas station 24/7 Car 5 Something else 6 Whatever receiver
Sortie:
df1
Receiver Category
0 Insurance company
1 Shop
2 Pizza place
3 Library
4 Gas station 24/7
5 Something else
6 Whatever receiver
df2
Category Searchterm
0 Insurances Insur
1 Groceries Shop
2 Groceries Market
3 Fastfood Pizza
4 Fastfood Burger
5 Car Gas
Je veux comparer df1 ["Receiver"] à df2 ["Searchterm"] ligne par et lorsque ce dernier correspond même partiellement au premier , attribuez df2 ["Category"] de cette ligne à df1 ["Category"] .
Par exemple, "Pizza" dans df2 ["Searchterm"] correspond partiellement à "Pizza place" dans df1 ["Receiver"] , donc je voulez attribuer "Fastfood" (qui est la catégorie Pizza dans df2 ["Category"] ) à la catégorie "Pizza place" dans df1 ["Category"] .
Le résultat souhaité serait:
import pandas as pd
df1 = pd.DataFrame({"Receiver": ["Insurance company", "Shop", "Pizza place", "Library", "Gas station 24/7", "Something else", "Whatever receiver"], "Category": ["","","","","","",""]})
df2 = pd.DataFrame({"Category": ["Insurances", "Groceries", "Groceries", "Fastfood", "Fastfood", "Car"], "Searchterm": ["Insurance", "Shop", "Market", "Pizza", "Burger", "Gas"]})
Alors, comment puis-je remplir df1 ["Category"] avec les bonnes catégories? Merci.
3 Réponses :
Sous l'hypothèse que le nombre de catégories est petit par rapport au nombre de récepteurs, une stratégie consiste à itérer les catégories. Avec cette solution, notez que la dernière correspondance ne restera que là où plusieurs catégories sont trouvées.
def jpp(df1, df2):
for tup in df2.itertuples(index=False):
df1.loc[df1['Receiver'].str.contains(tup.Searchterm, regex=False), 'Category'] = tup.Category
return df1
def user347(df1, df2):
df1['Category'] = df1['Receiver'].replace((df2['Searchterm'] + r'.*').values,
df2['Category'].values,
regex=True)
df1.loc[df1['Receiver'].isin(df1['Category']), 'Category'] = ''
return df1
df1 = pd.concat([df1]*10**4, ignore_index=True)
df2 = pd.concat([df2], ignore_index=True)
%timeit jpp(df1, df2) # 145 ms per loop
%timeit user347(df1, df2) # 364 ms per loop
df1 = pd.concat([df1], ignore_index=True)
df2 = pd.concat([df2]*100, ignore_index=True)
%timeit jpp(df1, df2) # 666 ms per loop
%timeit user347(df1, df2) # 88 ms per loop
Comme indiqué , cette solution évolue mieux avec les lignes de df1 qu'avec les catégories de df2 . Pour illustrer cela, examinez les performances ci-dessous pour des cadres de données d'entrée de tailles différentes.
for tup in df2.itertuples(index=False):
mask = df1['Receiver'].str.contains(tup.Searchterm, regex=False)
df1.loc[mask, 'Category'] = tup.Category
print(df1)
# Category Receiver
# 0 Insurances Insurance company
# 1 Groceries Shop
# 2 Fastfood Pizza place
# 3 Library
# 4 Car Gas station 24/7
# 5 Something else
# 6 Whatever receiver
Merci beaucoup, j'ai passé des heures et des heures et essayé des centaines de lignes de méthodes différentes sans succès, mais cela a finalement réussi. J'ai essayé d'éviter les boucles for avec des dataframes, mais cela semble beaucoup plus simple que les propres méthodes des pandas, et n'a probablement aucun problème à moins qu'il n'y ait des milliers de récepteurs? Pouvez-vous expliquer ce que vous entendez par "la dernière correspondance ne restera que là où plusieurs catégories se trouvent"?
@KMFR, Premier point, la méthode ralentit davantage lorsque vous avez un plus grand nombre de catégories; sinon devrait être bien. Deuxième point, disons que vous avez un récepteur appelé "Insurance Shop" ... il sera mappé à l'épicerie au lieu de la compagnie d'assurance.
Ok merci @jpp, j'éviterai les termes de recherche trop vagues.
Peut-être Series.replace pour une solution vectorisée lorsque les données sont volumineuses?
@ user3471881, Non, car OP veut des correspondances partielles, replace nécessite des correspondances exactes.
J'allais juste vous demander de montrer les performances lorsque les catégories sont grandes - très bonne mise à jour et merci de l'avoir fait pour que je n'ai pas à: D
@Vaishali, j'ai essayé mais il y a eu une erreur d'indexation: (. Je vous laisse libre cours pour mettre à jour le benchmarking si vous le gérez. Ou ajoutez-le au vôtre et je le supprimerai de mon message.
Oh ok, puisque le df2 a été mis à jour en utilisant concat, il contenait des index en double qui ne fonctionneraient pas avec map. Création d'un mapper dict et mise à jour du benchmark dans mai post. Je ne voulais pas modifier le vôtre
Une autre solution utilisant str.extract
def jpp(df1, df2):
for tup in df2.itertuples(index=False):
df1.loc[df1['Receiver'].str.contains(tup.Searchterm, regex=False), 'Category'] = tup.Category
return df1
def user347(df1, df2):
df1['Category'] = df1['Receiver'].replace((df2['Searchterm'] + r'.*').values,
df2['Category'].values,
regex=True)
df1.loc[df1['Receiver'].isin(df1['Category']), 'Category'] = ''
return df1
def vai(df1, df2):
pat = '('+'|'.join(df2['Searchterm'])+')'
df1["Category"] = df1['Receiver'].str.extract(pat)[0].map(df2.set_index('Searchterm')['Category'].to_dict()).fillna('')
df1 = pd.concat([df1]*10**4, ignore_index=True)
df2 = pd.concat([df2], ignore_index=True)
%timeit jpp(df1, df2)
%timeit user347(df1, df2)
%timeit vai(df1, df2)
120 ms ± 2.26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
221 ms ± 4.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
78.2 ms ± 1.56 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
df1 = pd.concat([df1], ignore_index=True)
df2 = pd.concat([df2]*100, ignore_index=True)
%timeit jpp(df1, df2)
%timeit user347(df1, df2)
%timeit vai(df1, df2)
11.4 s ± 276 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
20.4 s ± 296 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
98.3 ms ± 408 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
pat = '('+'|'.join(df2['Searchterm'])+')'
df1["Category"] = df1['Receiver'].str.extract(pat)[0].map(df2.set_index('Searchterm')['Category'].to_dict()).fillna('')
Receiver Category
0 Insurance company Insurances
1 Shop Groceries
2 Pizza place Fastfood
3 Library
4 Gas station 24/7 Car
5 Something else
6 Whatever receiver
Très cool, merci! J'ai essayé d'utiliser str.contains (), str.extract () est nouveau pour moi.
Très agréable :). Je ne suis pas sûr que les résultats soient exactement comparables car df2.set_index ('Searchterm') ['Category']. To_dict () se réduit à un petit conteneur. Aussi pour 10 ** 5 je vois la performance jpp: 1,52 s par boucle; user347: 3,82 s par boucle; vai: 1,62 s par boucle . Donc un peu un peu entre nos méthodes.
Le deuxième critère ici est totalement inutile pour être juste. Peut-être le supprimer ou trouver une solution différente avec str.extract ?
Vous pouvez utiliser Series.replace avec regex pour une approche vectorisée:
df1['Category'] = df1['Receiver'].replace(
(df2['Searchterm'] + r'.*').values,
df2['Category'].values,
regex=True
)
df1.loc[df1['Receiver'].isin(df1['Category']), 'Category'] = ''
print(df1)
Category Receiver
0 Insurances Insurance company
1 Groceries Shop
2 Fastfood Pizza place
3 Library
4 Car Gas station 24/7
5 Something else
6 Whatever receiver
Notez que cela suppose que chaque La chaîne Searchterm se trouve au début de chaque chaîne Receiver . Si ce n'est pas le cas, ajustez l'expression régulière en conséquence.
C'est en effet une bonne solution, surtout si vous avez un grand nombre de catégories . J'ai ajouté quelques points de repère dans ma réponse pour illustrer.