У меня есть два фрейма данных:
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 Они относятся к индексу относительно группы, определяемой текущими значениями A
и B
. Возможно, чтобы избежать путаницы со значениями C
, я могу определить C
как "C":np.random.random(500)
. Таким образом, очевидно, что [0,1]
не может быть подмножеством значений C
. Что вы думаете? Сделает ли это вопрос более читабельным?
@DeltaV спасибо, это понятно, я дал ответ;)
Не на 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
s
(ключ g.name
), извлеките значения s.loc[g.name]
, получите соответствующие индексы из относительного положения в группе: g.index[s.loc[g.name]]
, подайте это, чтобы удалить. Если индекс A/B отсутствует, вернуть группу без изменений.@DeltaIV да, должно быть data
, мой плохой, привычка использовать df
, позвольте мне взглянуть на ваше обновление
@DeltaIV Я вижу «проблему», мои решения не на месте, вам нужно назначить вывод. Я сделал это явным;)
это полезно. Не могли бы вы также показать, по желанию, как вносить изменения на месте? Или «вставка» потребует слишком много изменений в вашем коде? Является ли «замена» порицаемой практикой? Мне бы это показалось очень удобным.
Большинство функций «inplace
» на самом деле не на месте и используют копии. Существует активное обсуждение, следует ли устареть параметры inplace
в pandas. Если вам действительно нужно изменить «на месте», вы можете изменить мой код, чтобы индексы удалялись, используя .loc[lambda d: d['_merge'].ne('left_only')].index
вместо последних двух строк и присваивая переменной idx
, а затем data.drop(idx, inplace=True)
.
спасибо, я улучшил свое голосование и принял ответ с учетом ваших изменений/комментариев. Просто из любопытства, если вы найдете время, было бы неплохо объяснить немного подробнее, как работает второе решение (я не смог его понять!). Но не торопитесь, первое решение работает и оно довольно понятное, так что ответ меня устраивает.
@DeltaIV Я добавил детали, надеюсь, это поможет;)
относятся ли значения в «точке» к абсолютному индексу? относительно группы? значение в "С"?