matriz numpy: reemplazar los valores de nan por el promedio de las columnas

Tengo una matriz de números llena principalmente con números reales, pero también hay algunos valores nanométricos.

¿Cómo puedo reemplazar las nan s con promedios de columnas donde están?

No se requieren bucles:

 print(a) [[ 0.93230948 nan 0.47773439 0.76998063] [ 0.94460779 0.87882456 0.79615838 0.56282885] [ 0.94272934 0.48615268 0.06196785 nan] [ 0.64940216 0.74414127 nan nan]] #Obtain mean of columns as you need, nanmean is just convenient. col_mean = np.nanmean(a, axis=0) print(col_mean) [ 0.86726219 0.7030395 0.44528687 0.66640474] #Find indicies that you need to replace inds = np.where(np.isnan(a)) #Place column means in the indices. Align the arrays using take a[inds] = np.take(col_mean, inds[1]) print(a) [[ 0.93230948 0.7030395 0.47773439 0.76998063] [ 0.94460779 0.87882456 0.79615838 0.56282885] [ 0.94272934 0.48615268 0.06196785 0.66640474] [ 0.64940216 0.74414127 0.44528687 0.66640474]] 

Usando matrices enmascaradas

La forma estándar de hacer esto usando solo numpy sería usar el módulo de matriz enmascarada .

Scipy es un paquete bastante pesado que se basa en bibliotecas externas, por lo que vale la pena tener un método de sólo numpy. Esto se basa en la respuesta de @DonaldHobson.

Edición: np.nanmean ahora es una función numpy. Sin embargo, no maneja columnas todo-nan …

Supongamos que tienes una matriz a :

 >>> a array([[ 0., nan, 10., nan], [ 1., 6., nan, nan], [ 2., 7., 12., nan], [ 3., 8., nan, nan], [ nan, 9., 14., nan]]) >>> import numpy.ma as ma >>> np.where(np.isnan(a), ma.array(a, mask=np.isnan(a)).mean(axis=0), a) array([[ 0. , 7.5, 10. , 0. ], [ 1. , 6. , 12. , 0. ], [ 2. , 7. , 12. , 0. ], [ 3. , 8. , 12. , 0. ], [ 1.5, 9. , 14. , 0. ]]) 

Tenga en cuenta que la media de la matriz enmascarada no necesita ser la misma forma que a , porque estamos aprovechando la difusión implícita sobre las filas.

También tenga en cuenta cómo se maneja bien la columna todo-nan. La media es cero ya que estás tomando la media de cero elementos. El método que usa nanmean no maneja columnas todo-nan:

 >>> col_mean = np.nanmean(a, axis=0) /home/praveen/.virtualenvs/numpy3-mkl/lib/python3.4/site-packages/numpy/lib/nanfunctions.py:675: RuntimeWarning: Mean of empty slice warnings.warn("Mean of empty slice", RuntimeWarning) >>> inds = np.where(np.isnan(a)) >>> a[inds] = np.take(col_mean, inds[1]) >>> a array([[ 0. , 7.5, 10. , nan], [ 1. , 6. , 12. , nan], [ 2. , 7. , 12. , nan], [ 3. , 8. , 12. , nan], [ 1.5, 9. , 14. , nan]]) 

Explicación

Convertir a en una matriz enmascarada te da

 >>> ma.array(a, mask=np.isnan(a)) masked_array(data = [[0.0 -- 10.0 --] [1.0 6.0 -- --] [2.0 7.0 12.0 --] [3.0 8.0 -- --] [-- 9.0 14.0 --]], mask = [[False True False True] [False False True True] [False False False True] [False False True True] [ True False False True]], fill_value = 1e+20) 

Y tomar la media sobre columnas le da la respuesta correcta , normalizando solo sobre los valores no enmascarados:

 >>> ma.array(a, mask=np.isnan(a)).mean(axis=0) masked_array(data = [1.5 7.5 12.0 --], mask = [False False False True], fill_value = 1e+20) 

Además, ¡note cómo la máscara maneja bien la columna que es todo-nan !

Finalmente, np.where hace el trabajo de reemplazo.


Media fila

Para reemplazar los valores de nan por medio de filas en lugar de por medio de columnas, se requiere un pequeño cambio para que la transmisión tenga efecto:

 >>> a array([[ 0., 1., 2., 3., nan], [ nan, 6., 7., 8., 9.], [ 10., nan, 12., nan, 14.], [ nan, nan, nan, nan, nan]]) >>> np.where(np.isnan(a), ma.array(a, mask=np.isnan(a)).mean(axis=1), a) ValueError: operands could not be broadcast together with shapes (4,5) (4,) (4,5) >>> np.where(np.isnan(a), ma.array(a, mask=np.isnan(a)).mean(axis=1)[:, np.newaxis], a) array([[ 0. , 1. , 2. , 3. , 1.5], [ 7.5, 6. , 7. , 8. , 9. ], [ 10. , 12. , 12. , 12. , 14. ], [ 0. , 0. , 0. , 0. , 0. ]]) 

