引言
在當今數字化時代,數據驅動的決策在各個行業中變得越來越重要。酒店業,作為旅游和休閑服務的核心部分,正面臨前所未有的機遇和挑戰。隨著在線預訂平臺的興起,客戶行為數據的積累為酒店提供了洞察消費者需求和優化運營策略的寶貴資源。
背景
酒店預訂預測是一個關鍵的業務問題,它涉及到預測在特定時間段內酒店房間的需求量。準確的預測不僅可以幫助酒店更好地管理庫存,減少空置率,還可以提高客戶滿意度和增加收入。然而,由于多種因素如季節性變化、經濟狀況、特殊事件等的影響,這一預測任務充滿了復雜性和不確定性。近年來,機器學習技術的發展為解決這一問題提供了新的視角,通過構建預測模型,我們可以從歷史數據中學習并預測未來的預訂趨勢。
數據特征信息
數據最初來自《酒店預訂需求數據集》,由 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 = '模型預測準確率橫向柱狀圖')
這篇內容有點多,慢慢消化吧!
點贊、評論、收藏是我創作的動力。