1

给定一个具有时间戳(ts)的 DataFrame,我想按小时(下采样)。以前由 ts 索引的值现在应该根据一小时内剩余的分钟数划分为比率。[注意:在进行重采样时将数据除以 NaN 列的比率]

          ts               event  duration
0   2020-09-09 21:01:00 a         12
1   2020-09-10 00:10:00 a         22 
2   2020-09-10 01:31:00 a         130
3   2020-09-10 01:50:00 b         60
4   2020-09-10 01:51:00 b         50
5   2020-09-10 01:59:00 b         26
6   2020-09-10 02:01:00 c         72
7   2020-09-10 02:51:00 b         51
8   2020-09-10 03:01:00 b         63
9   2020-09-10 04:01:00 c         79



def create_dataframe():
    df = pd.DataFrame([{'duration':12, 'event':'a',  'ts':'2020-09-09 21:01:00'},
                         {'duration':22, 'event':'a',  'ts':'2020-09-10 00:10:00'},
                         {'duration':130, 'event':'a', 'ts':'2020-09-10 01:31:00'},
                         {'duration':60, 'event':'b',  'ts':'2020-09-10 01:50:00'},
                         {'duration':50, 'event':'b',  'ts':'2020-09-10 01:51:00'},
                         {'duration':26, 'event':'b',  'ts':'2020-09-10 01:59:00'},
                         {'duration':72, 'event':'c',  'ts':'2020-09-10 02:01:00'},
                         {'duration':51, 'event':'b',  'ts':'2020-09-10 02:51:00'},
                         {'duration':63, 'event':'b',  'ts':'2020-09-10 03:01:00'},
                         {'duration':79, 'event':'c',  'ts':'2020-09-10 04:01:00'},
                         {'duration':179, 'event':'c', 'ts':'2020-09-10 06:05:00'},
                         ])
    df.ts = pd.to_datetime(df.ts)
    return df

我想根据时间花费和生产的比率来估计生产。这可以类比how many lines of code have been completed or find how many actual lines per hour?

例如:在“2020-09-10 00:10:00”我们有 22 个。然后在 21:01 - 00:10 期间,我们根据

 59 min of 21:00 hours -> 7   => =ROUND(22/189*59,0)
 60 min of 22:00 hours -> 7   => =ROUND(22/189*60,0)
 60 min of 23:00 hours -> 7   => =ROUND(22/189*60,0)
 10 min of 00:00 hours -> 1   => =ROUND(22/189*10,0)

结果应该是这样的。

  ts                  event    duration
0   2020-09-09 20:00:00 a         NaN
1   2020-09-10 21:00:00 a         7
2   2020-09-10 22:00:00 a         7
3   2020-09-10 23:00:00 a         7
4   2020-09-10 00:00:00 a         1
5   2020-09-10 01:00:00 b         ..
6   2020-09-10 02:01:00 c         ..

这种方法的问题:

在我看来,这种方法存在严重问题。如果您查看行 [1] -> 2020-09-10 07:00:00,我们有 4 个,我们需要将其划分为 3 个小时。将基本持续时间值视为 1(基本单位),我们得到

def create_dataframe2():
    df = pd.DataFrame([{'duration':4, 'event':'c', 'c':'event3.5', 'ts':'2020-09-10 07:00:00'},
                        {'duration':4, 'event':'c', 'c':'event3.5', 'ts':'2020-09-10 10:00:00'}])
    df.ts = pd.to_datetime(df.ts)
    return df

资源

    duration    event   c   ts
0   4   c   event3.5    2020-09-10 07:00:00
1   4   c   event3.5    2020-09-10 10:00:00

预期产出

    ts_hourly           mins    duration
0   2020-09-10 07:00:00 60.0    2
1   2020-09-10 08:00:00 60.0    1
2   2020-09-10 09:00:00 60.0    1
3   2020-09-10 10:00:00 0.0     0
4

2 回答 2

1

第一步是在源 DataFrame 中添加“previous ts”列:

df['tsPrev'] = df.ts.shift()

然后将ts列设置为索引:

df.set_index('ts', inplace=True)

第三步,创建辅助索引,由原始索引和“全时”组成:

ind = df.event.resample('H').asfreq().index.union(df.index)

然后创建一个辅助 DataFrame,用刚刚创建的索引和“回填”事件列重新索引:

df2 = df.reindex(ind)
df2.event = df2.event.bfill()

从df2定义要应用于每组行的函数:

def parts(grp):
    lstRow = grp.iloc[-1]       # Last row from group
    if pd.isna(lstRow.tsPrev):  # First group
        return pd.Series([lstRow.duration], index=[grp.index[0]], dtype=int)
    # Other groups
    return -pd.Series([0], index=[lstRow.tsPrev]).append(grp.duration)\
        .interpolate(method='index').round().diff(-1)[:-1].astype(int)

