【问题标题】:How to get minimum of each group for each day based on hour criteria如何根据小时标准获得每天每个组的最小值
【发布时间】:2020-01-02 07:57:45
【问题描述】:

我在下面给出了两个数据框供您测试

df = pd.DataFrame({
    'subject_id':[1,1,1,1,1,1,1,1,1,1,1],
    'time_1' :['2173-04-03 12:35:00','2173-04-03 17:00:00','2173-04-03 
         20:00:00','2173-04-04 11:00:00','2173-04-04 11:30:00','2173-04-04 
       12:00:00','2173-04-05 16:00:00','2173-04-05 22:00:00','2173-04-06 
       04:00:00','2173-04-06 04:30:00','2173-04-06 06:30:00'],
  'val' :[5,5,5,10,5,10,5,8,3,8,10]
 })


df1 = pd.DataFrame({
 'subject_id':[1,1,1,1,1,1,1,1,1,1,1],
 'time_1' :['2173-04-03 12:35:00','2173-04-03 12:50:00','2173-04-03 
           12:59:00','2173-04-03 13:14:00','2173-04-03 13:37:00','2173-04-04 
           11:30:00','2173-04-05 16:00:00','2173-04-05 22:00:00','2173-04-06 
           04:00:00','2173-04-06 04:30:00','2173-04-06 08:00:00'],
 'val' :[5,5,5,5,10,5,5,8,3,4,6]
 })

我想做的是

1) 在each day for each subject_id 中查找所有已为same for more than 1 hour 的值(来自val 列)并获取minimum of it

请注意,值也可以在 every 15 min duration 处捕获,因此您可能需要考虑 5 条记录才能看到 > 1 hr 条件)。请参阅下面的示例屏幕截图

2) 如果一天内没有same for more than 1 hour 的值,则只需获取minimum of that day for that subject_id

以下一个主题的屏幕截图将帮助您理解,我尝试过的代码如下

这是我尝试过的

df['time_1'] = pd.to_datetime(df['time_1'])
df['time_2'] = df['time_1'].shift(-1)
df['tdiff'] = (df['time_2'] - df['time_1']).dt.total_seconds() / 3600
df['reading_day'] = pd.DatetimeIndex(df['time_1']).day

