Биннинг статистика с нерегулярными и чередующимися бинами

1

Это короткий, полный пример более сложного реального приложения.

Использованные библиотеки:

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])

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

У меня есть куча данных временных рядов, похожих на случайные данные, созданные ниже:

# 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);

Изображение 174551

Цель:

Я хотел бы использовать последовательность интервалов для сбора статистики (среднее значение, процентили и т.д.) На данных - но только с использованием длинных интервалов, то есть желтых на эскизе.

Допущения и уточнения:

Края длинных интервалов никогда не перекрываются; другими словами, между длинными интервалами всегда есть короткий интервал. Кроме того, первый интервал всегда длинный.

Текущее решение:

Один из способов сделать это - использовать 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);

Изображение 174551

Вопрос: Есть ли более Pythonic способ сделать это (используя любой из Numpy, Scipy или Pandas)?

Теги:
numpy
scipy
statistics
binning

1 ответ

1
Лучший ответ

Я думаю, что использование некоторой комбинации 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
  • 0
    Спасибо за Ваш ответ. Так получилось, что в реальном случае у меня есть данные в Pandas DataFrame, но я решил сделать небольшой пример более независимым. Ваше решение выглядит аккуратно, оно работает на небольшом примере (я получаю тот же график, что и мой предыдущий, с 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 могут быть немного пугающими - и ссылаться довольно сухо (даже для не новичков, ИМХО).
  • 0
    Я думаю, что ключ здесь в том, что pandas.IntervalIndex позволяет использовать кортежи (определенные вашим bins = list(zip(bin_starts, bin_ends)) для определения ребер бина. Умный.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню