DataFrame.interpolate () extrapola sobre datos perdidos al final

Considere el siguiente ejemplo en el que configuramos un conjunto de datos de muestra, creamos un MultiIndex, desastackmos el dataframe y luego ejecutamos una interpolación lineal donde llenamos fila por fila:

import pandas as pd # version 0.14.1 import numpy as np # version 1.8.1 df = pd.DataFrame({'location': ['a', 'b'] * 5, 'trees': ['oaks', 'maples'] * 5, 'year': range(2000, 2005) * 2, 'value': [np.NaN, 1, np.NaN, 3, 2, np.NaN, 5, np.NaN, np.NaN, np.NaN]}) df.set_index(['trees', 'location', 'year'], inplace=True) df = df.unstack() df = df.interpolate(method='linear', axis=1) 

Donde el conjunto de datos sin astackr se ve así:

  value year 2000 2001 2002 2003 2004 trees location maples b NaN 1 NaN 3 NaN oaks a NaN 5 NaN NaN 2 

Como método de interpolación , espero la salida:

  value year 2000 2001 2002 2003 2004 trees location maples b NaN 1 2 3 NaN oaks a NaN 5 4 3 2 

pero en cambio el método produce (note el valor extrapolado):

  value year 2000 2001 2002 2003 2004 trees location maples b NaN 1 2 3 3 oaks a NaN 5 4 3 2 

¿Hay alguna forma de indicar a los pandas que no extrapolan más allá del último valor no faltante de una serie?

EDITAR:

Todavía me encantaría ver esta funcionalidad en pandas, pero por ahora lo he implementado como una función en números y luego uso df.apply() para modificar el df . Era la funcionalidad de los parámetros left y right en np.interp() que me estaba perdiendo en pandas.

 def interpolate(a, dec=None): """ :param a: a 1d array to be interpolated :param dec: the number of decimal places with which each value should be returned :return: returns an array of integers or floats """ # default value is the largest number of decimal places in the input array if dec is None: dec = max_decimal(a) # detect array format convert to numpy as necessary if type(a) == list: t = 'list' b = np.asarray(a, dtype='float') if type(a) in [pd.Series, np.ndarray]: b = a # return the row if it's all nan's if np.all(np.isnan(b)): return a # interpolate x = np.arange(b.size) xp = np.where(~np.isnan(b))[0] fp = b[xp] interp = np.around(np.interp(x, xp, fp, np.nan, np.nan), decimals=dec) # return with proper numerical type formatting # check to make sure there aren't nan's before converting to int if dec == 0 and np.isnan(np.sum(interp)) == False: interp = interp.astype(int) if t == 'list': return interp.tolist() else: return interp # two little helper functions def count_decimal(i): try: return int(decimal.Decimal(str(i)).as_tuple().exponent) * -1 except ValueError: return 0 def max_decimal(a): m = 0 for i in a: n = count_decimal(i) if n > m: m = n return m 

Funciona como un encanto en el conjunto de datos de ejemplo:

 In[1]: df.apply(interpolate, axis=1) Out[1]: value year 2000 2001 2002 2003 2004 trees location maples b NaN 1 2 3 NaN oaks a NaN 5 4 3 2 

Reemplace la siguiente línea:

 df = df.interpolate(method='linear', axis=1) 

con este:

 df = df.interpolate(axis=1).where(df.bfill(axis=1).notnull()) 

Encuentra una máscara para los NaN finales mediante el uso de relleno. No es extremadamente eficiente porque realiza dos operaciones de llenado de NaN, pero esos problemas probablemente no sean un problema en general.

Esta es ciertamente una funcionalidad desconcertante. Aquí hay una solución más compacta que se puede aplicar después de la interpolación inicial.

 def de_extrapolate(row): extrap = row[row==row[-1]] if extrap.size > 1: first_index = extrap.index[1] row[first_index:] = np.nan return row 

Como antes, tenemos:

 In [1]: df.interpolate(axis=1).apply(de_extrapolate, axis=1) Out[1]: value year 2000 2001 2002 2003 2004 trees location maples b NaN 1 2 3 NaN oaks a NaN 5 4 3 2 

A partir de la versión 0.21.0 de Pandas, limit_area='inside' tells df.interpolate` que solo rellene los NaN rodeados de valores válidos:

 import pandas as pd # version 0.21.0 import numpy as np df = pd.DataFrame({'location': ['a', 'b'] * 5, 'trees': ['oaks', 'maples'] * 5, 'year': list(range(2000, 2005)) * 2, 'value': [np.NaN, 1, np.NaN, 3, 2, np.NaN, 5, np.NaN, np.NaN, np.NaN]}) df.set_index(['trees', 'location', 'year'], inplace=True) df = df.unstack() df2 = df.interpolate(method='linear', axis=1, limit_area='inside') print(df2) 

rendimientos

  value year 2000 2001 2002 2003 2004 trees location maples b NaN 1.0 2.0 3.0 NaN oaks a NaN 5.0 4.0 3.0 2.0