Удалить строки из фрейма данных pandas на основе списка точек в другом фрейме данных

У меня есть два фрейма данных:

data = pd.DataFrame({"A": np.repeat(np.arange(1.,11.),50), 
                    "B": np.tile(np.repeat(np.arange(0.,5.),10),10), 
                    "C":np.arange(500)})
bad_data = pd.DataFrame({"A": [1., 2., 7., 9.], 
                           "B": [0., 3., 0., 2.], 
                           "points": [[0, 1],[0],[1],[0,1]]})
data.head(15)
bad_data
>>> data.head(15)
      A    B   C
0   1.0  0.0   0
1   1.0  0.0   1
2   1.0  0.0   2
3   1.0  0.0   3
4   1.0  0.0   4
5   1.0  0.0   5
6   1.0  0.0   6
7   1.0  0.0   7
8   1.0  0.0   8
9   1.0  0.0   9
10  1.0  1.0  10
11  1.0  1.0  11
12  1.0  1.0  12
13  1.0  1.0  13
14  1.0  1.0  14
>>> bad_data
     A    B  points
0  1.0  0.0  [0, 1]
1  2.0  3.0     [0]
2  7.0  0.0     [1]
3  9.0  2.0  [0, 1]

Для каждой строки data я хочу удалить все строки в bad_data с одинаковыми A и B и проиндексировать значения points. Например, первая строка bad_data говорит мне, что мне нужно удалить первые две строки data:

      A    B   C
0   1.0  0.0   0
1   1.0  0.0   1

Как я могу это сделать? Я смог состряпать этот ужас, но читать его довольно некрасиво. Можете ли вы помочь мне написать более Pythonic/удобочитаемое решение?

rows_to_remove = []
for A, B in zip(bad_data['A'], bad_data['B']):
    rows_in_data = (data['A'] == A) & (data['B'] == B)
    rows_in_bad_data = (bad_data['A'] == A) & (bad_data['B'] == B)
    bad_points = bad_data.loc[rows_in_bad_data, 'points'].values[0]
    indices = data[rows_in_data].index.values[bad_points]
    rows_to_remove.extend(indices)
    print(rows_to_remove)
data.drop(data.index[rows_to_remove], inplace=True)

относятся ли значения в «точке» к абсолютному индексу? относительно группы? значение в "С"?

mozway 18.03.2022 16:33

@mozway Они относятся к индексу относительно группы, определяемой текущими значениями A и B. Возможно, чтобы избежать путаницы со значениями C, я могу определить C как "C":np.random.random(500). Таким образом, очевидно, что [0,1] не может быть подмножеством значений C. Что вы думаете? Сделает ли это вопрос более читабельным?

DeltaIV 18.03.2022 16:39

@DeltaV спасибо, это понятно, я дал ответ;)

mozway 18.03.2022 16:53
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
48
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Не на 100%, если я правильно понял, или моя попытка - самый элегантный способ, поэтому дайте мне знать, если это сработает для вас:

bad_indexes = []
labels = ['A', 'B']

for _, s in bad_data.iterrows():
    p = data.loc[s['points']]
    p = p[p[labels].eq(s[labels]).all(1)]
    bad_indexes.extend(p.index)

result = data.loc[data.index.difference(bad_indexes)]

Я предположил, что индекс data имеет уникальные значения.

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

IIUC, вы можете выполнить обратное слияние для разнесенных bad_data:

data2 = (data
.assign(points=data.groupby(['A', 'B']).cumcount())  # get index per group (=points)
.merge(bad_data.explode('points'), on=['A', 'B', 'points'], # outer merge
       indicator=True, how='outer')
.loc[lambda d: d['_merge'].eq('left_only')]  # keep the rows unique to the left
.drop(columns=['points', '_merge'])          # remove helper columns
)

Другой вариант — использовать GroupBy.apply:

# craft a Series of list of points indexed by A/B
s = bad_data.set_index(['A', 'B'])['points']
    # group by A/B
data2 = (data
     .groupby(['A', 'B'], as_index=False, group_keys=False)
     # get the real index names from "index" and drop if the key is present in s
     # else leave the group unchanged
     .apply(lambda g: g.drop(g.index[s.loc[g.name]]) if g.name in s else g)
)

Оба подхода дают тот же фрейм данных, что и ваш собственный код.

выходная форма:

data2.shape
# (494, 3)

Подробности о втором решении:

  • создайте серию s, чтобы она была похожа на:
A    B  
1.0  0.0    [0, 1]
2.0  3.0       [0]
7.0  0.0       [1]
9.0  2.0    [0, 1]
Name: points, dtype: object
  • создавать группы по A/B
  • для каждой группы, если она присутствует в индексе s (ключ g.name), извлеките значения s.loc[g.name], получите соответствующие индексы из относительного положения в группе: g.index[s.loc[g.name]], подайте это, чтобы удалить. Если индекс A/B отсутствует, вернуть группу без изменений.

@DeltaIV да, должно быть data, мой плохой, привычка использовать df, позвольте мне взглянуть на ваше обновление

mozway 22.03.2022 11:21

@DeltaIV Я вижу «проблему», мои решения не на месте, вам нужно назначить вывод. Я сделал это явным;)

mozway 22.03.2022 11:23

это полезно. Не могли бы вы также показать, по желанию, как вносить изменения на месте? Или «вставка» потребует слишком много изменений в вашем коде? Является ли «замена» порицаемой практикой? Мне бы это показалось очень удобным.

DeltaIV 22.03.2022 11:25

Большинство функций «inplace» на самом деле не на месте и используют копии. Существует активное обсуждение, следует ли устареть параметры inplace в pandas. Если вам действительно нужно изменить «на месте», вы можете изменить мой код, чтобы индексы удалялись, используя .loc[lambda d: d['_merge'].ne('left_only')].index вместо последних двух строк и присваивая переменной idx, а затем data.drop(idx, inplace=True).

mozway 22.03.2022 11:30

спасибо, я улучшил свое голосование и принял ответ с учетом ваших изменений/комментариев. Просто из любопытства, если вы найдете время, было бы неплохо объяснить немного подробнее, как работает второе решение (я не смог его понять!). Но не торопитесь, первое решение работает и оно довольно понятное, так что ответ меня устраивает.

DeltaIV 22.03.2022 11:35

@DeltaIV Я добавил детали, надеюсь, это поможет;)

mozway 22.03.2022 11:42

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