1
votes

Fusionner 2 dataframes sur des valeurs notnull

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.

Modifier - comment j'ai fini par résoudre ce problème

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 >


2 commentaires

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


3 Réponses :


0
votes

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


2 commentaires

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



0
votes

Essayez la fonction Combined_first

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.combine_first.html#pandas.DataFrame.combine_first

>>> df1 = pd.DataFrame([[1, np.nan]])
>>> df2 = pd.DataFrame([[3, 4]])
>>> df1.combine_first(df2)

   0    1
0  1  4.0


1 commentaires

déjà essayé, mais la sortie est incohérente / incorrecte. Il fusionne les lignes qu'il ne doit pas toucher



1
votes

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.


0 commentaires