基于不同数据帧中的日期时间的子集熊猫数据帧

Posted

技术标签:

【中文标题】基于不同数据帧中的日期时间的子集熊猫数据帧【英文标题】:Subset pandas dataframe based on datetime in different dataframe 【发布时间】:2019-10-28 20:48:45 【问题描述】:

我需要对每 5 分钟记录一次测量值 (temp) 的数据帧 (df1) 进行子集化,并以日期时间为索引。

Dataframe df2,包含有关何时发生事件的数据。 0 是事件的开始,1 是事件的结束。 df2 有一个名为 date 的列,它是相应事件的开始和结束的日期时间。所有事件的开始和结束都记录到最接近的秒数。

我想根据发生事件的时间对 df1 进行子集化,使用与 df1 中包含的相同日期时间格式(每 5 分钟一次的时间)。

在下面的示例中,在 00:07:00 和 00:14:00 之间发生了一个事件,所以我希望 df3 包含 df1['temp'] 00:05:00 和 00:10:00 . 00:41:00 到 00:44:00 之间还发生了一个事件,所以我也希望 df3 包含 00:40:00。

import numpy as np
import pandas as pd

df1 = pd.DataFrame('temp' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
                    index=pd.date_range('2019-05-02T00:00:00', '2019-05-02T01:00:00', freq='5T'))

df2 = pd.DataFrame('event' : [0, 1, 0, 1],
                    'date' : ['2019-05-02-00:07:00', '2019-05-02-00:14:00', '2019-05-02-00:41:00', '2019-05-02-00:44:00'])

df2['date'] = pd.to_datetime(df2['date'])

df3 = pd.DataFrame('result' : [2, 3, 9],
                    'date' :['2019-05-02-00:05:00', '2019-05-02-00:10:00', '2019-05-02-00:40:00'])

在我的实际工作中,我有 7 个单独的 df,每个都包含不同的事件,我想对 df1 进行子集化并组合,所以我最终得到一个 df,它是 df1 中所有数据的子集,当有是其他 7 个 df 中的任何一个事件。实际上,df1 有 37 列,其中包含我想要传输到最终 df3 的数据。获得上述子集的代码后,我将合并所有子集数据并删除所有重复项。

【问题讨论】:

所以您想通过 df2 事件过滤 df1 但有 7 个“df2”? 是的,这正是我想要做的 here has been an event between 00:07:00 and 00:14:00, so I would like df3 to contain df1['temp'] 00:05:00 and 00:10:00. - 为什么是00:05:00 包含00:05:00不是必须的,如果只捕获00:10:00就可以了 有没有办法让代码在 5 分钟内查找任何事件?有些事件持续不到 5 分钟,在这些情况下,代码可能不包含它们? 【参考方案1】:

继续您给定的最小示例:

# create from df2 a data frame with a 'from' and 'to' column (range data frame)
def df2_like_to_from_to(df2, date_col = 'date'):
    """
    Takes 'event' column, collects all '0' and all '1' event rows
    and concatenates the columns 'date' to a data frame.
    It preserves all other columns from the first ('o') data frame.
    (That is why the code is a little more complicated).
    And renames the date_col column to 'from' and 'to' and puts them upfront.
    """
    df_from = df2[df2.event == 0]
    df_to   = df2[df2.event == 1]
    col_names = [ x if x != date_col else 'from' for x in df2_like.columns]
    df_from_to = pd.concat([df_from.reset_index(), df_to.loc[:, 'date'].reset_index()], axis=1)
    df_from_to = df_from_to.drop(columns=['index'])
    df_from_to.columns = col_names + ['to']
    df_res = df_from_to.loc[:, ['from', 'to'] + [x for x in col_names if x != 'from']]
    return df_res

range_df = df2_like_to_from_to(df2)

# 
#                  from                  to  event
# 0 2019-05-02 00:07:00 2019-05-02 00:14:00      0
# 1 2019-05-02 00:41:00 2019-05-02 00:44:00      0
# 

