因果推断与机器学习结合:探索酒店预订取消的影响因素

机器学习算法大数据

picture.image

✨ 欢迎关注Python机器学习AI ✨

本节介绍:

因果推断与机器学习结合:探索酒店预订取消的影响因素。数据采用模拟数据,作者根据个人对机器学习的理解进行代码实现与图表输出,细节并不保证与原文一定相同,仅供参考。

详细数据和代码将在稍后上传至交流群,付费群成员可在交流群中获取下载。需要的朋友可关注公众文末提供的购买方式。

购买前请咨询,避免不必要的问题。

✨ 代码实现 ✨

✨ 数据整理 ✨


          
import pandas as pd
          
dataset = pd.read_excel("hotels.xlsx") 
          
# 显示数据
          
dataset.head()
      

picture.image


          
# 计算总住宿天数(工作日和周末天数之和)
          
dataset['total_stay'] = dataset['stays_in_week_nights'] + dataset['stays_in_weekend_nights']
          
# 计算总人数(成人、儿童和婴儿之和)
          
dataset['guests'] = dataset['adults'] + dataset['children'] + dataset['babies']
          
# 创建“不同房间分配”特征
          
dataset['different_room_assigned'] = 0
          
slice_indices = dataset['reserved_room_type'] != dataset['assigned_room_type']
          
dataset.loc[slice_indices, 'different_room_assigned'] = 1
          
# 删除不再需要的旧特征
          
dataset = dataset.drop(['stays_in_week_nights', 'stays_in_weekend_nights', 'adults', 'children', 'babies',                                  'reserved_room_type', 'assigned_room_type'], axis=1)                        
      

          
# 检查数据集中的缺失值
          
dataset.isnull().sum()  # 'Country', 'Agent', 'Company' 这三列分别包含 488, 16340, 112593 个缺失值
          
# 删除包含过多缺失值的列('agent' 和 'company')
          
dataset = dataset.drop(['agent', 'company'], axis=1)
          
# 用出现频率最高的国家填充缺失的 'country' 列
          
dataset['country'] = dataset['country'].fillna(dataset['country'].mode()[0])
      

          
# 删除 'reservation_status', 'reservation_status_date' 和 'arrival_date_day_of_month' 列
          
dataset = dataset.drop(['reservation_status', 'reservation_status_date', 'arrival_date_day_of_month'], axis=1)
          
# 删除 'arrival_date_year' 列
          
dataset = dataset.drop(['arrival_date_year'], axis=1)
          
# 删除 'distribution_channel' 列
          
dataset = dataset.drop(['distribution_channel'], axis=1)
      

          
# 将 'different_room_assigned' 列中的 1 替换为 True,0 替换为 False
          
dataset['different_room_assigned'] = dataset['different_room_assigned'].replace(1, True)
          
dataset['different_room_assigned'] = dataset['different_room_assigned'].replace(0, False)
          
# 将 'is_canceled' 列中的 1 替换为 True,0 替换为 False
          
dataset['is_canceled'] = dataset['is_canceled'].replace(1, True)
          
dataset['is_canceled'] = dataset['is_canceled'].replace(0, False)
          
# 删除数据集中的所有缺失值
          
dataset.dropna(inplace=True)
          
# 打印列名
          
print(dataset.columns)
          
# 查看数据集的第 5 到第 20 列的前 100 行
          
dataset.iloc[:, 5:20].head(100)
      

picture.image

创建一些新的有意义的特征,以减少数据集的维度。

总住宿天数 = stays_in_weekend_nights + stays_in_week_nights

客人数量 = adults + children + babies

是否分配了不同的房间 = 如果reserved_room_type与assigned_room_type不同,则为1,否则为0

删除了包含NULL值或具有过多唯一值的其他列(例如,代理ID)。还用出现频率最高的国家来填补缺失的值。由于与.country、distribution_channel和market_segment有很高的重叠,我们将其删除

由于取消次数和分配不同房间的次数严重失衡,首先随机选择1000个观察值,查看在多少情况下变量‘is_cancelled’和‘different_room_assigned’具有相同的值。然后,这个过程重复10000次,预期结果接近50%(即这两个变量随机取得相同值的概率)。因此,从统计学角度来看,在这个阶段无法得出明确的结论。因此,分配与客户之前预定时不同的房间,可能会导致他/她取消预订,也可能不会


          
# 初始化计数总和为 0
          
counts_sum = 0
          

          
# 进行 10000 次迭代
          
