web-dev-qa-db-fra.com

Comparer Python Pandas DataFrames pour les lignes correspondantes

J'ai ce DataFrame (df1) Dans Pandas:

df1 = pd.DataFrame(np.random.Rand(10,4),columns=list('ABCD'))
print df1

       A         B         C         D
0.860379  0.726956  0.394529  0.833217
0.014180  0.813828  0.559891  0.339647
0.782838  0.698993  0.551252  0.361034
0.833370  0.982056  0.741821  0.006864
0.855955  0.546562  0.270425  0.136006
0.491538  0.445024  0.971603  0.690001
0.911696  0.065338  0.796946  0.853456
0.744923  0.545661  0.492739  0.337628
0.576235  0.219831  0.946772  0.752403
0.164873  0.454862  0.745890  0.437729

Je voudrais vérifier si une ligne (toutes les colonnes) d'une autre trame de données (df2) Est présente dans df1. Voici df2:

df2 = df1.ix[4:8]
df2.reset_index(drop=True,inplace=True)
df2.loc[-1] = [2, 3, 4, 5]
df2.loc[-2] = [14, 15, 16, 17]
df2.reset_index(drop=True,inplace=True)
print df2

           A         B         C         D
    0.855955  0.546562  0.270425  0.136006
    0.491538  0.445024  0.971603  0.690001
    0.911696  0.065338  0.796946  0.853456
    0.744923  0.545661  0.492739  0.337628
    0.576235  0.219831  0.946772  0.752403
    2.000000  3.000000  4.000000  5.000000
   14.000000 15.000000 16.000000 17.000000

J'ai essayé d'utiliser df.lookup Pour rechercher une ligne à la fois. Je l'ai fait de cette façon:

list1 = df2.ix[0].tolist()
cols = df1.columns.tolist()
print df1.lookup(list1, cols)

mais j'ai reçu ce message d'erreur:

  File "C:\Users\test.py", line 19, in <module>
    print df1.lookup(list1, cols)
  File "C:\python27\lib\site-packages\pandas\core\frame.py", line 2217, in lookup
    raise KeyError('One or more row labels was not found')
KeyError: 'One or more row labels was not found'

J'ai également essayé .all() en utilisant:

print (df2 == df1).all(1).any()

