Pandas .drop_duulates позволяет нам указать, что мы хотим сохранить первый или последний (или ни одного) из найденных дубликатов. У меня более сложная ситуация. Допустим, у меня есть набор предпочтительных значений для столбца. Если найдена повторяющаяся пара и одна из них находится в предпочтительном наборе, я хочу сохранить ее, независимо от того, первая она или последняя. Если оба варианта предпочтительнее, следует применить обычное поведение drop_duplications. Если ни один из них не входит в предпочтительный набор, то опять же должно применяться обычное поведение drop_duulates.
Я пробовал играть с масками, но у меня не получилось. Я думаю, что это может быть неправильный путь. Вот что я пробовал.
import pandas as pd
def conditional_remove_duplicates(df, preferred_tags):
duplicates_mask = df.duplicated(subset=['id', 'val'], keep=False)
preferred_mask = df['tag'].isin(preferred_tags)
mask = duplicates_mask | preferred_mask
df = df[mask].drop_duplicates(subset=['id', 'val'], keep='first')
return df
data = {'id': ['A', 'A', 'A', 'A', 'B', 'B', 'C', 'D', 'D'],
'val': [10, 10, 11, 10, 20, 20, 30, 40, 40],
'tag': ['X', 'Z', 'X', 'Y', 'Z', 'X', 'X', 'Z', 'Z']}
preferred_tags = {'X', 'Y'}
df = pd.DataFrame(data)
print(df)
"""
id val tag
0 A 10 X
1 A 10 Z
2 A 11 X
3 A 10 Y
4 B 20 Z
5 B 20 X
6 C 30 X
7 D 40 Z
8 D 40 Z
"""
result_df = conditional_remove_duplicates(df, preferred_tags)
print(result_df)
""" Produces:
id val tag
0 A 10 X
2 A 11 X
4 B 20 Z
6 C 30 X
7 D 40 Z
Should be:
id val tag
0 A 10 X
2 A 11 X
5 B 20 X
6 C 30 X
7 D 40 Z
"""
сортировка с добавлением preferred_mask, а не tag. действительно правильный путь. Я не уверен, насколько велик ваш фрейм данных, но обычно, если вы можете поместить его в память, сортировка не так уж и дорога.






Вам следует создать столбец, в котором будет указан приоритет, который вы хотите отдать дубликатам, и отсортировать его на основе этого. Ключевым моментом здесь является использование mergesort, чтобы он был стабильным и не менял порядок текущих связей.
Затем вы можете удалить дубликаты.
df['rank'] = df['tag'].isin(preferred_tags)
df = df.sort_values(by='rank', ascending=False, kind='mergesort')
df.drop_duplicates(['id', 'val'])
id val tag rank
0 A 10 X True
2 A 11 X True
5 B 20 X True
6 C 30 X True
7 D 40 Z False
Я бы сделал это вот так. Однако комментарий ОП под вопросом просит не использовать sort_values.
Верно, хотя, по крайней мере, здесь это всего лишь один вид, может быть, есть другой очень умный способ сделать это, хотя в любом случае может быть просто какая-то скрытая сортировка/организация.
Возможно, это не самый элегантный способ, который вы искали, но вот один из способов сделать это.
def conditional_remove_duplicates(df, preferred_tags):
df = pd.DataFrame(list(pd.Series(df.itertuples(index=False)).drop_duplicates(keep='first')))
duplicates_mask = df.duplicated(subset=['id', 'val'], keep=False)
preferred_mask = df['tag'].isin(preferred_tags)
mask = (~duplicates_mask | preferred_mask ).rename('mask')
df = df[mask].drop_duplicates(subset=['id', 'val'], keep='first')
return df
Я новичок в Python, поэтому буду рад любым комментариям и предложениям.
Я бы использовал isin / idxmax для каждого «id/val», чтобы имитировать drop_duplicates(how = "not preferred") :
def pref_dup(g):
return g.loc[[g.isin(preferred_tags).idxmax()]]
out = (
df
.groupby(["id", "val"])["tag"]
.apply(pref_dup, include_groups=False)
.reset_index(level=[0, 1])
)
Выход :
id val tag
0 A 10 X
2 A 11 X
5 B 20 X
6 C 30 X
7 D 40 Z
Мне пришло в голову, что сортировка по ['id', 'val', 'tag'] могла бы приблизить меня к решению, но это довольно дорогая операция для большого фрейма данных. Я надеялся на решение, которое имело бы общее применение для решения проблем такого типа, то есть на условную дедупликацию.