Удаление строк, сумма которых равна нулю в 1 столбце, но в остальном являются дубликатами в pandas

#python #pandas

#python #pandas

Вопрос:

У меня есть фрейм данных pandas следующей структуры:

 df = pd.DataFrame({'ID':['A001', 'A001', 'A001', 'A002', 'A002', 'A003', 'A003', 'A004', 'A004', 'A004', 'A005', 'A005'],
                   'Val1':[2, 2, 2, 5, 6, 8, 8, 3, 3, 3, 7, 7],
                   'Val2':[100, -100, 50, -40, 40, 60, -50, 10, -10, 10, 15, 15]})
  
     ID    Val1  Val2
 0  A001     2   100
 1  A001     2  -100
 2  A001     2    50
 3  A002     5   -40
 4  A002     6    40
 5  A003     8    60
 6  A003     8   -50
 7  A004     3    10
 8  A004     3   -10
 9  A004     3    10
10  A005     7    15
11  A005     7    15
  

Я хочу удалить повторяющиеся строки, где ID и Val1 являются дубликатами, и где Val2 суммируется до нуля в двух строках. Положительные / отрицательные строки Val2 также могут не быть последовательными, даже при groupby

В приведенных выше примерах данных строки 0 и 1, а также 7, 8, 9 соответствуют этим критериям. Я бы хотел удалить [0, 1], а также [7, 8] или [8, 9] .

Другим ограничением здесь является то, что могут быть полностью повторяющиеся строки ([10, 11] ). В этом случае я хочу сохранить обе строки.

Таким образом, желаемый результат:

     ID    Val1  Val2
 2  A001     2    50
 3  A002     5   -40
 4  A002     6    40
 5  A003     8    60
 6  A003     8   -50
 9  A004     3    10
10  A005     7    15
11  A005     7    15
  

Если не перебирать каждую строку и искать другие строки, соответствующие критериям, у меня нет идей для более «питонического» способа сделать это. Любая помощь очень ценится.

Ответ №1:

Я добавил несколько комментариев в код, так что, надеюсь, моя мысль должна быть ясна :

 cond = df.assign(temp=df.Val2.abs())
# a way to get the same values (differentiated by their sign)
# to follow each other
cond = cond.sort_values(["ID", "Val1", "temp"])

# cumsum should yield a zero for numbers that are different
# only by their sign
cond["check"] = cond.groupby(["ID", "temp"]).Val2.cumsum()
cond["check"] = np.where(cond.check != 0, np.nan, cond.check)

# the backward fill here allows us to assign an identifier
# to the two values that summed to zero
cond["check"] = cond["check"].bfill(limit=1)

# this is where we implement your other condition
# essentially, it looks for rows that are duplicates
# and rows that any two rows sum to zero
cond.loc[
    ~(cond.duplicated(["ID", "Val1"], keep=False) amp; (cond.check == 0)),
    ["ID", "Val1", "Val2"],
]



     ID Val1    Val2
2   A001    2   50
3   A002    5   -40
4   A002    6   40
6   A003    8   -50
5   A003    8   60
9   A004    3   10
  

Комментарии:

1. Я добавил некоторые пояснения, а также дополнительное ограничение, согласно которому, если две строки являются полными дубликатами (строки 10 и 11 в образце данных), их следует сохранить. Надеюсь, это прояснит ситуацию

2. Я не совсем следовал логике в первый раз, поэтому подумал, что это не сработает из-за добавленного ограничения, которое я отредактировал, но я попробовал, и оно сработало идеально на примере данных, которые я показал. Пытаюсь проверить результаты на моих фактических данных, но я пометил ваш ответ как правильный, спасибо за помощь!

Ответ №2:

Используйте groupby и cumsum , чтобы найти, какой индекс Val2 сумм равен нулю

 s = df.groupby(['ID', 'Val1']).Val2.cumsum() == 0

n = np.where(s==1)[0]

to_remove = np.concatenate((n, (n-1))) 

new_df = df[~df.index.isin(to_remove)]

new_df 

      ID  Val1  Val2
2   A001     2    50
3   A002     5   -40
4   A002     6    40
5   A003     8    60
6   A003     8   -50
9   A004     3    10
10  A005     7    15
11  A005     7    15
  

