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.

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

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 .


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()))

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

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

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()))

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)

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.