Python 代码来比较匹配 ID 中的日期?

Python code to compare dates within matching IDs?

提问人:Chris Linke 提问时间:5/16/2023 最后编辑:MattDMoChris Linke 更新时间:5/16/2023 访问量:56

问:

我的表中有以下数据:

编号 订单日期 出院日期
001 1/2/2023 1/2/2023
001 2/27/2023
002 2/14/2023 2/15/2023
002 3/29/2023 4/1/2023
002 7/2/2023
003 4/1/2023
003 5/15/2023

我想添加一个列,该列为任何“出院日期”返回“是”,其中“订单日期”为 30 天,它可以是匹配 ID 的任何订单日期。它应该看起来像这样:

编号 订单日期 出院日期 30天回顾
001 1/2/2023 1/2/2023 是的
001 2/27/2023
002 2/14/2023 2/15/2023 是的
002 3/29/2023 4/1/2023 是的
002 4/18/2023 是的
003 4/1/2023
003 5/15/2023

我试过:

df['30 day review'] = 'No'

for match_id in df['ID'].unique():
    rows = df[df['ID'] == match_id]
    if any(rows['Order Date'] <= rows rows['Discharge Date'] + pd.DateOffset(days=30)):
        df.loc[df['ID'] == match_id, '30 day review'] = 'Yes'

但这给了我一个“是”,对于给定匹配 ID 作为一个组的每一行,而不是将该行出货日期与该 ID 组中的所有可用订单日期进行比较。例如,在我上面的表格中,它将返回 ID 001 中的所有 Yes,而不是在第 1 行中返回 Yes,在第 2 行中返回 No

Python Pandas 日期 筛选器 匹配

评论


答:

1赞 mozway 5/16/2023 #1

用:ffill

df['30 Day Review'] = (
 pd.to_datetime(df['Order Date'], dayfirst=False)
   .groupby(df['ID']).ffill()
   .rsub(pd.to_datetime(df['Discharge Date'], dayfirst=False))
   .le('30D')
)

铌。仅当值已排序且“出院日期”始终晚于组中前一行的“订单日期”时,此解决方案才有效。有关独立于行顺序的可靠解决方案,请参阅带有 merge_asof 的第二种方法。

输出:

   ID Order Date Discharge Date  30 Day Review
0   1   1/2/2023       1/2/2023           True
1   1        NaN      2/27/2023          False
2   2  2/14/2023      2/15/2023           True
3   2  3/29/2023       4/1/2023           True
4   2        NaN       7/2/2023          False
5   3        NaN       4/1/2023          False
6   3        NaN      5/15/2023          False

为/:YesNo

df['30 Day Review'] = np.where(
 pd.to_datetime(df['Order Date'], dayfirst=False)
   .groupby(df['ID']).ffill()
   .rsub(pd.to_datetime(df['Discharge Date'], dayfirst=False))
   .le('30D'),
    'Yes', 'No'
)

输出:

   ID Order Date Discharge Date 30 Day Review
0   1   1/2/2023       1/2/2023           Yes
1   1        NaN      2/27/2023            No
2   2  2/14/2023      2/15/2023           Yes
3   2  3/29/2023       4/1/2023           Yes
4   2        NaN       7/2/2023            No
5   3        NaN       4/1/2023            No
6   3        NaN      5/15/2023            No

使用merge_asof

m = pd.merge_asof(
    df[['ID']].assign(discharge=pd.to_datetime(df['Discharge Date'], dayfirst=False))
              .reset_index().sort_values(by='discharge'),
    df[['ID']].assign(order=pd.to_datetime(df['Order Date'], dayfirst=False))
              .sort_values(by='order').dropna(subset='order'),
    by='ID', left_on='discharge', right_on='order',
    tolerance=pd.Timedelta('30D')
).set_index('index')['order'].notna().reindex(df.index)

df['30 Day Review'] = np.where(m, 'Yes', 'No')

输出:

   ID Order Date Discharge Date 30 Day Review
0   1   1/2/2023       1/2/2023           Yes
1   1        NaN      2/27/2023            No
2   2  2/14/2023      2/15/2023           Yes
3   2  3/29/2023       4/1/2023           Yes
4   2        NaN      4/18/2023           Yes
5   3        NaN       4/1/2023            No
6   3        NaN      5/15/2023            No

评论

0赞 Chris Linke 5/16/2023
我尝试了 merge_asof() 方法,但得到了一个 TypeError:Index(...) 必须使用某种集合调用,“order”被传递了
0赞 Chris Linke 5/16/2023
我能够通过在dropna(subset=['order'])周围添加右括号[]来使merge_asof代码工作。不是 100% 为什么需要这样做,但很高兴让它发挥作用。
0赞 mozway 5/16/2023
@ChrisLinke是的,这是一个 pandas 版本问题,旧版本需要括号,而不是最新版本;)
0赞 Rawson 5/16/2023 #2

您可以使用 ,它允许您根据条件返回一个值或另一个值:numpy.where()

import pandas as pd
import numpy as np

l1 = ["001", "001", "002", "002", "002", "003", "003"]
l2 = ["1/2/2023", None, "2/14/2023", "3/29/2023", None, None, None]
l3 = ["1/2/2023", "2/27/2023", "2/15/2023", "4/1/2023", "7/2/2023", "4/1/2023", "5/15/2023"]

df = pd.DataFrame({"ID": l1, "Order Date": l2, "Discharge Date": l3})
df["Order Date"] = pd.to_datetime(df["Order Date"])
df["Discharge Date"] = pd.to_datetime(df["Discharge Date"])

# group by ID and forward fill all order dates, then fill any that are still "none" as 1/1/1900
# then if the difference between these and the discharge date (in days) is greater than 30
# return "No", else "Yes"
df["30-day review"] = np.where(
    (df["Discharge Date"] - df.groupby("ID")["Order Date"].ffill().fillna(
        pd.Timestamp(1900,1,1))).dt.days > 30,
    "No",  # if greater than 30 days difference
    "Yes")  # if less than or equal to 30 days difference

df
Out[]: 
#    ID Order Date Discharge Date 30-day review
#0  001 2023-01-02     2023-01-02           Yes
#1  001        NaT     2023-02-27            No
#2  002 2023-02-14     2023-02-15           Yes
#3  002 2023-03-29     2023-04-01           Yes
#4  002        NaT     2023-07-02            No
#5  003        NaT     2023-04-01            No
#6  003        NaT     2023-05-15            No

评论

0赞 Rawson 5/16/2023
@mozway的另一个答案几乎完全相同,但计算的语法略有不同。
0赞 mozway 5/16/2023
我实际上认为这种方法并不可靠,我添加了一个更好的替代方案,它应该始终有效。