pandas obtêm linhas que NÃO estão em outro dataframe

229

Eu tenho dois quadros de dados de pandas que têm algumas linhas em comum.

Suponha que dataframe2 seja um subconjunto de dataframe1.

Como posso obter as linhas do dataframe1 que não estão no 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]})
pense em coisas legais
fonte
1
@TedPetrou Não vejo como a resposta que você forneceu é a correta. Se eu tiver dois quadros de dados, dos quais um é um subconjunto do outro, preciso remover todas as linhas que estão no subconjunto. Não quero remover duplicatas. Eu quero remover completamente o subconjunto.
Jukebox

Respostas:

172

Um método seria armazenar o resultado de um formulário de mesclagem interna de ambos os dfs; então, podemos simplesmente selecionar as linhas quando os valores de uma coluna não estiverem tão comuns:

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

EDITAR

Outro método que você encontrou é usar o isinque produzirá NaNlinhas que você pode soltar:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

No entanto, se o df2 não iniciar linhas da mesma maneira, isso não funcionará:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

produzirá o df inteiro:

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
EdChum
fonte
13
df1[~df1.isin(df2)].dropna(how = 'all')parece fazer o truque. De qualquer forma, obrigado - sua resposta me ajudou a encontrar uma solução.
pense em coisas legais
5
Note que o uso isinrequer que ambos os dfs começar com os mesmos valores de linha assim, por exemplo, se df2 era df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})então o seu método não vai funcionar
EdChum
2
isso converteu todas as entradas em carros alegóricos!
Chris Nielsen
3
@SergeyZakharov, esta resposta postada quase 3 anos atrás estava correta no que diz respeito ao OP e para o problema deles, a outra resposta é uma resposta melhor e lida com um problema mais amplo que nunca fez parte da pergunta original, é incorreto afirmar que isso resposta está errada, está correta, dado o problema conforme estabelecido. Além disso alguém downvoted isso sem explicação, há pouco que posso fazer como este é uma resposta aceito, o OP não mudou sua mente e eu não estou indo para canibalizar outra resposta para torná-lo direito .
EdChum 29/04
1
@Cecilia você precisa passar keep=False: df0.append(df1).drop_duplicates(keep=False), por padrão, ele mantém o primeiro duplicado, você quer deixar cair todas as duplicatas
EdChum
189

A solução atualmente selecionada produz resultados incorretos. Para resolver corretamente esse problema, podemos executar uma junção esquerda de df1para df2, certificando-se de obter primeiro apenas as linhas exclusivas df2.

