Pandas: llenando los valores faltantes por media en cada grupo

83

Esto debería ser sencillo, pero lo más cercano que he encontrado es esta publicación: pandas: llenando los valores faltantes dentro de un grupo , y todavía no puedo resolver mi problema ...

Supongamos que tengo el siguiente marco de datos

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

y me gustaría completar "NaN" con el valor medio en cada grupo de "nombre", es decir

      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

No estoy seguro de a dónde ir después:

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

Gracias un montón.

BlueFeet
fuente

Respuestas:

91

Una forma sería utilizar 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
fuente
3
Me resultó útil al empezar a sentarme y leer los documentos. Éste está cubierto en la groupbysección. Hay demasiadas cosas para recordar, pero elige reglas como "la transformación es para operaciones por grupo que desea indexar como el marco original" y así sucesivamente.
DSM
Busque también el libro de Wes McKinney. Personalmente, creo que los documentos de groupby son abismales, el libro es un poco mejor.
Woody Pride
35
si tiene más de dos columnas, asegúrese de especificar el nombre de la columna df ["valor"] = df.groupby ("nombre"). transform (lambda x: x.fillna (x.mean ())) ['valor ']
Lauren
16
@Lauren Buen punto. Me gustaría agregar que, por razones de rendimiento, podría considerar mover la especificación de la columna de valor más a la izquierda a la cláusula group-by. De esta manera, la función lambda solo se llama para los valores en esa columna en particular, y no para cada columna y luego elige la columna. Hice una prueba y fue dos veces más rápido cuando se usan dos columnas. Y, naturalmente, obtendrá un mejor rendimiento cuantas más columnas no necesite imputar:df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean()))
André C. Andersen
He estado buscando esto durante dos días. Sólo una pregunta para ti. ¿Por qué es demasiado difícil hacer esto con bucles? Porque en mi caso hay dos índices múltiples, es decir, Statey Age_Groupluego estoy tratando de completar los valores que faltan en esos grupos con medias de grupo (del mismo estado dentro del mismo grupo de edad, tome la media y complete las faltas en el grupo). Gracias
Ozkan Serttas
45

fillna+ groupby+ transform+mean

Esto parece intuitivo:

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

La sintaxis groupby+ transformasigna la media grupal al índice del marco de datos original. Esto es aproximadamente equivalente a la solución de @ DSM , pero evita la necesidad de definir una lambdafunción anónima .

jpp
fuente
25

@DSM tiene IMO la respuesta correcta, pero me gustaría compartir mi generalización y optimización de la pregunta: Varias columnas para agrupar y tener múltiples columnas de valor:

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

... da ...

  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

En este caso generalizado, nos gustaría agrupar por categoryy name, e imputar solo en value.

Esto se puede resolver de la siguiente manera:

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

Observe la lista de columnas en la cláusula group-by, y que seleccionamos la valuecolumna justo después de group-by. Esto hace que la transformación solo se ejecute en esa columna en particular. Puede agregarlo al final, pero luego lo ejecutará para todas las columnas solo para descartar todas las columnas de medida menos una al final. Un planificador de consultas SQL estándar podría haber podido optimizar esto, pero pandas (0.19.2) no parece hacer esto.

Prueba de rendimiento aumentando el conjunto de datos haciendo ...

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

... confirma que esto aumenta la velocidad proporcionalmente a la cantidad de columnas que no tiene que imputar:

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

En una nota final, puede generalizar aún más si desea imputar más de una columna, pero no todas:

df[['value', 'other_value']] = df.groupby(['category', 'name'])['value', 'other_value']\
    .transform(lambda x: x.fillna(x.mean()))
André C. Andersen
fuente
Gracias por este gran trabajo. Me pregunto cómo pude lograr la misma transformación con el uso de forbucles. La velocidad no es mi preocupación, ya que estoy tratando de encontrar métodos manuales. Gracias @ AndréC.Andersen
Ozkan Serttas
12

Lo haría de esta manera

df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')
piRSquared
fuente
1
Una versión ligeramente diferente a estodf['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value)
tsando
9

La mayoría de las respuestas anteriores involucraron el uso de "groupby" y "transform" para completar los valores faltantes.

Pero prefiero usar "groupby" con "aplicar" para completar los valores faltantes, lo cual es más intuitivo para mí.

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

Atajo: Groupby + Aplicar / Lambda + Fillna + Media

Esta solución aún funciona si desea agrupar por varias columnas para reemplazar los valores faltantes.

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

La respuesta destacada de alto rango solo funciona para un marco de datos de pandas con solo dos columnas. Si tiene un caso de más columnas, utilice en su lugar:

df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
    lambda x: x.fillna(x.mean()))
Philipp Schwarz
fuente
Esta respuesta funcionó para mí, gracias. También para cualquier persona nueva en pandas, también puede indexar usando notación de corte df.groupby("continent")['Crude_Birth_rate']... . Creo que esta es la covnention sugerida
Adam Hughes
2
def groupMeanValue(group):
    group['value'] = group['value'].fillna(group['value'].mean())
    return group

dft = df.groupby("name").transform(groupMeanValue)
Prajit Patil
fuente
0
df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)
Vino Vincent
fuente
5
Por favor, explique su respuesta. ¿Por qué alguien que se topa con esta página de Google debería usar su solución sobre las otras 6 respuestas?
Divibisan
1
@vino por favor agregue alguna explicación
Nursnaaz
-1

También puede utilizar "dataframe or table_name".apply(lambda x: x.fillna(x.mean())).

Hardik Pachgade
fuente