@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 category
y 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 value
columna 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)
t = datetime.now()
df = generate_data()
df["value"] = df.groupby(['category', 'name'])\
.transform(lambda x: x.fillna(x.mean()))['value']
print(datetime.now()-t)
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()))
groupby
secció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.