for i in range(1, 10000):
          
    counts_i = 0
          
    # 从数据集中随机抽取 1000 条样本
          
    rdf = dataset.sample(1000)
          
    # 计算 'is_canceled' 和 'different_room_assigned' 相等的行数
          
    counts_i = rdf[rdf["is_canceled"] == rdf["different_room_assigned"]].shape[0]
          
    # 累加到计数总和
          
    counts_sum += counts_i
          

          
# 计算 10000 次实验的平均值
          
counts_sum / 10000
      

picture.image

现在,考虑没有预订更改的场景,并重新计算预期计数


          
# 计算没有预订更改时的预期计数
          
counts_sum = 0
          

          
# 进行 10000 次迭代
          
for i in range(1, 10000):
          
    counts_i = 0
          
    # 从数据集中选择 'booking_changes' 等于 0 的行,并随机抽取 1000 条样本
          
    rdf = dataset[dataset["booking_changes"] == 0].sample(1000)
          
    # 计算 'is_canceled' 和 'different_room_assigned' 相等的行数
          
    counts_i = rdf[rdf["is_canceled"] == rdf["different_room_assigned"]].shape[0]
          
    # 累加到计数总和
          
    counts_sum += counts_i
          

          
# 计算 10000 次实验的平均值
          
counts_sum / 10000
      

picture.image

在第二种情况下,采用存在预订更改 (>0) 的场景,并重新计算预期计数


          
# 计算有预订更改时的预期计数 = 66.4%
          
counts_sum = 0
          

          
# 进行 10000 次迭代
          
for i in range(1, 10000):
          
    counts_i = 0
          
    # 从数据集中选择 'booking_changes' 大于 0 的行,并随机抽取 1000 条样本
          
    rdf = dataset[dataset["booking_changes"] > 0].sample(1000)
          
    # 计算 'is_canceled' 和 'different_room_assigned' 相等的行数
          
    counts_i = rdf[rdf["is_canceled"] == rdf["different_room_assigned"]].shape[0]
          
    # 累加到计数总和
          
    counts_sum += counts_i
          

          
# 计算 10000 次实验的平均值
          
counts_sum / 10000
      

picture.image

当预订更改的数量不为零时,肯定会发生一些变化。因此,它暗示了 Booking Changes 可能会影响客房取消。

但是 Booking Changes 是唯一令人困惑的变量吗?如果有一些未观察到的混杂因素,的数据集中没有关于这些信息(特征)怎么办。还能像以前一样提出索赔吗?

✨ 使用 DoWhy 估计因果效应

步骤 1.创建因果图

以下是关于预测建模问题的假设,并将其转换为因果图(Causal Diagram)的列表:

  • 市场细分有两个级别,“TA”指的是“旅行代理商”,而“TO”指的是“旅游运营商”,因此它会影响提前时间(Lead Time)(即预订和到达之间的天数)。
  • 国家也会在决定一个人是否提前预定(因此更长的提前时间)以及他/她偏好哪种餐饮类型上发挥作用。
  • 提前时间(Lead Time)无疑会影响等待名单天数(Days in Waitlist)(如果你晚些预定,找到空房的机会较小)。此外,更长的提前时间也可能导致取消预订(Cancellations)。
  • 等待名单天数、总住宿天数和客人数量可能会影响预订是否被取消或保留。
  • 过去的预订保留记录会影响一个客户是否会保留预订。此外,这两个变量都会影响预订是否会被取消(例如:一个过去保留了五次预订的客户,这次也更有可能保留;同样,曾经取消过预订的客户,这次也更可能再次取消)。
  • 预订变更(Booking Changes)会影响客户是否被分配到不同的房间,这也可能导致取消预订。
  • 最后,预订变更作为唯一影响处理和结果(Treatment and Outcome)的变量是不太可能的,可能存在一些未观察到的混杂因素(Unobserved Confounders),关于这些因素,我们的数据中没有任何信息。

          
import pygraphviz
          