Si parcial es su información original, y reemplazar es una matriz de la misma forma que contiene valores promediados, entonces este código utilizará el valor de parcial si existe uno.

 Complete= np.where(np.isnan(partial),replace,partial) 

Alternativa : Reemplazo de NaNs con interpolación de columnas.

 def interpolate_nans(X): """Overwrite NaNs with column value interpolations.""" for j in range(X.shape[1]): mask_j = np.isnan(X[:,j]) X[mask_j,j] = np.interp(np.flatnonzero(mask_j), np.flatnonzero(~mask_j), X[~mask_j,j]) return X 

Ejemplo de uso:

 X_incomplete = np.array([[10, 20, 30 ], [np.nan, 30, np.nan], [np.nan, np.nan, 50 ], [40, 50, np.nan ]]) X_complete = interpolate_nans(X_incomplete) print X_complete [[10, 20, 30 ], [20, 30, 40 ], [30, 40, 50 ], [40, 50, 50 ]] 

Utilizo este bit de código para datos de series de tiempo en particular, donde las columnas son atributos y las filas son muestras ordenadas por tiempo.

Esto no está muy limpio, pero no puedo pensar en una forma de hacerlo que no sea iterar

 #example a = np.arange(16, dtype = float).reshape(4,4) a[2,2] = np.nan a[3,3] = np.nan indices = np.where(np.isnan(a)) #returns an array of rows and column indices for row, col in zip(*indices): a[row,col] = np.mean(a[~np.isnan(a[:,col]), col]) 

Para extender la respuesta de Donald, proporciono un ejemplo mínimo. Digamos que a es un ndarray y queremos reemplazar sus valores cero con la media de la columna.

 In [231]: a Out[231]: array([[0, 3, 6], [2, 0, 0]]) In [232]: col_mean = np.nanmean(a, axis=0) Out[232]: array([ 1. , 1.5, 3. ]) In [228]: np.where(np.equal(a, 0), col_mean, a) Out[228]: array([[ 1. , 3. , 6. ], [ 2. , 1.5, 3. ]]) 

Usando funciones simples con bucles:

 a=[[0.93230948, np.nan, 0.47773439, 0.76998063], [0.94460779, 0.87882456, 0.79615838, 0.56282885], [0.94272934, 0.48615268, 0.06196785, np.nan], [0.64940216, 0.74414127, np.nan, np.nan], [0.64940216, 0.74414127, np.nan, np.nan]] print("------- original array -----") for aa in a: print(aa) # GET COLUMN MEANS: ta = np.array(a).T.tolist() # transpose the array; col_means = list(map(lambda x: np.nanmean(x), ta)) # get means; print("column means:", col_means) # REPLACE NAN ENTRIES WITH COLUMN MEANS: nrows = len(a); ncols = len(a[0]) # get number of rows & columns; for r in range(nrows): for c in range(ncols): if np.isnan(a[r][c]): a[r][c] = col_means[c] print("------- means added -----") for aa in a: print(aa) 

Salida:

 ------- original array ----- [0.93230948, nan, 0.47773439, 0.76998063] [0.94460779, 0.87882456, 0.79615838, 0.56282885] [0.94272934, 0.48615268, 0.06196785, nan] [0.64940216, 0.74414127, nan, nan] [0.64940216, 0.74414127, nan, nan] column means: [0.82369018599999999, 0.71331494500000003, 0.44528687333333333, 0.66640474000000005] ------- means added ----- [0.93230948, 0.71331494500000003, 0.47773439, 0.76998063] [0.94460779, 0.87882456, 0.79615838, 0.56282885] [0.94272934, 0.48615268, 0.06196785, 0.66640474000000005] [0.64940216, 0.74414127, 0.44528687333333333, 0.66640474000000005] [0.64940216, 0.74414127, 0.44528687333333333, 0.66640474000000005] 

Los bucles for también se pueden escribir con una lista de comprensión:

 new_a = [[col_means[c] if np.isnan(a[r][c]) else a[r][c] for c in range(ncols) ] for r in range(nrows) ] 

es posible que desee probar esta función incorporada:

 x = np.array([np.inf, -np.inf, np.nan, -128, 128]) np.nan_to_num(x) array([ 1.79769313e+308, -1.79769313e+308, 0.00000000e+000, -1.28000000e+002, 1.28000000e+002])