Установить разницу для панд

Простой вопрос панд:

Есть ли drop_duplicates() функциональность для удаления каждой строки, участвующей в дублировании?

Эквивалентный вопрос заключается в следующем: есть ли у pandas разница между наборами данных?

Например:

In [5]: df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})

In [6]: df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})

In [7]: df1
Out[7]: 
   col1  col2
0     1     2
1     2     3
2     3     4

In [8]: df2
Out[8]: 
   col1  col2
0     4     6
1     2     3
2     5     5

так что может быть что-то вроде df2.set_diff(df1) будет производить это:

   col1  col2
0     4     6
2     5     5

Однако я не хочу полагаться на индексы, потому что в моем случае мне приходится иметь дело с фреймами данных, которые имеют разные индексы.

Кстати, я изначально думал о расширении текущего drop_duplicates() метод, но теперь я понимаю, что второй подход, использующий свойства теории множеств, был бы гораздо более полезным в целом. Оба подхода решают мою текущую проблему, все же.

Спасибо!

13 ответов

Решение
from pandas import  DataFrame

df1 = DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
df2 = DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})

print df2[~df2.isin(df1).all(1)]
print df2[(df2!=df1)].dropna(how='all')
print df2[~(df2==df1)].dropna(how='all')

Немного запутанный, но если вы хотите полностью игнорировать данные индекса. Преобразуйте содержимое фреймов данных в наборы кортежей, содержащих столбцы:

ds1 = set([tuple(line) for line in df1.values])
ds2 = set([tuple(line) for line in df2.values])

Этот шаг избавит от любых дубликатов в фреймах данных (индекс игнорируется)

set([(1, 2), (3, 4), (2, 3)])   # ds1

Затем можно использовать методы set, чтобы найти что-нибудь. Например, чтобы найти различия:

ds1.difference(ds2)

дает: set([(1, 2), (3, 4)])

может вернуть это в dataframe, если это необходимо. Примечание: необходимо преобразовать набор в список 1-й, так как набор не может быть использован для построения кадра данных:

pd.DataFrame(list(ds1.difference(ds2)))

Вот еще один ответ, который хранит индекс и не требует идентичных индексов в двух фреймах данных. ( http://kechengpuzi.com/q/s37313691)

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

Это быстро и результат

   col1  col2
0     4     6
2     5     5

Есть 3 метода, которые работают, но два из них имеют некоторые недостатки.

Метод 1 (метод хеширования):

Это работало для всех случаев, которые я проверял.

df1.loc[:, "hash"] = df1.apply(lambda x: hash(tuple(x)), axis = 1)
df2.loc[:, "hash"] = df2.apply(lambda x: hash(tuple(x)), axis = 1)
df1 = df1.loc[~df1["hash"].isin(df2["hash"]), :]

Метод 2 (метод Dict):

Сбой, если DataFrames содержат столбцы даты и времени.

df1 = df1.loc[~df1.isin(df2.to_dict(orient="list")).all(axis=1), :]

Метод 3 (метод MultiIndex):

Я сталкивался со случаями, когда он терпел неудачу на столбцах с None или NaN.

df1 = df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)

Setdiff1d Numpy будет работать и, возможно, будет быстрее.

Для каждого столбца:np.setdiff1(df1.col1.values, df2.col1.values)

Так что-то вроде:

setdf = pd.DataFrame({
    col: np.setdiff1d(getattr(df1, col).values, getattr(df2, col).values)
    for col in df1.columns
})

numpy.setdiff1d docs

Применить по столбцам объекта, который вы хотите отобразить (df2); найти строки, которых нет в наборе (isin это как оператор множества)

In [32]: df2.apply(lambda x: df2.loc[~x.isin(df1[x.name]),x.name])
Out[32]: 
   col1  col2
0     4     6
2     5     5

То же самое, но включить все значения в df1, но в столбце в df2

In [33]: df2.apply(lambda x: df2.loc[~x.isin(df1.values.ravel()),x.name])
Out[33]: 
   col1  col2
0   NaN     6
2     5     5

2-й пример

In [34]: g = pd.DataFrame({'x': [1.2,1.5,1.3], 'y': [4,4,4]})

In [35]: g.columns=df1.columns

In [36]: g
Out[36]: 
   col1  col2
0   1.2     4
1   1.5     4
2   1.3     4

In [32]: g.apply(lambda x: g.loc[~x.isin(df1[x.name]),x.name])
Out[32]: 
   col1  col2
0   1.2   NaN
1   1.5   NaN
2   1.3   NaN

Обратите внимание, что в 0.13 будет isin оператор на уровне кадра, так что-то вроде: df2.isin(df1) должно быть возможно

