web-dev-qa-db-fra.com

Comment supprimer un cadre de données pandas d'un autre cadre de données

Comment supprimer un cadre de données pandas d'un autre cadre de données, tout comme la soustraction d'ensemble:

a=[1,2,3,4,5]
b=[1,5]
a-b=[2,3,4]

Et maintenant, nous avons deux pandas dataframe, comment supprimer df2 de df1:

In [5]: df1=pd.DataFrame([[1,2],[3,4],[5,6]],columns=['a','b'])
In [6]: df1
Out[6]:
   a  b
0  1  2
1  3  4
2  5  6


In [9]: df2=pd.DataFrame([[1,2],[5,6]],columns=['a','b'])
In [10]: df2
Out[10]:
   a  b
0  1  2
1  5  6

Alors nous nous attendons à ce que le résultat de df1-df2 soit:

In [14]: df
Out[14]:
   a  b
0  3  4

Comment faire? 

Je vous remercie.

12
176coding

Solution

Utilisez pd.concat suivi de drop_duplicates(keep=False)

pd.concat([df1, df2, df2]).drop_duplicates(keep=False)

Ça ressemble à

   a  b
1  3  4

Explication

pd.concat ajoute les deux DataFrames en ajoutant les uns après les autres. s'il y a un chevauchement, il sera capturé par la méthode drop_duplicates. Cependant, drop_duplicates laisse par défaut la première observation et supprime toutes les autres observations. Dans ce cas, nous voulons que chaque copie soit supprimée. Par conséquent, le paramètre keep=False qui fait exactement cela.

Une note spéciale pour le df2 répété. Avec un seul df2, toute ligne dans df2 ne figurant pas dans df1 ne sera pas considérée comme un doublon et restera. Cette solution avec un seul df2 ne fonctionne que lorsque df2 est un sous-ensemble de df1. Cependant, si nous concatrons df2 deux fois, il est garanti qu'il s'agit d'un doublon et sera ensuite supprimé.

20
piRSquared

Vous pouvez utiliser .duplicated, qui présente l'avantage d'être assez expressif:

%%timeit
combined = df1.append(df2)
combined[~combined.index.duplicated(keep=False)]

1000 loops, best of 3: 875 µs per loop

En comparaison:

%timeit df1.loc[pd.merge(df1, df2, on=['a','b'], how='left', indicator=True)['_merge'] == 'left_only']

100 loops, best of 3: 4.57 ms per loop


%timeit pd.concat([df1, df2, df2]).drop_duplicates(keep=False)

1000 loops, best of 3: 987 µs per loop


%timeit df2[df2.apply(lambda x: x.value not in df2.values, axis=1)]

1000 loops, best of 3: 546 µs per loop

En résumé, l’utilisation de la comparaison np.array est la plus rapide. Vous n'avez pas besoin du .tolist() ici.

5
Stefan

Une approche logique définie. Tournez les lignes de df1 et df2 en ensembles. Utilisez ensuite la soustraction set pour définir une nouvelle DataFrame

idx1 = set(df1.set_index(['a', 'b']).index)
idx2 = set(df2.set_index(['a', 'b']).index)

pd.DataFrame(list(idx1 - idx2), columns=df1.columns)

   a  b
0  3  4
2
piRSquared

Mon coup avec merge df1 et df2 de la question.

Utilisation du paramètre 'indicateur'

In [74]: df1.loc[pd.merge(df1, df2, on=['a','b'], how='left', indicator=True)['_merge'] == 'left_only']
Out[74]: 
   a  b
1  3  4
2
knagaev

Une approche de masquage

df1[df1.apply(lambda x: x.values.tolist() not in df2.values.tolist(), axis=1)]

   a  b
1  3  4
1
piRSquared

Je pense que le premier tolist() doit être supprimé, mais conservez le second:

df1[df1.apply(lambda x: x.values() not in df2.values.tolist(), axis=1)]
0
Peter Abdou