Панды: заполнение пропущенных значений средним в каждой группе


88

Это должно быть просто, но самое близкое, что я нашел, это этот пост: pandas: Заполнение недостающих значений внутри группы , и я все еще не могу решить свою проблему ....

Предположим, у меня есть следующий фрейм данных

df = pd.DataFrame({'value': [1, np.nan, np.nan, 2, 3, 1, 3, np.nan, 3], 'name': ['A','A', 'B','B','B','B', 'C','C','C']})

  name  value
0    A      1
1    A    NaN
2    B    NaN
3    B      2
4    B      3
5    B      1
6    C      3
7    C    NaN
8    C      3

и я хочу заполнить "NaN" средним значением в каждой группе "name", т.е.

      name  value
0    A      1
1    A      1
2    B      2
3    B      2
4    B      3
5    B      1
6    C      3
7    C      3
8    C      3

Я не уверен, куда идти после:

grouped = df.groupby('name').mean()

Огромное спасибо.

Ответы:


94

Один из способов - использовать transform:

>>> df
  name  value
0    A      1
1    A    NaN
2    B    NaN
3    B      2
4    B      3
5    B      1
6    C      3
7    C    NaN
8    C      3
>>> df["value"] = df.groupby("name").transform(lambda x: x.fillna(x.mean()))
>>> df
  name  value
0    A      1
1    A      1
2    B      2
3    B      2
4    B      3
5    B      1
6    C      3
7    C      3
8    C      3

3
Я счел полезным сесть и прочитать документацию. Об этом рассказывается в groupbyразделе. Слишком много вещей, которые нужно запомнить, но вы выбираете такие правила, как «преобразование выполняется для групповых операций, которые вы хотите индексировать, как исходный фрейм» и так далее.
DSM

Также ищите книгу Уэса МакКинни. Лично я считаю, что документация по groupby отвратительна, книга немного лучше.
Woody Pride

38
если у вас более двух столбцов, не забудьте указать имя столбца df ["value"] = df.groupby ("name"). transform (lambda x: x.fillna (x.mean ())) ['value ']
Лорен

16
@Lauren Хорошее замечание. Я хотел бы добавить, что по соображениям производительности вы можете подумать о том, чтобы переместить спецификацию столбца значений дальше влево в предложение group-by. Таким образом, лямбда-функция вызывается только для значений в этом конкретном столбце, а не для каждого столбца, а затем выбирается столбец. Сделал тест, и он был вдвое быстрее при использовании двух столбцов. И, естественно, чем больше столбцов вам не нужно приписывать, тем выше производительность:df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean()))
André C. Andersen

Искал это два дня .. Просто вопрос к вам. Почему с петлями это сделать слишком сложно? Потому что в моем случае есть два мультииндекса, т.е. Stateа Age_Groupзатем я пытаюсь заполнить недостающие значения в этих группах с помощью групповых средних (из того же штата в той же возрастной группе взять среднее и заполнить пропущенные значения в группе) .. Спасибо
Озкан Серттас

51

fillna+ groupby+ transform+mean

Это кажется интуитивно понятным:

df['value'] = df['value'].fillna(df.groupby('name')['value'].transform('mean'))

groupby+ transformСинтаксис отображает GROUPWISE среднее значение для индекса исходного dataframe. Это примерно эквивалентно решению @ DSM , но позволяет избежать необходимости определять анонимную lambdaфункцию.


25

@DSM имеет правильный ответ IMO, но я хотел бы поделиться своим обобщением и оптимизацией вопроса: несколько столбцов для группировки и столбцы с несколькими значениями:

