【问题标题】:Moving average on pandas.groupby object that respects time尊重时间的 pandas.groupby 对象的移动平均值
【发布时间】:2018-12-04 08:56:13
【问题描述】:

给定以下格式的 pandas 数据框:

toy = pd.DataFrame({
'id': [1,2,3,
       1,2,3,
       1,2,3],
'date': ['2015-05-13', '2015-05-13', '2015-05-13', 
         '2016-02-12', '2016-02-12', '2016-02-12', 
         '2018-07-23', '2018-07-23', '2018-07-23'],
'my_metric': [395, 634, 165, 
              144, 305, 293, 
              23, 395, 242]
})
# Make sure 'date' has datetime format
toy.date = pd.to_datetime(toy.date)

my_metric 列包含一些(随机)指标,我希望以id 列为条件计算其随时间变化的移动平均值 在我自己指定的某个指定时间间隔内。我将这个时间间隔称为“回溯时间”;这可能是 5 分钟 或 2 年。为了确定哪些观察要包含在回溯计算中,我们使用date 列(如果您愿意,它可以是索引)。

令我沮丧的是,我发现使用 pandas 内置函数不容易执行这样的过程,因为我需要有条件地执行计算 在id 上,同时只能根据回溯时间内的观察结果进行计算(使用date 列进行检查)。因此,输出数据框应包含每个id-date 组合的一行,my_metric 列现在是回顾时间内包含的所有观察值的平均值(例如 2 年,包括今天的日期) .

为清楚起见,我在使用 2 年回溯时间时包含了具有所需输出格式的图(对于过大的图表示歉意):

我有一个解决方案,但它没有使用特定的 pandas 内置函数,并且可能不是最佳的(列表理解和单个 for 循环的组合)。我正在寻找的解决方案不会使用 for 循环,因此更具可扩展性/高效/快速。

谢谢!

【问题讨论】:

    标签: python pandas moving-average


    【解决方案1】:

    计算回溯时间:(Current_year - 2 年)

    from dateutil.relativedelta import relativedelta
    from dateutil import parser
    import datetime
    
    In [1691]: dt = '2018-01-01'
    
    In [1695]: dt = parser.parse(dt)
    
    In [1696]: lookback_time = dt - relativedelta(years=2)
    

    现在,根据回溯时间过滤数据帧并计算滚动平均值

    In [1722]: toy['new_metric'] = ((toy.my_metric + toy[toy.date > lookback_time].groupby('id')['my_metric'].shift(1))/2).fillna(toy.my_metric)
    
    In [1674]: toy.sort_values('id')
    Out[1674]: 
            date  id  my_metric  new_metric
    0 2015-05-13   1        395       395.0
    3 2016-02-12   1        144       144.0
    6 2018-07-23   1         23        83.5
    1 2015-05-13   2        634       634.0
    4 2016-02-12   2        305       305.0
    7 2018-07-23   2        395       350.0
    2 2015-05-13   3        165       165.0
    5 2016-02-12   3        293       293.0
    8 2018-07-23   3        242       267.5
    

    【讨论】:

    • 2-year lookback time 在哪里?
    • 感谢您的努力@Mayank;但正如@jezrael 指出的那样,这根本不会一概而论。如果我们有超过 1 个观测值(除了当前观测值)落在回溯时间内,那么这个公式就完全失效了。
    • 我想挑战在于我们不知道有多少行在回溯时间内——我看过熊猫窗口函数,但它们在分组时不能正常工作数据帧,出于某种原因。
    • @Magnus 稍后分享我的更新答案,包括回溯时间。
    • @Magnus 请检查我更新的答案。此外,我已经将lookback time 使用了 2 年,这从您的数据框中排除了日期为2015 的行。我的输出就是基于此。
    【解决方案2】:

    所以,经过一番修修补补,我找到了一个可以充分概括的答案。我使用了一个稍微不同的“玩具”数据框(与我的情况稍微相关)。为了完整起见,以下是数据:

    现在考虑以下代码:

    # Define a custom function which groups by time (using the index)
    def rolling_average(x, dt):
        xt = x.sort_index().groupby(lambda x: x.time()).rolling(window=dt).mean()
        xt.index = xt.index.droplevel(0)
        return xt
    
    dt='730D' # rolling average window: 730 days = 2 years
    
    # Group by the 'id' column
    g = toy.groupby('id')
    
    # Apply the custom function
    df = g.apply(rolling_average, dt=dt)
    
    # Massage the data to appropriate format
    df.index = df.index.droplevel(0)
    df = df.reset_index().drop_duplicates(keep='last', subset=['id', 'date'])
    

    结果如预期:

    【讨论】:

    • 说实话,我对这个解决方案在性能方面不太满意。如果有多个条目具有相同的 'id' 和 'date'(但 'my_metric' 的值不同),那么对于每个 id-date “复制”,数据框将获得一个带有中间结果的额外行 - 因此应用 ' drop_duplicates' 在最后一行。
    猜你喜欢
    • 2020-05-29
    • 1970-01-01
    • 2020-03-28
    • 2014-02-17
    • 1970-01-01
    • 2019-07-06
    • 2017-09-02
    • 2019-04-22
    • 1970-01-01
    相关资源
    最近更新 更多