# filter df1 by its dates overlapping with the range in the range data frame
def filter_by_overlap(dates, df, df_from_to, from_to_col=['from', 'to']):
    """
    Filters df rows by overlaps of given dates (one per row) with da data frame
    which contains ranges (column names to be given by 'from_to_col' - first for 'from' and second for 'to' values).
    The dates are used to build a pseudo-interval which then is searched for
    any overlap with the ranges in the ranges data frame.
    The df is subsetted for any overlap and returned.
    """
    ranges_from_to = df_from_to.loc[:, from_to_col].apply(lambda x: pd.Interval(*x),  axis=1)
    ranges_date = [pd.Interval(x, x) for x in dates] # pseudo range for data points
    selector = [any(x.overlaps(y) for y in ranges_from_to) for x in ranges_date]
    return df.loc[selector, :]

filter_by_overlap(df1.index, df1, range_df)
# first argument: the data list/column for which overlaps should be searched
# second argument: the to-be-filtered data frame
# third argument: the range data frame which should select the dates (first argument)

# output:
#                      temp
# 2019-05-02 00:10:00     3

【讨论】:

【参考方案2】:

您可以使用resample 和concat 来做到这一点。 由于您的事件产生的时间可能超过两个 bin,因此您还需要一个自定义重采样函数(我发现没有办法做得更好)。

event_on = 0

def event_tracker(x):
    global event_on
    if len(x) > 0:
        event_on += x.sum()
        return 1
    else:
        if event_on > 0:
            return 1
        else:
            return 0

idf2 = df2.set_index('date')
idf2['event'].loc[idf2['event'] == 0] = -1
rbdf2 = idf2.resample('5T').apply(event_tracker)
concatenated = pd.concat([df1, rbdf2], axis=1)
df3 = concatenated.loc[concatenated['event'] > 0.0]
df3 = df3.drop('event', axis=1)

使用您的示例数据框,这将产生 df3:

                     temp
2019-05-02 00:05:00     2
2019-05-02 00:10:00     3
2019-05-02 00:40:00     9

这里将日期设置为索引,如果出于某种原因您需要将它们作为一列添加最后一行 df3 = df3.reset_index()

让我一步一步解释我在上面做了什么:

首先我为重采样器定义了一个自定义函数event_tracker。它有点脏,因为它使用了一个全局变量,但这是我发现的最快的方法。基本上,全局变量用于跟踪是否有事件正在进行。如果 bin 没有正在进行的事件,则返回 0,否则返回 1。

那我可以一行一行的去:

    'date' 列设置为索引。 将idf2(事件开始)中的0 设置为-1。需要在 event_tracker 中正确执行数学运算。 使用resampe。此函数使用DatetimeIndex 重新采样数据帧。我使用了 5 分钟的重采样 ('5T') 来匹配 df1 中的垃圾箱(打印 rbdf2 来查看它,你就会明白)。 .apply() 用于将event_tracker 应用于每个 bin 并得到 0 或 1,如前所述。 使用concat 连接两个数据帧。 仅选择event > 0 的行,即事件正在进行的行。 删除'event' 列。

即使df2 日期没有排序,这种方法也有效。


由于您有 7 个df2s,您需要在之前使用上述过程连接它们。只需这样做:

df2 = pd.concat([df21, df22])

其中df21df22 是两个数据帧,具有与df2 相同的结构。如果您有 7 个数据帧,则提供给 concat 的列表必须包含所有 7 个数据帧:[df21, df22, df23, ...]

【讨论】:

您好 Valentino,感谢您非常详细的回复。就我而言,数据的范围可以从 30 秒到 4 小时或更长时间。事件长度变化很大。修改代码需要什么才能处理更长的事件? @CairanVanRooyen 我编辑了我的答案,现在应该也适用于更长的事件。 嗨华伦天奴。当我到达rbdf2 = idf2.resample('5T').apply(event_tracker) 时,我在真实数据中的所有结果都返回为 1,没有 0 您确定所有七个数据帧df2 都发现了区间吗?如果至少有一个覆盖时间,则计为 1。尝试仅使用一个 df2 看看会发生什么

以上是关于基于不同数据帧中的日期时间的子集熊猫数据帧的主要内容,如果未能解决你的问题,请参考以下文章

计算pandas数据帧中日期时间间隔之间的实例数

Python合并两个具有不同日期时间的数据框[重复]

如果日期介于第二个数据帧中的两个日期之间,则 r 标记第一个数据帧中的行

基于分组数据帧中的两个条件的子集

将火花数据帧中的日期时间时间戳转换为 epocTimestamp

使用数据帧中的某些参数计算日期之间的差异