Pandy: średnie wypełnianie brakujących wartości w każdej grupie

87

To powinno być proste, ale najbliższą rzeczą, jaką znalazłem, jest ten post: pandy: Uzupełnianie brakujących wartości w grupie , a nadal nie mogę rozwiązać swojego problemu ....

Załóżmy, że mam następującą ramkę danych

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

i chciałbym wypełnić „NaN” wartością średnią w każdej grupie „name”, tj

      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

Nie jestem pewien, dokąd iść:

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

Wielkie dzięki.

BlueFeet
źródło

Odpowiedzi:

93

Jednym ze sposobów byłoby użycie 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
DSM
źródło
3
Uważam, że to pomocne, kiedy zaczynałam usiąść i czytać dokumenty. Ten jest omówiony w groupbysekcji. Jest zbyt wiele rzeczy do zapamiętania, ale wybierasz reguły takie jak „transformacja dotyczy operacji na grupę, które chcesz indeksować jak oryginalna ramka” i tak dalej.
DSM
Poszukaj także książki Wesa McKinneya. Osobiście uważam, że dokumenty z Groupby są absurdalne, książka jest nieznacznie lepsza.
Woody Pride
38
jeśli masz więcej niż dwie kolumny, podaj nazwę kolumny df ["wartość"] = df.groupby ("nazwa"). transform (lambda x: x.fillna (x.mean ())) ['wartość ']
Lauren,
16
@Lauren Słuszna uwaga. Chciałbym dodać, że ze względu na wydajność możesz rozważyć przeniesienie specyfikacji kolumny wartości dalej w lewo do klauzuli grupowania. W ten sposób funkcja lambda jest wywoływana tylko dla wartości w tej konkretnej kolumnie, a nie dla każdej kolumny, a następnie wybiera kolumnę. Zrobiłem test i było dwa razy szybciej przy użyciu dwóch kolumn. I naturalnie uzyskujesz lepszą wydajność, im więcej kolumn nie musisz przypisywać:df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean()))
André C. Andersen
Szukałem tego od dwóch dni… Tylko pytanie do Ciebie. Dlaczego jest to zbyt trudne w przypadku pętli? Ponieważ w moim przypadku są dwa multi-indeksy tj. StateA Age_Groupwtedy staram się uzupełnić braki w tych grupach średnimi grupowymi (z tego samego stanu w tej samej grupie wiekowej należy wziąć średnią i wypełnić braki w grupie). Dzięki
Ozkan Serttas
51

fillna+ groupby+ transform+mean

Wydaje się to intuicyjne:

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

groupby+ transformSkładnia mapuje średnią GroupWise do indeksu pierwotnego dataframe. Jest to w przybliżeniu odpowiednik rozwiązania @ DSM , ale pozwala uniknąć konieczności definiowania lambdafunkcji anonimowej .

jpp
źródło
25

@DSM ma IMO właściwą odpowiedź, ale chciałbym podzielić się moim uogólnieniem i optymalizacją pytania: Wiele kolumn do grupowania i posiadających wiele kolumn wartości:

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],
    }
)

... daje ...

  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

W tym uogólnionym przypadku chcielibyśmy pogrupować według categoryi name, a imputować tylko według value.

Można to rozwiązać w następujący sposób:

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

Zwróć uwagę na listę kolumn w klauzuli group-by i że wybieramy valuekolumnę tuż po funkcji group-by. To sprawia, że ​​transformacja jest uruchamiana tylko w tej konkretnej kolumnie. Możesz dodać go na końcu, ale wtedy uruchomisz go dla wszystkich kolumn tylko po to, aby na końcu wyrzucić wszystkie kolumny miar z wyjątkiem jednej. Standardowy planer zapytań SQL mógł to zoptymalizować, ale wydaje się, że pandy (0.19.2) tego nie robią.

Test wydajności poprzez zwiększenie zbioru danych poprzez wykonanie ...

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

... potwierdza, że ​​zwiększa to prędkość proporcjonalnie do liczby kolumn, których nie musisz imputować:

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

Na koniec możesz uogólnić jeszcze bardziej, jeśli chcesz przypisać więcej niż jedną kolumnę, ale nie wszystkie:

df[['value', 'other_value']] = df.groupby(['category', 'name'])['value', 'other_value']\
    .transform(lambda x: x.fillna(x.mean()))
André C. Andersen
źródło
Dziękuję za tę wspaniałą pracę. Zastanawiam się, jak mógłbym pomyślnie przeprowadzić tę samą transformację przy użyciu forpętli. Szybkość nie jest moim zmartwieniem, ponieważ próbuję znaleźć metody ręczne. Dzięki @ AndréC.Andersen
Ozkan Serttas
12

Zrobiłbym to w ten sposób

df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')
piRSquared
źródło
1
Nieco inna wersja niż tadf['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value)
tsando
9

Większość z powyższych odpowiedzi dotyczyła użycia „grupowania” i „transformacji” do wypełnienia brakujących wartości.

Ale wolę używać „grupuj” z „zastosuj”, aby uzupełnić brakujące wartości, co jest dla mnie bardziej intuicyjne.

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

Skrót: Grupuj + Zastosuj / Lambda + Fillna + Średnia

To rozwiązanie nadal działa, jeśli chcesz grupować według wielu kolumn, aby zastąpić brakujące wartości.

     >>> 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
Ashish Anand
źródło
5

Wyróżniona wysoko sklasyfikowana odpowiedź działa tylko dla pandy Dataframe z tylko dwiema kolumnami. Jeśli masz więcej kolumn, użyj zamiast tego:

df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
    lambda x: x.fillna(x.mean()))
Philipp Schwarz
źródło
Ta odpowiedź zadziałała dla mnie, dzięki. Również dla każdego, kto jest nowy w pandach, może również indeksować przy użyciu notacji do wycinania. df.groupby("continent")['Crude_Birth_rate']... Uważam, że jest to sugerowane porozumienie
Adam Hughes,
2
def groupMeanValue(group):
    group['value'] = group['value'].fillna(group['value'].mean())
    return group

dft = df.groupby("name").transform(groupMeanValue)
Prajit Patil
źródło
0
df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)
Vino Vincent
źródło
5
Proszę wyjaśnić swoją odpowiedź. Dlaczego ktoś, kto natknie się na tę stronę w Google, miałby używać Twojego rozwiązania zamiast pozostałych 6 odpowiedzi?
divibisan
1
@vino proszę dodać wyjaśnienie
Nursnaaz
-1

Możesz także użyć "dataframe or table_name".apply(lambda x: x.fillna(x.mean())).

Hardik Pachgade
źródło