我有一个事件列表。每个案例(案例 ID)最终都会经历事件(事件 A、事件 B)。我有包含各个事件的时间戳的列。我正在尝试使用 Python 根据以下内容查找事件 A 和事件 B 之间的日期。然而,有些案例不止一次经历事件 a,我需要时间流逝以最近的日期为基础。在下面的示例中,案例 ID 1 的最晚日期为 23 年 1 月 20 日,忽略了此案例 ID 之前两次出现的事件 A。
|案例编号|事件|日期| ——————————————————- |1|A|1/1/23| |1|A|1/3/23| |1|A|1/20/23| |1|B|2/1/23| |8|A|1/2/23| |100|A|23/3/1| |100|B|23/3/2| |35|A|2/13/23| |35|B|2/27/23| |6|A|2/14/23| |33|A|2/26/23| |2|A|23/3/4| |2|B|4/30/23|
我试过按重复项排序,但我使用 Python 不如使用 SQL 舒服,而且我无法使用 SQL。还试图避免手动删除重复项。 我期望的输出如下所示: |caseID|busdaysbweventAB|
您可以使用:
# Convert to datetime64 if needed
df['DATE'] = pd.to_datetime(df['DATE'])
# Pandas part: reshape your dataframe
out = df.sort_values('DATE').pivot_table(index='CaseID', columns='EVENT', values='DATE', aggfunc='last')
m = out.notna().all(axis=1)
# Numpy part: compute business day
arr = out[m].values.astype('datetime64[D]')
out.loc[m, 'bdays'] = np.busday_count(arr[:, 0], arr[:, 1])
输出:
>>> out
EVENT A B bdays
CaseID
1 2023-01-20 2023-02-01 8.0
2 2023-03-04 2023-04-30 40.0
6 2023-02-14 NaT NaN
8 2023-01-02 NaT NaN
33 2023-02-26 NaT NaN
100 2023-03-01 2023-03-02 1.0
您的输入数据采用竖线分隔的 CSV 文件格式。它非常简单,因此不需要任何模块导入来处理它。
建立一个以 CaseID 为关键字的字典。每个关联值都应该是一个可以同时具有“A”和“B”键的字典。日期应该在与这些键关联的列表中。
您需要解析日期以找到最大值(最近的)然后做一些算术。
给定具有以下内容的输入文件 foo.csv:
|CaseID|EVENT|DATE|
|1|A|1/1/23|
|1|A|1/3/23|
|1|A|1/20/23|
|1|B|2/1/23|
|8|A|1/2/23|
|100|A|3/1/23|
|100|B|3/2/23|
|6|A|2/14/23|
|33|A|2/26/23|
|2|A|3/4/23|
|2|B|4/30/23|
...代码可能如下所示:
from datetime import datetime
def parse(d):
return datetime.strptime(d, '%m/%d/%y')
mydict = dict()
with open('foo.csv') as data:
next(data) # skip column headers
for line in data:
_, case, event, date, *_ = line.split('|')
_date = parse(date)
if case in mydict:
mydict[case].setdefault(event, []).append(_date)
else:
mydict[case] = {event: [_date]}
for k, v in mydict.items():
if 'A' in v and 'B' in v:
maxa = max(v['A'])
maxb = max(v['B'])
print('CaseID', k, abs((maxa-maxb).days))
输出:
CaseID 1 12
CaseID 100 1
CaseID 2 57
假设数据如所示(即 caseID 值和 DATE 已排序 - 否则您可以在进一步处理之前进行排序)然后 首先将CSV数据读入pandas DataFrame;然后使用:
#convert date strings to datetime format
df['DATE'] = pd.to_datetime(df['DATE'], dayfirst = False)
#drop duplicated A rows
df = df.drop_duplicates(subset = ['CaseID', 'EVENT'], keep = 'last')
#calculate difference of days
df['days'] = df.groupby('CaseID')['DATE'].diff().dt.days
#and finally lose the case without an A and a B event
df = df.dropna()
结果:
CaseID EVENT DATE days
3 1 B 2023-02-01 12.0
6 100 B 2023-03-02 1.0
10 2 B 2023-04-30 57.0
另一种方法是按事件和 id 分组,获取最大日期,再次按 id 分组并使用 apply 获取日期之间的差异。
from io import StringIO
import pandas as pd
table = """
CaseID,EVENT,DATE
1,A,1/1/23
1,A,1/3/23
1,A,1/20/23
1,B,2/1/23
8,A,1/2/23
100,A,3/1/23
100,B,3/2/23
6,A,2/14/23
33,A,2/26/23
2,A,3/4/23
2,B,4/30/23
"""
df = pd.read_csv(StringIO(table), sep=',')
df['DATE'] = pd.to_datetime(df['DATE'])
d = df.groupby(['CaseID', 'EVENT']).max().reset_index().groupby('CaseID').aggregate({'DATE': lambda x: x.diff().to_numpy()[-1]})
输出:
DATE
CaseID
1 12 days
2 57 days
6 NaT
8 NaT
33 NaT
100 1 days
案例编号 | 活动 | 日期 |
---|---|---|
1 | A | 1/1/23 |
1 | A | 1/3/23 |
1 | A | 1/20/23 |
1 | 乙 | 2/1/23 |
8 | A | 1/2/23 |
100 | A | 3/1/23 |
100 | 乙 | 3/2/23 |
35 | A | 2/13/23 |
35 | 乙 | 2/27/23 |
6 | A | 2/14/23 |
33 | A | 2/26/23 |
2 | A | 3/4/23 |
2 | 乙 | 4/30/23 |
import pandas as pd
def get_time_lapsed(df, case_id, event, date):
df = df.sort_values(by=[case_id, date])
df["time_lapsed"] = df.groupby(case_id)[date].diff()
return df
def test_get_time_lapsed():
df = pd.DataFrame(
{
"CaseID": [1, 1, 1, 1, 8, 100, 100, 35, 35, 6, 33, 2, 2],
"EVENT": ["A", "A", "A", "B", "A", "A", "B", "A", "B", "A", "A", "A", "B"],
"DATE": [
"1/1/23",
"1/3/23",
"1/20/23",
"2/1/23",
"1/2/23",
"3/1/23",
"3/2/23",
"2/13/23",
"2/27/23",
"2/14/23",
"2/26/23",
"3/4/23",
"4/30/23",
],
}
)
df["DATE"] = pd.to_datetime(df["DATE"])
df = get_time_lapsed(df, "CaseID", "EVENT", "DATE")
print(df)
CaseID EVENT DATE time_lapsed
0 1 A 2023-01-01 NaT
1 1 A 2023-01-03 2 days
2 1 A 2023-01-20 17 days
3 1 B 2023-02-01 12 days
11 2 A 2023-03-04 NaT
12 2 B 2023-04-30 57 days
9 6 A 2023-02-14 NaT
4 8 A 2023-01-02 NaT
10 33 A 2023-02-26 NaT
7 35 A 2023-02-13 NaT
8 35 B 2023-02-27 14 days
5 100 A 2023-03-01 NaT
6 100 B 2023-03-02 1 days