causal_graph = """digraph {
          
different_room_assigned[label="Different Room Assigned"];
          
is_canceled[label="Booking Cancelled"];
          
booking_changes[label="Booking Changes"];
          
previous_bookings_not_canceled[label="Previous Booking Retentions"];
          
days_in_waiting_list[label="Days in Waitlist"];
          
lead_time[label="Lead Time"];
          
market_segment[label="Market Segment"];
          
country[label="Country"];
          
U[label="Unobserved Confounders",observed="no"];
          
is_repeated_guest;
          
total_stay;
          
guests;
          
meal;
          
hotel;
          
U->{different_room_assigned,required_car_parking_spaces,guests,total_stay,total_of_special_requests};
          
market_segment -> lead_time;
          
lead_time->is_canceled; country -> lead_time;
          
different_room_assigned -> is_canceled;
          
country->meal;
          
lead_time -> days_in_waiting_list;
          
days_in_waiting_list ->{is_canceled,different_room_assigned};
          
previous_bookings_not_canceled -> is_canceled;
          
previous_bookings_not_canceled -> is_repeated_guest;
          
is_repeated_guest -> {different_room_assigned,is_canceled};
          
total_stay -> is_canceled;
          
guests -> is_canceled;
          
booking_changes -> different_room_assigned; booking_changes -> is_canceled;
          
hotel -> {different_room_assigned,is_canceled};
          
required_car_parking_spaces -> is_canceled;
          
total_of_special_requests -> {booking_changes,is_canceled};
          
country->{hotel, required_car_parking_spaces,total_of_special_requests};
          
market_segment->{hotel, required_car_parking_spaces,total_of_special_requests};
          
}"""
          

          
# 创建 DoWhy 因果模型
          
model = dowhy.CausalModel(
          
    data = dataset,  # 使用的数据集
          
    graph = causal_graph.replace("\n", " "),  # 将因果图中的换行符替换为空格,确保图形格式正确
          
    treatment = "different_room_assigned",  # 因果模型的处理变量(即干预变量)
          
    outcome = 'is_canceled'  # 因果模型的结果变量(即输出变量)
          
)
          

          
# 可视化模型
          
model.view_model()
          

          
# 显示生成的因果模型图
          
from IPython.display import Image, display
          
display(Image(filename="causal_model.png"))
      

picture.image

步骤 2.确定因果效应

处理(Treatment)导致结果(Outcome),如果改变处理会导致结果发生变化,同时保持其他因素不变。因此,在这一步中,通过利用因果图的属性,识别出需要估计的因果效应


          
# 确定因果效应
          
identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
          

          
# 打印已识别的因果效应估算
          
print(identified_estimand)
      

picture.image

步骤 3.估计已识别的估计值和


          
# 使用 backdoor 方法和倾向评分加权法来估算因果效应
          
estimate = model.estimate_effect(identified_estimand,
          
                                 method_name="backdoor.propensity_score_weighting",  # 使用 backdoor 方法和倾向评分加权
          
                                 target_units="ate")  # 目标单位为 ATE(平均处理效应)
          

          
# 打印估算结果
          
# ATE = 平均处理效应
          
# ATT = 处理组的平均处理效应(即那些分配到不同房间的人)
          
# ATC = 对照组的平均处理效应(即那些没有分配到不同房间的人)
          
print(estimate)
      

picture.image

估算的平均处理效应(ATE)值为 -0.262。这个结果意味着,如果分配不同房间(different_room_assigned),则取消预订的概率会减少约 26.2%。负值表示分配不同房间可能降低取消预订的几率。从因果效应的估算来看,分配不同的房间(different_room_assigned)似乎会减少取消预订的概率(is_canceled)。这个结果与之前的关联分析中发现的正相关不同,表明可能存在其他机制或未观察到的变量在起作用。例如,分配不同房间可能只发生在房间不可用时,或者发生在入住时,可能会影响顾客的体验,降低取消的概率。

结果令人惊讶。这意味着分配不同的房间会减少取消预订的概率。这里有更多的内容需要解读:这是正确的因果效应吗?是否可能是因为只有在预定的房间不可用时才会分配不同的房间,因此分配不同的房间实际上对顾客有积极影响(与不分配房间相比)?

也许还有其他机制在起作用。或许,只有在入住时才会分配不同的房间,而一旦顾客已经到达酒店,取消预订的概率就会很低?在这种情况下,图中缺少了一个关键变量,即这些事件发生的时间。是否大部分发生在预订当天?知道这个变量可以帮助改善图形和我们的分析。

虽然之前的关联分析显示了不同房间分配与取消预订之间的正相关,但使用 DoWhy 估算因果效应则呈现出不同的图景。它暗示,减少酒店中不同房间分配的决策或政策可能适得其反。

步骤 4.反驳结果


          
# 使用随机共同原因方法来反驳因果估算
          
refute1_results = model.refute_estimate(identified_estimand, estimate,
          
        method_name="random_common_cause")  # 通过引入随机共同原因来测试因果估算的稳定性
          

          
# 打印反驳结果
          
print(refute1_results)  # 输出反驳结果,查看因果估算是否受到随机共同原因的影响
      

picture.image

由于添加随机共同原因后,因果估算几乎没有变化,并且 p 值为 1.0,说明我们的原始假设和因果估算是可靠的。换句话说,随机共同原因的加入没有显著影响估算结果,因此可以认为我们的假设是正确的,且因果效应的结果是稳定的。


          
# 使用安慰剂处理反驳器来反驳因果估算
          
