Как заставить pandas выполнять скользящее среднее для неоднородной x-сетки

#python #pandas #numpy #scipy

#python #панды #numpy #scipy #pandas

Вопрос:

Я хотел бы выполнить скользящее среднее значение, но с окном, которое имеет только конечное «видение» в x. Я хотел бы что-то похожее на то, что у меня есть ниже, но мне нужен диапазон окон, основанный на значении x, а не на индексе позиции.

Хотя предпочтительнее делать это в pandas, эквиваленты numpy / scipy также в порядке

 import numpy as np 
import pandas as pd 

x_val = [1,2,4,8,16,32,64,128,256,512]
y_val = [x np.random.random()*200 for x in x_val]

df = pd.DataFrame(data={'x':x_val,'y':y_val})
df.set_index('x', inplace=True)

df.plot()
df.rolling(1, win_type='gaussian').mean(std=2).plot()
  

Поэтому я бы ожидал, что первые 5 значений будут усреднены вместе, потому что они находятся в пределах 10 единиц друг от друга, но последние значения останутся неизменными.

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

1. Вариантом может быть создание дополнительного (отфильтрованного) столбца, содержащего только значения в пределах определенного диапазона (например, <=10); затем выполните скользящее среднее значение для этого столбца.

2. Что вы можете гарантировать в отношении значений x? Гарантируется ли, что они будут строго увеличиваться? Или возможно ли иметь последовательность x, подобную [1,2,4,3]?

3. Если это поможет, мы можем гарантировать, что x строго увеличивается (всегда можно отсортировать x amp; y, чтобы гарантировать это).

4. Не могли бы вы также добавить фиктивный пример ввода и ожидаемого результата? Я немного смущен той частью, где вы упомянули but I want only values within a certain range (e.g. only values within a range of 10). , что здесь 10? Это размер окна?

Ответ №1:

Согласно pandas документации по rolling

Размер движущегося окна. Это количество наблюдений, используемых для вычисления статистики. Каждое окно будет иметь фиксированный размер.

Поэтому, возможно, вам нужно подделать операцию прокрутки с различными размерами окна, подобными этому

 test_df = pd.DataFrame({'x':np.linspace(1,10,10),'y':np.linspace(1,10,10)})
test_df['win_locs'] = np.linspace(1,10,10).astype('object')
for ind in range(10): test_df.at[ind,'win_locs'] = np.random.randint(0,10,np.random.randint(5)).tolist()

    
# rolling operation with various window sizes
def worker(idx_list):
    
    x_slice = test_df.loc[idx_list,'x']
    return np.sum(x_slice)

test_df['rolling'] = test_df['win_locs'].apply(worker)
  

Как вы можете видеть, test_df является

       x     y      win_locs  rolling
0   1.0   1.0        [5, 2]      9.0
1   2.0   2.0  [4, 8, 7, 1]     24.0
2   3.0   3.0            []      0.0
3   4.0   4.0           [9]     10.0
4   5.0   5.0     [6, 2, 9]     20.0
5   6.0   6.0            []      0.0
6   7.0   7.0     [5, 7, 9]     24.0
7   8.0   8.0            []      0.0
8   9.0   9.0            []      0.0
9  10.0  10.0  [9, 4, 7, 1]     25.0
  

где операция прокатки выполняется с apply помощью метода.

Однако этот подход значительно медленнее, чем родной rolling , например,

 test_df = pd.DataFrame({'x':np.linspace(1,10,10),'y':np.linspace(1,10,10)})
test_df['win_locs'] = np.linspace(1,10,10).astype('object')
for ind in range(10): test_df.at[ind,'win_locs'] = np.arange(ind-1,ind 1).tolist() if ind >= 1 else []
  

используя описанный выше подход

 %%timeit
# rolling operation with various window sizes
def worker(idx_list):
    
    x_slice = test_df.loc[idx_list,'x']
    return np.sum(x_slice)

test_df['rolling_apply'] = test_df['win_locs'].apply(worker)
  

результат

 41.4 ms ± 4.44 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
  

при использовании native rolling на ~ x50 быстрее

 %%timeit
test_df['rolling_native'] = test_df['x'].rolling(window=2).sum()

863 µs ± 118 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  

Ответ №2:

Остается ключевой вопрос: чего вы хотите достичь с помощью скользящего среднего?

Математически чистый способ:

  1. интерполировать до наилучшего dx x-данных
  2. выполните скользящее среднее
  3. извлеките нужные точки данных (но будьте осторожны: этот шаг тоже является типом усреднения!)

Вот код для интерполяции:

 import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d

x_val = [1,2,4,8,16,32,64,128,256,512]
y_val = [x np.random.random()*200 for x in x_val]

df = pd.DataFrame(data={'x':x_val,'y':y_val})
df.set_index('x', inplace=True)

#df.plot()
df.rolling(5, win_type='gaussian').mean(std=200).plot()


#---- Interpolation -----------------------------------
f1 = interp1d(x_val, y_val)
f2 = interp1d(x_val, y_val, kind='cubic')

dx = np.diff(x_val).min()  # get the smallest dx in the x-data set

xnew = np.arange(x_val[0], x_val[-1] dx, step=dx)
ynew1 = f1(xnew)
ynew2 = f2(xnew)

#---- plot ---------------------------------------------
fig = plt.figure(figsize=(15,5))
plt.plot(x_val, y_val, '-o', label='data', alpha=0.5)
plt.plot(xnew, ynew1, '|', ms = 15, c='r', label='linear', zorder=1)
#plt.plot(xnew, ynew2, label='cubic')
plt.savefig('curve.png')
plt.legend(loc='best')
plt.show()
  

введите описание изображения здесь

Ответ №3:

Надеюсь, кто-нибудь найдет более быстрое решение.
Между тем, вы можете использовать DataFrame.iterrows() для этого:

 for idx,row in df.iterrows():
    df.loc[idx, 'avg'] = df.loc[idx-10:idx, 'y'].mean()
  

Вывод:

               y         avg
x                          
1     26.540168   26.540168
2     28.255431   27.397799
4    114.941475   56.579025
8    156.347716   81.521197
16   168.563203  162.455459
32    36.054945   36.054945
64   179.384703  179.384703
128  225.098994  225.098994
256  340.718363  340.718363
512  551.927011  551.927011