然后分两步为“produced”列生成源数据:

  1. 生成详细数据:

    prodDet = df2.groupby(np.isfinite(df2.duration.values[::-1]).cumsum()[::-1],
        sort=False).apply(parts).reset_index(level=0, drop=True)
    

    源以df2的这种方式分组,每个组以持续时间列中具有非空值的行终止。然后用parts函数对每个组进行处理。

    结果是:

    2020-09-09 21:00:00    12
    2020-09-09 21:01:00     7
    2020-09-09 22:00:00     7
    2020-09-09 23:00:00     7
    2020-09-10 00:00:00     1
    2020-09-10 00:10:00    80
    2020-09-10 01:00:00    50
    2020-09-10 01:31:00    60
    2020-09-10 01:50:00    50
    2020-09-10 01:51:00    26
    2020-09-10 01:59:00    36
    2020-09-10 02:00:00    36
    2020-09-10 02:01:00    51
    2020-09-10 02:51:00    57
    2020-09-10 03:00:00     6
    2020-09-10 03:01:00    78
    2020-09-10 04:00:00     1
    2020-09-10 04:01:00    85
    2020-09-10 05:00:00    87
    2020-09-10 06:00:00     7
    dtype: int32
    
  2. 生成聚合数据,暂时也作为一个Series

    prod = prodDet.resample('H').sum().rename('produced')
    

    这次prodDet被重新采样(按小时细分),结果是值的总和。

    结果是:

    2020-09-09 21:00:00     19
    2020-09-09 22:00:00      7
    2020-09-09 23:00:00      7
    2020-09-10 00:00:00     81
    2020-09-10 01:00:00    222
    2020-09-10 02:00:00    144
    2020-09-10 03:00:00     84
    2020-09-10 04:00:00     86
    2020-09-10 05:00:00     87
    2020-09-10 06:00:00      7
    Freq: H, Name: produced, dtype: int32
    

让我们描述一下prodDet的内容:

  1. 2020-09-09 20:00:00没有行,因为这个小时没有源行(您的数据从21:01:00开始)。

  2. Row21:00:00 12来自第一个源行(您忘记了它写的预期结果)。

  3. 21:01:00 、22:00:0023:00:0000:00:00的行来自 row 的“分区” 作为您预期结果的一部分。00:10:00 a 22

  4. 具有8050的行来自包含130的行,分为小时00:01:0001:00:00的行。

  5. 等等。

现在我们开始组装最终结果。

  1. 使用事件列加入prod(转换为 DataFrame) :

    result = prod.to_frame().join(df2.event)
    
  2. 添加tsMin列 -每小时的最小ts (正如您在评论之一中询问的那样):

    result['tsMin'] = df.duration.resample('H').apply(lambda grp: grp.index.min())
    
  3. 将索引更改为常规列并将其名称设置为ts (如在源 DataFrame 中):

    result = result.reset_index().rename(columns={'index': 'ts'})
    

最终结果是:

                   ts  produced event               tsMin
0 2020-09-09 21:00:00        19     a 2020-09-09 21:01:00
1 2020-09-09 22:00:00         7     a                 NaT
2 2020-09-09 23:00:00         7     a                 NaT
3 2020-09-10 00:00:00        81     a 2020-09-10 00:10:00
4 2020-09-10 01:00:00       222     a 2020-09-10 01:31:00
5 2020-09-10 02:00:00       144     c 2020-09-10 02:01:00
6 2020-09-10 03:00:00        84     b 2020-09-10 03:01:00
7 2020-09-10 04:00:00        86     c 2020-09-10 04:01:00
8 2020-09-10 05:00:00        87     c                 NaT
9 2020-09-10 06:00:00         7     c 2020-09-10 06:05:00

例如,00: 00 :00的值81是180的总和(第一部分由130的行产生),请参见上面的prodDet

tsMin列中的某些值是空的,在没有源行的小时内。