refute2_results = model.refute_estimate(identified_estimand, estimate,
          
        method_name="placebo_treatment_refuter")  # 随机将一个协变量作为处理,并重新估算
          

          
# 打印反驳结果
          
print(refute2_results)  # 输出反驳结果,查看安慰剂处理是否导致因果估算接近 0
      

picture.image

在使用安慰剂处理后,新的估算结果显示了一个显著不同的因果效应(正值),而且 p 值为 0。这表明,原始因果估算可能是受到某些假设的影响或误导,因为如果我们的假设是正确的,使用随机协变量(安慰剂处理)进行分析时应该得到一个接近于零的结果。因此,新的估算结果与原始结果的显著差异可能暗示着我们最初的因果假设存在一定问题或其他潜在的混杂因素影响了我们的估算。


          
# 使用数据子集反驳器来反驳因果估算
          
refute3_results = model.refute_estimate(identified_estimand, estimate,
          
        method_name="data_subset_refuter")  # 创建数据子集并检查因果估算的变化
          

          
# 打印反驳结果
          
print(refute3_results)  # 输出反驳结果,查看因果估算在不同子集中的变化情况
      

picture.image

在使用数据子集进行估算后,因果估算几乎没有变化(新的效应与原始效应非常接近)。此外,p 值接近 1,表示因果估算在不同子集中的变化是非常小的,差异不显著。这表明我们的假设是稳健的,原始的因果效应估算在不同的数据子集上是稳定的,没有受到子集变化的显著影响。

随机共同原因测试 ✅ 通过

数据子集测试 ✅ 通过

安慰剂处理测试 ❌ 未通过

由于安慰剂处理测试未通过,说明原始因果估算可能存在问题,或者某些关键假设不成立。

虽然 Method-2 显示了潜在的问题,但在整体上,结果表明因果估算大致上是可靠的,尤其是在 Method-1 和 Method-3 中的稳定性表明,模型的稳健性得到了验证。

总的来说,尽管 Method-2 显示了异常,我们仍然可以说模型通过了大部分反驳测试,增加了对因果估算结果的信心。

✨ 该文章案例 ✨

picture.image

在上传至交流群的文件中,像往期文章一样,将对案例进行逐步分析,确保读者能够达到最佳的学习效果。内容都经过详细解读,帮助读者深入理解模型的实现过程和数据分析步骤,从而最大化学习成果。

同时,结合提供的免费AI聚合网站进行学习,能够让读者在理论与实践之间实现融会贯通,更加全面地掌握核心概念。

✨ 购买介绍 ✨

本节介绍到此结束,有需要学习数据分析和Python机器学习相关的朋友欢迎到 淘宝店铺:Python机器学习AI,或添加作者微信deep_ML联系 ,购买作者的公众号合集。截至目前为止,合集已包含200多篇文章,购买合集的同时,还将提供免费稳定的AI大模型使用,包括但不限于ChatGPT、Deepseek、Claude等。

更新的内容包含数据、代码、注释和参考资料。

作者仅分享案例项目,不提供额外的答疑服务。项目中将提供详细的代码注释和丰富的解读,帮助您理解每个步骤

购买前请咨询,避免不必要的问题。

✨ 群友反馈 ✨

picture.image

✨ 淘宝店铺 ✨

picture.image

请大家打开淘宝扫描上方的二维码,进入店铺,获取更多Python机器学习和AI相关的内容,或者添加作者微信 deep_ML联系

避免淘宝客服漏掉信息

,希望能为您的学习之路提供帮助!

往期推荐

GeoShapley算法:基于地理数据的Shapley值在空间效应测量中的应用——位置重要性与特征交互作用分析

期刊配图:基于‘UpSet图’展示不同数据预处理对模型性能的影响

期刊配图:结合残差分析的模型预测性能可视化

J Clean Prod:结合K-means聚类确定样本分组方式再结合shap初步解释模型的模拟实现

文献配图:如何通过雷达图全面评估机器学习模型的预测性能

nature communications:结合LightGBM特征选择与RF模型的机器学习方法及SHAP解释

期刊配图:SHAP特征重要性与相关系数的联合可视化

期刊配图:结合lightgbm回归模型与K折交叉验证的特征筛选可视化

Nature新算法:准确的小数据预测与表格基础模型TabPFN分类实现及其模型解释

Nature新算法:准确的小数据预测与表格基础模型TabPFN回归实现及其模型解释

picture.image

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论