# don't know how to apply if else condition here to check for 1 hr criteria
t1 = df.groupby(['subject_id','reading_start_day','tdiff])['val'].min() 

由于我必须将其应用于数百万条记录,因此任何优雅高效的解决方案都会有所帮助

【问题讨论】:

  • 正确语法:df.groupby(['subject_id','reading_start_day','tdiff])['val'].min() if tdiff > 1 else do_something
  • 但这并没有给出预期的输出
  • @shaikmoed - 你确定这是语法吗?它不起作用
  • 什么是df['time_2']?我在上面的数据框中没有看到 time_2 列..
  • 我已经在代码中创建了该列。参考我的代码

标签: python python-3.x pandas vectorization pandas-groupby


【解决方案1】:

我想出了一个像下面这样的方法,它正在工作。欢迎任何建议

s=pd.to_timedelta(24,unit='h')-(df.time_1-df.time_1.dt.normalize())
df['tdiff'] = df.groupby(df.time_1.dt.date).time_1.diff().shift(-1).fillna(s)
df['t_d'] = df['tdiff'].dt.total_seconds()/3600
df['hr'] = df['time_1'].dt.hour
df['date'] = df['time_1'].dt.date
df['day'] = pd.DatetimeIndex(df['time_1']).day

# here I get the freq and cumsum of each val for each day and each hour. Since sort = 'False', timeorder is retained as is

temp_1 = pd.DataFrame(df.groupby(['subject_id','date','hr','val'], sort=False)['t_d'].agg({'cumduration':sum,'freq':'count'}).reset_index())

# here i remove the `hour` component and sum the value duration in same day but different hours (for example `5` was in 12th hour and 13th hour. we sum them)

temp_2 = pd.DataFrame(temp_1.groupby(['subject_id','date','val'], sort=False)['cumduration'].agg({'sum_of_cumduration':sum,'freq':'count'}).reset_index())

# Later, I create a mask for `> 1` hr criteria  

mask = temp_2.groupby(['subject_id','date'])['sum_of_cumduration'].apply(lambda x: x > 1)
output_1 = pd.DataFrame(temp_2[mask].groupby(['subject_id','date'])['val'].min()).reset_index()

 # I check for `< 1 ` hr records here 

output_2 = pd.DataFrame(temp_2[~mask].groupby(['subject_id','date'])['val'].min()).reset_index()

 # I finally check for `subject_id` and `date` and then append
output = output_1.append(output_2[~output_2['subject_id'].isin(output_1['subject_id'])])

output

【讨论】:

    【解决方案2】:

    试试这个

    from datetime import timedelta
    
    df1['time_1']= pd.to_datetime(df1['time_1'])
    df1['date'] = df1['time_1'].dt.date
    df1['t_d'] = df1.groupby(['date'])['time_1'].diff().shift(-1)
    mask= df1['t_d']>pd.Timedelta(1,'h')
    dfa=df1[mask]
    dfb=df1[~mask].groupby('date').first().reset_index()
    df_f = dfa.merge(dfb, how='outer')
    df_f.drop_duplicates(subset='date', keep='first', inplace=True)
    df_f.drop(['date','t_d'], axis=1, inplace=True)
    df_f.sort_values('time_1')
    

    【讨论】:

    • 当然。我下班回来了。今晚会尝试检查并更新它。你想更新你以前的答案吗?感谢您的努力。
    • 对于任何一天,第一次是最短时间吗?不是吗?我完全糊涂了。
    【解决方案3】:

    试试这个。

    from datetime import timedelta
    
    def f(x):
        dif = (x.iloc[0]-x.iloc[-1])//timedelta(minutes=1)
        return dif
    df1['time_1']= pd.to_datetime(df1['time_1'])
    df1['flag']= df1.val.diff().ne(0).cumsum()
    df1['t_d']=df1.groupby('flag')['time_1'].transform(f)
    df1['date'] = df1['time_1'].dt.date
    mask= df1['t_d'].ne(0)
    dfa=df1[mask].groupby(['flag','date']).first().reset_index()
    dfb=df1[~mask].groupby('date').first().reset_index().dropna(how='any')
    df_f = dfa.merge(dfb, how='outer')
    df_f.drop_duplicates(subset='date', keep='first', inplace=True)
    df_f.drop(['flag','date','t_d'], axis=1, inplace=True)
    df_f
    

    输出。

     subject_id     time_1         val
    0   1   2173-04-03 12:35:00     5
    1   1   2173-04-04 11:30:00     5
    2   1   2173-04-05 16:00:00     5
    5   1   2173-04-06 04:00:00     3
    

    【讨论】:

    • 无论如何都赞成这项努力。现在将尝试并更新答案
    • 不幸的是,它在最后一行产生了与3 相同的输出。它应该是 4
    • 2173-04-06 的值是 3,4&6 而不是 4,4,4。所以,我不明白 4 是如何维持一个多小时的。
    • 如果您看到这 3 条记录的时间,则为 4-4.30(30 分钟)、4.30-8(3 小时 30 分钟)
    • 有帮助吗?有什么困惑吗?
    【解决方案4】:
    df = pd.DataFrame({
     'subject_id':[1,1,1,1,1,1,1,1,1,1],
     'time_1' :['2173-04-03 12:35:00','2173-04-03 17:00:00','2173-04-03 20:00:00','2173-04-04 11:00:00','2173-04-04 11:30:00','2173-04-04 12:00:00','2173-04-04 16:00:00','2173-04-04 22:00:00','2173-04-05 04:00:00','2173-04-05 06:30:00'],
      'val' :[5,5,5,10,5,10,5,8,8,10]
     })
    
    # Separate Date and time
    df['time_1']=pd.to_datetime(df['time_1'])
    df['new_date'] = [d.date() for d in df['time_1']]
    df['new_time'] = [d.time() for d in df['time_1']]
    
    
    # find time diff in group with the first element to check > 1 hr
    df['shift_val'] = df['val'].shift()
    df1=df.assign(time_diff=df.groupby(['subject_id','new_date']).time_1.apply(lambda x: x - x.iloc[0]))
    
    # Verify if time diff > 1 and value is not changed
    df2=df1.loc[(df1['time_diff']/ np.timedelta64(1, 'h') >= 1) & (df1.val == df1.groupby('new_date').first().val[0])]
    df3=df1.loc[(df1['time_diff']/ np.timedelta64(1, 'h') <= 1) & (df1.val == df1.shift_val)]
    
    # Get the minimum within the group
    df4=df2.append(df3).groupby(['new_date'], sort=False).min()
    
    # drop unwanted columns
    df4.drop(['new_time','shift_val','time_diff'],axis=1, inplace=True)
    
    df4
    

    输出

              subject_id    time_1     val
    new_date            
    2173-04-03  1   2173-04-03 17:00:00 5
    2173-04-04  1   2173-04-04 16:00:00 5
    2173-04-05  1   2173-04-05 04:00:00 8
    

    【讨论】:

    • 输出中缺少一个(最后)行是否有任何原因?无论如何感谢您的支持。会尝试
    • 三个日期所以三行
    • 不幸的是,这不起作用。我尝试使用帖子中提供的其他数据框。但我正在进一步尝试。让我看看我是否得到输出
    • 哪个数据框不工作?你能在问题中分享吗?我认为所有的规则都是一步一步执行的
    • 我的问题中提到的另一个(df1)
    猜你喜欢
    • 2016-09-10
    • 2017-03-22
    • 1970-01-01
    • 1970-01-01
    • 2017-01-02
    • 2015-11-07
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多