Tengo un marco de datos como el siguiente,
import pandas as pd data = { 'brand': ['Mercedes', 'Renault', 'Ford', 'Mercedes', 'Mercedes', 'Mercedes', 'Renault'], 'model': ['X', 'Y', 'Z', 'X', 'X', 'X', 'Q'], 'year': [2011, 2010, 2009, 2010, 2012, 2020, 2011], 'price': [None, 1000.4, 2000.3, 1000.0, 1100.3, 3000.5, None] } df = pd.DataFrame(data) print(df) brand model year price 0 Mercedes X 2011 NaN 1 Renault Y 2010 1000.4 2 Ford Z 2009 2000.3 3 Mercedes X 2010 1000.0 4 Mercedes X 2012 1100.3 5 Mercedes X 2020 3000.5 6 Renault Q 2011 NaNY aquí está el otro caso para probar su solución,
data = { 'brand': ['Mercedes', 'Mercedes', 'Mercedes', 'Mercedes', 'Mercedes'], 'model': ['X', 'X', 'X', 'X', 'X'], 'year': [2017, 2018, 2018, 2019, 2019], 'price': [None, None, None, 1000.0, 1200.50] }Rendimiento esperado,
brand model year price 0 Mercedes X 2017 NaN 1 Mercedes X 2018 1100.25 2 Mercedes X 2018 1100.25 3 Mercedes X 2019 1000.00 4 Mercedes X 2019 1200.50Quiero completar los valores que faltan con el promedio de las observaciones que contienen año-1, año y año+1 y también la misma marca y modelo. Por ejemplo, el modelo Mercedes X tiene un precio nulo en 2011. Cuando miro los datos,
2011 - 1 = 2010 2011 + 1 = 2012 The 4th observation -> Mercedes,X,2010,1000.0 The 5th observation -> Mercedes,X,2012,1100.3 The mean -> (1000.0 + 1100.3) / 2 = 1050.15He intentado algo de la siguiente manera,
for c_key, _ in df.groupby(['brand', 'model', 'year']): fc = ( (df['brand'] == c_key[0]) & (df['model'] == c_key[1]) & (df['year'].isin([c_key[2] + 1, c_key[2], c_key[2] - 1])) ) sc = ( (df['brand'] == c_key[0]) & (df['model'] == c_key[1]) & (df['year'] == c_key[2]) & (df['price'].isnull()) ) mean_val = df[fc]['price'].mean() df.loc[sc, 'price'] = mean_val print(df) brand model year price 0 Mercedes X 2011 1050.15 1 Renault Y 2010 1000.40 2 Ford Z 2009 2000.30 3 Mercedes X 2010 1000.00 4 Mercedes X 2012 1100.30 5 Mercedes X 2020 3000.50 6 Renault Q 2011 NaN Pero esta solución lleva mucho tiempo para 90 000 filas y 27 columnas, entonces, ¿hay una solución más efectiva? Por ejemplo, ¿puedo usar groupby para los valores año-1, año, año+1, marca y modelo?
Gracias por adelantado.
Creo que en realidad una forma más eficiente sería ordenar por Brand y luego Year , y luego usar interpolate :
df = df.sort_values(['brand', 'year']).groupby('brand').apply(lambda g: g.interpolate(limit_area='inside'))Producción:
>>> df brand model year price 0 Mercedes X 2011 1050.15 1 Renault Y 2010 1000.40 2 Ford Z 2009 2000.30 3 Mercedes X 2010 1000.00 4 Mercedes X 2012 1100.30 5 Mercedes X 2020 3000.50 6 Renault Q 2011 1000.40Eso también maneja todas las columnas.
Basado en la solución de @richardec, pero con algún añadido para corregir el precio si se conoce el precio del próximo año. Sin embargo, no estoy seguro de si es más rápido que su solución original
# Make an interpolated average df_out = df.sort_values(['brand', 'year']).groupby('brand').apply(lambda g: g.interpolate(limit_area='inside')) # Make an average per brand/year/model df1 = df.sort_values(['brand', 'year']).groupby(['brand','year','model']).mean().reset_index() # Check if the next line has the same brand and model. If so, take the next average price when the price isNa mask1 = df1["model"] == df1["model"].shift(-1) mask2 = df1["brand"] == df1["brand"].shift(-1) mask3 = df1["price"].isna() df1["priceCorr"] = np.where(mask1 & mask2 & mask3 ,df1["price"].shift(-1),df1["price"] ) # Merge everything together df_out = df_out.merge(df1[["brand", "year", "model","priceCorr"]], on=["brand", "year", "model"]) df_out["price"] = np.where(df_out["price"].isna(),df_out["priceCorr"], df_out["price"])Aquí va una solución que parece más simple:
Ordenar valores en el marco de datos original:
df = df.sort_values(["brand", "model", "year"])Agrupa por "marca" y "modelo", y almacena los grupos en una variable (para calcular una sola vez):
groups = df.groupby(["brand", "model"])Rellene los valores de nan utilizando el promedio de las filas anterior y siguiente ( Importante : esto supone que tiene datos de años consecutivos, lo que significa que si le faltan datos de 2015, conoce los valores de 2014 y 2016. Si no tiene datos de años consecutivos, los valores nulos seguirán siendo nulos).
df["price"] = df["price"].fillna((groups["price"].ffill(limit=1) + groups["price"].bfill(limit=1)) / 2)Código resultante:
df = df.sort_values(["brand", "model", "year"]) groups = df.groupby(["brand", "model"]) df["price"] = df["price"].fillna((groups["price"].ffill(limit=1) + groups["price"].bfill(limit=1)) / 2) print(df)Producción:
brand model year price 2 Ford Z 2009 2000.30 3 Mercedes X 2010 1000.00 0 Mercedes X 2011 1050.15 4 Mercedes X 2012 1100.30 5 Mercedes X 2020 3000.50 6 Renault Q 2011 NaN 1 Renault Y 2010 1000.40def fill_it(x): return df[(df.brand==df.iat[x,0])&(df.model==df.iat[x,1])&((df.year==df.iat[x,2]-1)|(df.year==df.iat[x,2]+1))].price.mean() df = df.apply(lambda x: x.fillna(fill_it(x.name)), axis=1) df Output 1: brand model year price 0 Mercedes X 2011 1050.15 1 Renault Y 2010 1000.40 2 Ford Z 2009 2000.30 3 Mercedes X 2010 1000.00 4 Mercedes X 2012 1100.30 5 Mercedes X 2020 3000.50 6 Renault Q 2011 NaN Output 2: brand model year price 0 Mercedes X 2017 NaN 1 Mercedes X 2018 1100.25 2 Mercedes X 2018 1100.25 3 Mercedes X 2019 1000.00 4 Mercedes X 2019 1200.50Esto es 3 veces más rápido
df.loc[df.price.isna(), 'price'] = df[df.price.isna()].apply(lambda x: x.fillna(fill_it(x.name)), axis=1) Probé con otro enfoque, usando pd.rolling y es mucho más rápido (en el marco de datos con 70k filas se ejecuta en 200ms). Las salidas siguen siendo como las querías.
df.year = pd.to_datetime(df.year, format='%Y') df.sort_values('year', inplace=True) df.groupby(['brand', 'model']).apply(lambda x: x.fillna(x.rolling('1095D',on='year', center=True).mean())).sort_index()Esta no es una solución bonita, pero según su descripción, creo que funcionaría y sería muy rápido. Es solo un montón de if está dentro de un np.where en un marco de datos ordenado.
import pandas as pd import numpy as np data = pd.DataFrame({ 'brand': ['Mercedes', 'Renault', 'Ford', 'Mercedes', 'Mercedes', 'Mercedes', 'Renault'], 'model': ['X', 'Y', 'Z', 'X', 'X', 'X', 'Q'], 'year': [2011, 2010, 2009, 2010, 2012, 2020, 2011], 'price': [None, 1000.4, 2000.3, 1000.0, 1100.3, 3000.5, None] }) data = data.sort_values(by=['brand', 'model', 'year']) data['adjusted_price'] = np.where(data['price'].isnull() & (data['brand']==data['brand'].shift(1)) & (data['brand']==data['brand'].shift(-1)) & (data['model']==data['model'].shift(1)) & (data['model']==data['model'].shift(-1)) & (data['year']==(data['year'].shift(1)+1))&(data['year']==(data['year'].shift(-1)-1)), (data['price'].shift(1)+data['price'].shift(-1))/2, data['price']) data['price'] = data['adjusted_price'] data = data.drop(['adjusted_price'], axis=1)