les pandas obtiennent des lignes qui ne sont PAS dans d'autres trames de données


230

J'ai deux trames de données pandas qui ont des lignes en commun.

Supposons que dataframe2 soit 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]})

1
@TedPetrou Je ne vois pas comment la réponse que vous avez fournie est la bonne. Si j'ai deux trames de données dont l'une est un sous-ensemble de l'autre, je dois supprimer toutes ces lignes, qui se trouvent dans le sous-ensemble. Je ne veux pas supprimer les doublons. Je veux complètement supprimer le sous-ensemble.
juke

Réponses:


172

Une méthode consisterait à stocker le résultat d'une fusion interne à partir des deux dfs, puis nous pouvons simplement sélectionner les lignes lorsque les valeurs d'une colonne ne sont pas dans cette commune:

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

ÉDITER

Une autre méthode que vous avez trouvée consiste à utiliser isince qui produira des NaNlignes 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 démarre 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

13
df1[~df1.isin(df2)].dropna(how = 'all')semble faire l'affaire. Merci quand même - votre réponse m'a aidé à trouver une solution.
pensez à de belles choses

5
Notez que l'utilisation isinnécessite que les deux dfs commencent avec les mêmes valeurs de ligne, par exemple si df2 était df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})alors votre méthode ne fonctionnera pas
EdChum

2
cela a converti tous les pouces en flotteurs!
Chris Nielsen

3
@SergeyZakharov cette réponse postée il y a près de 3 ans était correcte en ce qui concerne le PO et pour leur problème, l'autre réponse est une meilleure réponse et gère un problème plus large qui n'a jamais fait partie de la question d'origine, il est incorrect de déclarer que ce la réponse est fausse, elle est correcte compte tenu du problème tel qu'il est exposé. De plus quelqu'un a downvoted cela sans explication, il y a peu que je peux le faire , car cela est une réponse acceptée, l'OP n'a pas changé d'avis et je ne vais pas cannibaliser une autre réponse à faire droit .
EdChum

1
@Cecilia vous devez passer keep=False: df0.append(df1).drop_duplicates(keep=False)par défaut , il conserve le premier doublon, vous voulez supprimer tous les doublons
EdChum

190

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 nous assurant d'obtenir d'abord uniquement les lignes uniques pour df2.

Tout d'abord, nous devons modifier le DataFrame d'origine pour ajouter la ligne avec les données [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 df2afin que chaque ligne de df1jointures avec exactement 1 ligne de df2. Utilisez le paramètre indicatorpour renvoyer une colonne supplémentaire indiquant de quelle table provient la ligne.

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éez 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, qui est unique mais contient les valeurs des deux colonnes de, df2expose 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 obtient le même mauvais résultat:

df1.isin(df2.to_dict('l')).all(1)

2
mais, je suppose, ils supposaient que le col1 est unique étant un indice (non mentionné dans la question, mais évident). Donc, s'il n'y a jamais un tel cas où il y a deux valeurs de col2 pour la même valeur de col1 (il ne peut pas y avoir deux col1 = 3 lignes) les réponses ci-dessus sont correctes.
pashute

14
Ce n'est certainement pas évident, donc votre argument n'est pas valable. Ma solution se généralise à plus de cas.
Ted Petrou

Question, ne serait-il pas plus facile de créer une tranche plutôt qu'un tableau booléen? Puisque l'objectif est d'obtenir les lignes.
Matías Romo

5
Utilisez df_all[df_all['_merge'] == 'left_only']pour avoir un df avec les résultats
gies0r

77

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

df1[~df1.index.isin(df2.index)]

1
@ChrisNielsen négation de la condition. Donc, dans cet exemple, cela signifie "prendre les lignes à partir df1desquelles les index ne sont PAS df2.index". Plus d'informations sur la négation: stackoverflow.com/q/19960077/304209 (étonnamment, je n'ai trouvé aucune mention de tilde dans les documents pandas).
Dennis Golomazov

On dirait que le dfs doit avoir la même longueur, non? Je reçoisValueError: Item wrong length x instead of y.
wordsforthewise

@wordsforthewise non, ils ne le font pas. Le masque a la longueur de df1 et est également appliqué à df1. Pouvez-vous donner votre exemple?
Dennis Golomazov

Pour corriger le problème de longueur de l'article, vous devez ajouter .loc
Moreno

13

Comme déjà indiqué, isin requiert que les colonnes et les indices soient identiques pour une correspondance. Si la correspondance ne doit concerner que le contenu des lignes, une façon d'obtenir le masque pour filtrer les 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 l'index doit être pris en compte, set_index a un argument de mot-clé ajouté pour ajouter des colonnes à l'index existant. Si les colonnes ne s'alignent pas, la 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 ce soit plus efficace.


@ Dev_123 Supprimez le ~ au début. Le noyau consiste à créer une liste de prédicats indiquant si les lignes de df1 se produisent également dans df2, de sorte que les lignes de df1 ne sont pas uniques à df1, ~ annule cela à une liste de prédicats indiquant si les lignes de df1 n'apparaissent pas dans df2.
Rune Lyngsoe

11

Supposons que vous ayez deux cadres de données, df_1 et df_2 ayant plusieurs champs (noms_colonnes) et que vous souhaitez rechercher les seules entrées dans df_1 qui ne sont pas dans df_2 sur la base de certains champs (par exemple, champs_x, champs_y), suivez les étapes suivantes.

Étape 1: ajoutez une colonne key1 et key2 à df_1 et df_2 respectivement.

Étape 2: fusionnez les cadres de données comme indiqué ci-dessous. field_x et field_y sont nos colonnes souhaitées.

Étape 3. Sélectionnez uniquement les lignes de df_1 où clé1 n'est pas égale à clé2.

Step4.Drop key1 et key2.

Cette méthode résoudra votre problème et fonctionne rapidement même avec des ensembles de données volumineux. Je l'ai essayé pour les trames 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)

Je ne pense pas que c'est techniquement ce qu'il veut - il veut savoir quelles lignes étaient uniques à quel df. mais, je pense que cette solution renvoie un df de lignes qui étaient soit uniques au premier df ou au deuxième df.
Legit Stack


3

vous pouvez le faire en utilisant la méthode isin (dict) :

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

Cela produit le mauvais résultat. Voir mon explication ci-dessous.
Ted Petrou

2

Vous pouvez également concaténer df1, df2:

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

puis supprimez tous les doublons:

y = x.drop_duplicates(keep=False, inplace=False)

Bienvenue dans StackOverflow: si vous publiez du code, du XML ou des échantillons de données, veuillez mettre ces lignes en surbrillance dans l'éditeur de texte et cliquer sur le bouton "exemples de code" ({}) dans la barre d'outils de l'éditeur ou en utilisant Ctrl + K sur votre clavier pour formater correctement et la syntaxe le met en valeur!
WhatsThePoint

4
Cela renverra toutes les données qui se trouvent dans l'un ou l'autre ensemble, pas seulement les données qui se trouvent uniquement dans df1.
Jamie Marshall

1

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

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

0

Ma façon de procéder consiste à ajouter une nouvelle colonne unique à une trame de données et à l'utiliser pour choisir de conserver 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 en sorte que chaque entrée dans df1 a un code - 0 s'il est unique à df1, 1 s'il se trouve dans les deux dataFrames. Vous l'utilisez ensuite pour vous limiter à ce que vous voulez

answer = nonuni[nonuni['Empt'] == 0]

0
extraire les lignes différentes à l'aide de la fonction de fusion
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
enregistrer les lignes différentes dans CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.