【问题标题】:Tricky slicing specifications on business-day datetimeindex工作日日期时间索引上的棘手切片规范
【发布时间】:2018-06-02 05:50:39
【问题描述】:

我有一个带有基于工作日的 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) 第 m 天到第 n 天。

示例可能是(该月的第 2 到第 4 个工作日)。所以 april 将是 1 (apr2)、4 (apr3) 和 5 (apr 6) 的平均值 = 3.33。 5 月是 33(5 月 4 日)、34(5 月 5 日)、35(5 月 6 日)= 34。我不认为索引中没有出现的周末/节假日是天数。

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

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

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

每当我开始使用 pandas 进行迭代时,我总是怀疑自己做错了。所以我想有一种更清洁、pythonic/矢量化的方式可以在所有月份都得到这个结果

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

【问题讨论】:

  • 所以你有索引并且你想根据这些索引取平均值?
  • @Dark 是的,我有索引和 foo 列——我想对 foo 切片采取手段。

标签: python pandas


【解决方案1】:

使用 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

【讨论】:

    【解决方案2】:

    最pythonic/矢量化(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
    

    【讨论】:

      【解决方案3】:

      对于您的第一个问题,您可以使用 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
      

      由于问题中指定的输出索引没有让我们知道输出索引是什么。

      【讨论】:

        【解决方案4】:

        这是我想出的:

        导入 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
        

        【讨论】:

          猜你喜欢
          • 2019-04-09
          • 1970-01-01
          • 2018-01-24
          • 2018-01-14
          • 2021-03-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多