web-dev-qa-db-fra.com

Pandas Anti-Jointures

J'ai deux tables et j'aimerais les ajouter afin que seules les données de la table A soient conservées et que les données de la table B ne soient ajoutées que si sa clé est unique (Les valeurs de clé sont uniques dans les tables A et B; La clé apparaîtra dans les tableaux A et B). 

Je pense que la manière de procéder implique une sorte de jointure de filtrage (anti-jointure) pour obtenir des valeurs dans le tableau B qui ne figurent pas dans le tableau A, puis ajouter les deux tableaux. 

Je connais R et c’est le code que j’utiliserais pour le faire en R.

library("dplyr")

## Filtering join to remove values already in "TableA" from "TableB"
FilteredTableB <- anti_join(TableB,TableA, by = "Key")

## Append "FilteredTableB" to "TableA"
CombinedTable <- bind_rows(TableA,FilteredTableB)

Comment pourrais-je y parvenir en python?

19
Ayelavan

Considérons les dataframes suivants

TableA = pd.DataFrame(np.random.Rand(4, 3),
                      pd.Index(list('abcd'), name='Key'),
                      ['A', 'B', 'C']).reset_index()
TableB = pd.DataFrame(np.random.Rand(4, 3),
                      pd.Index(list('aecf'), name='Key'),
                      ['A', 'B', 'C']).reset_index()

TableA

 enter image description here


TableB

 enter image description here

C'est une façon de faire ce que tu veux

Méthode 1

# Identify what values are in TableB and not in TableA
key_diff = set(TableB.Key).difference(TableA.Key)
where_diff = TableB.Key.isin(key_diff)

# Slice TableB accordingly and append to TableA
TableA.append(TableB[where_diff], ignore_index=True)

 enter image description here

Méthode 2

rows = []
for i, row in TableB.iterrows():
    if row.Key not in TableA.Key.values:
        rows.append(row)

pd.concat([TableA.T] + rows, axis=1).T

Timing

4 lignes avec 2 chevauchements

La méthode 1 est beaucoup plus rapide 

 enter image description here

10 000 lignes 5 000 chevauchements

les boucles sont mauvaises

 enter image description here

12
piRSquared

J'ai eu le même problème. Cette réponse utilisant how='outer' et indicator=True de fusion m'a inspiré pour proposer cette solution:

import pandas as pd
import numpy as np

TableA = pd.DataFrame(np.random.Rand(4, 3),
                      pd.Index(list('abcd'), name='Key'),
                      ['A', 'B', 'C']).reset_index()
TableB = pd.DataFrame(np.random.Rand(4, 3),
                      pd.Index(list('aecf'), name='Key'),
                      ['A', 'B', 'C']).reset_index()

print('TableA', TableA, sep='\n')
print('TableB', TableB, sep='\n')

TableB_only = pd.merge(
    TableA, TableB,
    how='outer', on='Key', indicator=True, suffixes=('_foo','')).query(
        '_merge == "right_only"')

print('TableB_only', TableB_only, sep='\n')

Table_concatenated = pd.concat((TableA, TableB_only), join='inner')

print('Table_concatenated', Table_concatenated, sep='\n')

Qui imprime cette sortie:

TableA
  Key         A         B         C
0   a  0.035548  0.344711  0.860918
1   b  0.640194  0.212250  0.277359
2   c  0.592234  0.113492  0.037444
3   d  0.112271  0.205245  0.227157
TableB
  Key         A         B         C
0   a  0.754538  0.692902  0.537704
1   e  0.499092  0.864145  0.004559
2   c  0.082087  0.682573  0.421654
3   f  0.768914  0.281617  0.924693
TableB_only
  Key  A_foo  B_foo  C_foo         A         B         C      _merge
4   e    NaN    NaN    NaN  0.499092  0.864145  0.004559  right_only
5   f    NaN    NaN    NaN  0.768914  0.281617  0.924693  right_only
Table_concatenated
  Key         A         B         C
