Хитроумные спецификации нарезки на рабочий день datetimeindex - PullRequest
0 голосов
/ 25 мая 2018

У меня есть фрейм данных pandas с DateTimeIndex на основе рабочего дня.Для каждого месяца, указанного в индексе, у меня также указывается один день «маркера».

Вот игрушечная версия этого фрейма данных:

# a dataframe with business dates as the index
df = pd.DataFrame(list(range(91)), pd.date_range('2015-04-01', '2015-6-30'), columns=['foo']).resample('B').last()

# each month has an single, arbitrary marker day specified
marker_dates = [df.index[12], df.index[33], df.index[57]]

Для каждого месяца в индексе мне нужно вычислить среднее значение столбца foo в определенном срезе строк в этом месяце.

Существует два разных способа указания этих срезов:

1) с m-го по n-й день.

Примером может быть (2-4 рабочий день в этом месяце).Таким образом, апрель будет в среднем 1 (апрель 2), 4 (апрель 3) и 5 ​​(апрель 6) = 3,33.Может быть 33 (4 мая), 34 (5 мая), 35 (6 мая) = 34. Я не считаю выходные / праздничные дни, которые не встречаются в индексе, днями.

2) m-й день до / после даты маркера до n-го дня до / после даты маркера.

Примером может быть «среднее значение среза за 1 день до маркера»дата до 1 дня после даты маркера в каждом месяце "Например.В апреле маркерная дата 17 апреля.Глядя на индекс, мы хотим получить среднее значение для apr16, apr17 и apr20.

Для примера 1 у меня было некрасивое решение: каждый месяц я бы вырезал строки этого месяца, а затем применил df_slice.iloc[m:n].mean()

Всякий раз, когда я начинаю делать итеративные вещи с пандами, я всегда подозреваю, что делаю это неправильно.Так что я думаю, что есть более чистый, питонический / векторизованный способ получения этого результата за все месяцы

Для примера 2 я не знаю хорошего способа сделать это усреднение среза на основе произвольных дат помного месяцев.

Ответы [ 4 ]

0 голосов
/ 02 июня 2018

Вот что мне удалось придумать:

Импорт панд и настройка фрейма данных

import pandas as pd
df = pd.DataFrame(list(range(91)), pd.date_range('2015-04-01', '2015-6-30'), columns=['foo']).resample('B')

Начните с чистого списка дат маркеров, так как я предполагаю, что выдействительно начинаются с:

marker_dates = [
    pd.to_datetime('2015-04-17', format='%Y-%m-%d'),
    pd.to_datetime('2015-05-18', format='%Y-%m-%d'),
    pd.to_datetime('2015-06-19', format='%Y-%m-%d')
]
marker_df = pd.DataFrame([], columns=['marker', 'start', 'end', 'avg'])
marker_df['marker'] = marker_dates

Для случая, когда вы хотите просто проверить диапазоны, введите здесь начало и конец вручную вместо того, чтобы вычислять его.Если вы хотите изменить диапазон, вы можете изменить аргументы на shift ():

marker_df['start'] = df.index.shift(-1)[df.index.isin(marker_df['marker'])]
marker_df['end'] = df.index.shift(1)[df.index.isin(marker_df['marker'])]

Наконец, используйте DataFrame.apply () для построчного вычисления средних:

marker_df.apply(
    lambda x: df[(x['start'] <= df.index) & (df.index <= x['end'])]['foo'].mean(), 
    axis=1
)

Что дает нам этот результат:

      marker      start        end        avg
0 2015-04-17 2015-04-16 2015-04-20  17.000000
1 2015-05-18 2015-05-15 2015-05-19  46.666667
2 2015-06-19 2015-06-18 2015-06-22  80.000000
0 голосов
/ 28 мая 2018

Используйте BDay () из pandas.tseries.offsets

import pandas as pd
from pandas.tseries.offsets import BDay 

M=2
N=4

