Je dois fusionner deux df. L'un est mon df principal, l'autre a beaucoup de NaN
df1 sample:
merged_df = merged_df.drop_duplicates()
df2 sample:
merged_df = pd.concat(merged_dfs, sort=False)
Le résultat que j'aimerais avoir est à peu près le suivant:
merged_dfs = []
for i in range(len(mps)):
merged_dfs.append(df1.merge(mps[i], on=(split_groups[i][1]), how='left'))
La fusion doit simplement "ignorer" les valeurs NaN et fusionner uniquement là où la valeur de la colonne n'est pas NaN. J'ai essayé différentes choses, mais les données dans df2 ont des dizaines de possibilités où les valeurs NaN pourraient apparaître. df1 a 168k lignes et df2 en a environ 170, et r_id doit être associé à tout code qui correspond à toutes les valeurs non-NaN. Quelqu'un a-t-il une idée de la manière de le faire efficacement?
Après des recherches approfondies sur différentes approches, il semble qu'une manière «magique» d'ignorer les NaN n'existe probablement pas. J'ai pensé à appliquer un masque sur df2 et à les diviser en groupes, à les parcourir en boucle, à fusionner chaque groupe avec df1 et à supprimer les doublons par la suite. C'est à dire. ici, j'aurais
mps = []
"""
First, we extract rows where i[0] is null. In the resulting df, we extract rows
where i[1] is not null. Then, we drop all columns with na values. In this way
we retain only columns good for the merge.
"""
for i in split_groups:
df = df2[(df2[i[0]].isnull()).all(1)]
df = df[(df[i[1]].notnull()).all(1)]
df = df.dropna(axis='columns', how='all')
mps.append(df)
Cependant, je ne suis pas sûr que ce soit la meilleure approche et franchement je suis perplexe quant à la façon dont je devrais l'implémenter.
J'ai fini par explorer l'approche ci-dessus - appliquer un masque sur df2 , le diviser en fonction du masque, le fusionner avec df1 .
Étape 1: créer un masque
split_groups = []
for index, row in group_mask.iterrows():
bool_groups = []
# If the whole group is False, then cannot be taken in consideration,
# as it would result in a merge on the whole df1
if not any(row.to_dict().values()):
pass
else:
bool_groups.append(
[key for key in row.to_dict().keys() if row.to_dict()[key] == False])
bool_groups.append(
[key for key in row.to_dict().keys() if row.to_dict()[key] == True])
split_groups.append(bool_groups)
Étape 2: grouper df, selon NaN ( = False ) valeurs
group_mask = mask.groupby(['hotel_region','hotel_country', 'chain_name','brand_name']).count().reset_index()
Étape 3: ajouter des groupes de colonnes dans df2 à un tableau split_groups code > selon les valeurs vrai / faux dans group_mask
masked = df2[['hotel_region', 'hotel_country', 'chain_name', 'brand_name']] mask = pd.notnull(masked)
Étape 4: créer un tableau de dfs divisé en fonction des colonnes dans df2 code > où toutes les valeurs ne sont pas False
(True, True, True, True, False), (True, False, False, True, False), (True, True, True, False, True)
Étape 5: Faites une boucle dans le tableau et fusionnez les 2 dfs en fonction des colonnes existantes
code hotel_region hotel_country chain_name brand_name r_id 9737 EUROPE ESTONIA Bridgestreet NaN 151 9776 LATIN AMERICA BRAZIL Independent W Hotels 47
Étape 6: concattez les fichiers dfs dans merged_dfs
r_id hotel_region hotel_country chain_name brand_name 78 LATIN AMERICA HONDURAS Barcelo Hotels and Resorts NaN 92 LATIN AMERICA SANDWICH ISL Barcelo Hotels and Resorts NaN 151 NaN NaN Bridgestreet NaN 117 NORTH AMERICA CANADA Magnuson Hotels NaN 47 LATIN AMERICA BRAZIL NaN W Hotels
Étape 7: supprimez les doublons
code hotel_region hotel_country chain_name brand_name 9737 EUROPE ESTONIA Bridgestreet NaN 5397 LATIN AMERICA COSTA RICA Independent No Brand 2392 LATIN AMERICA ARUBA DIVI RESORTS NaN 9776 LATIN AMERICA BRAZIL Independent W Hotels 4720 LATIN AMERICA ARGENTINA Independent No Brand
Étape 8 i s appelant merged_df.columns.tolist () et ne conservant que les colonnes qui sont utiles pour le résultat final.
Je pense que cette approche n'est pas optimale - si quelqu'un a des idées sur la façon pour rendre cela plus efficace, j'apprécierai vraiment. Merci @qingshan pour la suggestion de bouclage, cela m'a donné l’indication de parcourir éventuellement différentes listes de dfs. P >
3 Réponses :
Vous pouvez fusionner des dataframes filtrés pour obtenir ce dont vous avez besoin. Utilisez ceci pour filtrer votre dataframe, puis effectuez une fusion à gauche pour obtenir la sortie.
out_df = df1[~df1.isnull().T.any().T].merge(df2[~df2.isnull().T.any().T], on=['hotel_region', 'hotel_country', 'chain_name', 'brand_name'], how='left')
malheureusement avec cette solution, je n'obtiens que des valeurs na dans les colonnes de df2 après la fusion
Cela fonctionnait dans l'ensemble de données que vous avez donné ci-dessus. Veuillez fournir quelques données supplémentaires sur lesquelles travailler
Essayez la fonction Combined_first
>>> df1 = pd.DataFrame([[1, np.nan]]) >>> df2 = pd.DataFrame([[3, 4]]) >>> df1.combine_first(df2) 0 1 0 1 4.0
déjà essayé, mais la sortie est incohérente / incorrecte. Il fusionne les lignes qu'il ne doit pas toucher
Je suppose que vous voulez fusionner deux lignes avec les mêmes valeurs de colonne (ignorez NaN). Si les données ne sont pas volumineuses, cela peut être fait avec deux boucles for.
Est-il obligatoire d'avoir
r_id?malheureusement oui, c'est le paramètre par lequel je dois marquer les lignes dans df1 qui sont liées à df2 :-)