0   a  0.035548  0.344711  0.860918
1   b  0.640194  0.212250  0.277359
2   c  0.592234  0.113492  0.037444
3   d  0.112271  0.205245  0.227157
4   e  0.499092  0.864145  0.004559
5   f  0.768914  0.281617  0.924693
4
tommy.carstensen

Réponse la plus simple que l'on puisse imaginer:

tableB = pd.concat([tableB, pd.Series(1)], axis=1)
mergedTable = tableA.merge(tableB, how="left" on="key")

answer = mergedTable[mergedTable.iloc[:,-1].isnull()][tableA.columns.tolist()]

Devrait être le plus rapide proposé aussi bien. 

2
Jamie Marshall

Vous aurez les deux tables TableA et TableB telles que les deux objets DataFrame aient des colonnes avec des valeurs uniques dans leurs tables respectives, mais certaines colonnes peuvent avoir des valeurs qui apparaissent simultanément (ont les mêmes valeurs pour une ligne) dans les deux tables. 

Ensuite, nous voulons fusionner les lignes dans TableA avec les lignes dans TableB qui ne correspondent à aucune dans TableA pour une colonne 'Clé'. Le concept est de l’imaginer en comparant deux séries de longueur variable et en combinant les lignes d’une série sA avec l’autre sB si les valeurs de sB ne correspondent pas à celles de sA. Le code suivant résout cet exercice:

import pandas as pd

TableA = pd.DataFrame([[2, 3, 4], [5, 6, 7], [8, 9, 10]])
TableB = pd.DataFrame([[1, 3, 4], [5, 7, 8], [9, 10, 0]])

removeTheseIndexes = []
keyColumnA = TableA.iloc[:,1] # your 'Key' column here
keyColumnB = TableB.iloc[:,1] # same

for i in range(0, len(keyColumnA)):
    firstValue = keyColumnA[i]
    for j in range(0, len(keyColumnB)):
        copycat = keyColumnB[j]
        if firstValue == copycat:
            removeTheseIndexes.append(j)

TableB.drop(removeTheseIndexes, inplace = True)
TableA = TableA.append(TableB)
TableA = TableA.reset_index(drop=True)

Notez que cela affecte également les données de TableB. Vous pouvez utiliser inplace=False et le réaffecter à une newTable, puis à TableA.append(newTable) alternativement.

# Table A
   0  1   2
0  2  3   4
1  5  6   7
2  8  9  10

# Table B
   0   1  2
0  1   3  4
1  5   7  8
2  9  10  0

# Set 'Key' column = 1
# Run the script after the loop

# Table A
   0   1   2
0  2   3   4
1  5   6   7
2  8   9  10
3  5   7   8
4  9  10   0

# Table B
   0   1  2
1  5   7  8
2  9  10  0
1
Jossie Calderon

Sur la base d’une des autres suggestions, voici une fonction qui devrait le faire. Utilisant uniquement des fonctions de pandas, pas de bouclage. Vous pouvez également utiliser plusieurs colonnes comme clé. Si vous modifiez la ligne output = merged.loc[merged.dummy_col.isna(),tableA.columns.tolist()] en output = merged.loc[~merged.dummy_col.isna(),tableA.columns.tolist()] , vous avez un semi-joint. 

def anti_join(tableA,tableB,on):

   #if joining on index, make it into a column
   if tableB.index.name is not None:
       dummy = tableB.reset_index()[on]
   else:
       dummy = tableB[on]

   #create a dummy columns of 1s
   if isinstance(dummy, pd.Series):
       dummy = dummy.to_frame()

   dummy.loc[:,'dummy_col'] = 1

   #preserve the index of tableA if it has one
   if tableA.index.name is not None:
       idx_name = tableA.index.name
       tableA = tableA.reset_index(drop = False)
   else:
       idx_name = None

   #do a left-join
   merged = tableA.merge(dummy,on=on,how='left')

   #keep only the non-matches
   output = merged.loc[merged.dummy_col.isna(),tableA.columns.tolist()]

   #reset the index (if applicable)
   if idx_name is not None:
       output = output.set_index(idx_name)

   return(output)
0
thrillhouse