array python arrays performance pandas numpy

array - pandas python



La forma más eficiente de reenviar valores de NaN en una matriz numpy (3)

Aquí hay un enfoque:

mask = np.isnan(arr) idx = np.where(~mask,np.arange(mask.shape[1]),0) np.maximum.accumulate(idx,axis=1, out=idx) out = arr[np.arange(idx.shape[0])[:,None], idx]

Si no desea crear otra matriz y simplemente rellenar los NaN en arr , reemplace el último paso con esto:

arr[mask] = arr[np.nonzero(mask)[0], idx[mask]]

Muestra de entrada, salida -

In [179]: arr Out[179]: array([[ 5., nan, nan, 7., 2., 6., 5.], [ 3., nan, 1., 8., nan, 5., nan], [ 4., 9., 6., nan, nan, nan, 7.]]) In [180]: out Out[180]: array([[ 5., 5., 5., 7., 2., 6., 5.], [ 3., 3., 1., 8., 8., 5., 5.], [ 4., 9., 6., 6., 6., 6., 7.]])

Problema de ejemplo

Como un ejemplo simple, considere la matriz numpy arr como se define a continuación:

import numpy as np arr = np.array([[5, np.nan, np.nan, 7, 2], [3, np.nan, 1, 8, np.nan], [4, 9, 6, np.nan, np.nan]])

donde arr ve así en la salida de la consola:

array([[ 5., nan, nan, 7., 2.], [ 3., nan, 1., 8., nan], [ 4., 9., 6., nan, nan]])

Ahora me gustaría "rellenar hacia delante" los valores nan en la matriz arr . Con eso me refiero a reemplazar cada valor nan por el valor válido más cercano desde la izquierda. El resultado deseado se vería así:

array([[ 5., 5., 5., 7., 2.], [ 3., 3., 1., 8., 8.], [ 4., 9., 6., 6., 6.]])

Intentado hasta ahora

He intentado usar bucles for:

for row_idx in range(arr.shape[0]): for col_idx in range(arr.shape[1]): if np.isnan(arr[row_idx][col_idx]): arr[row_idx][col_idx] = arr[row_idx][col_idx - 1]

También he intentado usar un marco de datos de pandas como un paso intermedio (ya que los marcos de datos de pandas tienen un método integrado muy ordenado para el llenado hacia adelante):

import pandas as pd df = pd.DataFrame(arr) df.fillna(method=''ffill'', axis=1, inplace=True) arr = df.as_matrix()

Las dos estrategias anteriores producen el resultado deseado, pero sigo preguntándome: ¿no sería la más eficiente una estrategia que use solo operaciones numéricas y rápidas?

Resumen

¿Hay otra forma más eficiente de "reenviar y rellenar" valores nan en matrices numpy? (Por ejemplo, mediante el uso de numerosas operaciones vectorizadas)

Actualización: Comparación de soluciones

He tratado de cronometrar todas las soluciones hasta ahora. Este fue mi script de configuración:

import numba as nb import numpy as np import pandas as pd def random_array(): choices = [1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan] out = np.random.choice(choices, size=(1000, 10)) return out def loops_fill(arr): out = arr.copy() for row_idx in range(out.shape[0]): for col_idx in range(1, out.shape[1]): if np.isnan(out[row_idx, col_idx]): out[row_idx, col_idx] = out[row_idx, col_idx - 1] return out @nb.jit def numba_loops_fill(arr): ''''''Numba decorator solution provided by shx2.'''''' out = arr.copy() for row_idx in range(out.shape[0]): for col_idx in range(1, out.shape[1]): if np.isnan(out[row_idx, col_idx]): out[row_idx, col_idx] = out[row_idx, col_idx - 1] return out def pandas_fill(arr): df = pd.DataFrame(arr) df.fillna(method=''ffill'', axis=1, inplace=True) out = df.as_matrix() return out def numpy_fill(arr): ''''''Solution provided by Divakar.'''''' mask = np.isnan(arr) idx = np.where(~mask,np.arange(mask.shape[1]),0) np.maximum.accumulate(idx,axis=1, out=idx) out = arr[np.arange(idx.shape[0])[:,None], idx] return out

seguido por esta entrada de consola:

%timeit -n 1000 loops_fill(random_array()) %timeit -n 1000 numba_loops_fill(random_array()) %timeit -n 1000 pandas_fill(random_array()) %timeit -n 1000 numpy_fill(random_array())

resultando en esta salida de consola:

1000 loops, best of 3: 9.64 ms per loop 1000 loops, best of 3: 377 µs per loop 1000 loops, best of 3: 455 µs per loop 1000 loops, best of 3: 351 µs per loop


Para aquellos que estén interesados ​​en el problema de tener np.nan líder después del llenado de datos, los siguientes trabajos:

mask = np.isnan(arr) first_non_zero_idx = (~mask!=0).argmax(axis=1) #Get indices of first non-zero values arr = [ np.hstack([ [arr[i,first_nonzero]]*(first_nonzero), arr[i,first_nonzero:]]) for i, first_nonzero in enumerate(first_non_zero_idx) ]


Utilice Numba . Esto debería dar una aceleración significativa:

import numba @numba.jit def loops_fill(arr): ...