Это короткий, полный пример более сложного реального приложения.
Использованные библиотеки:
import numpy as np
import scipy as sp
import scipy.stats as scist
import matplotlib.pyplot as plt
from itertools import zip_longest
Данные:
У меня есть массив с нерегулярными ячейками, определенными с началом и концом, например, вот так (в реальном случае этот формат является заданным, поскольку он является выходом другого процесса):
bin_starts = np.array([0, 93, 184, 277, 368])
bin_ends = np.array([89, 178, 272, 363, 458])
который я объединяю с:
bns = np.stack(zip_longest(bin_starts, bin_ends)).flatten()
bns
>>> array([ 0, 89, 93, 178, 184, 272, 277, 363, 368, 458])
давая регулярно чередующиеся последовательности длинных и коротких интервалов, все из неправильной длины. Это схематичное представление заданных длинных и коротких интервалов:
У меня есть куча данных временных рядов, похожих на случайные данные, созданные ниже:
# make some random example data to bin
np.random.seed(45)
x = np.arange(0,460)
y = 5+np.random.randn(460).cumsum()
plt.plot(x,y);
Цель:
Я хотел бы использовать последовательность интервалов для сбора статистики (среднее значение, процентили и т.д.) На данных - но только с использованием длинных интервалов, то есть желтых на эскизе.
Допущения и уточнения:
Края длинных интервалов никогда не перекрываются; другими словами, между длинными интервалами всегда есть короткий интервал. Кроме того, первый интервал всегда длинный.
Текущее решение:
Один из способов сделать это - использовать scipy.stats.binned_statistic
на всех интервалах, а затем scipy.stats.binned_statistic
результат на части, чтобы сохранить только все остальные (т.е. [::2]
), например, так (отличная помощь для некоторой статистики, например, np.percentile
, читал этот SO ответ @ali_m):
ave = scist.binned_statistic(x, y,
statistic = np.nanmean,
bins=bns)[0][::2]
что дает мне желаемый результат:
plt.plot(np.arange(0,5), ave);
Вопрос: Есть ли более Pythonic способ сделать это (используя любой из Numpy
, Scipy
или Pandas
)?
Я думаю, что использование некоторой комбинации IntervalIndex
, pd.cut
, groupby
и agg
- это относительно простой и легкий способ получить то, что вы хотите.
Сначала я бы сделал DataFrame (не уверен, что это лучший способ перейти от массивов np):
df = pd.DataFrame()
df['x'], df['y'] = x, y
Затем вы можете определить свои корзины как список кортежей:
bins = list(zip(bin_starts, bin_ends))
Используйте pandas IntervalIndex, который имеет метод from_tuples()
, чтобы создать ячейки для последующего использования в cut
. Это полезно, потому что вам не нужно полагаться на нарезку массива bns
чтобы распутать "регулярно чередующиеся последовательности длинных и коротких интервалов" - вместо этого вы можете явно определить интересующие вас бины:
ii = pd.IntervalIndex.from_tuples(bins, closed='both')
closed
kwarg указывает, следует ли включать номера конечных членов в интервал. Например, для кортежа (0, 89)
при closed='both'
интервал будет включать в себя как 0, так и 89 (в отличие от left
, right
или neither
).
Затем создайте столбец категории в pd.cut()
с помощью pd.cut()
, который является методом для объединения значений в интервалы. Объект IntervalIndex
можно указать с помощью bin
kwarg:
df['bin'] = pd.cut(df.x, bins=ii)
Наконец, используйте df.groupby()
и .agg()
чтобы получить любую статистику, которую вы хотите:
df.groupby('bin')['y'].agg(['mean', np.std])
какие выводы:
mean std
bin
[0, 89] -4.814449 3.915259
[93, 178] -7.019151 3.912347
[184, 272] 7.223992 5.957779
[277, 363] 15.060402 3.979746
[368, 458] -0.644127 3.361927
plt.plot(np.arange(0,5), df.groupby('bin')['y'].agg(['mean', np.std]).loc[:, 'mean']);
), с удовольствием выбираю ответ, который когда-то тестировался на реальныхplt.plot(np.arange(0,5), df.groupby('bin')['y'].agg(['mean', np.std]).loc[:, 'mean']);
. Если у вас есть время, вы бы расширили его,pandas.IntervalIndex
больше объясненийpandas.IntervalIndex
иpandas.cut
поскольку Pandas могут быть немного пугающими - и ссылаться довольно сухо (даже для не новичков, ИМХО).pandas.IntervalIndex
позволяет использовать кортежи (определенные вашимbins = list(zip(bin_starts, bin_ends))
для определения ребер бина. Умный.