web-dev-qa-db-fra.com

les pandas obtiennent des lignes qui ne sont PAS dans une autre base de données

J'ai deux bases de données de pandas qui ont quelques lignes en commun.

Supposons que dataframe2 est un sous-ensemble de dataframe1.

Comment puis-je obtenir les lignes de dataframe1 qui ne sont pas dans dataframe2?

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})
135

Une méthode consisterait à stocker le résultat d'une fusion interne sous les deux dfs. Nous pouvons simplement sélectionner les lignes lorsque les valeurs d'une colonne ne sont pas dans ce commun

In [119]:

common = df1.merge(df2,on=['col1','col2'])
print(common)
df1[(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))]
   col1  col2
0     1    10
1     2    11
2     3    12
Out[119]:
   col1  col2
3     4    13
4     5    14

MODIFIER

Une autre méthode que vous avez trouvée consiste à utiliser isin qui produira des lignes NaN que vous pouvez supprimer:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

Cependant, si df2 ne commence pas les lignes de la même manière, cela ne fonctionnera pas:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

produira le df entier:

In [140]:

df1[~df1.isin(df2)].dropna()
Out[140]:
   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
111
EdChum

La solution actuellement sélectionnée produit des résultats incorrects. Pour résoudre correctement ce problème, nous pouvons effectuer une jointure à gauche de df1 à df2, en veillant à ne récupérer que les lignes uniques de df2

Tout d'abord, nous devons modifier le DataFrame d'origine pour ajouter la ligne avec data [3, 10].

df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                           'col2' : [10, 11, 12, 13, 14, 10]}) 
df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                           'col2' : [10, 11, 12]})

df1

   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
5     3    10

df2

   col1  col2
0     1    10
1     2    11
2     3    12

Effectuez une jointure à gauche en éliminant les doublons dans df2, de sorte que chaque ligne de df1 se joint à exactement une ligne de df2. Utilisez le paramètre indicator pour renvoyer une colonne supplémentaire indiquant la table d'origine.

df_all = df1.merge(df2.drop_duplicates(), on=['col1','col2'], 
                   how='left', indicator=True)
df_all

   col1  col2     _merge
0     1    10       both
1     2    11       both
2     3    12       both
3     4    13  left_only
4     5    14  left_only
5     3    10  left_only

Créer une condition booléenne:

df_all['_merge'] == 'left_only'

0    False
1    False
2    False
3     True
4     True
5     True
Name: _merge, dtype: bool

Pourquoi les autres solutions sont mauvaises

Quelques solutions font la même erreur - elles vérifient seulement que chaque valeur est indépendamment dans chaque colonne, pas ensemble dans la même ligne. L'ajout de la dernière ligne, unique mais contenant les valeurs des deux colonnes de df2, expose l'erreur:

common = df1.merge(df2,on=['col1','col2'])
(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))
0    False
1    False
2    False
3     True
4     True
5    False
dtype: bool

Cette solution donne le même résultat faux:

df1.isin(df2.to_dict('l')).all(1)
98
Ted Petrou

En supposant que les index soient cohérents dans les cadres de données (sans tenir compte des valeurs de col réelles):

df1[~df1.index.isin(df2.index)]
55
Dennis Golomazov

Comme déjà indiqué, isin requiert des colonnes et des index identiques pour une correspondance. Si correspondance ne doit concerner que le contenu des lignes, une méthode pour obtenir le masque de filtrage des lignes présentes consiste à convertir les lignes en un (Multi) Index:

In [77]: df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 'col2' : [10, 11, 12, 13, 14, 10]})
In [78]: df2 = pandas.DataFrame(data = {'col1' : [1, 3, 4], 'col2' : [10, 12, 13]})
In [79]: df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)]
Out[79]:
   col1  col2
1     2    11
4     5    14
5     3    10

Si index doit être pris en compte, set_index a l'argument de mot clé append pour ajouter des colonnes à l'index existant. Si les colonnes ne sont pas alignées, liste (df.columns) peut être remplacée par des spécifications de colonne pour aligner les données.

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

pourrait également être utilisé pour créer les indices, bien que je doute que cela soit plus efficace.

11
Rune Lyngsoe

Supposons que vous avez deux images, df_1 et df_2 ayant plusieurs champs (nom_colonne) et que vous souhaitez rechercher les seules entrées de df_1 qui ne se trouvent pas dans df_2 sur la base de certains champs (par exemple, champs_x, champs_y), suivez les étapes suivantes.

Étape1.Ajoutez une colonne clé1 et clé2 à df_1 et à df_2 respectivement.

Step2.Merge les images comme indiqué ci-dessous. field_x et field_y sont nos colonnes souhaitées.

Step3.Sélectionnez uniquement les lignes de df_1 où key1 n'est pas égal à key2.

Step4.Drop key1 et key2.

Cette méthode résoudra votre problème et fonctionnera rapidement, même avec des ensembles de données volumineux. Je l'ai essayé pour les cadres de données avec plus de 1 000 000 de lignes.

df_1['key1'] = 1
df_2['key2'] = 1
df_1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'left')
df_1 = df_1[~(df_1.key2 == df_1.key1)]
df_1 = df_1.drop(['key1','key2'], axis=1)
10

Vous pouvez également concatrer df1, df2:

x = pd.concat([df1, df2])

puis supprimez tous les doublons:

y = x.drop_duplicates(keep=False, inplace=False)
4
Semeon Balagula

un peu tard, mais il peut être intéressant de vérifier le paramètre "indicateur" de pd.merge.

Voir cette autre question pour un exemple: Comparez PandaS DataFrames et renvoie les lignes manquantes dans la première

3
jabellcu

vous pouvez le faire en utilisant isin (dict) method:

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

Explication:

In [75]: df2.to_dict('l')
Out[75]: {'col1': [1, 2, 3], 'col2': [10, 11, 12]}

In [76]: df1.isin(df2.to_dict('l'))
Out[76]:
    col1   col2
0   True   True
1   True   True
2   True   True
3  False  False
4  False  False

In [77]: df1.isin(df2.to_dict('l')).all(1)
Out[77]:
0     True
1     True
2     True
3    False
4    False
dtype: bool
3
MaxU

Voici une autre façon de résoudre ce problème:

df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

Ou:

df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]
1
Sergey Zakharov

Que dis-tu de ça:

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 
                               'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 
                               'col2' : [10, 11, 12]})
records_df2 = set([Tuple(row) for row in df2.values])
in_df2_mask = np.array([Tuple(row) in records_df2 for row in df1.values])
result = df1[~in_df2_mask]
1
adamwlev

Pour ce faire, j'ajoute une nouvelle colonne propre à un seul cadre de données et l'utilise pour choisir de conserver ou non une entrée.

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

Cela fait que chaque entrée de df1 a un code - 0 s'il est unique à df1, 1 s'il se trouve dans les deux cadres de données. Vous utilisez ensuite ceci pour limiter à ce que vous voulez

answer = nonuni[nonuni['Empt'] == 0]
0
r.rz