【问题标题】:Match Dataframes by Time Difference Threshold按时差阈值匹配数据帧
【发布时间】:2018-11-16 03:32:01
【问题描述】:

我有两个数据框,并希望通过时间戳来匹配它们。例如:

A    
    Time                X
0   05-01-2017 09:08    3
1   05-01-2017 09:09    6
2   07-01-2017 09:09    5
3   07-01-2017 09:19    4
4   07-01-2017 09:19    8
5   07-02-2017 09:19    7
6   07-02-2017 09:19    5

B    
    Time                Y
0   06-01-2017 14:45    1
1   04-01-2017 03:31    9
2   07-01-2017 03:31    4
3   07-01-2017 14:57    5
4   09-01-2017 14:57    7

有太多数据无法将 df_A 中的每个项目与 df_B 中的每个项目进行比较。相反,我想找到在受控时间阈值内的每个匹配项,例如 2 天。那就是:

dT = Time A – Time B
-2 < dT < 2

结果应该是:

C                        
Index A Time A          X   Index B Time B          Y   dT
0   05-01-2017 09:08    3   0   06-01-2017 14:45    1   -1.2
0   05-01-2017 09:08    3   1   04-01-2017 03:31    9   1.2
0   05-01-2017 09:08    3   2   07-01-2017 03:31    4   -1.8
1   05-01-2017 09:09    6   0   06-01-2017 14:45    1   -1.2
1   05-01-2017 09:09    6   1   04-01-2017 03:31    9   1.2
1   05-01-2017 09:09    6   2   07-01-2017 03:31    4   -1.8
2   07-01-2017 09:09    5   0   06-01-2017 14:45    1   0.8
2   07-01-2017 09:09    5   2   07-01-2017 03:31    4   0.2
2   07-01-2017 09:09    5   3   07-01-2017 14:57    5   -0.2
3   07-01-2017 09:19    4   0   06-01-2017 14:45    1   0.8
3   07-01-2017 09:19    4   2   07-01-2017 03:31    4   0.2
3   07-01-2017 09:19    4   3   07-01-2017 14:57    5   -0.2
4   07-01-2017 09:19    8   0   06-01-2017 14:45    1   0.8
4   07-01-2017 09:19    8   2   07-01-2017 03:31    4   0.2
4   07-01-2017 09:19    8   3   07-01-2017 14:57    5   -0.2
5   07-02-2017 09:19    7                
6   07-02-2017 09:19    5                
                            4   09-01-2017 14:57    7    

我尝试了以下代码,但它不起作用:

import pandas as pd
import datetime as dt
from   datetime import timedelta

# Data
df_A = pd.DataFrame({'X':[3,6,5,4,8,7,5], 'Time_A': [dt.datetime(2017,1,5,9,8),   dt.datetime(2017,1,5,9,9),  dt.datetime(2017,1,7,9,19), dt.datetime(2017,1,7,9,19),  dt.datetime(2017,1,7,9,19), dt.datetime(2017,2,7,9,19), dt.datetime(2017,2,7,9,19)]})
df_B = pd.DataFrame({'Y':[1,9,4,5,7],     'Time_B': [dt.datetime(2017,1,6,14,45), dt.datetime(2017,1,4,3,31), dt.datetime(2017,1,7,3,31), dt.datetime(2017,1,7,14,57), dt.datetime(2017,1,9,14,57)]})

# Match
def slice_datetime(Time, window):

return (Time + timedelta(hours=window)).strftime('%Y-%m-%d %H:%m')

lst = []
for Time in df_A[['X', 'Time_A']].iterrows():
    tmp = df_B.ix[slice_datetime(Time,-48):slice_datetime(Time,48)] # Define the time threshold (hours)
    if not tmp.empty:
        _match = pd.DataFrame()
        for Time_A, (X, Y, Time_B) in tmp.iterrows():
            lst.append([X, Y, Time_A, Time_B])

df_C = pd.DataFrame(lst, columns = ['X', 'Y', 'Time_A', 'Time_B'])