df = pd.DataFrame(
    {
        'category': ['X', 'X', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y'],
        'name': ['A','A', 'B','B','B','B', 'C','C','C'],
        'other_value': [10, np.nan, np.nan, 20, 30, 10, 30, np.nan, 30],
        'value': [1, np.nan, np.nan, 2, 3, 1, 3, np.nan, 3],
    }
)

... дает ...

  category name  other_value value
0        X    A         10.0   1.0
1        X    A          NaN   NaN
2        X    B          NaN   NaN
3        X    B         20.0   2.0
4        X    B         30.0   3.0
5        X    B         10.0   1.0
6        Y    C         30.0   3.0
7        Y    C          NaN   NaN
8        Y    C         30.0   3.0

В этом обобщенном случае мы хотели бы сгруппировать по categoryи name, а вменять только по value.

Это можно решить следующим образом:

df['value'] = df.groupby(['category', 'name'])['value']\
    .transform(lambda x: x.fillna(x.mean()))

Обратите внимание на список столбцов в предложении group-by, и что мы выбираем valueстолбец сразу после group-by. Это заставляет преобразование запускаться только в этом конкретном столбце. Вы можете добавить его в конец, но затем вы запустите его для всех столбцов, чтобы выбросить все столбцы, кроме одного, в конце. Стандартный планировщик запросов SQL мог бы оптимизировать это, но pandas (0.19.2), похоже, этого не делает.

Тест производительности путем увеличения набора данных, выполнив ...

big_df = None
for _ in range(10000):
    if big_df is None:
        big_df = df.copy()
    else:
        big_df = pd.concat([big_df, df])
df = big_df

... подтверждает, что это увеличивает скорость пропорционально тому, сколько столбцов вам не нужно вменять:

import pandas as pd
from datetime import datetime

def generate_data():
    ...

t = datetime.now()
df = generate_data()
df['value'] = df.groupby(['category', 'name'])['value']\
    .transform(lambda x: x.fillna(x.mean()))
print(datetime.now()-t)

# 0:00:00.016012

t = datetime.now()
df = generate_data()
df["value"] = df.groupby(['category', 'name'])\
    .transform(lambda x: x.fillna(x.mean()))['value']
print(datetime.now()-t)

# 0:00:00.030022

В заключение вы можете обобщить еще больше, если хотите вменять более одного столбца, но не все:

df[['value', 'other_value']] = df.groupby(['category', 'name'])['value', 'other_value']\
    .transform(lambda x: x.fillna(x.mean()))

Спасибо за отличную работу. Мне интересно, как я могу добиться того же преобразования с использованием forциклов. Скорость меня не волнует, так как я пытаюсь найти ручные методы. Спасибо @ AndréC.Andersen
Озкан Серттас

12

Я бы сделал это так

df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')

1
Немного другая версияdf['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value)
tsando

10

В большинстве приведенных выше ответов использовались «groupby» и «transform» для заполнения отсутствующих значений.

Но я предпочитаю использовать "groupby" с "apply" для заполнения отсутствующих значений, что для меня более интуитивно понятно.

>>> df['value']=df.groupby('name')['value'].apply(lambda x:x.fillna(x.mean()))
>>> df.isnull().sum().sum()
    0 

Ярлык: Groupby + Apply / Lambda + Fillna + Mean

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

     >>> df = pd.DataFrame({'value': [1, np.nan, np.nan, 2, 3, np.nan,np.nan, 4, 3], 
    'name': ['A','A', 'B','B','B','B', 'C','C','C'],'class':list('ppqqrrsss')})  

     >>> df
   value name   class
0    1.0    A     p
1    NaN    A     p
2    NaN    B     q
3    2.0    B     q
4    3.0    B     r
5    NaN    B     r
6    NaN    C     s
7    4.0    C     s
8    3.0    C     s

>>> df['value']=df.groupby(['name','class'])['value'].apply(lambda x:x.fillna(x.mean()))

>>> df
        value name   class
    0    1.0    A     p
    1    1.0    A     p
    2    2.0    B     q
    3    2.0    B     q
    4    3.0    B     r
    5    3.0    B     r
    6    3.5    C     s
    7    4.0    C     s
    8    3.0    C     s

5

Представленный ответ с высоким рейтингом работает только для фрейма данных pandas только с двумя столбцами. Если у вас есть больше столбцов, используйте вместо этого:

df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
    lambda x: x.fillna(x.mean()))

Этот ответ сработал для меня, спасибо. Также для всех, кто новичок в pandas, также можно индексировать, используя нотацию срезов, df.groupby("continent")['Crude_Birth_rate']... я считаю, что это предлагаемое соглашение
Адам Хьюз


0
df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)

5
Пожалуйста, объясните свой ответ. Почему тот, кто наткнулся на эту страницу из Google, должен использовать ваше решение вместо остальных 6 ответов?
divibisan

1
@vino, пожалуйста, добавьте пояснение
Нурснааз

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.