Подтвердить что ты не робот

Групповые столбцы с значениями 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. (Я хочу включить эти строки!)

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

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

4b9b3361

Ответ 1

Это упомянутое в разделе "Пропущенные данные" :

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

Обходным путем является использование заполнителя перед выполнением groupby (например, -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 - который использует один и тот же врезщик).

Ответ 2

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

in:
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
out:
    a
b   
4   1
6   3
nan 2

Ответ 3

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

В любом случае, просто хочу указать, что решение М. Кивиша не работает так, как есть, и может потребоваться больше настроек. Рассмотрим, например,

>>> 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 как строки вместо того, чтобы добавлять их в число.

Ответ 4

Одна маленькая точка для решения Энди Хейдена - она ​​не работает (больше?), потому что 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. Извините, что добавьте его как другой ответ, у меня недостаточно репутации для комментариев.)

Ответ 5

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

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

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

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

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

Ответ 6

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

Менее хакерское решение - использовать 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')]))

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

Ответ 7

Хотя по умолчанию в групповых агрегатах нет функции skipna, есть простой и чистый способ сделать это:

def custom_mean(df):
    return df.mean(skipna=False)

group.agg({"your_col_name_to_be_aggregated":custom_mean})

Что это!

Заметьте, что я не сравнивал это, но я ожидаю, что это будет быстрее, чем обсуждалось в предыдущих ответах.

Ответ найден в doc.