在 groupby 数据帧(Python)中查找给定日期的最接近日期

tbk*_*tbk 2 python datetime group-by

我正在尝试Last_Payment_Date在我的 Pandas 数据框中生成字段,并且需要为每个客户(即 groupby)Payment_Date在给定之前找到最接近的字段Order_Date

Payment_Date将始终发生在 之后Order_Date,但可能需要不同的时间段,这很难使用排序和移位来找到最近的日期。

掩蔽似乎是一种可能的方式,但我一直无法想出如何使用它的方法。

感谢我能得到的所有帮助!

Cust_No  Order_Date  Payment_Date  Last_Payment_Date
      A    5/8/2014      6/8/2014                Nat
      B    6/8/2014      1/5/2015                Nat
      B    7/8/2014      7/8/2014                Nat
      A    8/8/2014      1/5/2015           6/8/2014
      A    9/8/2014     10/8/2014           6/8/2014
      A  10/11/2014    12/11/2014          10/8/2014
      B  11/12/2014      1/1/2015           7/8/2014
      B    1/2/2015      2/2/2015           1/1/2015
      A    2/5/2015      5/5/2015           1/5/2015
      B    3/5/2015      4/5/2015           2/2/2015
Run Code Online (Sandbox Code Playgroud)

unu*_*tbu 5

Series.searchsorted很大程度上可以满足您的需求——它可用于查找Order_Dates 适合Payment_Dates 的位置。特别是,它返回与每个Order_Date需要插入以保持Payment_Dates 排序的位置相对应的序数索引 。例如,假设

In [266]: df['Payment_Date']
Out[266]: 
0   2014-06-08
2   2014-07-08
4   2014-10-08
5   2014-12-11
6   2015-01-01
1   2015-01-05
3   2015-01-05
7   2015-02-02
9   2015-04-05
8   2015-05-05
Name: Payment_Date, dtype: datetime64[ns]

In [267]: df['Order_Date']
Out[267]: 
0   2014-05-08
2   2014-07-08
4   2014-09-08
5   2014-10-11
6   2014-11-12
1   2014-06-08
3   2014-08-08
7   2015-01-02
9   2015-03-05
8   2015-02-05
Name: Order_Date, dtype: datetime64[ns]
Run Code Online (Sandbox Code Playgroud)

然后searchsorted返回

In [268]: df['Payment_Date'].searchsorted(df['Order_Date'])
Out[268]: array([0, 1, 2, 3, 3, 0, 2, 5, 8, 8])
Run Code Online (Sandbox Code Playgroud)

第一值,例如0,表明Order_Date2014-05-08,将具有在顺序索引0(前的要被插入Payment_Date 2014-06-08),以保持Payment_DateS IN排序顺序。第二值,如图1所示,表明Order_Date2014-07-08,将具有在顺序索引1(后要被插入Payment_Date 2014-06-08和之前2014-07-08),以保持Payment_DateS IN排序顺序。其他指数依此类推。

当然,现在有一些复杂情况:

  1. Payment_Dates需要在排序顺序searchsorted返回有意义的结果:

    df = df.sort_values(by=['Payment_Date'])    
    
    Run Code Online (Sandbox Code Playgroud)
  2. 我们需要按 Cust_No

    grouped = df.groupby('Cust_No')
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我们希望的指标Payment_Date,其来之前Order_Date。因此,我们真的需要将索引减一:

    idx = grp['Payment_Date'].searchsorted(grp['Order_Date']) 
    result = grp['Payment_Date'].iloc[idx-1]
    
    Run Code Online (Sandbox Code Playgroud)

这样grp['Payment_Date'].iloc[idx-1]会抢了 Payment_Date

  1. searchsorted返回 0 时,Order_Date小于所有 Payment_Dates。在这种情况下,我们想要一个 NaT。

    result[idx == 0] = pd.NaT
    
    Run Code Online (Sandbox Code Playgroud)

所以把这一切放在一起,

import pandas as pd
NaT = pd.NaT
T = pd.Timestamp
df = pd.DataFrame({
    'Cust_No': ['A', 'B', 'B', 'A', 'A', 'A', 'B', 'B', 'A', 'B'],
    'expected': [
        NaT,  NaT,  NaT, T('2014-06-08'), T('2014-06-08'), T('2014-10-08'), 
        T('2014-07-08'), T('2015-01-01'), T('2015-01-05'), T('2015-02-02')], 
    'Order_Date': [
        T('2014-05-08'), T('2014-06-08'), T('2014-07-08'), T('2014-08-08'), 
        T('2014-09-08'), T('2014-10-11'), T('2014-11-12'), T('2015-01-02'), 
        T('2015-02-05'), T('2015-03-05')], 
    'Payment_Date': [
        T('2014-06-08'), T('2015-01-05'), T('2014-07-08'), T('2015-01-05'), 
        T('2014-10-08'), T('2014-12-11'), T('2015-01-01'), T('2015-02-02'), 
        T('2015-05-05'), T('2015-04-05')]})

def last_payment_date(s, df):
    grp = df.loc[s.index]
    idx = grp['Payment_Date'].searchsorted(grp['Order_Date']) 
    result = grp['Payment_Date'].iloc[idx-1]
    result[idx == 0] = pd.NaT
    return result

df = df.sort_values(by=['Payment_Date'])    
grouped = df.groupby('Cust_No')
df['Last_Payment_Date'] = grouped['Payment_Date'].transform(last_payment_date, df)

print(df)
Run Code Online (Sandbox Code Playgroud)

产量

  Cust_No Order_Date Payment_Date   expected Last_Payment_Date
0       A 2014-05-08   2014-06-08        NaT               NaT
2       B 2014-07-08   2014-07-08        NaT               NaT
4       A 2014-09-08   2014-10-08 2014-06-08        2014-06-08
5       A 2014-10-11   2014-12-11 2014-10-08        2014-10-08
6       B 2014-11-12   2015-01-01 2014-07-08        2014-07-08
1       B 2014-06-08   2015-01-05        NaT               NaT
3       A 2014-08-08   2015-01-05 2014-06-08        2014-06-08
7       B 2015-01-02   2015-02-02 2015-01-01        2015-01-01
9       B 2015-03-05   2015-04-05 2015-02-02        2015-02-02
8       A 2015-02-05   2015-05-05 2015-01-05        2015-01-05
Run Code Online (Sandbox Code Playgroud)