Панды получают строки, которых нет в другом фрейме данных


230

У меня есть два фрейма данных панд, у которых есть несколько общих строк.

Предположим, dataframe2 является подмножеством dataframe1.

Как я могу получить строки dataframe1, которых нет в 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]})

1
@TedPetrou Я не вижу, как вы ответили правильно. Если у меня есть два кадра данных, один из которых является подмножеством другого, мне нужно удалить все те строки, которые находятся в этом подмножестве. Я не хочу удалять дубликаты. Я полностью хочу удалить подмножество.
Музыкальный автомат

Ответы:


172

Одним из методов было бы сохранить результат внутренней формы слияния обоих dfs, тогда мы можем просто выбрать строки, когда значения одного столбца находятся не в этом общем:

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

РЕДАКТИРОВАТЬ

Вы нашли еще один метод, isinкоторый будет использовать NaNстроки, которые вы можете удалить:

In [138]:

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

Однако, если df2 не запускает строки таким же образом, это не будет работать:

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

будет производить весь DF:

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

13
df1[~df1.isin(df2)].dropna(how = 'all')кажется, добивается цели. В любом случае, спасибо - ваш ответ помог мне найти решение.
Подумайте о хорошем

5
Обратите внимание, что использование isinтребует, чтобы оба dfs начинались с одинаковых значений строк, например, если df2 был, df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})тогда ваш метод не будет работать
EdChum

2
это преобразовало все целые числа в поплавки!
Крис Нильсен

3
@SergeyZakharov, этот ответ, опубликованный почти 3 года назад, был верным в том, что касается ОП и для их проблемы, другой ответ - лучший ответ и решает более широкую проблему, которая никогда не была частью первоначального вопроса, неверно утверждать, что это ответ неверный, он правильный, если проблема изложена. Кроме того, кто-то отклонил это без объяснения причин, я мало что могу сделать, так как это принятый ответ, ОП не передумал, и я не собираюсь каннибализировать другой ответ, чтобы сделать его правильным .
EdChum

1
@Cecilia вам нужно передать keep=False: df0.append(df1).drop_duplicates(keep=False)по умолчанию он сохраняет первый дубликат, вы хотите удалить все дубликаты
EdChum

190

Текущее выбранное решение дает неверные результаты. Чтобы правильно решить эту проблему, мы можем выполнить левое соединение от df1до df2, убедившись, что сначала получим только уникальные строки для df2.