Комментарии:

1. Привет, в то время как ваш код работал с образцом набора данных, я должен был упомянуть, что положительные / отрицательные строки Val2 могут не находиться в начале группы ID-Val1, и поэтому итоговая сумма не всегда может быть равна 0

Ответ №3:

Как насчет :

 temp = df.groupby('ID')[['Val2']].rolling(2).sum()
ix = temp[temp.Val2==0].index
ar = np.array([x[1] for x in ix.values])
ix2 = ar.tolist()   (ar-1).tolist()
df.drop(ix2, inplace=True)
df.drop_duplicates(['ID', 'Val1'], keep='first', inplace=True)
  

Но эти ответы относятся к вашему «текстовому» ответу: строки 8 и 9 «Val2» фактически суммируются до нуля (что не соответствует тому, что вы «желаемый результат», который вы опубликовали)…

Комментарии:

1. Потенциальная проблема заключается в том, что мой набор данных не отсортирован таким образом, чтобы положительные / отрицательные значения Val2 были последовательными. Есть ли способ преодолеть это?

2. @weirdpotatoes да, если вы можете отсортировать свой фрейм данных раньше (что-то вроде df.sort_values([‘ID’, ‘Val1’], inplace=True); но это зависит от способа создания вашего набора данных до этого…

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

4. @weirdpotatoes : вы можете отсортировать это, если добавите временный столбец с абсолютным значением… Но если у вас были дубликаты в абсолютных значениях (-50, 50, -50), это может иметь странные эффекты…

Ответ №4:

Используйте drop_duplicates метод для удаления дубликатов.

Вот пример кода:

 >>> df = pd.DataFrame({'ID':['A001', 'A001', 'A001', 'A002', 'A002', 'A003', 'A003', 'A004', 'A004', 'A004'],
...                    'Val1':[2, 2, 2, 5, 6, 8, 8, 3, 3, 3],
...                    'Val2':[100, -100, 50, 40, 45, 60, -50, 10, -10, 10]})
>>> 
>>> df.drop_duplicates(subset="Val2", keep= "last", inplace = True)
>>> df
     ID  Val1  Val2
0  A001     2   100
1  A001     2  -100
2  A001     2    50
3  A002     5    40
4  A002     6    45
5  A003     8    60
6  A003     8   -50
8  A004     3   -10
9  A004     3    10
>>> df.drop_duplicates(subset="Val1", keep= "last", inplace = True)
>>> df
     ID  Val1  Val2
2  A001     2    50
3  A002     5    40
4  A002     6    45
6  A003     8   -50
9  A004     3    10
>>> ~

  

Если вы можете более четко объяснить, что это значит:
and where Val2 sums to zero across two rows. ?

Это может помочь вам в дальнейшем перейти к полному решению.

Комментарии:

1. Я добавил некоторые пояснения, а также дополнительное ограничение, согласно которому, если две строки являются полными дубликатами (строки 10 и 11 в образце данных), их следует сохранить. Надеюсь, это прояснит ситуацию

Ответ №5:

Я полагаю, что может быть менее «грубый» метод, чем этот, но он имеет то преимущество, что он прозрачен.

импортируйте pandas как pd

 df = pd.DataFrame({'ID':['A001', 'A001', 'A001', 'A002', 'A002', 'A003', 'A003', 'A004', 'A004', 'A004'],
                   'Val1':[2, 2, 2, 5, 6, 8, 8, 3, 3, 3],
                   'Val2':[100, -100, 50, 40, 45, 60, -50, 10, -10, 10]})
df['Val3'] = df['Val2'].abs()
df2        = df.drop_duplicates()
df2        = df.drop(['Val2'], axis = 1)
df3        = df2.drop_duplicates()
result     = pd.merge(df3, df, left_index=True, right_index=True, how='inner')
results    = result.drop(['ID_x', 'Val1_x', 'Val3_x', 'Val3_y'],axis = 1)
  

Комментарии:

1. Привет, спасибо за ответ. Я отредактировал вопрос, чтобы добавить ограничение, что, если вся строка дублируется, я бы хотел их сохранить. (См. Строки 10, 11 в примере выше) Надеюсь, я выразился яснее.