Объекты Pandas MultiIndex имеют быстрые операции установки, реализованные в виде методов, поэтому вы можете преобразовать DataFrames в MultiIndexes, используя difference() метод, а затем преобразовать результат обратно в DataFrame. Это решение должно быть намного быстрее (примерно в 100 раз или более из моего краткого тестирования), чем решения, приведенные здесь до сих пор, и оно не будет зависеть от индексации строк исходных кадров. Как Петр упомянул в своем ответе, это потерпит неудачу с нулевыми значениями, так как np.nan!= Np.nan. Любая строка в df2 с нулевым значением всегда будет отображаться в разнице. Кроме того, столбцы должны быть в одинаковом порядке для обоих фреймов данных.

df1mi = pd.MultiIndex.from_arrays(df1.values.transpose(), names=df1.columns)
df2mi = pd.MultiIndex.from_arrays(df2.values.transpose(), names=df2.columns)
dfdiff = df2mi.difference(df1mi).to_frame().reset_index(drop=True)

Получите индексы пересечения со слиянием, затем отбросьте их:

>>> df_all = pd.DataFrame(np.arange(8).reshape((4,2)), columns=['A','B']); df_all
   A  B
0  0  1
1  2  3
2  4  5
3  6  7
>>> df_completed = df_all.iloc[::2]; df_completed
   A  B
0  0  1
2  4  5
>>> merged = pd.merge(df_all.reset_index(), df_completed); merged
   index  A  B
0      0  0  1
1      2  4  5
>>> df_pending = df_all.drop(merged['index']); df_pending
   A  B
1  2  3
3  6  7

Предположение:

  1. df1 и df2 имеют одинаковые столбцы
  2. это заданная операция, поэтому дубликаты игнорируются
  3. наборы не очень большие, поэтому вы не беспокоитесь о памяти
union = pd.concat([df1,df2])
sym_diff = union[~union.duplicated(keep=False)]
union_of_df1_and_sym_diff = pd.concat([df1, sym_diff])
diff = union_of_df1_and_sym_diff[union_of_df1_and_sym_diff.duplicated()]

Самый простой способ, который я знаю, - использоватьpd.mergeсhow='outer'иindicator=True

      df3 = pd.merge(df1,df2,how='outer',left_on=['col1'],right_on=['col1'],indicator=True)

В результирующей таблице появится новый столбец._mergeс ценностямиright_only,left_onlyиbothчто вы можете затем отфильтровать. ИЕ

      df3[df3['_merge']=='left_only']

Преимуществом этого подхода является гибкость, заключающаяся в том, что вы можете использовать упорядоченные списки столбцов для определения равенства в двух таблицах. ИЕleft_on=['col1','col2'],right_on=['col1','col3'].

Или, если под разницей в наборах вы имели в виду что-то другое, вы можете использоватьdf3[~(df3['_merge']=='both')]илиdf3[~(df3['_merge']=='right_only')]или то, что соответствует вашим потребностям.

Обратной стороной является то, что вы выполняете полное внешнее соединение двух таблиц и можете получить беспорядочную таблицу, требующую дополнительной очистки.

Я не уверен как pd.concat() неявно объединяет перекрывающиеся столбцы, но мне пришлось немного подправить ответ @ radream.

Концептуально разность множеств ( симметричная) для нескольких столбцов - это объединение множеств (внешнее соединение) минус пересечение множеств (или внутреннее соединение):

df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})
o = pd.merge(df1, df2, how='outer')
i = pd.merge(df1, df2)
set_diff = pd.concat([o, i]).drop_duplicates(keep=False)

Это дает:

   col1  col2
0     1     2
2     3     4
3     4     6
4     5     5

В Pandas 1.1.0 вы можете подсчитывать уникальные строки с помощью value_counts и найдите разницу между счетчиками:

      df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})

diff = df2.value_counts().sub(df1.value_counts(), fill_value=0)

Результат:

      col1  col2
1     2      -1.0
2     3       0.0
3     4      -1.0
4     6       1.0
5     5       1.0
dtype: float64

Получите положительные результаты:

      diff[diff > 0].reset_index(name='counts')


   col1  col2  counts
0     4     6     1.0
1     5     5     1.0

Это должно работать, даже если у вас есть несколько столбцов в обоих фреймах данных. Но убедитесь, что имена столбцов обоих информационных фреймов совпадают.

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

С несколькими столбцами вы также можете использовать:

col_names=['col_1','col_2']
set_difference = pd.concat([df2[col_names], df1[col_names], 
df1[col_names]]).drop_duplicates(keep=False)
Другие вопросы по тегам