Primeiro, precisamos modificar o DataFrame original para adicionar a linha aos dados [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

Execute uma junção esquerda, eliminando duplicatas df2para que cada linha de df1junções tenha exatamente 1 linha de df2. Use o parâmetro indicatorpara retornar uma coluna extra indicando de qual tabela a linha era.

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

Crie uma condição booleana:

df_all['_merge'] == 'left_only'

0    False
1    False
2    False
3     True
4     True
5     True
Name: _merge, dtype: bool

Por que outras soluções estão erradas

Algumas soluções cometem o mesmo erro - elas apenas verificam se cada valor está independentemente em cada coluna, não juntas na mesma linha. A adição da última linha, única, mas com os valores de ambas as colunas, df2expõe o erro:

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

Esta solução obtém o mesmo resultado errado:

df1.isin(df2.to_dict('l')).all(1)
Ted Petrou
fonte
2
mas, suponho, eles estavam assumindo que o col1 é único, sendo um índice (não mencionado na pergunta, mas óbvio). Portanto, se nunca houver um caso em que haja dois valores de col2 para o mesmo valor de col1 (não pode haver duas col1 = 3 linhas), as respostas acima estão corretas.
Pashute
14
Certamente não é óbvio, então seu argumento é inválido. Minha solução generaliza para mais casos.
Ted Petrou #
Pergunta, não seria mais fácil criar uma fatia do que uma matriz booleana? Uma vez que o objetivo é obter as linhas.
Matías Romo
5
Use df_all[df_all['_merge'] == 'left_only']a ter uma df com os resultados
gies0r
77

Supondo que os índices sejam consistentes nos quadros de dados (sem levar em consideração os valores reais da coluna):

df1[~df1.index.isin(df2.index)]
Dennis Golomazov
fonte
1
@ChrisNielsen negação da condição. Portanto, neste exemplo, significa "pegue as linhas nas df1quais os índices NÃO estão df2.index". Mais sobre negação: stackoverflow.com/q/19960077/304209 (surpreendentemente, não consegui encontrar nenhuma menção a til nos documentos do pandas).
Dennis Golomazov 8/17
Parece que o DFS tem que ter o mesmo comprimento, não? Estou recebendoValueError: Item wrong length x instead of y.
wordsforthewise
@wordsforhewise não, eles não. A máscara tem o comprimento de df1 e é aplicada a df1 também. Você pode dar o seu exemplo?
Dennis Golomazov 14/09
Para corrigir o problema comprimento item que você deve adicionar .loc
Moreno
13

Como já foi sugerido, o isin exige que as colunas e os índices sejam os mesmos para uma correspondência. Se a correspondência só deve estar no conteúdo das linhas, uma maneira de obter a máscara para filtrar as linhas presentes é convertê-las em um (Multi) Índice:

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

Se o índice deve ser levado em consideração, set_index possui um argumento de palavra-chave anexado para anexar colunas ao índice existente. Se as colunas não se alinharem, a lista (df.columns) poderá ser substituída pelas especificações da coluna para alinhar os dados.

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

alternativamente, poderia ser usado para criar os índices, embora eu duvide que isso seja mais eficiente.

Rune Lyngsoe
fonte
@ Dev_123 Remova o ~ no início. O núcleo é criar uma lista de predicados se as linhas no df1 também ocorrem no df2; portanto, as linhas no df1 não são exclusivas do df1, ~ nega isso a uma lista de predicados, se as linhas no df1 não ocorrem no df2.
Rune Lyngsoe
11

Suponha que você tenha dois quadros de dados, df_1 e df_2 com vários campos (nome_da_coluna) e deseje encontrar apenas as entradas no df_1 que não estão no df_2 com base em alguns campos (por exemplo, campos_x, campos_y), siga as etapas a seguir.

Etapa1.Adicione uma coluna key1 e key2 a df_1 e df_2 respectivamente.

Etapa 2.Merge os quadros de dados conforme mostrado abaixo. field_x e field_y são nossas colunas desejadas.

Etapa 3. Selecione apenas as linhas de df_1 em que chave1 não é igual a chave2.

Passo4.Drop chave1 e chave2.

Este método resolverá seu problema e funciona rapidamente, mesmo com grandes conjuntos de dados. Eu tentei para quadros de dados com mais de 1.000.000 de linhas.

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)
Pragalbh kulshrestha
fonte
Eu não acho que isso é tecnicamente o que ele quer - ele quer saber quais linhas foram exclusivas para qual df. mas acho que essa solução retorna um df de linhas que eram únicas para o primeiro df ou o segundo df.
Legit Stack
3

você pode fazer isso usando o método isin (dict) :

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

Explicação:

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
MaxU
fonte
Isso produz o resultado errado. Veja minha explicação abaixo.
precisa
2

Você também pode concat df1, df2:

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

e remova todas as duplicatas:

y = x.drop_duplicates(keep=False, inplace=False)
Semeon Balagula
fonte
Bem-vindo ao StackOverflow: se você postar amostras de código, XML ou dados, realce essas linhas no editor de texto e clique no botão "amostras de código" ({}) na barra de ferramentas do editor ou usando Ctrl + K no teclado para formatar de forma adequada e sintaxe destacá-lo!
WhatsThePoint
4
Isso retornará todos os dados que estão em um dos conjuntos, não apenas os dados que estão apenas no df1.
Jamie Marshall
1

Que tal agora:

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]
adamwlev
fonte
1

Aqui está outra maneira de resolver isso:

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)]
Sergey Zakharov
fonte
0

Minha maneira de fazer isso envolve adicionar uma nova coluna exclusiva a um dataframe e usá-la para escolher se deseja manter uma entrada

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

Isso faz com que cada entrada no df1 tenha um código - 0 se for exclusivo do df1, 1 se estiver nos dois DataFrames. Você então usa isso para restringir o que deseja

answer = nonuni[nonuni['Empt'] == 0]
r.rz
fonte
0
extrair as linhas diferentes usando a função de mesclagem
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
salve as linhas diferentes no CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Gajanan Kothawade
fonte