引言
在当今数字化时代,数据驱动的决策在各个行业中变得越来越重要。酒店业,作为旅游和休闲服务的核心部分,正面临前所未有的机遇和挑战。随着在线预订平台的兴起,客户行为数据的积累为酒店提供了洞察消费者需求和优化运营策略的宝贵资源。
背景
酒店预订预测是一个关键的业务问题,它涉及到预测在特定时间段内酒店房间的需求量。准确的预测不仅可以帮助酒店更好地管理库存,减少空置率,还可以提高客户满意度和增加收入。然而,由于多种因素如季节性变化、经济状况、特殊事件等的影响,这一预测任务充满了复杂性和不确定性。近年来,机器学习技术的发展为解决这一问题提供了新的视角,通过构建预测模型,我们可以从历史数据中学习并预测未来的预订趋势。
数据特征信息
数据最初来自《酒店预订需求数据集》,由 Nuno Antonio、Ana Almeida 和 Luis Nunes 收集填写,用于 Data in Brief。从出版物(https://www.sciencedirect.com/science/article/pii/S2352340918315191)中我们知道,这两家酒店都位于(南欧)葡萄牙(“Hotel1在阿尔加维度假区,Hotel2在里斯本市”)。这两个地点之间的距离约为 280 公里,两个地点都与北大西洋接壤。该数据包含“2015 年 7 月 1 日至 2017 年 8 月 31 日期间到达的预订”。以下是对该数据集主要特征的归纳:
酒店信息:
- hotel:酒店类型(度假酒店/城市酒店)。
客户信息:
- adults:成年人数量。
- children:儿童数量。
- babies:婴儿数量。
- country:客户的原籍国。
- customer_type:预订类型。
预订信息:
- is_canceled:是否取消预定(这是一个重要的目标变量)。
- lead_time:从预订日期到到达日期之间的天数。
- arrival_date_year:到达酒店的年份。
- arrival_date_month:到达酒店的月份。
- arrival_date_week_number:到达日期的周数。
- arrival_date_day_of_month:抵达日期。
- stays_in_weekend_nights:周末(周六或周日)的入住晚数。
- stays_in_week_nights:工作日(周一至周五)的入住晚数。
- meal:预定的餐食类型(例如,无餐、早餐等)。
- market_segment:市场细分指定。
- distribution_channel:预订的分销渠道。
- is_repeated_guest:预订名称是否来自重复的客人。
- previous_cancellations:客户在当前预订之前取消的先前预订的数量。
- previous_bookings_not_canceled:客户在当前预订之前未取消的先前预订的数量。
- reserved_room_type:保留房间类型的代码。
- assigned_room_type:分配给预订的房间类型的代码。
- booking_changes:从预订到办理入住或取消期间所做的更改数量。
- deposit_type:是否支付押金以保证预订。
- agent:进行预订的旅行社的ID(可能存在缺失值)。
- company:预订或负责付款的公司的ID(可能存在大量缺失值)。
- days_in_waiting_list:在向客户确认之前,预订在等待名单上的天数。
- adr:平均每日房价,定义为所有住宿交易的总和除以总住宿天数。
- required_car_parking_spaces:客户所需的停车位数量。
- total_of_special_requests:客户提出的特殊要求数量。
- reservation_status:预订的最后状态。
- reservation_status_date:设置最后状态的日期。
读入数据
# 导入库
# 导入pandas库,用于数据处理和分析
import pandas as pd
# 导入numpy库,用于数值计算
import numpy as np
# 导入matplotlib.pyplot库,用于绘图
import matplotlib.pyplot as plt
# 导入seaborn库,用于数据可视化(更高级的绘图库)
import seaborn as sns
# 导入missingno库,用于可视化数据中的缺失值
import missingno as msno
# 导入warnings库,用于控制警告信息的显示
import warnings
warnings.filterwarnings('ignore') # 忽略所有的警告信息
# 从sklearn库中导入一些模型选择和评估的模块
from sklearn.model_selection import train_test_split, GridSearchCV # 用于数据分割和网格搜索
# 从sklearn库中导入StandardScaler,用于特征缩放
from sklearn.preprocessing import StandardScaler
# 从sklearn库中导入一些评估指标
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report # 用于评估分类模型
# 从sklearn库中导入一些分类模型
from sklearn.linear_model import LogisticRegression # 逻辑回归
from sklearn.neighbors import KNeighborsClassifier # K近邻分类器
from sklearn.svm import SVC # 支持向量机
from sklearn.tree import DecisionTreeClassifier # 决策树分类器
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
from sklearn.ensemble import AdaBoostClassifier # AdaBoost分类器
from sklearn.ensemble import GradientBoostingClassifier # 梯度提升分类器
from xgboost import XGBClassifier # XGBoost分类器
from catboost import CatBoostClassifier # CatBoost分类器
from sklearn.ensemble import ExtraTreesClassifier # 额外树分类器
from lightgbm import LGBMClassifier # LightGBM分类器
from sklearn.ensemble import VotingClassifier # 投票分类器
# 导入folium库,用于创建交互式地图
import folium
# 从folium库中导入HeatMap插件,用于在地图上绘制热力图
from folium.plugins import HeatMap
# 导入plotly.express库,用于创建交互式图表
import plotly.express as px
# 设置matplotlib的样式为'fivethirtyeight',这是一种常用的绘图样式
plt.style.use('fivethirtyeight')
# %matplotlib inline 是一个魔法命令,它使matplotlib绘制的图形直接显示在Jupyter Notebook中
%matplotlib inline
# 设置pandas的显示选项,使得在Jupyter Notebook中显示更多的列
pd.set_option('display.max_columns', 32)
df = pd.read_csv('hotel_bookings.csv')
df.head()
显示后5行数据,本文原始数据119389行,列名有点多截取不完,我提取出来供大家查看。
描述性统计
部分列有缺失值,我们编写代码让大家看的更清楚一点:
# 检查数据框df中的空值(NaN或None)
# 使用pandas的DataFrame构造函数创建一个新的DataFrame,名为'null'
# 这个新DataFrame包含两列:'Null Values' 和 'Percentage Null Values'
# 'Null Values' 列表示原始数据框df中每一列的空值数量
# 'Percentage Null Values' 列表示原始数据框df中每一列的空值所占的百分比
null = pd.DataFrame({'空值合计' : df.isna().sum(),
'空值占比' : (df.isna().sum()) / (df.shape[0]) * (100)})
null
# 注释各子表达式:
# df.isna() # 对数据框df中的每个元素检查是否为空值(NaN或None),返回一个布尔值DataFrame
# df.isna().sum() # 对每个布尔值列求和,得到每列的空值数量
# df.shape[0] # 获取数据框df的行数
# (df.isna().sum()) / (df.shape[0]) # 计算每列空值数量占所在列的比例
# (df.isna().sum()) / (df.shape[0]) * (100) # 将比例转换为百分比
# 最终,'null' DataFrame将包含两列,一列是每列的空值数量,另一列是每列空值的百分比
看上图缺失值最多的列有112593个缺失值,最少的也有488个。下面我将所有缺失值填充0,再可视化缺失值:
看上图很直观,黑柱上没有白色横条了,说明缺失值已经处理完成。
### 筛选儿童、婴儿、成年人同时为0的行
filter = (df.children == 0) & (df.adults == 0) & (df.babies == 0)
df[filter]
我们筛选儿童、婴儿、成年人同时为0的行,共计180行,预定住房人数是不可能为0的,因此这类数据在后面将删除掉。
删除有问题的数据,可以看到数据现在变为119210行:
客人最多的地方是哪里?
country_wise_guests = df[df['is_canceled'] == 0]['country'].value_counts().reset_index()
country_wise_guests.columns = ['国家', '客人人数']
country_wise_guests
大多数客人来自葡萄牙和欧洲其他国家,下面我们来可视化查看:
# 使用 Plotly Express 的 choropleth 函数来创建一个基于国家的数据可视化地图。
# country_wise_guests 应该是一个包含国家信息和对应“No of guests”数据的 DataFrame。
# locations 参数指定了用于绘制地图的数据中代表国家位置的列(这里假设是 'country' 列)。
# color 参数指定了用于着色地图的数据列(这里假设是 'No of guests' 列)。
# hover_name 参数指定了当鼠标悬停在地图上的某个国家时,将显示哪个列的数据(这里同样是 'country' 列)。
guests_map = px.choropleth(country_wise_guests, locations=country_wise_guests['国家'],
color=country_wise_guests['客人人数'], hover_name=country_wise_guests['国家'])
# 显示 Plotly Express 创建的 choropleth 地图。这将在你的 Jupyter Notebook 或其他支持 Plotly 的环境中直接渲染地图。
guests_map.show()
上图是可以交互的(鼠标指哪显示哪)但博客不能发录屏,我就指个大家看的懂的USA美丽坚和众国,国家名为英文缩写,我就没有处理了,有点费时间,意义也不太大。
# df是原始的数据框,它应该包含'is_canceled'这一列,该列表示订单是否被取消(0表示未取消,非0值表示已取消)。
data = df[df['is_canceled'] == 0]
# 这行代码使用Plotly Express的box函数来创建一个箱线图(box plot)。
# 箱线图是一种用于显示一组数据分散情况资料的统计图。
# 参数解释如下:
# - data_frame:指定要用于绘制箱线图的数据框,这里使用的是前面筛选出来的data。
# - x:指定箱线图中不同类别的列名,这里使用'reserved_room_type'列,表示不同类型的房间预订。
# - y:指定要绘制箱线图的数值列名,这里使用'adr'列,可能表示的是平均每日房价(Average Daily Rate)。
# - color:指定用于区分不同分类的颜色列名,这里使用'hotel'列,可能表示不同的酒店或酒店品牌。
# - template:指定图表的样式模板,这里使用'plotly_dark'模板,为图表提供一个暗色的主题。
px.box(data_frame=data, x='reserved_room_type', y='adr', color='hotel', template='plotly_dark')
两家酒店都有不同的房型和不同的膳食安排。季节性因素也很重要,所以价格差异很大,从图可以看出每个房间的平均价格取决于其类型和标准差。
拓展一下,这个图还可以用于观测异常值,就是四分位法的可视化版。我用鼠标指的哪个点(点的数据在长方形粉框内交互)就是超出范围的值,这个数据的异常值,我们可以理解为旺季涨价或者淡季降价幅度较大的数据。
一年中平均每月房价是多少?
# 一年中平均每月房价是多少?
data_resort = df[(df['hotel'] == 'Resort Hotel') & (df['is_canceled'] == 0)]
data_city = df[(df['hotel'] == 'City Hotel') & (df['is_canceled'] == 0)]
resort_hotel = data_resort.groupby('arrival_date_month')['adr'].mean().reset_index()
city_hotel = data_city.groupby('arrival_date_month')['adr'].mean().reset_index()
display(resort_hotel, city_hotel)
上面代码分别按照度假酒店和城市酒店的房价平均值进行分组统计,生成了两张表,这样看不太好看,我们接着处理,把两长表合并,并重命名列名,让大家能够看的懂。
# 合并两张表格
final_hotel = resort_hotel.merge(city_hotel, on = 'arrival_date_month')
final_hotel.columns = ['月份', '度假酒店', '城市酒店']
final_hotel
这样看是不是好一点,可以对比,但我觉得还有问题,哪个月份是英文,我想按照1、2、3…12月这样顺序来排列,下面我们来点花的,用代码按英文月份升序排列。
import sort_dataframeby_monthorweek as sd
# 定义一个名为 sort_month 的函数,它接受两个参数:df(可能是一个pandas DataFrame)和 column_name(可能是DataFrame中某一列的名字)。
def sort_month(df, column_name):
# 在函数体内,您调用了 sd 模块中的 Sort_Dataframeby_Month 函数,并将 df 和 column_name 作为参数传递给它。
# 注意,这里的函数名 Sort_Dataframeby_Month 与您定义的别名 sd 结合起来,构成了完整的函数引用 sd.Sort_Dataframeby_Month。
# 这个函数可能会对 DataFrame 进行某种基于月份的排序操作,但具体的实现细节取决于 Sort_Dataframeby_Month 函数在 sort_dataframeby_monthorweek 模块中的定义。
return sd.Sort_Dataframeby_Month(df, column_name)
final_prices = sort_month(final_hotel, '月份')
final_prices
初中英文还没忘的同学,可以看出来月份正常了,我们挨着数就行了。
plt.figure(figsize = (17, 8))
px.line(final_prices, x='月份', y=['度假酒店', '城市酒店'],
title='按月统计每晚平均房价趋势图', template='plotly_dark')
我们接着写代码可视化数据,该图清楚地表明,度假酒店的价格在夏季要高得多,而城市酒店的价格变化较小,在春季和秋季最贵。
哪些月份更忙?
下面步骤跟上面类似,我代码揉到一起发,不详细解释了:
resort_guests = data_resort['arrival_date_month'].value_counts().reset_index()
resort_guests.columns = ['月份', '客人人数']
city_guests = data_city['arrival_date_month'].value_counts().reset_index()
city_guests.columns = ['月份', '客人人数']
display(resort_guests, city_guests)
final_guests = resort_guests.merge(city_guests, on='月份')
final_guests.columns = ['月份', '度假酒店人数', '城市酒店人数']
final_guests = sort_month(final_guests,'月份')
final_guests
继续写代码来可视化表格数据,从图可以看出城市酒店在春季和秋季的客人较多,此时价格也最高,7月和8月的游客较少,淡季价格较低。
px.line(final_guests, x = '月份', y = ['度假酒店人数','城市酒店人数'],
title='每月接待客人总数趋势图', template = 'plotly_dark')
今天的绘图都是可以交互的,批哪显哪,我指了城市酒店人数最高的月份,大家可以对比下表格有没有错误。
人们在酒店停留多长时间?
这里我们要增加一个维度,新增一列用于存储工作日+周末的游客人数。
filter = df['is_canceled'] == 0
data = df[filter]
data['total_nights'] = data['stays_in_weekend_nights'] + data['stays_in_week_nights']
data.head(3)
我们只展示3行数据,大家看最后一列,我们新增了一列total_nights。
stay = data.groupby(['total_nights', 'hotel']).agg('count').reset_index()
stay = stay.iloc[:, :3]
stay = stay.rename(columns={'is_canceled': '游客停留天数'})
stay
上面代码计算了游客分别在度假酒店和城市酒店停留的天数。
可视化来查看分布情况:
px.bar(data_frame = stay, x = 'total_nights', y = '游客停留天数', color = 'hotel', barmode = 'group',
template = 'plotly_dark')
上图鼠标指的最高的绿柱显示,工作日和周末3人一组的订房客人总共停留了11889天。最高的组团人数是14人就是最右边的红柱,入住的是度假酒店。
相关性分析
# 数据预处理
# 选择数值列
numeric_columns_simplified = df.select_dtypes(include='number')
# figsize参数用于设置图形窗口的大小,这里设置为宽24英寸,高12英寸。
plt.figure(figsize = (24, 12))
# 假设df是一个Pandas DataFrame对象,这里使用它的corr方法计算DataFrame中所有列之间的皮尔逊相关系数。
# corr方法返回一个DataFrame,其中包含了原始DataFrame中每对列之间的相关系数。
corr = numeric_columns_simplified.corr()
# 导入seaborn库,并为其设置别名sns。Seaborn是一个基于matplotlib的数据可视化库,提供了许多高级接口。
# 这里虽然代码中没有直接导入,但根据上下文,我们假设您已经通过`import seaborn as sns`导入了它。
# 使用seaborn库的heatmap函数来绘制一个热图。
# 热图用于显示数据矩阵中的数值,其中颜色表示数值的大小。
# 这里,我们将上面计算得到的相关系数矩阵corr作为数据输入。
# annot=True表示在每个单元格内显示数值。
# linewidths=1表示每个单元格之间的线条宽度为1。
sns.heatmap(corr, annot = True, linewidths = 1)
# 显示图形窗口。如果没有这行代码,图形可能不会立即显示出来。
plt.show()
我们选取了数值型的数据进行皮尔逊相关系数统计看下图:
是不是看不懂或看不清楚,没关系我今天想了一个办法让大家能看懂。
# 假设df是一个Pandas DataFrame,并且这个DataFrame中包含了一些数值型列,我们想要计算这些列与'is_canceled'列之间的相关性。
# 使用df.corr()方法计算DataFrame中所有数值型列之间的相关系数矩阵。这将返回一个DataFrame,其中索引和列都是原始DataFrame的列名,值是对应列之间的相关系数。
# 我们对'is_canceled'列感兴趣,所以我们通过索引这个列名从相关系数矩阵中获取与'is_canceled'列相关的所有相关系数。这将返回一个Series,其中索引是其他列的名字,值是这些列与'is_canceled'列的相关系数。
# 接下来,我们使用abs()方法计算这些相关系数的绝对值。这样做的原因是相关系数可以是正的(正相关)也可以是负的(负相关),但我们通常只关心相关性的强弱,而不关心是正相关还是负相关。
# 最后,我们使用sort_values()方法对这个Series进行排序,根据相关系数的绝对值从大到小排序。ascending=False表示我们希望得到降序排序的结果。
# 现在,correlation这个Series包含了与'is_canceled'列相关性最强的列(按相关性绝对值排序),我们可以进一步分析或可视化这个结果。
correlation = numeric_columns_simplified.corr()['is_canceled'].abs().sort_values(ascending = False)
correlation
今天我们的预测目标是“is_canceled”列,也就是客人是否取消预订列,上面代码提取了所有跟“is_canceled”相关的系数,最相关的肯定是它自己了是1也是100%相关,第2相关的是“ead_time:从预订日期到到达日期之间的天数。”以此类推大家感兴趣的话对着上面的数据特征对比。
需要注意的是由于便于排序,我将相关系数进行了绝对值处理也就是没有负数(负相关)全是正数。
特征工程
删除无用的列:
# 删除无用的列
useless_col = ['days_in_waiting_list', 'arrival_date_year', 'arrival_date_year', 'assigned_room_type', 'booking_changes',
'reservation_status', 'country', 'days_in_waiting_list']
df.drop(useless_col, axis = 1, inplace = True)
将非数值数据赋值到cat_cols:
# 如果当前列的数据类型(dtype)是字符串类型(在pandas中,字符串通常被表示为'O'即对象类型)
cat_cols = [col for col in df.columns if df[col].dtype == 'O']
cat_cols
将字符串类型的列赋值给cat_df,并展示前5行:
cat_df = df[cat_cols]
cat_df.head()
可以看到最后一列的时间格式为“2015-07-03”,我们现在需要将其变为3列,变为年月日单独。
# 将 DataFrame cat_df 中的 'reservation_status_date' 列转换为 datetime 类型。
# pandas 的 to_datetime 函数可以将字符串或其他类型的数据转换为 datetime 类型,
# 这样我们就可以进行日期相关的操作,比如提取年份、月份等。
cat_df['reservation_status_date'] = pd.to_datetime(cat_df['reservation_status_date'])
# 从 'reservation_status_date' 列中提取年份,并将结果存储在新的 'year' 列中。
# dt 是 datetime 类型的一个属性,它提供了很多日期和时间的访问器,
# 其中 year 就是用来获取年份的。
cat_df['year'] = cat_df['reservation_status_date'].dt.year
# 从 'reservation_status_date' 列中提取月份,并将结果存储在新的 'month' 列中。
# 类似于上面提取年份的方法,month 访问器是用来获取月份的。
cat_df['month'] = cat_df['reservation_status_date'].dt.month
# 从 'reservation_status_date' 列中提取日期(即一个月中的哪一天),
# 并将结果存储在新的 'day' 列中。
# day 访问器就是用来获取一个月中的具体日期的。
cat_df['day'] = cat_df['reservation_status_date'].dt.day
cat_df.drop(['reservation_status_date','arrival_date_month'] , axis = 1, inplace = True)
cat_df.head()
上表新增了三列year,month,day用以储存年、月、日。
# 循环遍历 DataFrame cat_df 中的所有列名
for col in cat_df.columns:
# 使用 f-string 格式化字符串,打印列名
# 在这里,{col} 会被替换为当前循环到的列名
print(f"{col}: \n{cat_df[col].unique()}\n")
# cat_df[col].unique() 会返回该列中所有唯一的值(去重后的值)
# .unique() 方法返回的是一个 NumPy 数组,包含列中所有唯一的元素
# \n 用于在列名和唯一值之间以及每个列的唯一值之后添加换行符,以便输出更清晰
我们编写代码想要知道非数值类型的列的唯一值并打印,后面编码需要:
之前的博文跟大家讲过,电脑是不认识字符串数值的,就算它认识也只是当它是分类数据类型,所以我们下面要做的操作就没有问题了,我们让字符串变成分类的数值。
# 编码分类变量
# 将 'hotel' 列中的 'Resort Hotel' 映射为 0,'City Hotel' 映射为 1
cat_df['hotel'] = cat_df['hotel'].map({'Resort Hotel' : 0, 'City Hotel' : 1})
# 将 'meal' 列中的不同餐食类型映射为从 0 到 4 的整数值
cat_df['meal'] = cat_df['meal'].map({'BB' : 0, 'FB': 1, 'HB': 2, 'SC': 3, 'Undefined': 4})
# 将 'market_segment' 列中的不同市场细分映射为从 0 到 7 的整数值
cat_df['market_segment'] = cat_df['market_segment'].map({'Direct': 0, 'Corporate': 1, 'Online TA': 2, 'Offline TA/TO': 3, 'Complementary': 4, 'Groups': 5, 'Undefined': 6, 'Aviation': 7})
# 将 'distribution_channel' 列中的不同分销渠道映射为从 0 到 4 的整数值
cat_df['distribution_channel'] = cat_df['distribution_channel'].map({'Direct': 0, 'Corporate': 1, 'TA/TO': 2, 'Undefined': 3, 'GDS': 4})
# 将 'reserved_room_type' 列中的不同房间类型映射为从 0 到 8 的整数值
cat_df['reserved_room_type'] = cat_df['reserved_room_type'].map({'C': 0, 'A': 1, 'D': 2, 'E': 3, 'G': 4, 'F': 5, 'H': 6,
'L': 7, 'B': 8})
# 将 'deposit_type' 列中的不同押金类型映射为从 0 到 3 的整数值(注意:'Non Refund' 映射为 3,而不是通常的 2,可能是为了区分 'Refundable' 和 'Non Refund')
cat_df['deposit_type'] = cat_df['deposit_type'].map({'No Deposit': 0, 'Refundable': 1, 'Non Refund': 3})
# 将 'customer_type' 列中的不同客户类型映射为从 0 到 3 的整数值
cat_df['customer_type'] = cat_df['customer_type'].map({'Transient': 0, 'Contract': 1, 'Transient-Party': 2, 'Group': 3})
# 将 'year' 列中的不同年份映射为从 0 到 3 的整数值(这里假设 2015 是最近的年份,因此映射为 0,其他年份按时间顺序递减)
cat_df['year'] = cat_df['year'].map({2015: 0, 2014: 1, 2016: 2, 2017: 3})
cat_df.head()
中文全部编码,变成数值类型的。
# 从原始 DataFrame 'df' 中删除指定的分类列('cat_cols' 应该是一个包含要删除列名的列表)
# 并创建一个新的 DataFrame 'num_df',它只包含非分类的数值列
num_df = df.drop(columns = cat_cols, axis = 1)
# 从 'num_df' DataFrame 中删除名为 'is_canceled' 的列
# 注意:由于使用了 'inplace=True',这个操作会直接修改 'num_df' 而不是返回一个新的 DataFrame
# 因此,'num_df' 在这行代码执行后将不再包含 'is_canceled' 列
num_df.drop('is_canceled', axis = 1, inplace = True)
num_df
# 是为了构建一个仅包含数值特征的子集,以便于后续的机器学习或统计分析
上面代码删除了非数值类型列,还有目标变量,构建用于机器学习的特征数据:
计算每列方差,从方差可以看出部分列的数据是非常离散的。
不太满意,需要处理,对相关列应用对数变换,将偏态分布的数据转换为更接近正态分布的形状。 为了防止对0或负值取对数,对所有数据加1作为偏移量。
# 为了防止对0或负值取对数,通常加1(或其他小的正数)作为偏移量。
num_df['lead_time'] = np.log(num_df['lead_time'] + 1)
# 对 'arrival_date_week_number' 列应用对数变换。同样,这可能是为了改善数据的分布。
# 在这里,我们假设 'arrival_date_week_number' 包含了年份内的周数,因此它应该是正整数。
num_df['arrival_date_week_number'] = np.log(num_df['arrival_date_week_number'] + 1)
# 对 'arrival_date_day_of_month' 列应用对数变换。这可能表示月份中的某天,所以值应该在1到31之间。
# 对数变换可以帮助减少数据中的极端值或异常值的影响。
num_df['arrival_date_day_of_month'] = np.log(num_df['arrival_date_day_of_month'] + 1)
# 对 'agent' 列应用对数变换。这里 'agent' 可能表示处理预订的代理或员工的数量或ID。
# 如果 'agent' 列包含计数数据或ID,并且这些ID与某种频率或重要性相关,对数变换可能有助于揭示这种关系。
num_df['agent'] = np.log(num_df['agent'] + 1)
# 对 'company' 列应用对数变换。这里 'company' 可能表示预订来自的公司或组织。
# 如果 'company' 列包含与公司大小或预订频率相关的数据,对数变换可能有助于揭示这种关系。
# 但请注意,如果 'company' 实际上是一组唯一的标识符(如公司ID),则对数变换可能不适用。
num_df['company'] = np.log(num_df['company'] + 1)
# 对 'adr' 列应用对数变换。'adr' 通常代表平均每日房价(Average Daily Rate),这是一个常见的酒店业指标。
# 对数变换在这里特别有用,因为它可以帮助稳定房价的方差,并可能使数据更接近正态分布。
num_df['adr'] = np.log(num_df['adr'] + 1)
# 重新查看方差
num_df.var()
现在看着就不这么难受了。
adr列有一个缺失值,填充为该列的平均值:
num_df['adr'] = num_df['adr'].fillna(value = num_df['adr'].mean())
num_df.head()
合并处理好的数据,将X作为训练集,y作为测试集:
X = pd.concat([cat_df, num_df], axis=1)
y = df['is_canceled']
X.shape, y.shape
今天我们将70%用于训练,30的数据用于测试(跟以往有点差别):
# 使用 sklearn 的 train_test_split 函数将数据集 X 和标签 y 划分为训练集和测试集
# train_test_split 函数会随机地将数据集划分为两部分,其中一部分用于训练,另一部分用于测试
# 参数 test_size=0.30 指定了测试集应占整个数据集的 30%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30)
X_train.head()
机器学习
逻辑回归:
# 创建一个逻辑回归分类器的实例
lr = LogisticRegression()
# 使用训练数据X_train和对应的标签y_train来训练逻辑回归模型
lr.fit(X_train, y_train)
# 使用训练好的逻辑回归模型对测试数据X_test进行预测,得到预测结果y_pred_lr
y_pred_lr = lr.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_lr与真实标签y_test之间的准确率
acc_lr = accuracy_score(y_test, y_pred_lr)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵,该矩阵表示真实标签与预测标签之间的对应关系
conf = confusion_matrix(y_test, y_pred_lr)
# 使用sklearn.metrics中的classification_report函数生成分类报告,该报告包含了每个类别的精确度、召回率、F1分数等信息
clf_report = classification_report(y_test, y_pred_lr)
# 打印逻辑回归的准确率
print(f"Accuracy Score of Logistic Regression is : {acc_lr}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
KNN:
# KNN
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
y_pred_knn = knn.predict(X_test)
acc_knn = accuracy_score(y_test, y_pred_knn)
conf = confusion_matrix(y_test, y_pred_knn)
clf_report = classification_report(y_test, y_pred_knn)
print(f"Accuracy Score of KNN is : {acc_knn}")
print(f"Confusion Matrix : \n{conf}")
print(f"Classification Report : \n{clf_report}")
决策树:
# 创建一个DecisionTreeClassifier的实例,使用默认参数(如最大深度、最小样本分割等)
dtc = DecisionTreeClassifier()
# 使用训练数据X_train和对应的标签y_train来训练决策树分类器
dtc.fit(X_train, y_train)
# 使用训练好的决策树分类器对测试数据X_test进行预测,得到预测结果y_pred_dtc
y_pred_dtc = dtc.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_dtc与真实标签y_test之间的准确率
acc_dtc = accuracy_score(y_test, y_pred_dtc)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵,该矩阵表示真实标签与预测标签之间的对应关系
conf = confusion_matrix(y_test, y_pred_dtc)
# 使用sklearn.metrics中的classification_report函数生成分类报告,该报告包含了每个类别的精确度、召回率、F1分数等信息
clf_report = classification_report(y_test, y_pred_dtc)
# 打印决策树的准确率
print(f"Accuracy Score of Decision Tree is : {acc_dtc}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
随机森林:
# 随机森林分类器
# 创建一个RandomForestClassifier的实例,使用默认参数(如树的数量、树的深度、最小样本分割等)
rd_clf = RandomForestClassifier()
# 使用训练数据X_train和对应的标签y_train来训练随机森林分类器
rd_clf.fit(X_train, y_train)
# 使用训练好的随机森林分类器对测试数据X_test进行预测,得到预测结果y_pred_rd_clf
y_pred_rd_clf = rd_clf.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_rd_clf与真实标签y_test之间的准确率
acc_rd_clf = accuracy_score(y_test, y_pred_rd_clf)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵,该矩阵表示真实标签与预测标签之间的对应关系
conf = confusion_matrix(y_test, y_pred_rd_clf)
# 使用sklearn.metrics中的classification_report函数生成分类报告,该报告包含了每个类别的精确度、召回率、F1分数等信息
clf_report = classification_report(y_test, y_pred_rd_clf)
# 打印随机森林的准确率
print(f"Accuracy Score of Random Forest is : {acc_rd_clf}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
Ada Boost:
# Ada Boost 分类器
# AdaBoostClassifier是一个集成学习算法,它使用AdaBoost(自适应提升)算法将多个弱分类器(如决策树)组合成一个强分类器
ada = AdaBoostClassifier()
# 使用训练数据X_train和对应的标签y_train来训练AdaBoost分类器
# AdaBoost算法会根据每个弱分类器的性能动态地调整它们的权重,以便在最终的分类决策中给予更准确的分类器更大的权重
ada.fit(X_train, y_train)
# 使用训练好的AdaBoost分类器对测试数据X_test进行预测,得到预测结果y_pred_ada
y_pred_ada = ada.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_ada与真实标签y_test之间的准确率
acc_ada = accuracy_score(y_test, y_pred_ada)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
# 混淆矩阵表示真实标签与预测标签之间的对应关系,可以帮助我们了解分类器在各个类别上的表现
conf = confusion_matrix(y_test, y_pred_ada)
# 使用sklearn.metrics中的classification_report函数生成分类报告
# 分类报告包含了每个类别的精确度、召回率、F1分数等信息,可以更全面地评估分类器的性能
clf_report = classification_report(y_test, y_pred_ada)
# 打印AdaBoost分类器的准确率
print(f"Accuracy Score of Ada Boost Classifier is : {acc_ada}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
梯度提升:
# 梯度提升分类器
# GradientBoostingClassifier是一个集成学习算法,它使用梯度提升框架来组合多个弱学习器(通常是决策树)
gb = GradientBoostingClassifier()
# 使用训练数据X_train和对应的标签y_train来训练梯度提升分类器
# 在训练过程中,梯度提升会依次训练多个弱学习器,并尝试通过最小化损失函数的梯度来改进模型的预测性能
gb.fit(X_train, y_train)
# 使用训练好的梯度提升分类器对测试数据X_test进行预测,得到预测结果y_pred_gb
y_pred_gb = gb.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_gb与真实标签y_test之间的准确率
acc_gb = accuracy_score(y_test, y_pred_gb)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
# 混淆矩阵表示真实标签与预测标签之间的对应关系,可以帮助我们了解分类器在各个类别上的表现
conf = confusion_matrix(y_test, y_pred_gb)
# 使用sklearn.metrics中的classification_report函数生成分类报告
# 分类报告包含了每个类别的精确度、召回率、F1分数等信息,可以更全面地评估分类器的性能
clf_report = classification_report(y_test, y_pred_gb)
# 打印梯度提升分类器的准确率,但这里有一个小错误:输出文本写的是"Ada Boost Classifier",而实际上应该是"Gradient Boosting Classifier"
print(f"Accuracy Score of Ada Boost Classifier is : {acc_gb}") # 应该改为:print(f"Accuracy Score of Gradient Boosting Classifier is : {acc_gb}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
XgBoost:
# XgBoost 分类器
# XGBClassifier是XGBoost库提供的分类器,它基于梯度提升决策树(Gradient Boosting Decision Tree)
xgb = XGBClassifier(booster = 'gbtree', learning_rate = 0.1, max_depth = 5, n_estimators = 180)
# 设置XGBoost分类器的参数
# booster='gbtree' 指定使用基于树的模型
# learning_rate=0.1 设置学习率,即每次迭代时权重更新的步长
# max_depth=5 设置树的最大深度
# n_estimators=180 设置弱学习器的数量(树的数量)
# 使用训练数据X_train和对应的标签y_train来训练XGBoost分类器
xgb.fit(X_train, y_train)
# 使用训练好的XGBoost分类器对测试数据X_test进行预测,得到预测结果y_pred_xgb
y_pred_xgb = xgb.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_xgb与真实标签y_test之间的准确率
acc_xgb = accuracy_score(y_test, y_pred_xgb)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
# 混淆矩阵表示真实标签与预测标签之间的对应关系,可以帮助我们了解分类器在各个类别上的表现
conf = confusion_matrix(y_test, y_pred_xgb)
# 使用sklearn.metrics中的classification_report函数生成分类报告
# 分类报告包含了每个类别的精确度、召回率、F1分数等信息,可以更全面地评估分类器的性能
clf_report = classification_report(y_test, y_pred_xgb)
# 打印XGBoost分类器的准确率
print(f"Accuracy Score of Ada Boost Classifier is : {acc_xgb}") # 应该改为:print(f"Accuracy Score of XGBoost Classifier is : {acc_xgb}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
Cat Boost:
# Cat Boost 分级机
# CatBoostClassifier是CatBoost库提供的分类器,它基于梯度提升算法,并特别考虑了类别型特征
cat = CatBoostClassifier(iterations=100)
# 初始化CatBoost分类器,并设置iterations参数为100
# iterations参数决定了模型将进行多少轮的梯度提升迭代
# 使用训练数据X_train和对应的标签y_train来训练CatBoost分类器
cat.fit(X_train, y_train)
# 训练完成后,分类器将学习数据中的模式,并准备进行预测
# 使用训练好的CatBoost分类器对测试数据X_test进行预测,得到预测结果y_pred_cat
y_pred_cat = cat.predict(X_test)
# predict方法将返回测试数据X_test的预测标签
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_cat与真实标签y_test之间的准确率
acc_cat = accuracy_score(y_test, y_pred_cat)
# 准确率是衡量分类器性能的一个简单指标,它计算了预测正确的样本占总样本的比例
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
conf = confusion_matrix(y_test, y_pred_cat)
# 混淆矩阵是一个表格,展示了真实类别与预测类别之间的交叉计数
# 它可以帮助我们了解分类器在各类别上的性能
# 使用sklearn.metrics中的classification_report函数生成分类报告
clf_report = classification_report(y_test, y_pred_cat)
# 分类报告提供了每个类别的精确度、召回率、F1分数等详细信息
# 这些指标可以更加详细地评估分类器的性能
# 打印CatBoost分类器的准确率
# 这里有一个小错误:输出文本写的是"Ada Boost Classifier",而实际上应该是"CatBoost Classifier"
print(f"Accuracy Score of Ada Boost Classifier is : {acc_cat}") # 应该改为:print(f"Accuracy Score of CatBoost Classifier is : {acc_cat}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
额外树:
# 额外树分类器
# ExtraTreesClassifier是scikit-learn库中的一个基于决策树的集成分类器,它使用随机森林的变种——极度随机树
etc = ExtraTreesClassifier()
# 使用训练数据X_train和对应的标签y_train来训练ExtraTreesClassifier
etc.fit(X_train, y_train)
# 训练完成后,使用训练好的模型对测试数据X_test进行预测,并将预测结果存储在y_pred_etc中
y_pred_etc = etc.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_etc与真实标签y_test之间的准确率
acc_etc = accuracy_score(y_test, y_pred_etc)
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
# 混淆矩阵可以帮助我们了解分类器在各个类别上的性能,比如真阳性、真阴性、假阳性和假阴性
conf = confusion_matrix(y_test, y_pred_etc)
# 使用sklearn.metrics中的classification_report函数生成分类报告
# 分类报告提供了每个类别的精确度、召回率、F1分数和样本数等信息
clf_report = classification_report(y_test, y_pred_etc)
# 打印ExtraTreesClassifier的准确率
# 这里有一个小错误:输出文本写的是"Ada Boost Classifier",而实际上应该是"ExtraTrees Classifier"
print(f"Accuracy Score of Ada Boost Classifier is : {acc_etc}") # 应该改为:print(f"Accuracy Score of ExtraTrees Classifier is : {acc_etc}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
LightGBM:
投票分类器:
# 投票分类器
# 注意:这里假设gb, cat, xgb, dtc, etc, lgbm, rd_clf, ada, lr, knn都是之前已经定义并训练好的分类器实例。
classifiers = [
('Gradient Boosting Classifier', gb),
('Cat Boost Classifier', cat),
('XGboost', xgb),
('Decision Tree', dtc),
('Extra Tree', etc),
('Light Gradient', lgbm),
('Random Forest', rd_clf),
('Ada Boost', ada),
('Logistic', lr),
('Knn', knn)
]
# 导入VotingClassifier类(假设已经通过from sklearn.ensemble import VotingClassifier导入了该类)
# VotingClassifier是一个元分类器,它基于一组基分类器的投票(或软投票)来进行分类预测。
# 使用VotingClassifier类,并传入之前定义的分类器列表作为estimators参数
vc = VotingClassifier(estimators=classifiers)
# 使用训练数据X_train和对应的标签y_train来训练VotingClassifier
vc.fit(X_train, y_train)
# 训练完成后,使用训练好的VotingClassifier对测试数据X_test进行预测,并将预测结果存储在y_pred_vc中
y_pred_vc = vc.predict(X_test)
# 使用sklearn.metrics中的accuracy_score函数计算预测结果y_pred_vc与真实标签y_test之间的准确率
acc_vtc = accuracy_score(y_test, y_pred_vc)
# 计算准确率后,将其存储在acc_vtc变量中
# 使用sklearn.metrics中的confusion_matrix函数计算混淆矩阵
conf = confusion_matrix(y_test, y_pred_vc)
# 混淆矩阵是一个二维表格,展示了模型预测结果与真实结果之间的对比情况
# 使用sklearn.metrics中的classification_report函数生成分类报告
clf_report = classification_report(y_test, y_pred_vc)
# 分类报告详细列出了每个类别的精确度、召回率、F1分数和支持数
# 打印VotingClassifier的准确率
# 这里有一个小错误:输出文本写的是"Ada Boost Classifier",而实际上应该是"Voting Classifier"
print(f"Accuracy Score of Ada Boost Classifier is : {acc_vtc}") # 应该改为:print(f"Accuracy Score of Voting Classifier is : {acc_vtc}")
# 打印混淆矩阵
print(f"Confusion Matrix : \n{conf}")
# 打印分类报告
print(f"Classification Report : \n{clf_report}")
好了模型搞完了,我们现在构建一个表格一列为算法名,一列为预测准确率:
models = pd.DataFrame({
'Model' : ['Logistic Regression', 'KNN', 'Decision Tree Classifier', 'Random Forest Classifier','Ada Boost Classifier',
'Gradient Boosting Classifier', 'XgBoost', 'Cat Boost', 'Extra Trees Classifier', 'LGBM', 'Voting Classifier'
],
'Score' : [acc_lr, acc_knn, acc_dtc, acc_rd_clf, acc_ada, acc_gb, acc_xgb, acc_cat, acc_etc, acc_lgbm, acc_vtc]
})
models.sort_values(by = 'Score', ascending = False)
可视化一下预测结果准确率:
px.bar(data_frame = models, x = 'Score', y = 'Model', color = 'Score', template = 'plotly_dark', title = '模型预测准确率横向柱状图')
这篇内容有点多,慢慢消化吧!
点赞、评论、收藏是我创作的动力。