Работа с NaN при поиске неполных повторяющихся строк в DataFrame - PullRequest
1 голос
/ 20 июня 2020

Это немного сложно объяснить, но потерпите меня. Предположим, у нас есть следующий набор данных:

df = pd.DataFrame({'foo': [1, 1, 1, 8, 1, 5, 5, 5],
                   'bar': [2, float('nan'), 2, 5, 2, 3, float('nan'), 6],
                   'abc': [3, 3, 3, 7, float('nan'), 9, 9, 7],
                   'def': [4, 4, 4, 2, 4, 8, 8, 8]})
print(df)
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
3    8  5.0  7.0    2
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8
7    5  6.0  7.0    8

Наша цель - найти все повторяющиеся строки. Однако некоторые из этих дубликатов неполные, поскольку имеют значения NaN. Тем не менее, мы хотим найти и эти дубликаты. Итак, ожидаемый результат:

   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8

Если мы попытаемся сделать это простым способом, это даст нам только полные строки:

print(df[df.duplicated(keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
2    1  2.0  3.0    4

Мы можем попытаться обойти это, используя только столбцы, в которых нет пропущенных значений:

print(df[df.duplicated(['foo', 'def'], keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8
7    5  6.0  7.0    8

Очень близко, но не совсем. Оказывается, мы упускаем важную информацию в столбце ab c, которая позволяет нам определить, что строка 7 не является дубликатом. Поэтому мы хотели бы включить его:

print(df[df.duplicated(['foo', 'def', 'abc'], keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8

И ему удалось удалить строку 7. Однако он также удаляет строку 4. NaN считается отдельным отдельным значением, а не чем-то, что может быть равно что угодно, поэтому его присутствие в строке 4 не позволяет нам обнаружить этот дубликат.

Теперь я знаю, что мы не знаем наверняка, действительно ли строка 4 является [1, 2, 3, 4]. Насколько нам известно, это может быть что-то совсем другое, например [1, 2, 9, 4]. Но предположим, что значения 1 и 4 на самом деле являются некоторыми другими значениями, которые странно указаны c. Например, 34900 и 23893. Допустим, есть еще много столбцов, которые также точно такие же. Более того, полные повторяющиеся строки - это не просто 0 и 2, их более двухсот, а затем еще 40 строк, которые имеют те же значения во всех столбцах, кроме 'ab c', где они имеют NaN. Таким образом, для этой конкретной группы дубликатов такие совпадения крайне маловероятны, и именно поэтому мы знаем наверняка, что запись [1, 2, 3, 4] проблематична c, а эта строка 4 почти наверняка является дубликатом.

Однако, если [1, 2, 3, 4] не единственная группа дубликатов, то возможно, что некоторые другие группы имеют очень нестандартные c значения в столбцах 'foo' и 'def'. , например, 1 и 500. И так случилось, что включение столбца «ab c» в подмножество было бы чрезвычайно полезно для решения этой проблемы, потому что значения в столбце «ab c» почти всегда очень конкретны c, и позволяют практически точно определять все дубликаты. Но есть недостаток - в столбце «ab c» отсутствуют значения, поэтому, используя его, мы жертвуем обнаружением некоторых дубликатов с NaN. Некоторые из них, как мы знаем, являются дубликатами (например, вышеупомянутые 40), так что это трудная дилемма.

Что было бы лучшим способом справиться с этой ситуацией? Было бы неплохо, если бы мы могли как-то сделать NaN равными всему, а не ничему, на время обнаружения дубликатов, что решило бы эту проблему. Но я сомневаюсь, что это возможно. Могу ли я просто go группировать по группам и проверять это вручную?

1 Ответ

1 голос
/ 22 июня 2020

Спасибо @ cs95 за помощь в этом разобраться. Когда мы сортируем значения, NaN помещаются в конец группы сортировки по умолчанию, и если у неполной записи есть дубликат с существующим значением вместо этого NaN, он окажется прямо поверх NaN. Это означает, что мы можем заполнить это NaN этим значением, используя метод ffill(). Таким образом, мы заполняем недостающие данные данными из ближайших к ним строк, чтобы затем мы могли более точно определить, является ли эта строка дубликатом.

Код, который я использовал (скорректировано к этому воспроизводимому примеру) выглядит так:

#printing all duplicates
col_list = ['foo', 'def', 'abc', 'bar']
show_mask = df.sort_values(col_list).ffill().duplicated(col_list, keep=False).sort_index()
df[show_mask].sort_values(col_list)

#deleting duplicates, but keeping one record per duplicate group
delete_mask = df.sort_values(col_list).ffill().duplicated(col_list).sort_index()
df = df[~delete_mask].reset_index(drop=True)

Можно использовать bfill() вместо ffill(), поскольку тот же принцип применяется в перевернутом виде. Но для этого нужно изменить некоторые параметры по умолчанию используемых методов на противоположные, а именно na_position='first' и keep='last'. sort_index() используется только для отключения предупреждения о переиндексации.

Обратите внимание, что порядок, в котором вы перечисляете столбцы, очень важен, поскольку он используется для сортировки приоритетов. Чтобы убедиться, что запись над пропущенным значением является правильным значением для копирования, вы должны сначала пронумеровать все столбцы, в которых нет отсутствующих значений, и только потом те, которые имеют. Для первых столбцов порядок не имеет значения. Для последних очень важно начинать с столбца, который имеет самые разнообразные / specifici c значения и заканчивать наименее разнообразными / specifici c одним (float -> int -> string -> bool - хорошее правило практического опыта, но это во многом зависит от того, какие именно переменные представляют столбцы в вашем наборе данных). В этом примере все они одинаковы, но даже здесь вы не получите правильного ответа, если поставите bar перед ab c.

И даже тогда это не идеальное решение. Он довольно хорошо справляется с помещением наиболее полной версии записи наверху и переносом информации из нее в менее полные версии ниже, когда это необходимо. Но есть вероятность, что полной версии записи просто не существует. Например, предположим, что есть записи [5 3 Nan 8] и [5 NaN 9 8] (и нет записи [5 3 9 8]). Это решение не позволяет им обмениваться недостающими частями друг с другом. В первом будет помещено 9, но NaN во втором останется пустым, и эти дубликаты будут go незамеченными.

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

...