mais j'ai reçu ce message d'erreur:

  File "C:\Users\test.py", line 12, in <module>
    print (df2 == df1).all(1).any()
  File "C:\python27\lib\site-packages\pandas\core\ops.py", line 884, in f
    return self._compare_frame(other, func, str_rep)
  File "C:\python27\lib\site-packages\pandas\core\frame.py", line 3010, in _compare_frame
    raise ValueError('Can only compare identically-labeled '
ValueError: Can only compare identically-labeled DataFrame objects

J'ai également essayé isin() comme ceci:

print df2.isin(df1)

mais j'ai eu False partout, ce qui n'est pas correct:

    A      B      C      D
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False

Est-il possible de rechercher un ensemble de lignes dans un DataFrame, en le comparant aux lignes d'un autre dataframe?

EDIT: Est-il possible de supprimer df2 Lignes si ces lignes sont également présentes dans df1?

25
edesz

Une solution possible à votre problème serait d'utiliser merge . Vérifier si une ligne (toutes les colonnes) d'une autre trame de données (df2) est présente dans df1 équivaut à déterminer l'intersection des deux trames de données. Cela peut être accompli en utilisant la fonction suivante:

pd.merge(df1, df2, on=['A', 'B', 'C', 'D'], how='inner')

Par exemple, si df1 était

    A           B            C          D
0   0.403846    0.312230    0.209882    0.397923
1   0.934957    0.731730    0.484712    0.734747
2   0.588245    0.961589    0.910292    0.382072
3   0.534226    0.276908    0.323282    0.629398
4   0.259533    0.277465    0.043652    0.925743
5   0.667415    0.051182    0.928655    0.737673
6   0.217923    0.665446    0.224268    0.772592
7   0.023578    0.561884    0.615515    0.362084
8   0.346373    0.375366    0.083003    0.663622
9   0.352584    0.103263    0.661686    0.246862

et df2 a été défini comme:

     A          B            C           D
0   0.259533    0.277465    0.043652    0.925743
1   0.667415    0.051182    0.928655    0.737673
2   0.217923    0.665446    0.224268    0.772592
3   0.023578    0.561884    0.615515    0.362084
4   0.346373    0.375366    0.083003    0.663622
5   2.000000    3.000000    4.000000    5.000000
6   14.000000   15.000000   16.000000   17.000000

La fonction pd.merge(df1, df2, on=['A', 'B', 'C', 'D'], how='inner') produit:

     A           B           C           D
0   0.259533    0.277465    0.043652    0.925743
1   0.667415    0.051182    0.928655    0.737673
2   0.217923    0.665446    0.224268    0.772592
3   0.023578    0.561884    0.615515    0.362084
4   0.346373    0.375366    0.083003    0.663622

Les résultats sont toutes les lignes (toutes les colonnes) qui sont à la fois dans df1 et df2.

Nous pouvons également modifier cet exemple si les colonnes ne sont pas les mêmes dans df1 et df2 et comparer simplement les valeurs de ligne qui sont les mêmes pour un sous-ensemble des colonnes. Si nous modifions l'exemple d'origine:

df1 = pd.DataFrame(np.random.Rand(10,4),columns=list('ABCD'))
df2 = df1.ix[4:8]
df2.reset_index(drop=True,inplace=True)
df2.loc[-1] = [2, 3, 4, 5]
df2.loc[-2] = [14, 15, 16, 17]
df2.reset_index(drop=True,inplace=True)
df2 = df2[['A', 'B', 'C']] # df2 has only columns A B C

Ensuite, nous pouvons regarder les colonnes communes en utilisant common_cols = list(set(df1.columns) & set(df2.columns)) entre les deux dataframes puis fusionner:

pd.merge(df1, df2, on=common_cols, how='inner')

EDIT: Nouvelle question (commentaires), après avoir identifié les lignes de df2 qui étaient également présentes dans la première trame de données (df1), est-il possible de prendre la résultat de pd.merge (), puis de supprimer les lignes de df2 qui sont également présentes dans df1

Je ne connais pas de moyen simple d'accomplir la tâche de suppression des lignes de df2 qui sont également présentes dans df1. Cela dit, vous pouvez utiliser les éléments suivants:

ds1 = set(Tuple(line) for line in df1.values)
ds2 = set(Tuple(line) for line in df2.values)
df = pd.DataFrame(list(ds2.difference(ds1)), columns=df2.columns)

Il existe probablement un meilleur moyen d'accomplir cette tâche, mais je ne connais pas une telle méthode/fonction.

EDIT 2: Comment supprimer les lignes de df2 qui sont également présentes dans df1 comme indiqué dans la réponse @WR.

La méthode fournie df2[~df2['A'].isin(df12['A'])] ne prend pas en compte tous les types de situations. Tenez compte des DataFrames suivants:

df1:

   A  B  C  D
0  6  4  1  6
1  7  6  6  8
2  1  6  2  7
3  8  0  4  1
4  1  0  2  3
5  8  4  7  5
6  4  7  1  1
7  3  7  3  4
8  5  2  8  8
9  3  2  8  4

df2:

   A  B  C  D
0  1  0  2  3
1  8  4  7  5
2  4  7  1  1
3  3  7  3  4
4  5  2  8  8
5  1  1  1  1
6  2  2  2  2

df12:

   A  B  C  D
0  1  0  2  3
1  8  4  7  5
2  4  7  1  1
3  3  7  3  4
4  5  2  8  8

L'utilisation des DataFrames ci-dessus dans le but de supprimer des lignes de df2 également présentes dans df1 entraînerait les conséquences suivantes:

   A  B  C  D
0  1  1  1  1
1  2  2  2  2

Les lignes (1, 1, 1, 1) et (2, 2, 2, 2) sont en df2 et non en df1. Malheureusement, l'utilisation de la méthode fournie (df2[~df2['A'].isin(df12['A'])]) entraîne:

   A  B  C  D
6  2  2  2  2

Cela se produit car la valeur de 1 dans la colonne A se trouve à la fois dans l'intersection DataFrame (c'est-à-dire (1, 0, 2, 3)) et df2 et supprime ainsi à la fois (1, 0, 2, 3) et (1, 1, 1, 1). Cela n'est pas voulu car la ligne (1, 1, 1, 1) n'est pas dans df1 et ne doit pas être supprimée.

Je pense que ce qui suit fournira une solution. Il crée une colonne factice qui est ensuite utilisée pour sous-définir le DataFrame aux résultats souhaités:

df12['key'] = 'x'
temp_df = pd.merge(df2, df12, on=df2.columns.tolist(), how='left')
temp_df[temp_df['key'].isnull()].drop('key', axis=1)
37
Andrew

@Andrew: Je pense avoir trouvé un moyen de supprimer les lignes d'une trame de données qui sont déjà présentes dans une autre (c'est-à-dire pour répondre à mon EDIT) sans utiliser de boucles - faites-moi savoir si vous n'êtes pas d'accord et/ou si mon OP + EDIT ne l'a pas clairement fait énoncer ceci:

CECI FONCTIONNE

Les colonnes des deux trames de données sont toujours les mêmes - A, B, C et D. Dans cet esprit, basé fortement sur l'approche d'Andrew, voici comment supprimer les lignes de df2 Qui sont également présentes dans df1:

common_cols = df1.columns.tolist()                         #generate list of column names
df12 = pd.merge(df1, df2, on=common_cols, how='inner')     #extract common rows with merge
df2 = df2[~df2['A'].isin(df12['A'])]

La ligne 3 fait ce qui suit:

  • Extraire uniquement les lignes de df2 Qui ne correspondent pas aux lignes de df1:
  • Pour que 2 lignes soient différentes, TOUTE colonne d'une ligne doit
    doit nécessairement être différente de la colonne correspondante d'une autre ligne.
  • Ici, j'ai choisi la colonne A pour faire cette comparaison - c'est
    possible d'utiliser n'importe quel nom de colonne, mais pas TOUS les
    noms de colonnes.

REMARQUE: cette méthode est essentiellement l'équivalent de SQL NOT IN().

8
edesz