столбцы pandas GroupBy со значениями NaN (отсутствующими)

У меня есть DataFrame со многими пропущенными значениями в столбцах, которые я хочу сгруппировать:

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

видите, что Pandas отбросил строки с целевыми значениями NaN. (Я хочу включить эти строки!)

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

Какие-либо предложения? Стоит ли писать для этого функцию или есть простое решение?


person Gyula Sámuel Karli    schedule 25.08.2013    source источник
comment
@PhillipCloud Я отредактировал этот вопрос, чтобы включить только вопрос, который на самом деле неплохой, относящийся к открытое улучшение панд Джеффа.   -  person Andy Hayden    schedule 25.08.2013
comment
Отсутствие возможности включать (и размножать) NaN в группы довольно неприятно. Цитирование R неубедительно, поскольку такое поведение не согласуется со многими другими вещами. Во всяком случае, фиктивный хак тоже довольно плохой. Однако размер (включая NaN) и количество (без учета NaN) группы будут отличаться, если есть NaN. dfgrouped = df.groupby (['b']). a.agg (['sum', 'size', 'count']) dfgrouped ['sum'] [dfgrouped ['size']! = dfgrouped ['count ']] = Нет   -  person    schedule 05.05.2017
comment
Можете ли вы резюмировать, чего конкретно вы пытаетесь достичь? т.е. мы видим результат, но каков желаемый результат?   -  person c-a    schedule 12.08.2017
comment
В pandas 1.1 вы скоро сможете указать dropna=False в groupby(), чтобы получить желаемый результат. Подробнее   -  person cs95    schedule 21.05.2020
comment
Обратите внимание, что на момент написания этой статьи существует ошибка, из-за которой dropna=False не работает с группировкой MultiIndex. Есть несколько открытых проблем, в которых упоминается об этом на их github, и, к сожалению, не так много усилий для их исправления.   -  person totalhack    schedule 12.04.2021


Ответы (7)


Это упоминается в разделе «Отсутствующие данные» документы:

Группы NA в GroupBy автоматически исключаются. Такое поведение согласуется с R

Один из обходных путей - использовать заполнитель перед выполнением группировки (например, -1):

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

Тем не менее, это выглядит довольно ужасно ... возможно, должна быть возможность включить NaN в groupby (см. эта проблема с github, в которой используется тот же хакерский прием).

Однако, как описано в другом ответе, из pandas 1.1 вы лучше контролируете это поведение, теперь значения NA разрешено в группировщике с использованием dropna = False