【问题讨论】:

    标签: python dataframe match timedelta threshold


    【解决方案1】:

    这里有一个想法如何在没有循环的情况下做到这一点:

    import pandas as pd
    df_A = pd.DataFrame({'X':[3,6,5,4,8,7,5], 
                         'Time_A': [pd.datetime(2017,1,5,9,8),   pd.datetime(2017,1,5,9,9),  
                                    pd.datetime(2017,1,7,9,19), pd.datetime(2017,1,7,9,19),  
                                    pd.datetime(2017,1,7,9,19), pd.datetime(2017,2,7,9,19), 
                                    pd.datetime(2017,2,7,9,19)]})
    df_B = pd.DataFrame({'Y':[1,9,4,5,7],     
                         'Time_B': [pd.datetime(2017,1,6,14,45), pd.datetime(2017,1,4,3,31), 
                                    pd.datetime(2017,1,7,3,31), pd.datetime(2017,1,7,14,57), 
                                    pd.datetime(2017,1,9,14,57)]})
    
    #first reset_index and rename
    df_A = df_A.reset_index().rename(columns = {'index':'index_A'})
    df_B = df_B.reset_index().rename(columns = {'index':'index_B'})
    
    #then create a list of index_B where time_B is within 2 days for each time_A
    time_delta = pd.Timedelta(days=2) #check the documentation for more parameter
    df_A['list_B'] = (df_A['Time_A'].apply(lambda time_A: 
                        df_B.index_B[(time_A - time_delta <= df_B['Time_B']) & 
                                     (time_A + time_delta >= df_B['Time_B'])].tolist()))
    
    #now use pd.Series and stack, with reset_index drop and rename 
    # for finally merge to achieve your goal 
    df_C = (df_A.set_index(['index_A','Time_A','X'])['list_B']
                .apply(pd.Series).stack().astype(int)
                .reset_index().drop('level_3',1).rename(columns={0:'index_B'})
                .merge(df_B).sort_values('index_A'))
    
    # Create the columns dT
    df_C['dT'] = ((df_C['Time_A'] - df_C['Time_B']).dt.total_seconds()/(24.*3600.)).round(1)
    
    #add the time from df_A and df_B without corresponding time in the other df
    # using append and ~ with isin 
    df_C = (df_C.append(df_A[~df_A['Time_A'].isin(df_C['Time_A'])].drop('list_B',1))
        .append(df_B[~df_B['Time_B'].isin(df_C['Time_B'])]).fillna(''))
    

    您可能需要在之后重新排序列,但您应该得到您想要的输出

    【讨论】:

    • 感谢这种方法适用于虚拟数据。当我用真实数据尝试它时,我得到了错误“df_A = df_A.reset_index().rename(columns = {'index':'index_A'}) # AttributeError: 'function' object has no attribute 'reset_index'”
    • @R.Cox 获取您的真实数据,您可以尝试 type(df_A) 吗,因为错误似乎表明 df_A 不是您的示例中的 pandas.Dataframe。
    • 谢谢。我一直在将我的数据框复制到 df_A = my_df.copy。我发现当我更改代码以将 df_A 替换为我的数据框时,它起作用了。我不知道为什么它会起作用。
    • 输出:pandas.core.frame.DataFrame
    • @R.Cox 所以我重现了您的错误错误了
    【解决方案2】:

    您可以创建两个带有时间边界的新列

    df_A["start_date"] = df_A["Time_A"]+datetime.timedelta(days=-2)
    df_A["end_date"] = df_A["Time_A"]+datetime.timedelta(days=2)
    

    然后用条件加入两个数据框

    (df_B.Time_B >= df_A.start_date)&(df_B.Time_B <= df_A.end_date)
    

    希望这会有所帮助!

    【讨论】:

    • 感谢 approack 看起来很棒。我试过了,得到了错误“ValueError:只能比较标签相同的系列对象”。
    • 如果我可以让这种方法发挥作用会很好,因为我认为在匹配时间段的情况下我需要它。 stackoverflow.com/questions/51187462/…
    猜你喜欢
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 2018-07-13
    • 2020-03-15
    • 2022-01-08
    • 2018-12-01
    • 2018-05-05
    • 2017-05-18
    相关资源
    最近更新 更多