Вопрос или проблема
При применении агрегирования к сгруппированному DataFrame pandas агрегированный вывод, похоже, содержит разные значения для агрегированных колонок с отсутствующими значениями, в зависимости от типа колонки DataFrame. Ниже приведен минимальный пример, содержащий одно значение, не отсутствующее (целое число, строку и кортеж), одно NaN
и одно None
:
import pandas as pd
import numpy as np
a1 = pd.DataFrame({'a': [3, np.nan, None], 'b': [0,1,2]})
a2 = pd.DataFrame({'a': ['tree', np.nan, None], 'b': [0,1,2]})
a3 = pd.DataFrame({'a': [(0,1,2), np.nan, None], 'b': [0,1,2]})
a1.groupby('b')['a'].first()
a2.groupby('b')['a'].first()
a3.groupby('b')['a'].first()
a1.groupby('b')['a'].agg('first')
a2.groupby('b')['a'].agg('first')
a3.groupby('b')['a'].agg('first')
Смотрев на dtypes
колонки 'a'
, можно увидеть, что это float64
, object
и object
для a1
, a2
и a3
соответственно. None
в a1
преобразуется в NaN
при создании DataFrame. Поэтому я ожидал бы следующее:
Ожидаемое поведение вывода:
a1
:NaN
для строк 1 и 2 (это так)a2
:NaN
иNone
для строк 1 и 2 (это не так)a3
:NaN
иNone
для строк 1 и 2 (это не так)
Фактический вывод:
b
0 3.0
1 NaN
2 NaN
Name: a, dtype: float64
b
0 tree
1 None
2 None
Name: a, dtype: object
b
0 (0, 1, 2)
1 None
2 None
Name: a, dtype: object
Почему агрегация изменяет данные с NaN
на None
для строки 1 в a2
и a3
? Поскольку колонка в любом случае имеет тип объекта, не должно быть проблемы с возвратом NaN
и None
для строк 1 и 2 соответственно; и мы не находимся в ситуации, когда какая-либо группа для агрегации содержит как NaNs
, так и None
. Документация (https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.DataFrameGroupBy.first.html) также не очень точна по этому поведению, она просто упоминает, что возвращаемое значение для всех-NA-колонок – NA.
По умолчанию, groupby.first
удаляет NaNs.
DataFrameGroupBy.first(numeric_only=False, min_count=-1, skipna=True)
Вычисляет первое значение в каждой колонке внутри каждой группы.
По умолчанию пропускает NA элементы.
Таким образом, агрегация игнорирует все ваши NaNs и выводит значение по умолчанию NA для вашего типа данных (NaN для числовых, None для объектов).
Вы должны использовать skipna=False
:
a2.groupby('b')['a'].first(skipna=False)
# с agg
a3.groupby('b')['a'].agg('first', skipna=False)
Вывод:
# для a2
b
0 tree
1 NaN
2 None
Name: a, dtype: object
# для a3
b
0 (0, 1, 2)
1 NaN
2 None
Name: a, dtype: object
смешанные NaN/None
Если у вас есть объектная серия и смесь NaN/None, то (с skipna=False
) возвращается первый объект (как и ожидалось):
(pd.DataFrame({'a': [np.nan, None, None, np.nan, 'X'],
'b': [0,0,1,1,2]})
.groupby('b')['a'].first(skipna=False)
)
b
0 NaN
1 None
2 X
Name: a, dtype: object
Ответ или решение
Обработка Пропущенных Значений при Агрегации DataFrame в Pandas
Вопрос о том, как Pandas обрабатывает пропущенные значения (NaN и None) при агрегации данных в сгруппированном DataFrame, является важным аспектом для разработчиков и аналитиков данных, использующих эту библиотеку. В данной статье мы рассмотрим поведение агрегации с использованием функции first()
на примере DataFrame с различными типами данных.
Постановка Проблемы
Рассмотрим три DataFrame с различными типами столбцов:
import pandas as pd
import numpy as np
a1 = pd.DataFrame({'a': [3, np.nan, None], 'b': [0, 1, 2]})
a2 = pd.DataFrame({'a': ['tree', np.nan, None], 'b': [0, 1, 2]})
a3 = pd.DataFrame({'a': [(0, 1, 2), np.nan, None], 'b': [0, 1, 2]})
При агрегации данных с использованием функции first()
мы получаем следующие результаты:
a1.groupby('b')['a'].first()
a2.groupby('b')['a'].first()
a3.groupby('b')['a'].first()
Результаты:
-
Для
a1
:b 0 3.0 1 NaN 2 NaN Name: a, dtype: float64
-
Для
a2
:b 0 tree 1 None 2 None Name: a, dtype: object
-
Для
a3
:b 0 (0, 1, 2) 1 None 2 None Name: a, dtype: object
Ожидаемое Поведение
Ожидания относительно получаемых результатов различаются в зависимости от типа данных:
- Для
a1
ожидается NaN для всех отсутствующих значений (это соответствует полученному результату). - Для
a2
иa3
ожидается, что будет возвращено NaN и None для отсутствующих данных, однако вывод показывает, что дляa2
иa3
в результате мы видим None вместо NaN для строк с отсутствующими значениями.
Это поведение может вызвать путаницу. Как объясняется в документации Pandas, метод first()
по умолчанию игнорирует NaN, что приводит к замене их на значения по умолчанию, специфичные для типа данных.
Понимание Проблемы
Причина изменения данных с NaN на None заключается в том, что метод groupby.first()
по умолчанию игнорирует NaNs. При этом, для столбцов типа object, когда ни одно значение не будет найдено (т. е. только NaN или None), метод вернет значение None.
Это можно изменить, передав аргумент skipna=False
:
a2.groupby('b')['a'].first(skipna=False)
a3.groupby('b')['a'].agg('first', skipna=False)
Результаты с учетом skipna=False:
-
Для
a2
:b 0 tree 1 NaN 2 None Name: a, dtype: object
-
Для
a3
:b 0 (0, 1, 2) 1 NaN 2 None Name: a, dtype: object
Заключение
Важно помнить, что агрегация в Pandas может иногда давать неожиданные результаты из-за обработки пропущенных значений. По умолчанию методы агрегации, такие как first()
, игнорируют NaN, что приводит к возврату альтернативных значений (таких как None) для столбцов типа object. Чтобы контролировать это поведение, обязательно используйте параметр skipna=False
, если вам нужно учитывать NaN в расчетах.
Понимание таких ситуаций позволяет оптимизировать процесс анализа данных и минимизировать ошибки, что является критически важным для каждого специалиста в области информационных технологий и анализа данных.