person Andy Hayden    schedule 25.08.2013
comment
Это логичное, но своего рода забавное решение, о котором я подумал ранее: Pandas создает поля NaN из пустых, и мы должны их изменить. Это причина, по которой я думаю о поиске других решений, таких как запуск SQL-сервера и запрос таблиц оттуда (выглядит слишком сложно), или поиск другой библиотеки, несмотря на Pandas, или использование моей собственной (что я хочу избавиться). Спасибо - person Gyula Sámuel Karli; 27.08.2013
comment
@ GyulaSámuelKarli Мне это кажется небольшой ошибкой (см. Отчет об ошибке выше), и мое решение - обходной путь. Мне кажется странным, что вы списываете со счетов всю библиотеку. - person Andy Hayden; 27.08.2013
comment
Я не хочу записывать Pandas, просто ищите инструмент, который больше всего соответствует моим запросам. - person Gyula Sámuel Karli; 27.08.2013
comment
Но что, если вы не хотите менять NaN на другие значения? Нет ли возможности использовать метод sum, включающий NaN? (например, как метод суммирования фрейма данных df.sum(skipna=True): pandas.pydata.org/pandas-docs/version/0.17.1/generated/ - person Guido; 12.01.2016
comment
@Guido Этот вопрос касается ключа groupby NaN, поэтому я не уверен, что следую за вопросом. - person Andy Hayden; 12.01.2016
comment
Ты прав. Я смотрел на целевые значения, которые тоже могут быть NaN. Если они NaN, их нельзя суммировать с помощью предложенного метода. Но я полагаю, что это выходит за рамки этой темы. - person Guido; 13.01.2016
comment
Кроме того, не забудьте переназначить df обратно на другую / ту же переменную. new_df = df.replace(np.nan, -1).groupby('b').sum() - person van_d39; 30.11.2016
comment
Взгляните на мой ответ ниже, я считаю, что нашел довольно хорошее (более чистое и, возможно, более быстрое) решение. stackoverflow.com/a/43375020/408853 - person c-a; 12.04.2017
comment
Лучший ответ - в комментарии к ответу @Tuetschek - person Yuval Atzmon; 18.05.2017
comment
@yuval, спасибо, я обновил эту часть, интересно, что поведение .replace (np.nan, x) изменилось бы! - person Andy Hayden; 18.05.2017
comment
@AndyHayden Большое спасибо за то, что спас меня остаток дня, эта проблема сводила меня с ума. - person gaborous; 29.12.2018
comment
Нет, это не согласуется с R. df% ›% group_by также выдаст сводки NA с предупреждением, которого можно избежать, передав столбец группировки через fct_explicit_na, а затем будет создан уровень (Отсутствует). - person Ravaging Care; 16.08.2019
comment
Согласитесь с Ravaging Care. Это несовместимо с пакетами обработки данных R. Такие пакеты обработки данных, как dplyr и data.table в R, по умолчанию включают NA при группировке. Исключение NA при группировке, как это делает Pandas, кажется мне нелогичным: даже в SQL при группировании включаются NULL. - person Fierr; 22.10.2019

панды ›= 1.1

В pandas 1.1 вы лучше контролируете это поведение, Значения NA теперь разрешены в группировке с использованием dropna=False:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

# Example from the docs
df

   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2

# without NA (the default)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5
# with NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4
person cs95    schedule 20.05.2020
comment
Надеюсь, этот ответ постепенно поднимется наверх. Это правильный подход. - person kdbanman; 01.06.2020
comment
Мне это не подходит. kroscek_jupyter_metabase = fromdb_1474_detail.groupby(groupby, dropna = False)[col_to_count].count() возвращает TypeError: groupby() got an unexpected keyword argument 'dropna' - person Cignitor; 23.01.2021
comment
@Cignitor, пожалуйста, запустите print (pd .__ version__) и дайте мне знать, что там написано. - person cs95; 23.01.2021
comment
К сожалению, это не удается с группировкой MultiIndex. Самый простой обходной путь, который я видел до сих пор, хотя и уродливый, похоже, заключается в замене значения NaN перед группировкой. - person totalhack; 12.04.2021
comment
Я завелся, как никто, чтобы быть в одной группе! - person Ievgen Naida; 03.06.2021
comment
Однако логически это не одно и то же. @IevgenNaida, в идеале вы должны иметь только один способ представления недостающих данных. Все остальное сбивает с толку, хрупкое, подвержено ошибкам - person cs95; 05.06.2021
comment
@ cs95 Извините, мой комментарий вводит в заблуждение. Мне просто нужно, чтобы мой None был сгруппирован, а dropna = False отлично справляется со своей задачей. - person Ievgen Naida; 07.06.2021

Древняя тема, если кто-то все еще спотыкается об этом - другой обходной путь - преобразовать через .astype (str) в строку перед группировкой. Это сохранит NaN.

df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
    a
b   
4   1
6   3
nan 2
person M. Kiewisch    schedule 04.10.2016
comment
@ K3 --- rnc: Смотрите комментарий к вашей ссылке - автор сообщения по вашей ссылке сделал что-то не так. - person Thomas; 06.06.2018
comment
@Thomas, да, именно так, как в примере выше. Пожалуйста, отредактируйте, если вы можете сделать пример безопасным (и таким же тривиальным). - person K3---rnc; 07.06.2018
comment
sum of a здесь является конкатенацией строк, а не числовой суммой. Это работает только потому, что «b» состоит из разных записей. Вам нужно, чтобы 'a' было числовым, а 'b' - строковым - person BallpointBen; 27.02.2019

Я не могу добавить комментарий к M. Kiewisch, так как у меня недостаточно очков репутации (всего 41, но мне нужно больше 50, чтобы прокомментировать).

В любом случае, просто хочу отметить, что решение M. Kiewisch не работает как есть и может потребовать дополнительных настроек. Рассмотрим, например,

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

который показывает, что для группы b = 4.0 соответствующее значение равно 15 вместо 6. Здесь 1 и 5 просто объединяются как строки, а не складываются как числа.

person Kamaraju Kusumanchi    schedule 25.11.2016
comment
Это потому, что вы преобразовали весь DF в str, а не только столбец b - person Korem; 04.08.2017
comment
Обратите внимание, что теперь это было исправлено в упомянутом ответе. - person Shaido; 21.08.2019
comment
Новое решение, на мой взгляд, лучше, но все же небезопасно. Рассмотрим случай, когда одна из записей в столбце «b» такая же, как строковый np.NaN. Затем эти вещи складываются вместе. df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ; df ['b'] = df ['b']. astype (строка); df.groupby (['b']). sum () - person Kamaraju Kusumanchi; 21.08.2019

Все ответы, предоставленные до сих пор, приводят к потенциально опасному поведению, поскольку вполне возможно, что вы выберете фиктивное значение, которое на самом деле является частью набора данных. Это становится все более вероятным, поскольку вы создаете группы с множеством атрибутов. Проще говоря, этот подход не всегда хорошо обобщается.

Менее хакерское решение - использовать pd.drop_duplicates () для создания уникального индекса комбинаций значений, каждая из которых имеет свой собственный идентификатор, а затем группировать по этому идентификатору. Он более подробный, но выполняет свою работу:

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    return df_agg[agg_col_order]

Обратите внимание, что теперь вы можете просто сделать следующее:

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

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

person Grant Langseth    schedule 25.10.2018
comment
Это лучшее решение для общего случая, но в тех случаях, когда я знаю недопустимую строку / число, которое я могу использовать вместо этого, я, вероятно, собираюсь пойти с ответом Энди Хайдена ниже ... Я надеюсь, что панды скоро исправят это поведение. - person Sarah Messer; 25.04.2020

Одно небольшое замечание по поводу решения Энди Хайдена - оно не работает (больше?), Потому что np.nan == np.nan дает False, поэтому функция replace на самом деле ничего не делает.

Для меня сработало следующее:

df['b'] = df['b'].apply(lambda x: x if not np.isnan(x) else -1)

(По крайней мере, это поведение для Pandas 0.19.2. Извините, что добавляю его как другой ответ, у меня недостаточно репутации, чтобы комментировать.)

person Tuetschek    schedule 23.01.2017
comment
Также есть df['b'].fillna(-1). - person K3---rnc; 06.02.2017

Я уже ответил на это, но почему-то ответ был преобразован в комментарий. Тем не менее, это наиболее эффективное решение:

Неспособность включать (и размножать) NaN в группы довольно неприятно. Цитирование R неубедительно, поскольку такое поведение не согласуется со многими другими вещами. Во всяком случае, фиктивный хак тоже довольно плохой. Однако размер (включая NaN) и количество (без учета NaN) группы будут отличаться, если есть NaN.

dfgrouped = df.groupby(['b']).a.agg(['sum','size','count'])

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None

Если они различаются, вы можете вернуть значение Нет для результата функции агрегирования для этой группы.

person Community    schedule 23.05.2017
comment
Это было очень полезно для меня, но оно отвечает на несколько иной вопрос, чем исходный. IIUC, ваше решение распространяет NaN при суммировании, но элементы NaN в столбце b по-прежнему отбрасываются как строки. - person Andrew; 06.06.2019