从列表中查找最接近给定日期且不晚于给定日期的日期

Find the closest date from a list to a given date that is not after the given date

我有一个用于每周培训 session 的数据框和一个用于参加者在这些培训 session 中提交的评估的数据框。

每个数据框都有一个日期列 - sessions,它是 session 发生的日期。对于评估,这是提交评估的日期。预计与会者将参加多次 session,因此将提交多次评估。

我需要将每个评估与特定的 session 联系起来。他们可能在 session 的同一天提交了评估,在这种情况下匹配很容易。但他们可以在下一次培训前的任何一天提交评估 session。

对于评估df中的每个日期,我需要return最接近评估日期但不晚于评估日期的session日期。

示例session 日期: 22 年 2 月 3 日、22 年 2 月 10 日、22 年 2 月 17 日

具有所需输出的示例评估日期: 2/3/22(应匹配 2/3/22)、2/4/22(应匹配 2/3/22)、2/11/22(应匹配 2/10/22)

这是一种方法。

sessions 数据框中,将 date 列设置为索引:

sessions = sessions.set_index('date')

按索引(即按日期)对会话进行排序:

sessions = sessions.loc[sessions.index.sort_values()]

向评估添加 session_evaluated 列,其中将包含评估适用的会话日期。我们通过首先在评估的 date 列上调用 sessions.index.get_indexer() 并将 method 参数设置为 'pad' 来计算这一点,因此我们在 non-matching 日期“向下舍入”,然后在会话索引(包含会话日期)中查找这些整数索引值:

evaluations['session_evaluated'] = pd.Series([sessions.index.to_list()[i] 
    for i in sessions.index.get_indexer(evaluations['date'], method='pad')])

下面是它与示例输入放在一起的样子:

import pandas as pd
sessions = pd.DataFrame({
    'date' : ['2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01', '2022-01-01'],
    'topic' : ['Easy 1', 'Easy 2', 'Intermediate', 'Advanced', 'Intro']
})
evaluations = pd.DataFrame({
    'date' : [
        '2022-01-05', '2022-01-10', '2022-01-15', '2022-01-20', '2022-01-25', 
        '2022-02-01', '2022-02-05', '2022-02-28',
        '2022-03-01', '2022-03-15', '2022-03-31',
        '2022-04-01', '2022-04-15'
    ],
    'rating' : [9,8,7,8,9,5,4,3,10,10,10,2,4]
})
sessions['date'] = pd.to_datetime(sessions['date'])
evaluations['date'] = pd.to_datetime(evaluations['date'])
sessions = sessions.set_index('date')
sessions = sessions.loc[sessions.index.sort_values()]
print(sessions)
print(evaluations)
evaluations['session_evaluated'] = pd.Series([sessions.index.to_list()[i]
    for i in sessions.index.get_indexer(evaluations['date'], method='pad')])
print(evaluations)

结果:

                   topic
date
2022-01-01         Intro
2022-02-01        Easy 1
2022-03-01        Easy 2
2022-04-01  Intermediate
2022-05-01      Advanced
         date  rating
0  2022-01-05       9
1  2022-01-10       8
2  2022-01-15       7
3  2022-01-20       8
4  2022-01-25       9
5  2022-02-01       5
6  2022-02-05       4
7  2022-02-28       3
8  2022-03-01      10
9  2022-03-15      10
10 2022-03-31      10
11 2022-04-01       2
12 2022-04-15       4
         date  rating session_evaluated
0  2022-01-05       9        2022-01-01
1  2022-01-10       8        2022-01-01
2  2022-01-15       7        2022-01-01
3  2022-01-20       8        2022-01-01
4  2022-01-25       9        2022-01-01
5  2022-02-01       5        2022-02-01
6  2022-02-05       4        2022-02-01
7  2022-02-28       3        2022-02-01
8  2022-03-01      10        2022-03-01
9  2022-03-15      10        2022-03-01
10 2022-03-31      10        2022-03-01
11 2022-04-01       2        2022-04-01
12 2022-04-15       4        2022-04-01

更新:

这是使用 merge_asof() 函数的另一种方法。它不需要日期列作为索引(尽管它确实要求两个数据框参数都按 date 排序):

sessions['date'] = pd.to_datetime(sessions['date'])
evaluations['date'] = pd.to_datetime(evaluations['date'])
evaluations = pd.merge_asof(
    evaluations.sort_values(by=['date']), 
    sessions.sort_values(by=['date'])['date'].to_frame().assign(session_evaluated=sessions['date']), 
    on='date')
print(evaluations)

输出:

         date  rating session_evaluated
0  2022-01-05       9        2022-01-01
1  2022-01-10       8        2022-01-01
2  2022-01-15       7        2022-01-01
3  2022-01-20       8        2022-01-01
4  2022-01-25       9        2022-01-01
5  2022-02-01       5        2022-02-01
6  2022-02-05       4        2022-02-01
7  2022-02-28       3        2022-02-01
8  2022-03-01      10        2022-03-01
9  2022-03-15      10        2022-03-01
10 2022-03-31      10        2022-03-01
11 2022-04-01       2        2022-04-01
12 2022-04-15       4        2022-04-01

更新#2: 上面代码中对 assign() 的调用也可以使用 **kwargs 语法编写,以防我们想要使用带空格的列名或者不是有效的 python 标识符(而是session_evaluated)。例如:

evaluations = pd.merge_asof(
    evaluations.sort_values(by=['date']), 
    sessions.sort_values(by=['date'])['date'].to_frame()
        .assign(**{'Evaluated Session (Date)' : lambda x: sessions['date']}), 
    on='date')

输出:

         date  rating Evaluated Session (Date)
0  2022-01-05       9               2022-01-01
1  2022-01-10       8               2022-01-01
2  2022-01-15       7               2022-01-01
3  2022-01-20       8               2022-01-01
4  2022-01-25       9               2022-01-01
5  2022-02-01       5               2022-02-01
6  2022-02-05       4               2022-02-01
7  2022-02-28       3               2022-02-01
8  2022-03-01      10               2022-03-01
9  2022-03-15      10               2022-03-01
10 2022-03-31      10               2022-03-01
11 2022-04-01       2               2022-04-01
12 2022-04-15       4               2022-04-01