#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:
Остается ключевой вопрос: чего вы хотите достичь с помощью скользящего среднего?
Математически чистый способ:
- интерполировать до наилучшего dx x-данных
- выполните скользящее среднее
- извлеките нужные точки данных (но будьте осторожны: этот шаг тоже является типом усреднения!)
Вот код для интерполяции:
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