【问题标题】:Pandas group by time with specified start time熊猫按时间分组,指定开始时间
【发布时间】:2020-11-13 02:50:37
【问题描述】:

-- 编辑我注意到我输入的时间不是我想要的。我将下午 12 点之后的时间转换为 24 小时制约定。不过,unutbu 的回答应该还是很清楚的。

-- 第二次编辑。我更改了数据以制作更好的示例。

以下是按日期索引的时间序列。我想从 start_datetime 开始进行聚合,并根据下面的 timedelta 继续聚合(9.5 小时 = 34200 秒)。

def main():

    # start_datetime = datetime.datetime(2013, 1, 1, 8)
    # end_datetime = datetime.datetime(2013, 1, 1, 5, 30)
    s = pd.Series(
        np.arange(2, 10),
        pd.to_datetime([
            '20130101 7:34:04', '20130101 8:34:08', '20130101 10:34:08',
            '20130101 12:34:15', '20130101 13:34:28', '20130101 12:34:54',
            '20130101 14:34:55', '20130101 17:29:12']))

    print(s)
    bar_size = datetime.timedelta(seconds=60*60*9.5)
    time_group = pd.Grouper(
        freq=pd.Timedelta(bar_size), closed='left', label='right')
    foobar = s.groupby(time_group).agg(np.sum)
    print(foobar)

if __name__ == "__main__":
    main()

运行上面的代码会输出如下:

2013-01-01 09:30:00     5
2013-01-01 19:00:00    39
Freq: 570T, dtype: int64

pandas 内部决定从午夜开始分组,而不是早上 8:00。我找不到强制数据框在上午 8:00 开始分组的方法。有人有使用 pandas 函数的解决方案吗?

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    使用 base=480 将起点移动 480 分钟(8 小时)。 单位是分钟,因为 Grouper 频率是570T(这里的 T 表示分钟):

    import datetime
    import pandas as pd
    
    def main():
    
        start_datetime = datetime.datetime(2013, 1, 1, 8)
        s = pd.Series(
            range(8),
            pd.to_datetime([
                '20130101 8:34:04', '20130101 10:34:08', '20130101 10:34:08',
                '20130101 12:34:15', '20130101 1:34:28', '20130101 3:34:54',
                '20130101 4:34:55', '20130101 5:29:12']))
    
        bar_size = datetime.timedelta(seconds=60*60*9.5)
        time_group = pd.Grouper(freq=bar_size, closed='left', label='right', 
                                base=480)
        foobar = s.groupby(time_group).agg(sum)
        print(foobar)
    
    if __name__ == "__main__":
        main()
    

    产量

    2013-01-01 08:00:00    22
    2013-01-01 17:30:00     6
    Freq: 570T, dtype: int64
    

    在内部,当给pd.Grouper一个频率时,a TimeGrouper is returned

    In [81]: time_group
    Out[81]: <pandas.core.resample.TimeGrouper at 0x7f1499a32198>
    

    所以传递给pd.Grouper的参数实际上是传递给pd.TimeGrouper

    In [82]: pd.TimeGrouper?
    Init signature: pd.TimeGrouper(self, freq='Min', closed=None, label=None,
                                   how='mean', nperiods=None, axis=0,
                                   fill_method=None, limit=None, loffset=None,
                                   kind=None, convention=None, base=0, **kwargs)
    

    TimeGrouper 文档没有解释base 参数,但它与df.resample 中的含义相同:

    In [83]: df.resample?
    Parameters
    ----------
    base : int, default 0
        For frequencies that evenly subdivide 1 day, the "origin" of the
        aggregated intervals. For example, for '5min' frequency, base could
        range from 0 through 4. Defaults to 0
    

    【讨论】:

    • 很好的答案!谢谢!
    • 这个答案没有解释它如何在与上午 8:00 不同的开始时间下工作,尤其是非圆形时间戳,如 8:41:00。
    【解决方案2】:

    以下内容可让您将日期的开始时间向前滑动八小时:

    (s.index + pd.Timedelta('9 hours 30 minutes')).strftime('%Y-%m-%d')
    # array([u'2013-01-01', u'2013-01-01', u'2013-01-01', u'2013-01-01', 
    # u'2013-01-01', u'2013-01-01', u'2013-01-01', u'2013-01-01'], 
    # dtype='<U10')
    

    然后您可以调用:

    s.groupby((s.index + pd.Timedelta('9 hours 30 minutes')).strftime('%Y-%m-%d')).agg(sum)
    # 2013-01-01    28
    

    您也可以完全依赖 pandas 日期时间模块来实现您的功能,而不是单独导入 datetime

    import pandas as pd
    
    
    def main():
    
        start_datetime = pd.datetime(2013, 1, 1, 8)
    
        s = pd.Series(
            range(8),
            pd.to_datetime([
                '20130101 8:34:04', '20130101 10:34:08', '20130101 10:34:08',
                '20130101 12:34:15', '20130101 1:34:28', '20130101 3:34:54',
                '20130101 4:34:55', '20130101 5:29:12']))
    
        time_group = (s.index + pd.Timedelta('9 hours 30 minutes')).strftime('%Y-%m-%d')
        foobar = s.groupby(time_group).agg(sum)
        print(foobar)
    

    【讨论】:

      【解决方案3】:

      非常有趣的是,pandas.Grouper 的文档说:

      pandas.Grouper(key=None, level=None, freq=None, axis=0, sort=False)

      ...

      base : int,默认为 0

      Only when freq parameter is passed.
      

      base参数没有解释。它甚至不在构造函数参数列表中。它只说它需要int。 但是,您实际上可以传入浮点数,以便它可以将分组间隔移动一小段时间。例如,如果您的 freq='1D' 并且您设置 base=0.5,则组边界将是每天下午 12 点,而不是凌晨 0 点。

      【讨论】:

        【解决方案4】:

        pandas 1.1.0 引入了origin 参数,并以一种直接的方式实现了这一点(另请注意freq9h30min 符号,更多符号here):

        df = pd.DataFrame(pd.to_datetime([
                    '2013-01-01 8:34:04', '2013-01-01 10:34:08', '2013-01-01 10:34:08',
                    '2013-01-01 12:34:15', '2013-01-01 1:34:28', '2013-01-01 3:34:54',
                    '2013-01-01 4:34:55', '2013-01-01 5:29:12']), columns =['the_date'])
        # dummy column with ones
        df['other'] = 1
        
        # sum the other column aggregated by the date
        df.groupby(pd.Grouper(key='the_date',freq='9h30min', origin='start'))['other'].sum()
        

        对于 pandas ,您可以这样做以获得更通用的解决方案:

        df.groupby(pd.Grouper(key='the_date',freq='9h30min', 
                             base=df.the_date.min().hour * 60 + df.the_date.min().minute))['other'].sum()
        

        基本上,您可以使用最小日期来计算基数 (df.the_date.min().hour * 60 + df.the_date.min().minute)。

        Link to documenation

        base 在此版本中也已弃用)

        【讨论】:

          猜你喜欢
          • 2019-06-22
          • 2021-05-07
          • 2014-11-17
          • 2018-08-31
          • 1970-01-01
          • 1970-01-01
          • 2012-10-29
          • 2013-01-29
          相关资源
          最近更新 更多