колонки pandas GroupBy зі значеннями NaN (відсутні)


147

У мене є 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 є відсутні значення), і я використовую більш складні функції, ніж просто медіани (як правило, випадкові ліси), я хочу уникати написання занадто складних фрагментів коду.

Будь-які пропозиції? Чи потрібно написати функцію для цього чи є просте рішення?


1
@PhillipCloud Я відредагував це питання, щоб включити лише запитання, яке є дуже хорошим, що стосується розширення відкритих панд Джеффа.
Енді Хайден

1
Неможливість включення (і розповсюдження) NaN в групи є досить обтяжуючим фактором. Цитування R не є переконливим, оскільки така поведінка не узгоджується з багатьма іншими речами. У будь-якому випадку, хамма-хак теж досить погана. Однак розмір (включає NaN) та кількість (ігнорує NaN) групи будуть відрізнятися, якщо є NaN. dfgrouped = df.groupby (['b']) ']] = Немає
Брайан Преслопський

Чи можете ви підсумувати те, що ви конкретно намагаєтесь досягти? тобто ми бачимо вихід, але що таке "бажаний" вихід?
ч

2
З пандами 1.1 ви скоро зможете вказати dropna=Falseв , groupby()щоб отримати бажаний результат. Більше інформації
cs95

Відповіді:


130

Про це йдеться в розділі Пропущені дані в документах :

Групи 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 - яка використовує той самий хакер-заповнювач).


4
Це логічне, але якесь смішне рішення, про яке я думав раніше, Pandas робить поля NaN із порожніх, і ми повинні їх змінити назад. Це причина, що я думаю про пошук інших рішень, таких як запуск SQL-сервера та запит таблиць звідти (виглядає трохи надто складно), або шукати іншу бібліотеку, незважаючи на Pandas, або використовувати свою власну (що я хочу позбутися). Thx
Gyula Sámuel Karli

@ GyulaSámuelKarli Мені це здається невеликою помилкою (див. Вищезгадану помилку), і моє рішення - це вирішення проблеми. Мені здається дивним, що ви списуєте всю бібліотеку.
Енді Хайден

1
Я не хочу записувати Pandas, просто шукайте інструмент, який найбільше відповідає моїм запитам.
Gyula Sámuel Karli

1
Подивіться на мою відповідь нижче, я вважаю, що я знайшов досить хороше (чистіше і, можливо, швидше) рішення. stackoverflow.com/a/43375020/408853
ча

4
Ні, це не узгоджується з R. df%>% group_by також подаватиме підсумки NA з попередженням, якого можна уникнути, перенісши стовпець групування через fct_explicit_na, а потім буде створено рівень (Відсутній).
Бурхливий догляд

40

Стародавня тема, якщо хтось все ще натрапляє на це - ще одне вирішення - перетворити через .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

@ K3 --- rnc: дивіться коментар до свого посилання - автор публікації у вашому посиланні зробив щось не так.
Томас

@Thomas, так, саме так, як у наведеному вище прикладі. Будь ласка, відредагуйте, чи можна зробити приклад безпечним (і як тривіальний).
K3 --- rnc

sumЗ aє конкатенація тут, а не цифровий сума. Це лише "працює", оскільки "b" складався з різних записів. Ви повинні «а» бути числовими і «б» бути рядок
BallpointBen

28

панди> = 1.1

З панд 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

4
Сподіваємось, ця відповідь робить поступовий похід до вершини. Це правильний підхід.
kdbanman

Я не думаю, що 1.1 ще випущено. Перевірено на conda та pip та версії ще є 1.0.4
sammywemmy

1
@sammywemmy Так, зараз це може бути запущено лише в середовищі розробки . Мені подобається займатись головою, коли справа стосується впровадження нових функцій у старих повідомленнях SO. ;-)
cs95

9

Я не в змозі додати коментар до М. Ківіша, оскільки мені не вистачає репутаційних балів (всього 41, але для коментарів потрібно більше 50).

У будь-якому випадку, просто хочу зазначити, що рішення М. 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 як рядки, а не додає їх у вигляді чисел.


12
Це тому, що ви перетворили весь DF на str, а не лише bстовпець
Korem

Зауважте, що це було зафіксовано у згаданій відповіді зараз.
Шаїдо

1
Нове рішення є кращим, але все-таки не безпечним, на мій погляд. Розглянемо випадок, коли один із записів у стовпці 'b' є таким же, як і закреслений np.NaN. Потім ці речі поєднуються між собою. df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ; df ['b'] = df ['b']. астип (str); df.groupby (['b']). sum ()
Kamaraju Kusumanchi

6

Одне невелике значення для рішення Енді Хейдена - воно не працює (більше?), Оскільки 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. Вибачте, додайте це як іншу відповідь, у мене недостатньо репутації для коментарів.)


12
Є також df['b'].fillna(-1).
K3 --- rnc

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')]))

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


Це найкраще рішення для загального випадку, але у випадках, коли мені відомо про недійсний рядок / номер, який я можу використовувати замість цього, я, мабуть, збираюся піти з відповіддю Енді Хайдена нижче ... Я сподіваюся, що панда скоро виправить цю поведінку.
Сара Мессер

4

Я вже відповів на це, але чомусь відповідь перетворили на коментар. Тим не менш, це найбільш ефективне рішення:

Неможливість включати (і розповсюджувати) NaN в групи досить обтяжує. Цитування R не є переконливим, оскільки така поведінка не узгоджується з багатьма іншими речами. У будь-якому випадку, хамма-хак теж досить погана. Однак розмір (включає NaNs) та кількість (ігнорує NaNs) групи відрізнятимуться, якщо є NaN.

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

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

Якщо вони різняться, ви можете встановити значення назад у None для результату функції агрегації для цієї групи.


1
Це було дуже корисно для мене, але воно відповідає дещо іншим питанням, ніж оригінальне. IIUC, ваше рішення поширює NaN в підсумовуванні, але елементи NaN у стовпці "b" все ще опускаються як рядки.
Андрій

0

Встановлено Pandas 1.1 в Анаконда

Я не можу коментувати відповідь cs95, але він допоміг мені вирішити проблему.

Я спробував встановити Pandas 1.1, але не вдалося скористатися його кодом, тому я google і міг встановити.

Я спершу запускаю підказку anaconda як адміністратор і вставляю наступний код:

pip install pandas==1.1.0rc0

Після цього включайте використання dropna = False

Посилання: https://libraries.io/pypi/pandas


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.