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

Это должно быть просто, но ближе всего, что я нашел, это сообщение: 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()

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


person BlueFeet    schedule 13.11.2013    source источник


Ответы (9)


Один из способов - использовать 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
person DSM    schedule 13.11.2013
comment
Я счел полезным сесть и прочитать документацию, когда только начинал. Это описано в разделе groupby. Слишком много вещей, которые нужно запомнить, но вы выбираете такие правила, как преобразование для групповых операций, которые вы хотите проиндексировать, как исходный фрейм, и так далее. - person DSM; 14.11.2013
comment
Также ищите книгу Уэса МакКинни. Лично я считаю, что документация по groupby отвратительна, книга немного лучше. - person Woody Pride; 14.11.2013
comment
если у вас более двух столбцов, обязательно укажите имя столбца df [value] = df.groupby (name) .transform (lambda x: x.fillna (x.mean ())) ['value'] - person Lauren; 10.01.2017
comment
@Lauren Хорошее замечание. Я хотел бы добавить, что по соображениям производительности вы можете подумать о том, чтобы переместить спецификацию столбца значений дальше влево в предложение group-by. Таким образом, лямбда-функция вызывается только для значений в этом конкретном столбце, а не для каждого столбца, а затем выбирается столбец. Сделал тест, и он был вдвое быстрее при использовании двух столбцов. И, естественно, чем больше столбцов вам не нужно вменять, тем выше производительность: df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean())) - person André C. Andersen; 28.07.2017
comment
Искал это два дня .. Просто вопрос к вам. Почему с петлями это сделать слишком сложно? Поскольку в моем случае есть два мультииндекса, то есть State и Age_Group, я пытаюсь заполнить недостающие значения в этих группах с помощью групповых средств (из того же состояния в той же возрастной группе взять среднее значение и заполнить пропущенные значения в группе). Спасибо - person Ozkan Serttas; 09.01.2019
comment
Да ладно, я вижу обобщенное решение благодаря @ AndréC.Andersen - person Ozkan Serttas; 10.01.2019

fillna + _ 2_ + transform + mean

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

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

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

person jpp    schedule 16.11.2018
comment
Спасибо !, я считаю, что лямбда-функция немного сбивает с толку, а ваша - гораздо более понятной. - person Anindhito Irmandharu; 17.03.2021
comment
Хорошее решение. Моя groupby возвращает 73k групп. Другими словами, необходимо было найти среднее из 73 тыс. Групп, чтобы заполнить значения NA для каждой группы. Меня больше всего беспокоит время, так как я хочу легко масштабировать его до более чем 73 тысяч групп. Решение лямбда заняло 21,39 секунды, в то время как это решение заняло 0,27 секунды. Настоятельно рекомендую воспользоваться этим решением! - person Sam; 31.03.2021

@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()))
person André C. Andersen    schedule 28.07.2017
comment
Спасибо за отличную работу. Мне интересно, как я могу добиться успеха в том же преобразовании с использованием for циклов. Скорость меня не волнует, так как я пытаюсь найти ручные методы. Спасибо @ AndréC.Andersen - person Ozkan Serttas; 10.01.2019

Ярлык:

Groupby + Apply + Lambda + Fillna + Mean

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

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

>>> 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']=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
 
person Ashish Anand    schedule 02.12.2019

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

df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')
person piRSquared    schedule 18.11.2016
comment
Версия, немного отличающаяся от этой df['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value) - person tsando; 16.07.2019

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

df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
    lambda x: x.fillna(x.mean()))
person Philipp Schwarz    schedule 13.10.2016
comment
Этот ответ сработал для меня, спасибо. Также для всех, кто плохо знаком с пандами, также можно индексировать, используя нотацию срезов df.groupby("continent")['Crude_Birth_rate']... Я считаю, что это предлагаемое соглашение - person Adam Hughes; 07.11.2019

Подводя итог всему вышесказанному относительно эффективности возможного решения, у меня есть набор данных с 97 906 строками и 48 столбцами. Я хочу заполнить 4 столбца медианой каждой группы. В столбце, который я хочу сгруппировать, 26 200 групп.

Первое решение

start = time.time()
x = df_merged[continuous_variables].fillna(df_merged.groupby('domain_userid')[continuous_variables].transform('median'))
print(time.time() - start)
0.10429811477661133 seconds

Второе решение

start = time.time()
for col in continuous_variables:
    df_merged.loc[df_merged[col].isnull(), col] = df_merged.groupby('domain_userid')[col].transform('median')
print(time.time() - start)
0.5098445415496826 seconds

Следующее решение я применил только к подмножеству, так как оно работало слишком долго.

start = time.time()
for col in continuous_variables:
    x = df_merged.head(10000).groupby('domain_userid')[col].transform(lambda x: x.fillna(x.median()))
print(time.time() - start)
11.685635566711426 seconds

Следующее решение следует той же логике, что и выше.

start = time.time()
x = df_merged.head(10000).groupby('domain_userid')[continuous_variables].transform(lambda x: x.fillna(x.median()))
print(time.time() - start)
42.630549907684326 seconds

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

person Sam    schedule 01.04.2021

Вы также можете использовать "dataframe or table_name".apply(lambda x: x.fillna(x.mean())).

person Hardik Pachgade    schedule 28.09.2019

person    schedule
comment
Пожалуйста, объясните свой ответ. Почему тот, кто наткнулся на эту страницу из Google, должен использовать ваше решение вместо остальных 6 ответов? - person divibisan; 04.10.2018
comment
@vino, пожалуйста, добавьте пояснение - person Nursnaaz; 16.02.2019