【问题标题】:Group dataframe based on two datetime values根据两个日期时间值对数据框进行分组
【发布时间】:2020-05-20 10:41:26
【问题描述】:

我有一个数据集,它显示了一组进程的开始和结束时间戳。我想计算任何给定分钟内活动的进程数。 pandas group/merge/join 操作似乎都不适合这里。这是一个测试数据集:

_range = pd.date_range(start='2020-03-01', end='2020-03-02', freq='1H')
start_ts = [random.choice(_range) for _ in range(48)]
end_ts = [r + datetime.timedelta(minutes=random.randint(1, 120)) for r in start_ts]

_processes = [random.choice(['Django', 'Flask', 'Pyramid', 'CherryPie']) for _ in start_ts]
df = pd.DataFrame({'start_time': start_ts, 'end_time': end_ts, 'process': _processes})
df.head()
           start_time            end_time    process
0 2020-03-01 00:00:00 2020-03-01 00:01:00     Django
1 2020-03-01 01:00:00 2020-03-01 01:04:00     Django
2 2020-03-01 02:00:00 2020-03-01 02:05:00      Flask
3 2020-03-01 03:00:00 2020-03-01 03:09:00      Flask
4 2020-03-01 04:00:00 2020-03-01 04:26:00  CherryPie

我必须计算 2020/03/01 到 2020/03/02 之间每 1 分钟间隔的活动进程数。 这是我能想到的一种解决方案:

  • 在所述开始/结束日期之间创建一个 1 分钟间隔的数据帧
  • 对于每 1 分钟的时间间隔,如果 start_ts 或 end_ts 介于​​给定分钟之间,则迭代主数据帧并将记录分桶。

这是一个示例解决方案:

df_stat = pd.DataFrame(index=pd.date_range(start='2020-03-01', end='2020-03-02', freq='1T'), columns=['count'])
for ts in df_stat.index:
    df_stat.loc[ts] = len(df[(df.start_time <= ts) & (df.end_time >= ts)])
df_stat.head()
                     count
2020-03-01 00:00:00      2
2020-03-01 00:01:00      2
2020-03-01 00:02:00      2
2020-03-01 00:03:00      2
2020-03-01 00:04:00      2

这看起来不是一个优雅的解决方案。当观察窗口很大时,迭代可能需要更长的时间。有没有我们可以在这里使用的原生 pandas 运算符?

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    再想一想,我想出了一个熊猫方式,我希望它对你的数据来说足够快。关键思想是我们可以解耦开始时间和结束时间,因为哪个开始时间属于哪个结束时间并不重要。重要的是在任何给定时刻有多少流程已经开始,多少流程已经结束。因此,如果我们编码以 1 开头并以 -1 结尾,则正在运行的进程数是它们的累积和。

    # create a dataframe with a '1'-column for starting processes
    # and a '-1'-column for ending processes
    df_times = df.drop('process', axis=1)
    df_times.columns = [1, -1]
    
    # melt the two columns into one (the increment column)
    df_long = pd.melt(df_times, var_name='inc', value_name='time')
    
    # group by time and sum the increments if events coincide
    df_stat = df_long.groupby('time').sum()
    
    # now the count of running processes is the cumulative sum of increments
    df_stat['count'] = df_stat.inc.cumsum()
    df_stat.drop('inc', axis=1, inplace=True)
    
    # upsample to minutely frequency
    df_stat = df_stat.resample('1T').ffill()
    

    【讨论】:

    • 对于一个相当简单的任务来说看起来有点复杂,但这很有效(至少对于我拥有的示例数据集),并且性能符合我的预期!
    【解决方案2】:

    创建一个一分钟间隔的DataFrame(或一个Series)确实很合适。但是,根据输入,迭代原始 DataFrame 并相应地增加计数可能会更快(与您的解决方案相比):

    df_stat = pd.DataFrame(index=pd.date_range(start='2020-03-01', end='2020-03-02', freq='1T'), 
                           columns=['count'])
    df_stat.fillna(0, inplace=True)
    
    for i in df.index:
        df_stat.loc[df.start_time.loc[i]:df.end_time.loc[i]] += 1
    

    【讨论】:

    • 谢谢@Arne。这绝对是对我最初提案的改进,但仍然不够快,无法遍历 50k+ 条记录。
    猜你喜欢
    • 2014-12-23
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 2020-03-20
    • 2015-10-30
    相关资源
    最近更新 更多