start_date = pd.datetime(2015,4,1)
end_date = pd.datetime(2015,6,30)

df = pd.DataFrame(list(range(91)), pd.date_range('2015-04-01', '2015-6-30'), columns=['foo']).resample('B').last()

# for month starts
marker_dates = pd.date_range(start=start_date, end=end_date, freq='BMS')

# create IntervalIndex
bins = pd.IntervalIndex.from_tuples([ (d + (M-1)*BDay(), d + (N-1)*BDay()) for d in marker_dates ], closed='both')

df.groupby(pd.cut(df.index, bins)).mean()
#[2015-04-02, 2015-04-06]   3.333333
#[2015-05-04, 2015-05-06]  34.000000
#[2015-06-02, 2015-06-04]  63.000000


# any markers
marker_dates = [df.index[12], df.index[33], df.index[57]]

# M Bday before, and N Bday after 
bins = pd.IntervalIndex.from_tuples([ (d - M*BDay(), d + N*BDay()) for d in marker_dates ], closed='both')

df.groupby(pd.cut(df.index, bins)).mean()
#[2015-04-15, 2015-04-23]  18.428571
#[2015-05-14, 2015-05-22]  48.000000
#[2015-06-17, 2015-06-25]  81.428571
0 голосов
/ 30 мая 2018

Самый питонический / векторизованный (pandonic?) Способ сделать это может состоять в использовании df.rolling и df.shift , чтобы сгенерировать окно, через которое вы возьметесреднее значение, затем df.reindex , чтобы выбрать значение в отмеченные вами даты.

Для вашего примера (2) это может выглядеть следующим образом:

df['foo'].rolling(3).mean().shift(-1).reindex(marker_dates)
Out[8]: 
2015-04-17    17.333333
2015-05-18    47.000000
2015-06-19    80.333333
Name: foo, dtype: float64

Это может быть заключено в небольшую функцию:

def window_mean_at_indices(df, indices, begin=-1, end=1):
    return df.rolling(1+end-begin).mean().shift(-end).reindex(indices)

Помогает прояснить, как применить это к ситуации (1):

month_starts = pd.date_range(df.index.min(), df.index.max(), freq='BMS')

month_starts
Out[11]: DatetimeIndex(['2015-04-01', '2015-05-01', '2015-06-01'],
                       dtype='datetime64[ns]', freq='BMS')

window_mean_at_indices(df['foo'], month_starts, begin=1, end=3)
Out[12]: 
2015-04-01     3.333333
2015-05-01    34.000000
2015-06-01    63.000000
Freq: BMS, Name: foo, dtype: float64
0 голосов
/ 28 мая 2018

Для вашей первой задачи вы можете использовать grouper и iloc, т.е.

low = 2
high= 4

slice_mean = df.groupby(pd.Grouper(level=0,freq='m')).apply(lambda x : x.iloc[low-1:high].mean())
# or df.resample('m').apply(lambda x : x.iloc[low-1:high].mean())
               foo
2015-04-30   3.333333
2015-05-31  34.000000
2015-06-30  63.000000

Для вашей второй задачи вы можете объединить даты и взять среднее значение за месяц, т. Е.

idx = pd.np.where(df.index.isin(pd.Series(marker_dates)))[0]

#array([12, 33, 57])
temp = pd.concat([df.iloc[(idx+i)] for i in [-1,0,1]])

            foo
2015-04-16   15
2015-05-15   46
2015-06-18   78
2015-04-17   18
2015-05-18   47
2015-06-19   81
2015-04-20   19
2015-05-19   48
2015-06-22   82

# Groupby mean
temp.groupby(pd.Grouper(level=0,freq='m')).mean()
# or temp.resample('m').mean()
              foo
2015-04-30  17.333333
2015-05-31  47.000000
2015-06-30  80.333333
dtype: float64

, так какУказанный в вопросе индекс вывода не дает нам знать, каким будет индекс выпуска.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...