Установить разницу для панд
Простой вопрос панд:
Есть ли 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
})
Применить по столбцам объекта, который вы хотите отобразить (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
Предположение:
- df1 и df2 имеют одинаковые столбцы
- это заданная операция, поэтому дубликаты игнорируются
- наборы не очень большие, поэтому вы не беспокоитесь о памяти
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)