Python Pandas - Encontre a diferença entre dois frames de dados

103

Tenho dois quadros de dados df1 e df2, onde df2 é um subconjunto de df1. Como obtenho um novo quadro de dados (df3), que é a diferença entre os dois quadros de dados?

Em outras palavras, um quadro de dados que possui todas as linhas / colunas em df1 que não estão em df2?

insira a descrição da imagem aqui

userPyGeo
fonte
3
A maneira mais fácil de fazer isso dependerá de como seus dataframes estão estruturados (ou seja, se os índices podem ser usados, etc.). Este é um bom exemplo de porque você deve sempre incluir um exemplo reproduzível nas perguntas dos pandas.
cmaher de
1
Eu adicionei a imagem de amostra do dataframe
userPyGeo
semelhante a stackoverflow.com/q/20225110
SpeedCoder5

Respostas:

163

Usando drop_duplicates

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

Update :

Above method only working for those dataframes they do not have duplicate itself, For example

df1=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})
df2=pd.DataFrame({'A':[1],'B':[2]})

A saída será como abaixo, o que está errado

Saída errada:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

Saída correta

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

Como conseguir isso?

Método 1: usando isincomtuple

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]
Out[657]: 
   A  B
1  2  3
2  3  4
3  3  4

Método 2: mergecomindicator

df1.merge(df2,indicator = True, how='left').loc[lambda x : x['_merge']!='both']
Out[421]: 
   A  B     _merge
1  2  3  left_only
2  3  4  left_only
3  3  4  left_only
BENY
fonte
4
Você também pode determinar quais colunas devem ser consideradas, ao procurar duplicatas:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn
1
@Szpaqn observe que este método não tratará o caso especial. :-)
BENY
Observe que isso pode fazer com que linhas inesperadas permaneçam no resultado se um dos seus tipos de dados for float(because 12.00000000001 != 12). Uma prática melhor é encontrar a interseção do conjunto dos IDs em dois quadros de dados e obter a diferença com base nisso.
Jiāgěng
1
@DtechNet você precisa fazer dois frames de dados com o mesmo nome
BENY 16/10/19
2
O método 2 ( indicator=True) é uma ferramenta muito versátil e útil, eu adoraria vê-lo no início desta resposta, mas com junção 'externa' e não 'esquerda' para cobrir todas as 3 situações.
mirekphd
34

Para linhas, tente isto, onde Nameé a coluna de índice comum (pode ser uma lista para várias colunas comuns ou especifique left_one right_on):

m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)

A indicator=Trueconfiguração é útil porque adiciona uma coluna chamada _merge, com todas as mudanças entre df1e df2, categorizada em 3 tipos possíveis: "left_only", "right_only" ou "both".

Para colunas, tente isto:

set(df1.columns).symmetric_difference(df2.columns)
jpp
fonte
9
Downvoter se importa em comentar? mergecom indicator=Trueé a solução clássica para comparar dataframes por campos dados.
jpp de
9

Resposta aceita O Método 1 não funcionará para quadros de dados com NaNs dentro, como pd.np.nan != pd.np.nan. Não tenho certeza se esta é a melhor maneira, mas pode ser evitada

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]
toecsnar42
fonte
7

edit2, descobri uma nova solução sem a necessidade de definir o índice

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

Ok, descobri que a resposta do maior voto já contém o que descobri. Sim, só podemos usar este código com a condição de que não haja duplicatas em cada dois dfs.


Eu tenho um método complicado. Primeiro, definimos 'Nome' como o índice de dois dataframes dados pela pergunta. Como temos o mesmo 'Nome' em dois dfs, podemos simplesmente remover o índice do df 'menor' do df 'maior'. Aqui está o código.

df1.set_index('Name',inplace=True)
df2.set_index('Name',inplace=True)
newdf=df1.drop(df2.index)
liangli
fonte
1
provavelmente você quis dizer pd.concat ([df1, df2]). drop_duplicates (keep = False)
Manaslu
4
import pandas as pd
# given
df1 = pd.DataFrame({'Name':['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa',],
    'Age':[23,45,12,34,27,44,28,39,40]})
df2 = pd.DataFrame({'Name':['John','Smith','Wale','Tom','Menda','Yuswa',],
    'Age':[23,12,34,44,28,40]})

# find elements in df1 that are not in df2
df_1notin2 = df1[~(df1['Name'].isin(df2['Name']) & df1['Age'].isin(df2['Age']))].reset_index(drop=True)

# output:
print('df1\n', df1)
print('df2\n', df2)
print('df_1notin2\n', df_1notin2)