Во-первых, нам нужно изменить исходный DataFrame, чтобы добавить строку с данными [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

Выполните левое соединение, исключив дубликаты, df2чтобы каждый ряд df1объединялся с ровно 1 строкой df2. Используйте параметр, indicatorчтобы получить дополнительный столбец, в котором указано, из какой таблицы была получена строка.

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

Создайте логическое условие:

df_all['_merge'] == 'left_only'

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

Почему другие решения неверны

Несколько решений допускают одну и ту же ошибку - они только проверяют, что каждое значение независимо в каждом столбце, а не вместе в одной строке. Добавление последней строки, которая является уникальной, но имеет значения из обоих столбцов, df2выявляет ошибку:

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

Это решение дает тот же неправильный результат:

df1.isin(df2.to_dict('l')).all(1)

2
но, я полагаю, они предполагали, что col1 является уникальным индексом (не упоминается в вопросе, но очевидно). Таким образом, если никогда не бывает такого случая, когда есть два значения col2 для одного и того же значения col1 (не может быть двух строк col1 = 3), ответы выше верны.
Пашуте

14
Это, конечно, неочевидно, поэтому ваша точка зрения неверна. Мое решение обобщает на большее количество случаев.
Тед Петру

Вопрос, не будет ли проще создать срез, а не логический массив? Поскольку цель состоит в том, чтобы получить строки.
Матиас Ромо

5
Используйте, df_all[df_all['_merge'] == 'left_only']чтобы иметь df с результатами
gies0r

77

Предполагая, что индексы согласованы в кадрах данных (без учета фактических значений col):

df1[~df1.index.isin(df2.index)]

1
@ChrisNielsen отрицание условия. Таким образом, в этом примере это означает «взять строки, из df1которых индексы НЕ находятся df2.index». Подробнее об отрицании: stackoverflow.com/q/19960077/304209 (удивительно, я не смог найти упоминаний о тильде в документах панд).
Денис Голомазов

Похоже, что DFS должны быть одинаковой длины, нет? Я получаюValueError: Item wrong length x instead of y.
wordsforthewise

@ слова, иначе нет. Маска имеет длину df1 и применяется также к df1. Можете ли вы привести свой пример?
Деннис Голомазов

Чтобы исправить проблему длины предмета, вы должны добавить .loc
Moreno

13

Как уже указывалось, isin требует, чтобы столбцы и индексы были одинаковыми для совпадения. Если соответствие должно быть только для содержимого строки, один из способов получить маску для фильтрации имеющихся строк - преобразовать строки в (мульти) индекс:

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

Если индекс должен быть принят во внимание, set_index имеет ключевое слово аргумент, добавляющий столбцы к существующему индексу. Если столбцы не совпадают, список (df.columns) можно заменить спецификациями столбцов для выравнивания данных.

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

в качестве альтернативы можно использовать для создания индексов, хотя я сомневаюсь, что это более эффективно.


@ Dev_123 Удалить ~ в начале. Суть заключается в том, чтобы создать список предикатов того, встречаются ли строки в df1 и в df2, поэтому строки в df1, не уникальные для df1, ~ сводит это к списку предикатов того, не появляются ли строки в df1 в df2.
Руна Лингсо

11

Предположим, у вас есть два кадра данных, df_1 и df_2, имеющие несколько полей (имена столбцов), и вы хотите найти только те записи в df_1, которые не находятся в df_2 на основе некоторых полей (например, fields_x, fields_y), выполните следующие шаги.

Шаг 1. Добавьте столбец key1 и key2 к df_1 и df_2 соответственно.

Step2.Merge фреймы данных, как показано ниже. field_x и field_y - наши желаемые столбцы.

Step3.Выберите только те строки из df_1, где key1 не равен key2.

Шаг 4. Удалите ключ1 и ключ2.

Этот метод решит вашу проблему и работает быстро даже с большими наборами данных. Я пробовал это для фреймов данных с более чем 1 000 000 строк.

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)

Я не думаю, что это технически то, что он хочет - он хочет знать, какие строки были уникальными для какого df. но я думаю, что это решение возвращает df строк, которые были уникальными для первого или второго df.
Legit Stack


3

Вы можете сделать это, используя метод isin (dict) :

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

Объяснение:

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

Это дает неправильный результат. Смотрите мое объяснение ниже.
Тед Петру

2

Вы также можете Concat df1, df2:

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

а затем удалите все дубликаты:

y = x.drop_duplicates(keep=False, inplace=False)

Добро пожаловать в StackOverflow: если вы публикуете код, XML или образцы данных, выделите эти строки в текстовом редакторе и нажмите кнопку «примеры кода» ({}) на панели инструментов редактора или используйте Ctrl + K на клавиатуре для удобного форматирования и синтаксис выделить его!
WhatsThePoint

4
Это вернет все данные, которые есть в любом наборе, а не только данные, которые есть только в df1.
Джейми Маршалл

1

Как насчет этого:

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]

1

Вот еще один способ решения этой проблемы:

df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

Или:

df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

0

Мой способ сделать это включает добавление нового столбца, уникального для одного кадра данных, и использование этого, чтобы выбрать, сохранять ли запись

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

Это делает так, чтобы каждая запись в df1 имела код - 0, если он уникален для df1, 1, если он находится в обоих фреймах данных. Затем вы используете это, чтобы ограничить то, что вы хотите

answer = nonuni[nonuni['Empt'] == 0]

0
извлечь разнородные строки с помощью функции слияния
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
сохранить разнородные строки в CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.