如果您想完全删除第一行的结果( 持续时间 == 12),请更改return pd.Series([lstRow.duration]...return pd.Series([0]...(第 4 行的部件函数)。

总而言之,我的解决方案比您的解决方案更加pandasonic并且明显更短(17 行(我的解决方案)与大约 70 行(您的解决方案,不包括评论)。

于 2020-12-07T19:34:20.280 回答
0

我无法在 中找到解决方案pandas,因此我使用纯 python 创建了一个解决方案。

基本上,我在排序和发送两个日期时间后迭代所有值,即发送start_timeend_time一个函数,该函数进行处理。

def get_ratio_per_hour(start_time: list, end_time: list, data_: int):

    # get total hours between the start and end, use this for looping
    totalhrs = lambda x: [1 for _ in range(int(x // 3600))
                          ] + [
                             (x % 3600 / 3600 
                              or 0.1 # added for loop fix afterwards
                              )]
              
    
    # check if Start and End are not in same hour
    if start_time.hour != end_time.hour:
        seconds = (end_time - start_time).total_seconds()
        if seconds < 3600:
            parts_ = [1] + totalhrs(seconds)
        else:
            parts_ = totalhrs(seconds)
    else:                       
        # parts_ define the loop iterations
        parts_ = totalhrs((end_time - start_time).total_seconds())
    
    sum_of_hrs = sum(parts_)
    
    # for Constructing DF
    new_hours = []
    mins = []

    # Clone data
    start_time_ = start_time
    end_time_ = end_time

    for e in range(len(parts_)):
        # print(parts_[e])
        if sum_of_hrs != 0:

            
            if sum_of_hrs > 1:
                
                if end_time_.hour != start_time_.hour:

                    # Floor > based on the startTime +1 hour
                    floor_time = (start_time_ + timedelta(hours=1)).floor('H')
    
                    #
                    new_hours.append(start_time_.floor('H'))
                    mins.append((floor_time - start_time_).total_seconds() // 60)
    
                    sum_of_hrs = sum_of_hrs - 1
    
                    start_time_ = floor_time
                
                else:
                    
                    # Hour is same.
                    floor_time = (start_time_ + timedelta(hours=1)).floor('H')
                    new_hours.append(start_time_.floor('H'))
                    mins.append((floor_time - start_time_).total_seconds() // 60)
                    
                    sum_of_hrs = sum_of_hrs - 1

                    start_time_ = floor_time
                    
            else:

                
                if end_time_.hour != start_time_.hour:
                    
                    # Get round off hour
                    floor_time = (end_time_ + timedelta(hours=1)).floor('H')

                    new_hours.append(end_time_.floor('H'))

                    mins.append(60 - ((floor_time - end_time_).total_seconds() // 60)
                                )
                    sum_of_hrs = sum_of_hrs - 1

                    start_time_ = floor_time

                else:

                    # Hour is same.
                    floor_time = (end_time_ + timedelta(hours=1)).floor('H')

                    new_hours.append(end_time_.floor('H'))
                    mins.append((end_time_ - start_time_).total_seconds() // 60)
                    sum_of_hrs = sum_of_hrs - 1

                    start_time_ = floor_time

    # Get DataFrame Build

    df_out = pd.DataFrame()

    df_out['hours'] = pd.Series(new_hours)
    df_out['mins'] = pd.Series(mins)

    df_out['ratios'] = round(data_ / sum(mins) * df_out['mins'])

    return df_out
    

现在,让我们为每次迭代运行代码

time_val=[]
split_f_val=[]

split_field = 'duration'
time_field = 'ts'
# creating DataFrames for intermediate results!
df_final = pd.DataFrame()
df2 = pd.DataFrame()

for ix, row in df.iterrows():

    time_val.append(row[str(time_field)])
    split_f_val.append(int(row[str(split_field)]))
    
    # Skipping First Element for Processing. Therefore, having minimum two data values
    if ix !=0:
        
        # getting Last Two Values
        new_time_list=time_val[-2:]
        new_data_list=split_f_val[-2:]
        
        # get times to compare
        start_time=new_time_list[: -1][0]
        end_time=new_time_list[1:][0]
        
        # get latest Data to divide
        data_ = new_data_list[1:][0]
        
        # print(start_time)
        # print(end_time)
        df2 = get_ratio_per_hour(start_time,end_time, data_ )   
        df_final = pd.concat([df_final
                              , df2], ignore_index=True)
        
    else:
        
        # Create Empty DataFrame for First Value.
        df_final = pd.DataFrame([[np.nan,np.nan,np.nan] ], 
                     columns=['hours', 'mins', 'ratios'])
              
               
        df_final = pd.concat([df_final
                              , df2], ignore_index=True)
                     
    result = df_final.groupby(['hours'])['ratios'].sum()

中间数据框:

                  hours mins    ratios
0           
0   2020-09-09 21:00:00 59.0    7.0
1   2020-09-09 22:00:00 60.0    7.0
2   2020-09-09 23:00:00 60.0    7.0
3   2020-09-10 00:00:00 10.0    1.0
0   2020-09-10 00:00:00 50.0    80.0
1   2020-09-10 01:00:00 31.0    50.0
0   2020-09-10 01:00:00 19.0    60.0
0   2020-09-10 01:00:00 1.0 50.0
0   2020-09-10 01:00:00 8.0 26.0
0   2020-09-10 01:00:00 1.0 36.0
1   2020-09-10 02:00:00 1.0 36.0
0   2020-09-10 02:00:00 50.0    51.0
0   2020-09-10 02:00:00 9.0 57.0
1   2020-09-10 03:00:00 1.0 6.0
0   2020-09-10 03:00:00 59.0    78.0
1   2020-09-10 04:00:00 1.0 1.0
0   2020-09-10 04:00:00 59.0    85.0
1   2020-09-10 05:00:00 60.0    87.0
2   2020-09-10 06:00:00 5.0 7.0

最终输出:

            hours   ratios
2020-09-09 21:00:00 7.0
2020-09-09 22:00:00 7.0
2020-09-09 23:00:00 7.0
2020-09-10 00:00:00 81.0
2020-09-10 01:00:00 222.0
2020-09-10 02:00:00 144.0
2020-09-10 03:00:00 84.0
2020-09-10 04:00:00 86.0
2020-09-10 05:00:00 87.0
2020-09-10 06:00:00 7.0
于 2020-12-07T17:55:21.653 回答