Удаление дубликатов Pandas с более сложными предпочтениями, чем первый или последний

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
"""

Мне пришло в голову, что сортировка по ['id', 'val', 'tag'] могла бы приблизить меня к решению, но это довольно дорогая операция для большого фрейма данных. Я надеялся на решение, которое имело бы общее применение для решения проблем такого типа, то есть на условную дедупликацию.

MikeP 20.03.2024 18:32

сортировка с добавлением preferred_mask, а не tag. действительно правильный путь. Я не уверен, насколько велик ваш фрейм данных, но обычно, если вы можете поместить его в память, сортировка не так уж и дорога.

Quang Hoang 20.03.2024 18:38
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
2
64
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вам следует создать столбец, в котором будет указан приоритет, который вы хотите отдать дубликатам, и отсортировать его на основе этого. Ключевым моментом здесь является использование 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.

Quang Hoang 20.03.2024 18:47

Верно, хотя, по крайней мере, здесь это всего лишь один вид, может быть, есть другой очень умный способ сделать это, хотя в любом случае может быть просто какая-то скрытая сортировка/организация.

ALollz 20.03.2024 18:59

Возможно, это не самый элегантный способ, который вы искали, но вот один из способов сделать это.

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

Другие вопросы по теме