# df1
#     Age   Name
# 0   23   John
# 1   45   Mike
# 2   12  Smith
# 3   34   Wale
# 4   27  Marry
# 5   44    Tom
# 6   28  Menda
# 7   39   Bolt
# 8   40  Yuswa
# df2
#     Age   Name
# 0   23   John
# 1   12  Smith
# 2   34   Wale
# 3   44    Tom
# 4   28  Menda
# 5   40  Yuswa
# df_1notin2
#     Age   Name
# 0   45   Mike
# 1   27  Marry
# 2   39   Bolt
SpeedCoder5
fonte
O que '~' significa?
Piotrek Leśniak
'~' não é para indexação booleana. Veja: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5
3

Talvez uma linha mais simples, com nomes de coluna idênticos ou diferentes. Funcionou mesmo quando df2 ['Name2'] continha valores duplicados.

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)
Cherif Diallo
fonte
2
simples e eficaz. Erros adicionados = 'ignorar' para resolver o problema para o caso em que os valores de destino não estão na origem (ou seja, interseção) e redefinir o índice no final traz um df que é semelhante ao original.
MrE de
1

Como mencionado aqui que

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

é a solução correta, mas produzirá uma saída errada se

df1=pd.DataFrame({'A':[1],'B':[2]})
df2=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})

Nesse caso, a solução acima fornecerá Empty DataFrame , em vez disso, você deve usar o concatmétodo após remover duplicatas de cada datframe.

Usar concate with drop_duplicates

df1=df1.drop_duplicates(keep="first") 
df2=df2.drop_duplicates(keep="first") 
pd.concat([df1,df2]).drop_duplicates(keep=False)
Arun Pal
fonte
O autor da pergunta solicitada a retornar todos os valores em df1 que não estão em df2. Portanto, df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]é a resposta correta, mesmo neste caso. Se você deseja obter valores que estão em df1 ou df2, mas não em ambos, sua abordagem sugerida está correta (com a ressalva de remover duplicatas dos dataframes originais).
ira
0

Uma ligeira variação da solução do nice @ liangli que não requer a alteração do índice de dataframes existentes:

newdf = df1.drop(df1.join(df2.set_index('Name').index))
Serge Ballesta
fonte
0

Encontrando diferença por índice. Assumindo que df1 é um subconjunto de df2 e os índices são transportados durante o subconjunto

df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

# Example

df1 = pd.DataFrame({"gender":np.random.choice(['m','f'],size=5), "subject":np.random.choice(["bio","phy","chem"],size=5)}, index = [1,2,3,4,5])

df2 =  df1.loc[[1,3,5]]

df1

 gender subject
1      f     bio
2      m    chem
3      f     phy
4      m     bio
5      f     bio

df2

  gender subject
1      f     bio
3      f     phy
5      f     bio

df3 = df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

df3

  gender subject
2      m    chem
4      m     bio

DOS
fonte
0

Além da resposta aceita, gostaria de propor mais uma solução mais ampla que pode encontrar uma diferença de conjunto 2D de dois dataframes com qualquer index/ columns(eles podem não coincidir para ambos os dados). Além disso, o método permite configurar tolerância para floatelementos para comparação de dataframe (ele usa np.isclose)


import numpy as np
import pandas as pd

def get_dataframe_setdiff2d(df_new: pd.DataFrame, 
                            df_old: pd.DataFrame, 
                            rtol=1e-03, atol=1e-05) -> pd.DataFrame:
    """Returns set difference of two pandas DataFrames"""

    union_index = np.union1d(df_new.index, df_old.index)
    union_columns = np.union1d(df_new.columns, df_old.columns)

    new = df_new.reindex(index=union_index, columns=union_columns)
    old = df_old.reindex(index=union_index, columns=union_columns)

    mask_diff = ~np.isclose(new, old, rtol, atol)

    df_bool = pd.DataFrame(mask_diff, union_index, union_columns)

    df_diff = pd.concat([new[df_bool].stack(),
                         old[df_bool].stack()], axis=1)

    df_diff.columns = ["New", "Old"]

    return df_diff

Exemplo:

In [1]

df1 = pd.DataFrame({'A':[2,1,2],'C':[2,1,2]})
df2 = pd.DataFrame({'A':[1,1],'B':[1,1]})

print("df1:\n", df1, "\n")

print("df2:\n", df2, "\n")

diff = get_dataframe_setdiff2d(df1, df2)

print("diff:\n", diff, "\n")
Out [1]

df1:
   A  C
0  2  2
1  1  1
2  2  2 

df2:
   A  B
0  1  1
1  1  1 

diff:
     New  Old
0 A  2.0  1.0
  B  NaN  1.0
  C  2.0  NaN
1 B  NaN  1.0
  C  1.0  NaN
2 A  2.0  NaN
  C  2.0  NaN 
Luchko
fonte