我有一个带有基于营业日的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,我不知道基于多个月的任意日期进行切片平均的好方法。
答案 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.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
答案 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