关于营业日datetimeindex

时间:2018-05-25 11:45:03

标签: python pandas

我有一个带有基于营业日的DateTimeIndex的pandas数据框。对于索引中的每个月,我还有一个标记'指定的一天。

这是该数据框的玩具版本:

# 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)第n天到第n天。

示例可能是(该月的第2至第4个工作日)。所以四月平均为1(apr2),4(apr3)和5(apr 6)= 3.33。可能是33(可能4),34(可能5),35(可能是6)= 34.我不认为在指数中不会出现的周末/假日为天。

2)在标记日期之前/之后的第二天到标记日期之前/之后的第n天。

示例可能是"切片的平均值,从标记日期前1天到每个月的标记日期后1天"例如。 4月,标记日期为17Apr。查看索引,我们想要apr16,apr17和apr20的平均值。

对于示例1,我有一个丑陋的解决方案,在那个月我将切掉那个月的行,然后应用df_slice.iloc[m:n].mean()

每当我开始用熊猫做迭代的事情时,我总是怀疑我做错了。所以我想有一种更清洁,更pythonic /矢量化的方式来制作这个月的结果

对于示例2,我不知道基于多个月的任意日期进行切片平均的好方法。

4 个答案:

答案 0 :(得分:3)

使用pandas.tseries.offsets中的BDay()

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

答案 1 :(得分:3)

执行此操作的最pythonic / vectorized(pandonic?)方法可能是使用df.rollingdf.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

答案 2 :(得分:2)

对于你的第一个问题,你可以使用石斑鱼和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

因为问题中指定的输出aint的索引确实让我们知道输出的索引是什么。

答案 3 :(得分:2)

这是我设法提出的:

导入pandas并设置数据框

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