Вычисление скользящего среднего в массиве numpy с помощью NaNs

#python #numpy #masked-array

#python #numpy #masked-массив

Вопрос:

Я пытаюсь вычислить скользящее среднее в большом массиве numpy, который содержит NaNs. В настоящее время я использую:

 import numpy as np

def moving_average(a,n=5):
      ret = np.cumsum(a,dtype=float)
      ret[n:] = ret[n:]-ret[:-n]
      return ret[-1:]/n
  

При вычислении с помощью замаскированного массива:

 x = np.array([1.,3,np.nan,7,8,1,2,4,np.nan,np.nan,4,4,np.nan,1,3,6,3])
mx = np.ma.masked_array(x,np.isnan(x))
y = moving_average(mx).filled(np.nan)

print y

>>> array([3.8,3.8,3.6,nan,nan,nan,2,2.4,nan,nan,nan,2.8,2.6])
  

Результат, который я ищу (ниже), в идеале должен иметь NaN только в том месте, где исходный массив x имел NaN, и усреднение должно выполняться по количеству элементов, отличных от NaN, в группировке (мне нужен какой-то способ изменить размер n в функции.)

 y = array([4.75,4.75,nan,4.4,3.75,2.33,3.33,4,nan,nan,3,3.5,nan,3.25,4,4.5,3])
  

Я мог бы перебрать весь массив и проверить индекс по индексу, но массив, который я использую, очень большой, и это заняло бы много времени. Есть ли numpythonic способ сделать это?

Комментарии:

1. Итак, это [4.75,4.75,nan,4.4,3.75,2.33,3.33,4,nan,nan,3,3.5,nan,3.25] ожидаемый результат? Если да, то почему в NaN качестве третьего элемента используется a?

2. @Divakar Это ожидаемый результат. В исходном массиве (x) в nan качестве третьей записи есть a .

3. Итак, почему у нас есть NaN в качестве предпоследней записи в ожидаемом выходе?

4. Отредактировал его, чтобы показать остальные средние; забыл добавить их, извините.

5. @Divakar ответ с np.cumsum подходом дал самый быстрый результат с моими фактическими данными (изменен принятый ответ). Все ответы дали желаемый результат

Ответ №1:

В Pandas есть много действительно приятных функций. Например:

 x = np.array([np.nan, np.nan, 3, 3, 3, np.nan, 5, 7, 7])

# requires three valid values in a row or the resulting value is null

print(pd.Series(x).rolling(3).mean())

#output
nan,nan,nan, nan, 3, nan, nan, nan, 6.333

# only requires 2 valid values out of three for size=3 window

print(pd.Series(x).rolling(3, min_periods=2).mean())

#output
nan, nan, nan, 3, 3, 3, 4, 6, 6.3333
  

Вы можете поиграть с windows / min_periods и рассмотреть возможность заполнения нулей в одной цепочке строк кода.

Ответ №2:

Я просто добавлю к замечательным ответам до этого, что вы все равно можете использовать cumsum для достижения этой цели:

 import numpy as np

def moving_average(a, n=5):
    ret = np.cumsum(a.filled(0))
    ret[n:] = ret[n:] - ret[:-n]
    counts = np.cumsum(~a.mask)
    counts[n:] = counts[n:] - counts[:-n]
    ret[~a.mask] /= counts[~a.mask]
    ret[a.mask] = np.nan

    return ret

x = np.array([1.,3,np.nan,7,8,1,2,4,np.nan,np.nan,4,4,np.nan,1,3,6,3])
mx = np.ma.masked_array(x,np.isnan(x))
y = moving_average(mx)
  

Ответ №3:

Вы могли бы создать временный массив и использовать np.nanmean() (новый в версии 1.8, если я не ошибаюсь):

 import numpy as np
temp = np.vstack([x[i:-(5-i)] for i in range(5)]) # stacks vertically the strided arrays
means = np.nanmean(temp, axis=0)
  

и верните исходный nan на место с помощью means[np.isnan(x[:-5])] = np.nan

Однако это выглядит избыточным как с точки зрения памяти (укладка одного и того же массива в 5 раз), так и с точки зрения вычислений.

Комментарии:

1. np.nanmean() не возвращает nan нигде в выходном массиве.

2. @krakenwagon, да, вы добавляете их обратно в строку, которую я отредактировал прямо перед вашим комментарием.

Ответ №4:

Если я правильно понимаю, вы хотите создать скользящее среднее, а затем заполнить результирующие элементы так, как nan если бы их индекс в исходном массиве был nan .

 import numpy as np

>>> inc = 5 #the moving avg increment 

>>> x = np.array([1.,3,np.nan,7,8,1,2,4,np.nan,np.nan,4,4,np.nan,1,3,6,3])
>>> mov_avg = np.array([np.nanmean(x[idx:idx inc]) for idx in range(len(x))])

# Determine indices in x that are nans 
>>> nan_idxs = np.where(np.isnan(x))[0]

# Populate output array with nans
>>> mov_avg[nan_idxs] = np.nan
>>> mov_avg
array([ 4.75, 4.75, nan, 4.4, 3.75, 2.33333333, 3.33333333, 4., nan, nan, 3., 3.5, nan, 3.25, 4., 4.5, 3.])
  

Ответ №5:

Вот подход, использующий шаги —

 w = 5 # Window size
n = x.strides[0]      
avgs = np.nanmean(np.lib.stride_tricks.as_strided(x, 
                        shape=(x.size-w 1,w), strides=(n,n)),1)

x_rem = np.append(x[-w 1:],np.full(w-1,np.nan))
avgs_rem = np.nanmean(np.lib.stride_tricks.as_strided(x_rem, 
                               shape=(w-1,w), strides=(n,n)),1)
avgs = np.append(avgs,avgs_rem)                               
avgs[np.isnan(x)] = np.nan
  

Ответ №6:

В настоящее время пакет bottleneck должен выполнить трюк достаточно надежно и быстро. Вот слегка скорректированный пример из https://kwgoodman.github.io/bottleneck-doc/reference.html#bottleneck.move_mean:

 >>> import bottleneck as bn
>>> a = np.array([1.0, 2.0, 3.0, np.nan, 5.0])
>>> bn.move_mean(a, window=2)
array([ nan,  1.5,  2.5,  nan,  nan])
>>> bn.move_mean(a, window=2, min_count=1)
array([ 1. ,  1.5,  2.5,  3. ,  5. ])
  

Обратите внимание, что результирующие средние соответствуют последнему индексу окна.

Пакет доступен из репозиториев Ubuntu, pip и т. Д. Он может работать над произвольной осью numpy-массива и т. Д. Кроме того, утверждается, что во многих случаях он быстрее, чем обычная реализация numpy.