【问题标题】:How to bin time in a pandas dataframe如何在熊猫数据框中划分时间
【发布时间】:2016-01-14 02:01:09
【问题描述】:

我正在尝试使用 pandas 数据框分析测量“X”在几周内的平均每日波动,但是时间戳/日期时间等被证明特别难以处理。花了好几个小时试图解决这个问题后,我的代码变得越来越混乱,我认为我离解决方案更近了,希望这里的人可以指导我朝着正确的方向前进。

我在不同的时间和不同的日子测量了 X,将每日结果保存到具有以下形式的数据框中:

    Timestamp(datetime64)         X 

0    2015-10-05 00:01:38          1
1    2015-10-05 06:03:39          4 
2    2015-10-05 13:42:39          3
3    2015-10-05 22:15:39          2

由于测量的时间每天都在变化,我决定使用 binning 来组织数据,然后计算出每个 bin 的平均值和 STD,然后我可以绘制出来。我的想法是创建一个带有 bin 的最终数据框和 X 的平均值用于测量,“Observations”列只是为了帮助理解:

        Time Bin       Observations     <X>  

0     00:00-05:59      [ 1 , ...]       2.3
1     06:00-11:59      [ 4 , ...]       4.6
2     12:00-17:59      [ 3 , ...]       8.5
3     18:00-23:59      [ 2 , ...]       3.1

但是,我在使用 pd.cutpd.groupby 时遇到了时间、日期时间、datetime64、timedelta 和 binning 之间不兼容的问题,基本上我觉得我在黑暗中刺伤,不知道解决这个问题的“正确”方法。我能想到的唯一解决方案是对数据帧进行逐行迭代,但我真的很想避免这样做。

【问题讨论】:

    标签: python pandas datetime pandas-groupby


    【解决方案1】:
    • bin a pandas.DataFrame 的正确方法是使用pandas.cut
    • 使用pandas.to_datetime 验证日期列的格式为datetime
    • 使用.dt.hour 提取小时,用于.cut 方法。
    • python 3.8.11pandas 1.3.1 中测试

    如何bin数据

    import pandas as pd
    import numpy as np  # for test data
    import random  # for test data
    
    # setup a sample dataframe; creates 1.5 months of hourly observations
    np.random.seed(365)
    random.seed(365)
    data = {'date': pd.bdate_range('2020-09-21', freq='h', periods=1100).tolist(),
            'x': np.random.randint(10, size=(1100))}
    df = pd.DataFrame(data)
    
    # the date column of the sample data is already in a datetime format
    # if the date column is not a datetime, then uncomment the following line
    # df.date= pd.to_datetime(df.date)
    
    # define the bins
    bins = [0, 6, 12, 18, 24]
    
    # add custom labels if desired
    labels = ['00:00-05:59', '06:00-11:59', '12:00-17:59', '18:00-23:59']
    
    # add the bins to the dataframe
    df['Time Bin'] = pd.cut(df.date.dt.hour, bins, labels=labels, right=False)
    
    # display(df.head())
                      date  x     Time Bin
    0  2020-09-21 00:00:00  2  00:00-05:59
    1  2020-09-21 01:00:00  4  00:00-05:59
    2  2020-09-21 02:00:00  1  00:00-05:59
    3  2020-09-21 03:00:00  5  00:00-05:59
    4  2020-09-21 04:00:00  2  00:00-05:59
    
    # display(df.tail())
                        date  x     Time Bin
    1095 2020-11-05 15:00:00  2  12:00-17:59
    1096 2020-11-05 16:00:00  3  12:00-17:59
    1097 2020-11-05 17:00:00  1  12:00-17:59
    1098 2020-11-05 18:00:00  2  18:00-23:59
    1099 2020-11-05 19:00:00  2  18:00-23:59
    

    分组'Time Bin'

    # groupby Time Bin and aggregate a list for the observations, and mean
    dfg = df.groupby('Time Bin', as_index=False)['x'].agg([list, 'mean'])
    
    # change the column names, if desired
    dfg.columns = ['X Observations', 'X mean']
    
    # display(dfg)
                          X Observations    X mean
    Time Bin                                 
    00:00-05:59  [2, 4, 1, 5, 2, 2, ...]  4.416667
    06:00-11:59  [9, 8, 4, 0, 3, 3, ...]  4.760870
    12:00-17:59  [7, 7, 7, 0, 8, 4, ...]  4.384058
    18:00-23:59  [3, 2, 6, 2, 6, 8, ...]  4.459559
    

    【讨论】:

      【解决方案2】:

      每当我按时间范围对时间序列数据进行分类时,这似乎就是您在这里所做的,我只是创建一个“一天中的小时”列并对其进行切片。另外,我通常将索引设置为日期时间值……尽管这里没有必要。

      # assuming your "timestamp" column is labeled ts: 
      df['hod'] = [r.hour for r in df.ts]
      
      # now you can calculate stats for each bin
      ave = df[ (df.hod>=0) & (df.hod<6) ].mean()
      

      我认为这里有一种使用 df.resample 的方法,但是由于您的时间序列中定义不明确的起点/终点,我认为这可能需要比上述方法更多的关注。

      这符合你想要的吗?

      【讨论】:

        【解决方案3】:

        不确定我是否有最佳答案,但我认为它仍然有效。
        首先,我将使用此帖子将datetime64 转换为datetime,例如: Converting between datetime, Timestamp and datetime64

        然后,如果我们假设您的第一列有 datetime 并称为 TimeStamp,我会这样做:

        def bin_f(x):
            if x.time() < datetime.time(6):
                return "00:00-05:59"
            elif x.time() < datetime.time(12):
                return "06:00-11:59"
            elif x.time() < datetime.time(18):
                return "12:00-17:59"
            else:
                return "18:00-23:59"
        
        df["Bin"] = df["TimeStamp"].apply(bin_f)
        grouped = df.groupby("Bin")
        grouped['X'].agg(np.std)
        

        X 是您的专栏名称。

        【讨论】:

          【解决方案4】:

          我发现 Mathiou 的回复对我的目的很有帮助,但将其修改如下:

          def bin_f(x):
              h = x.time()
              if h < 6:
                  return "00:00-05:59"
              elif h < 12:
                  return "06:00-11:59"
              elif h < 18:
                  return "12:00-17:59"
              else:
                  return "18:00-23:59"
          

          【讨论】:

            【解决方案5】:

            虽然这是一个旧线程,但要为其添加另一种方法。 使用 pandas resample 方法可以在更少的代码行中获得所需的结果。

            data = {'date': pd.bdate_range('2020-09-21', freq='h', periods=24).tolist(),
                'x': np.random.randint(10, size=(24))}
            df = pd.DataFrame(data)
            df
            # This line will resample data by 6H timeframe
            dfrs=df.resample('6H',on='date').agg({'x':[list,'mean']})
            dfrs
                                    X Observations    X mean
            date                                             
            2020-09-21 00:00:00  [2, 4, 1, 5, 2, 2]  2.666667
            2020-09-21 06:00:00  [9, 8, 4, 0, 3, 3]  4.500000
            2020-09-21 12:00:00  [7, 7, 7, 0, 8, 4]  5.500000
            2020-09-21 18:00:00  [3, 2, 6, 2, 6, 8]  4.500000
            

            【讨论】:

              猜你喜欢
              • 2016-08-05
              • 1970-01-01
              • 1970-01-01
              • 2014-08-30
              • 1970-01-01
              • 2019-05-29
              • 2023-03-20
              • 2018-11-10
              相关资源
              最近更新 更多