Удаление строк на основе значений в других строках

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

Вот мой кадр данных:

product product_id  account_status
prod-A  100         active
prod-A  100         cancelled
prod-A  300         active
prod-A  400         cancelled

Если для комбинации product & и product_id существует строка с account_status='active', сохраните эту строку и удалите другие строки.

Желаемый результат:

product product_id  account_status
prod-A  100         active
prod-A  300         active
prod-A  400         cancelled

Я видел решение, упомянутое здесь, но не смог воспроизвести его для строк .

Пожалуйста, предложите.


person Prasanna    schedule 21.12.2018    source источник
comment
Возможно ли, что account_status будет иметь больше, чем просто активный и отмененный статусы?   -  person cs95    schedule 21.12.2018


Ответы (2)


Для более общего решения удаляются только другие значения account_status для каждой группы, если там существует хотя бы одно значение active:

print (df)
  product  product_id account_status
0  prod-A         100         active
1  prod-A         100      cancelled <- necessary remove
2  prod-A         300         active
3  prod-A         400      cancelled
4  prod-A         500         active
5  prod-A         500         active
6  prod-A         600      cancelled
7  prod-A         600      cancelled

s = df['account_status'].eq('active')
g = df.assign(A=s).groupby(['product','product_id'])['A']
mask = ~g.transform('any') | g.transform('all') | s
df = df[mask]
print (df)
  product  product_id account_status
0  prod-A         100         active
2  prod-A         300         active
3  prod-A         400      cancelled
4  prod-A         500         active
5  prod-A         500         active
6  prod-A         600      cancelled
7  prod-A         600      cancelled

Также хорошо работает с несколькими категориями:

print (df)
  product  product_id account_status
0  prod-A         100         active
1  prod-A         100      cancelled <- necessary remove
2  prod-A         100        pending <- necessary remove
3  prod-A         300         active
4  prod-A         300        pending <- necessary remove
5  prod-A         400      cancelled
6  prod-A         500         active
7  prod-A         500         active
8  prod-A         600        pending
9  prod-A         600      cancelled

s = df['account_status'].eq('active')
g = df.assign(A=s).groupby(['product','product_id'])['A']
mask = ~g.transform('any') | g.transform('all') | s
df = df[mask]
print (df)
  product  product_id account_status
0  prod-A         100         active
3  prod-A         300         active
5  prod-A         400      cancelled
6  prod-A         500         active
7  prod-A         500         active
8  prod-A         600        pending
9  prod-A         600      cancelled
person jezrael    schedule 21.12.2018

ИМО, groupby не обязательно (я говорю это, потому что вы соответствующим образом пометили свой вопрос), вы можете использовать sort_values и drop_duplicates, воспользовавшись тем фактом, что «активно» ‹ «отменено», лексикографически:

(df.sort_values(['account_status'])
   .drop_duplicates(['product', 'product_id'])
   .sort_index())

  product  product_id account_status
0  prod-A         100         active
2  prod-A         300         active
3  prod-A         400      cancelled

В духе согласованности с другими ответами вы можете взглянуть на решение на основе groupby, включающее duplicated и маскировку.

df
  product  product_id account_status
0  prod-A         100         active
1  prod-A         100      cancelled
2  prod-A         100        pending
3  prod-A         300         active
4  prod-A         300        pending
5  prod-A         400      cancelled
6  prod-A         500         active
7  prod-A         500         active
8  prod-A         600        pending
9  prod-A         600      cancelled


m1 = (df.assign(m=df.account_status.eq('active'))
        .groupby(['product', 'product_id'])['m']
        .transform('any'))
m2 = df.duplicated(['product', 'product_id'])

df[~(m1 & m2)]

  product  product_id account_status
0  prod-A         100         active
3  prod-A         300         active
5  prod-A         400      cancelled
6  prod-A         500         active
8  prod-A         600        pending
9  prod-A         600      cancelled

Как и другое решение, это также «хорошо» обобщает несколько категорий и удаляет строки, соответствующие другим статусам, только в группах, где также присутствует «активный».

person cs95    schedule 21.12.2018
comment
довольно умное решение именно этого вопроса (у) - person Shrey; 21.12.2018
comment
согласен, не универсальное решение, но для этого самого вопроса оно будет работать хорошо - person Shrey; 21.12.2018
comment
@Shrey Я добавил универсальное решение. - person cs95; 21.12.2018