如何用Python和Pandas基于患者历史预约计算医疗预约爽约率?
问题:计算患者历史预约爽约率
我正在使用巴西医疗预约数据集,该数据集包含巴西医疗预约信息及患者是否到场的记录,字段包括预约ID、患者ID、预约日期时间、挂号日期时间等。我需要为每个预约计算基于该患者此前所有预约的爽约率:例如患者有3次预约,其中2次到场,其第4次预约的爽约率为1/3;首次预约的爽约率为0。
我尝试了以下代码,但它没有正确高效地计算历史预约的爽约率:
# convert appointment and scheduled dates to datetime format df['AppointmentDay'] = pd.to_datetime(df['AppointmentDay']) df['ScheduledDay'] = pd.to_datetime(df['ScheduledDay']) # sort the DataFrame by PatientId and AppointmentDay df = df.sort_values(['PatientId', 'AppointmentDay']) # create a new column with the time difference between scheduled and appointment date df['time_diff'] = (df['AppointmentDay'].dt.date - df['ScheduledDay'].dt.date).dt.days # create a new column to store the no-show rate for each appointment df['no_show_rate'] = 0 # loop through each row of the DataFrame for index, row in df.iterrows(): # get the PatientId and AppointmentDay for the current row patient_id = row['PatientId'] appointment_day = row['AppointmentDay'] # select all previous appointments for the current patient previous_appointments = df.loc[(df['PatientId'] == patient_id) & (df['AppointmentDay'] < appointment_day)] # calculate the no-show rate based on the previous appointments appointment_count = len(previous_appointments) no_show_count = len(previous_appointments.loc[previous_appointments['No-show'] == 'Yes']) if appointment_count > 0: no_show_rate = no_show_count / appointment_count else: no_show_rate = 0 # update the 'no_show_rate' column for the current row df.at[index, 'no_show_rate'] = no_show_rate # print first 5 rows print(df.head())
问题分析
原代码逻辑方向正确,但存在两个核心问题:
- 效率极低:
iterrows()逐行循环处理大数据集时速度极慢,Pandas原生的向量化操作性能远超循环。 - 日期筛选误差:如果
AppointmentDay包含时间戳,AppointmentDay < appointment_day会排除同一天晚于当前预约的记录,但实际上同一天的预约如果是先发生的应该被计入历史(需根据业务逻辑调整,这里默认按日期排序后,同一天的预约按顺序处理)。
修正后的代码
使用Pandas的分组和窗口函数实现高效计算:
import pandas as pd # 转换日期格式并提取日期部分,避免时间戳干扰 df['AppointmentDay'] = pd.to_datetime(df['AppointmentDay']).dt.date df['ScheduledDay'] = pd.to_datetime(df['ScheduledDay']).dt.date # 按患者ID和预约日期排序,确保历史顺序正确 df = df.sort_values(['PatientId', 'AppointmentDay']).reset_index(drop=True) # 将爽约标记转为数值:爽约为1,到场为0 df['no_show_flag'] = (df['No-show'] == 'Yes').astype(int) # 计算每个患者累计爽约数(排除当前预约,用shift(1)滞后一行) df['cumulative_no_shows'] = df.groupby('PatientId')['no_show_flag'].cumsum().shift(1) # 计算每个患者累计预约数(排除当前预约,cumcount从0开始计数) df['cumulative_appointments'] = df.groupby('PatientId').cumcount() # 计算历史爽约率,首次预约爽约率为0 df['no_show_rate'] = df.apply( lambda row: row['cumulative_no_shows'] / row['cumulative_appointments'] if row['cumulative_appointments'] > 0 else 0, axis=1 ) # 可选:删除中间辅助字段 df = df.drop(['no_show_flag', 'cumulative_no_shows', 'cumulative_appointments'], axis=1) print(df.head())
代码说明
- 日期处理:用
dt.date提取日期部分,确保同一天的预约能按排序顺序正确区分历史与当前。 - 爽约标记数值化:将字符串转为整数,方便后续累计计算。
- 累计计算:
cumsum().shift(1):先累计每个患者的爽约数,再将结果向后偏移一行,这样当前行的数值就是之前所有预约的爽约总数。cumcount():对每个患者的预约从0开始计数,数值正好等于当前预约之前的历史预约数量。
- 爽约率计算:通过
apply处理除以0的情况,首次预约时累计数为0,爽约率设为0。
内容的提问来源于stack exchange,提问作者pooya ensafi
相关产品推荐
相关产品推荐

