Pandas: Füllen fehlender Werte mit dem Mittelwert in jeder Gruppe

83

Dies sollte einfach sein, aber das Nächste, was ich gefunden habe, ist dieser Beitrag: pandas: Füllen fehlender Werte innerhalb einer Gruppe , und ich kann mein Problem immer noch nicht lösen ...

Angenommen, ich habe den folgenden Datenrahmen

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

und ich möchte "NaN" mit dem Mittelwert in jeder "Namen" -Gruppe ausfüllen, d. h

      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

Ich bin mir nicht sicher, wohin ich gehen soll:

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

Vielen Dank.

BlueFeet
quelle

Antworten:

91

Ein Weg wäre zu verwenden 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
quelle
3
Ich fand es hilfreich, als ich anfing, mich hinzusetzen und die Dokumente durchzulesen. Dieser wird in diesem groupbyAbschnitt behandelt. Es gibt zu viel zu merken, aber Sie lernen Regeln wie "Transformation ist für Operationen pro Gruppe, die wie der ursprüngliche Frame indiziert werden sollen" und so weiter.
DSM
Achten Sie auch auf das Buch von Wes McKinney. Persönlich denke ich, dass die Dokumente auf groupby abgrundtief sind, das Buch ist geringfügig besser.
Woody Pride
35
Wenn Sie mehr als zwei Spalten haben, geben Sie den Spaltennamen df ["value"] = df.groupby ("name") an. transform (lambda x: x.fillna (x.mean ())) ['value ']
Lauren
16
@Lauren Guter Punkt. Ich möchte hinzufügen, dass Sie aus Leistungsgründen in Betracht ziehen könnten, die Wertespaltenspezifikation weiter links in die group-by-Klausel zu verschieben. Auf diese Weise wird die Lambda-Funktion nur für Werte in dieser bestimmten Spalte und nicht für jede Spalte aufgerufen und dann die Spalte ausgewählt. Habe einen Test gemacht und es war doppelt so schnell, wenn zwei Spalten verwendet wurden. Und natürlich erhalten Sie eine bessere Leistung, je mehr Spalten Sie nicht unterstellen müssen:df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean()))
André C. Andersen
Ich habe zwei Tage lang danach gesucht. Nur eine Frage an Sie. Warum ist es zu schwierig, dies mit Schleifen zu tun? Weil es in meinem Fall zwei Multi-Indizes gibt, dh Stateund Age_Groupdann versuche ich, fehlende Werte in diesen Gruppen mit Gruppenmitteln zu füllen (aus demselben Bundesstaat innerhalb derselben Altersgruppe nehmen Mittelwerte und fehlende Werte in der Gruppe). Danke
Ozkan Serttas
45

fillna+ groupby+ transform+mean

Dies scheint intuitiv zu sein:

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

Die groupby+ transform-Syntax ordnet den gruppenweisen Mittelwert dem Index des ursprünglichen Datenrahmens zu. Dies entspricht in etwa der Lösung von @ DSM , vermeidet jedoch die Notwendigkeit, eine anonyme lambdaFunktion zu definieren .

jpp
quelle
25

@DSM hat IMO die richtige Antwort, aber ich möchte meine Verallgemeinerung und Optimierung der Frage teilen: Mehrere Spalten zum Gruppieren und mit mehreren Wertespalten:

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

... gibt ...

  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

In diesem verallgemeinerten Fall würden wir gerne Gruppe durch categoryund nameund zurechnet nur auf value.

Dies kann wie folgt gelöst werden:

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

Beachten Sie die Spaltenliste in der Gruppierungsklausel und dass wir die valueSpalte direkt nach der Gruppierungsklausel auswählen . Dadurch wird die Transformation nur für diese bestimmte Spalte ausgeführt. Sie können es am Ende hinzufügen, aber dann werden Sie es für alle Spalten ausführen, um am Ende alle bis auf eine Messspalte zu löschen. Ein Standard-SQL-Abfrageplaner hätte dies möglicherweise optimieren können, aber Pandas (0.19.2) scheint dies nicht zu tun.

Leistungstest durch Erhöhen des Datensatzes durch Ausführen von ...

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

... bestätigt, dass dies die Geschwindigkeit proportional zu der Anzahl der Spalten erhöht, die Sie nicht unterstellen müssen:

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

Abschließend können Sie noch weiter verallgemeinern, wenn Sie mehr als eine Spalte, aber nicht alle unterstellen möchten:

df[['value', 'other_value']] = df.groupby(['category', 'name'])['value', 'other_value']\
    .transform(lambda x: x.fillna(x.mean()))
André C. Andersen
quelle
Vielen Dank für diese großartige Arbeit. Ich frage mich, wie ich mit der Verwendung von forSchleifen dieselbe Transformation erreichen kann . Geschwindigkeit ist nicht mein Anliegen, da ich versuche, manuelle Methoden zu finden. Danke @ AndréC.Andersen
Ozkan Serttas
12

Ich würde es so machen

df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')
piRSquared
quelle
1
Eine etwas andere Version als diesedf['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value)
Tsando
9

Die meisten der oben genannten Antworten betrafen die Verwendung von "groupby" und "transform", um die fehlenden Werte zu füllen.

Aber ich bevorzuge es, "groupby" mit "apply" zu verwenden, um die fehlenden Werte zu füllen, was für mich intuitiver ist.

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

Verknüpfung: Groupby + Apply / Lambda + Fillna + Mean

Diese Lösung funktioniert weiterhin, wenn Sie nach mehreren Spalten gruppieren möchten, um fehlende Werte zu ersetzen.

     >>> 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
quelle
5

Die vorgestellte hochrangige Antwort funktioniert nur für einen Pandas-Datenrahmen mit nur zwei Spalten. Wenn Sie mehr Spalten haben, verwenden Sie stattdessen:

df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
    lambda x: x.fillna(x.mean()))
Philipp Schwarz
quelle
Diese Antwort hat bei mir funktioniert, danke. Auch für jeden, der neu in Pandas ist, kann auch mit Slicing-Notation indizieren. df.groupby("continent")['Crude_Birth_rate']... Ich glaube, dies ist die vorgeschlagene Covnention
Adam Hughes
2
def groupMeanValue(group):
    group['value'] = group['value'].fillna(group['value'].mean())
    return group

dft = df.groupby("name").transform(groupMeanValue)
Prajit Patil
quelle
0
df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)
Vino Vincent
quelle
5
Bitte erläutern Sie Ihre Antwort. Warum sollte jemand, der auf diese Seite von Google stößt, Ihre Lösung über die anderen 6 Antworten verwenden?
Divibisan
1
@ Vino bitte einige Erklärung hinzufügen
Nursnaaz
-1

Sie können auch verwenden "dataframe or table_name".apply(lambda x: x.fillna(x.mean())).

Hardik Pachgade
quelle