使用日期索引从pandas DataFrame计算3个月滚动中位数

时间:2016-02-25 10:36:57

标签: python pandas time-series

我从输入DataFrame开始,看起来像是:

df = pd.DataFrame({"created_on":[datetime(2015, 1, 3),
                                 datetime(2015 , 1, 5),
                                 datetime(2015, 2, 24),
                                 datetime(2015, 3, 6),
                                 datetime(2015, 3, 17),
                                 datetime(2015, 5, 31),
                                 datetime(2015, 6, 3)],
           "value":[3, 2, 1, 1, 3, 2, 2]
           }
   )

  created_on  value
0 2015-01-03      3
1 2015-01-05      2
2 2015-02-24      1
3 2015-03-06      1
4 2015-03-17      3
5 2015-05-31      2
6 2015-06-03      2

我希望每月获得created_on在当月或过去2个月内从当月开始的观察的中位数。

上述输入数据的预期输出为:

month        median_value
2015-01-01   2.5
2015-02-01   2
2015-03-01   2
2015-04-01   1
2015-05-01   2
2015-06-01   2

即" 2015-01-01",仅观察" 2015-01-03"和" 2015-01-05"使用了一个月" 2015-02-01",我采取" 2015-01-03"," 2015-01-05"和" 2015-02-24"等等。

我想从日期列中提取月份,然后像这样使用groupby

 df['created_on_month'] = df['created_on'].apply(
                   lambda dt: datetime(dt.year, dt.month, 1)
 )
 df.groupby('created_on_month').median()

但我不知道如何在groupby中的3个月内聚合,以便一行可以属于多个群组。此解决方案的其他问题是空白月(" 2015-04-01"在上面的示例中)不会出现在结果中。

我也尝试过使用pandas提供的rolling_median可以完成这项工作,但它只有一个resample只能计算观察次数,但中位数的中位数不一样。

最后,我还可以使用一个简单的循环:

months = pd.date_range('2015-01-01', '2015-06-01', freq='MS')
output = pd.DataFrame(index=months, columns=("month", "median_value"))

for m in months:
    tmp = df [ (df.created_on >= (m - pd.DateOffset(months=2))) 
             & (df.created_on <= m+pd.DateOffset(months=1))]
    res = {"month":m, "median_value":tmp["value"].median()}
    output.loc[m] = res

print output 

产生:

                          month median_value
2015-01-01  2015-01-01 00:00:00          2.5
2015-02-01  2015-02-01 00:00:00            2
2015-03-01  2015-03-01 00:00:00            2
2015-04-01  2015-04-01 00:00:00            1
2015-05-01  2015-05-01 00:00:00            2
2015-06-01  2015-06-01 00:00:00            2

但如果存在更优雅的解决方案,我会很乐意学习它。

1 个答案:

答案 0 :(得分:1)

好的,这应该非常接近。我正在使用90天的窗口b / c我不确定我是否可以做3个月的窗口。否则,它似乎工作得很好。

df2 = pd.rolling_apply( df.set_index('created_on')['value'], window=90, 
                        func=np.nanmedian, freq='d', min_periods=1 )

df2[ (df2.index.day == 1)[1:] ]   # [1:] is a kludge to get end of month
                                  # rather than beginning, probably a 
                                  # better way to do that...

created_on
2015-01-31    2.5
2015-02-28    2.0
2015-03-31    2.0
2015-04-30    1.0
2015-05-31    2.0

请注意,由于我的方法与您的方法不同,因此它会标注为月末而不是月初,但这不会以任何方式影响结果,我认为月末实际上更准确。< / p>

我不确定将2015年6月的结果打印出来的最佳方法,但它在6月3日的df2中正确存储:

df2.tail(1)

created_on
2015-06-03    2

所以这只是提取和显示信息的最佳方式。我想只需填写6月30日缺少值的原始数据帧就可以了。