某酒店预定需求分析
分析流程
了解數據信息分析問題作出假設數據清洗-缺失值/異常值處理探索性分析-結合可視化做出結論1. 了解數據信息
使用pandas_profiling中的profilereport能夠得到關于數據的概覽;
import pandas_profilingfile_path = "F:/jupyter/kaggle/數據集/1、Hotel booking demand酒店預訂需求\hotel_booking_demand.csv" hb_df = pd.read_csv(file_path)# hb_df.profile_report() pandas_profiling.ProfileReport(hb_df)
簡單了解各字段分布后,我打算從以下三個方面分析:
| 酒店運營情況——取消數、入住率、人均每晚房間價格、不同月份人均每晚價格 |
| 旅客情況——來自國家、餐食選擇情況、居住時長、提前預定時長 |
| 預定渠道情況——不同市場細分下的價格 |
2. 數據清洗
2.1 缺失值
hb_df.isnull().sum()[hb_df.isnull().sum()!=0]確定含缺失值的字段
輸出:
children 4 country 488 agent 16340 company 112593處理:
| 假設agent中缺失值代表未指定任何機構,即nan=0 |
| country則直接使用其字段內眾數填充 |
| childred使用其字段內眾數填充 |
| company因缺失數值過大,且其信息較雜(單個值分布太多),所以直接刪除 |
代碼如下:
hb_new = hb_df.copy(deep=True) hb_new.drop("company", axis=1, inplace=True)hb_new["agent"].fillna(0, inplace=True) hb_new["children"].fillna(hb_new["children"].mode()[0], inplace=True) hb_new["country"].fillna(hb_new["country"].mode()[0], inplace=True)2.2 異常值
此數據集中異常值為那些總人數(adults+children+babies)為0的記錄,同時,因為先前已指名“meal”中“SC”和“Undefined”為同一類別,因此也許處理一下。
代碼如下:
hb_new["children"] = hb_new["children"].astype(int) hb_new["agent"] = hb_new["agent"].astype(int)hb_new["meal"].replace("Undefined", "SC", inplace=True) # 處理異常值 # 將 變量 adults + children + babies == 0 的數據刪除 zero_guests = list(hb_new["adults"] +hb_new["children"] +hb_new["babies"] == 0) # hb_new.info() hb_new.drop(hb_new.index[zero_guests], inplace=True)此時數據基本已經沒有問題,可以開始結合可視化的探索性分析了。
3. 探索性分析(+可視化)
3.1 酒店運營方面
3.1.1 取消數、入住率
plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["font.serif"] = ["SimHei"] # 從預定是否取消考慮 rh_iscancel_count = hb_new[hb_new["hotel"]=="Resort Hotel"].groupby(["is_canceled"])["is_canceled"].count() ch_iscancel_count = hb_new[hb_new["hotel"]=="City Hotel"].groupby(["is_canceled"])["is_canceled"].count()rh_cancel_data = pd.DataFrame({"hotel": "度假酒店","is_canceled": rh_iscancel_count.index,"count": rh_iscancel_count.values})ch_cancel_data = pd.DataFrame({"hotel": "城市酒店","is_canceled": ch_iscancel_count.index,"count": ch_iscancel_count.values}) iscancel_data = pd.concat([rh_cancel_data, ch_cancel_data], ignore_index=True)plt.figure(figsize=(12, 8)) w, t, autotexts = plt.pie(hb_new["hotel"].value_counts(), autopct="%.2f%%",textprops={"fontsize":18}) plt.title("酒店總預定數分布", fontsize=16) plt.legend(w, (iscancel_data.loc[iscancel_data.is_canceled==1, "hotel"].value_counts().index)[::-1], loc="upper right",fontsize=14) # plt.savefig("F:/文章/酒店總預定數分布.png") plt.show();此為獲得圖形:
plt.figure(figsize=(12, 8))cmap = plt.get_cmap("tab20c") outer_colors = cmap(np.arange(2)*4) inner_colors = cmap(np.array([1, 2, 5, 6]))w , t, at = plt.pie(hb_new["is_canceled"].value_counts(), autopct="%.2f%%",textprops={"fontsize":18},radius=0.7, wedgeprops=dict(width=0.3), pctdistance=0.75, colors=outer_colors) plt.legend(w, ["未取消預定", "取消預定"], loc="upper right", bbox_to_anchor=(0, 0, 0.2, 1), fontsize=12)val_array = np.array((iscancel_data.loc[(iscancel_data.hotel=="城市酒店")&(iscancel_data.is_canceled==0), "count"].values,iscancel_data.loc[(iscancel_data.hotel=="度假酒店")&(iscancel_data.is_canceled==0), "count"].values,iscancel_data.loc[(iscancel_data.hotel=="城市酒店")&(iscancel_data.is_canceled==1), "count"].values,iscancel_data.loc[(iscancel_data.hotel=="度假酒店")&(iscancel_data.is_canceled==1), "count"].values))w2, t2, at2 = plt.pie(val_array, autopct="%.2f%%",textprops={"fontsize":16}, radius=1,wedgeprops=dict(width=0.3), pctdistance=0.85, colors=inner_colors) plt.title("不同酒店預定情況", fontsize=16)bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72) kw = dict(arrowprops=dict(arrowstyle="-", color="k"), bbox=bbox_props, zorder=3, va="center") for i, p in enumerate(w2): # print(i, p, sep="---")text = ["城市酒店", "度假酒店", "城市酒店", "度假酒店"]ang = (p.theta2 - p.theta1) / 2. + p.theta1y = np.sin(np.deg2rad(ang))x = np.cos(np.deg2rad(ang))horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]connectionstyle = "angle, angleA=0, angleB={}".format(ang)kw["arrowprops"].update({"connectionstyle": connectionstyle})plt.annotate(text[i], xy=(x, y), xytext=(1.15*np.sign(x), 1.2*y),horizontalalignment=horizontalalignment, **kw, fontsize=18)可以看到,城市酒店的預定數要大于度假酒店,但城市酒店的取消率也相對較高。
3.1.2 酒店人均價格
接下來可以從人均價格入手,看看兩家酒店的運營情況。
因為babies年齡過小,所以人均價格中未將babies帶入計算。
人均價格/晚=adradults+children人均價格/晚 = \frac{adr}{adults+children} 人均價格/晚=adults+childrenadr?
此時來查看不同月份下的平均酒店價格,代碼如下:
輸出圖形:
同時,我認為結合15-17年內平均人流量(即未取消預定下的不同月份的預定數),會有一個更加清晰的認識:
注意:因為求的是均值,所以最后繪圖前需要把不同月份的總預定數除以此月份的計數(15-17年這個月份出現過幾次)。
# 查看月度人流量 rh_bookings_monthly = full_data_guests[full_data_guests.hotel=="Resort Hotel"].groupby("arrival_date_month")["hotel"].count() ch_bookings_monthly = full_data_guests[full_data_guests.hotel=="City Hotel"].groupby("arrival_date_month")["hotel"].count()rh_bookings_data = pd.DataFrame({"arrival_date_month": list(rh_bookings_monthly.index),"hotel": "度假酒店","guests": list(rh_bookings_monthly.values)}) ch_bookings_data = pd.DataFrame({"arrival_date_month": list(ch_bookings_monthly.index),"hotel": "城市酒店","guests": list(ch_bookings_monthly.values)}) full_booking_monthly_data = pd.concat([rh_bookings_data, ch_bookings_data], ignore_index=True)ordered_months = ["January", "February", "March", "April", "May", "June", "July", "August","September", "October", "November", "December"] month_che = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]for en, che in zip(ordered_months, month_che):full_booking_monthly_data["arrival_date_month"].replace(en, che, inplace=True)full_booking_monthly_data["arrival_date_month"] = pd.Categorical(full_booking_monthly_data["arrival_date_month"],categories=month_che, ordered=True)full_booking_monthly_data.loc[(full_booking_monthly_data["arrival_date_month"]=="七月")|\(full_booking_monthly_data["arrival_date_month"]=="八月"), "guests"] /= 3 full_booking_monthly_data.loc[~((full_booking_monthly_data["arrival_date_month"]=="七月")|\(full_booking_monthly_data["arrival_date_month"]=="八月")), "guests"] /= 2 plt.figure(figsize=(12, 8)) sns.lineplot(x="arrival_date_month",y="guests",hue="hotel", hue_order=["城市酒店", "度假酒店"],data=full_booking_monthly_data, size="hotel", sizes=(2.5, 2.5)) plt.title("不同月份平均旅客數", fontsize=16) plt.xlabel("月份", fontsize=16) plt.ylabel("旅客數", fontsize=16) # plt.savefig("F:/文章/不同月份平均旅客數")得到圖形:
結合上述兩幅圖可以了解到:
3.2 游客簡易畫像
3.2.1 游客分布
可以簡單了解一下選擇這兩家酒店入住的旅客都來自于哪些國家。
這次使用了plotly中的一個map圖形,有一定交互性,便于查看。
map = px.choropleth(country_data, locations="country", color="總游客數", hover_name="country",color_continuous_scale=px.colors.sequential.Plasma,title="游客分布") map.show()可以明顯看到游客主要還是集中在歐洲地區。
3.2.2 餐食選擇
現在我們可以了解以下對餐食的選擇是否會影響游客取消預定這一行為。
meal_data = hb_new[["hotel", "is_canceled", "meal"]] # meal_dataplt.figure(figsize=(12, 8)) plt.subplot(121) plt.pie(meal_data.loc[meal_data["is_canceled"]==0, "meal"].value_counts(), labels=meal_data.loc[meal_data["is_canceled"]==0, "meal"].value_counts().index, autopct="%.2f%%") plt.title("未取消預訂旅客餐食選擇", fontsize=16) plt.legend(loc="upper right")plt.subplot(122) plt.pie(meal_data.loc[meal_data["is_canceled"]==1, "meal"].value_counts(), labels=meal_data.loc[meal_data["is_canceled"]==1, "meal"].value_counts().index, autopct="%.2f%%") plt.title("取消預訂旅客餐食選擇", fontsize=16) plt.legend(loc="upper right")很明顯,取消預訂旅客和未取消預訂旅客有基本相同的餐食選擇。
我們不能因為一位游客bed&breakfast選擇的是就說他一定會取消預定,我們趕緊不要管他;或者說他一定不會取消預訂,這位客人很重要。
3.2.3 居住時長
那么在不同酒店居住的旅客通常會選擇住幾天呢?我們可以使用柱形圖來看一下其時長的不同分布;
首先計算出總時長:總時長=周末停留夜晚數+工作日停留夜晚數
full_data_guests["total_nights"] = full_data_guests["stays_in_weekend_nights"] + full_data_guests["stays_in_week_nights"]因為居住時長獨立值過多,所以我新建一個變量來將其變為分類型數據:
# 新建字段:total_nights_bin——居住時長區間 full_data_guests["total_nights_bin"] = "住1晚" full_data_guests.loc[(full_data_guests["total_nights"]>1)&(full_data_guests["total_nights"]<=5), "total_nights_bin"] = "2-5晚" full_data_guests.loc[(full_data_guests["total_nights"]>5)&(full_data_guests["total_nights"]<=10), "total_nights_bin"] = "6-10晚" full_data_guests.loc[(full_data_guests["total_nights"]>10), "total_nights_bin"] = "11晚以上"此時,再來繪圖:
ch_nights_count = full_data_guests["total_nights_bin"][full_data_guests.hotel=="City Hotel"].value_counts() rh_nights_count = full_data_guests["total_nights_bin"][full_data_guests.hotel=="Resort Hotel"].value_counts()ch_nights_index = full_data_guests["total_nights_bin"][full_data_guests.hotel=="City Hotel"].value_counts().index rh_nights_index = full_data_guests["total_nights_bin"][full_data_guests.hotel=="Resort Hotel"].value_counts().indexch_nights_data = pd.DataFrame({"hotel": "城市酒店","nights": ch_nights_index,"guests": ch_nights_count}) rh_nights_data = pd.DataFrame({"hotel": "度假酒店","nights": rh_nights_index,"guests": rh_nights_count}) # 繪圖數據 nights_data = pd.concat([ch_nights_data, rh_nights_data], ignore_index=True) order = ["住1晚", "2-5晚", "6-10晚", "11晚以上"] nights_data["nights"] = pd.Categorical(nights_data["nights"], categories=order, ordered=True)plt.figure(figsize=(12, 8)) sns.barplot(x="nights", y="guests", hue="hotel", data=nights_data) plt.title("旅客居住時長分布", fontsize=16) plt.xlabel("居住時長", fontsize=16) plt.ylabel("旅客數", fontsize=16)plt.legend()輸出:
不論哪家游客基本選擇都在1-5晚,而其中度假酒店中的旅客還有另外一種選擇——6-10晚。
3.2.4 提前預定時長
提前預定期對旅客是否選擇取消預訂也有很大影響,因為lead_time字段中的值分布多且散亂,所以使用散點圖比較合適,同時還可以繪制一條回歸線。
lead_cancel_data = pd.DataFrame(hb_new.groupby("lead_time")["is_canceled"].describe()) # lead_cancel_data # 因為lead_time中值范圍大且數量分布不勻,所以選取lead_time>10次的數據(<10的數據不具代表性) lead_cancel_data_10 = lead_cancel_data[lead_cancel_data["count"]>10]y = list(round(lead_cancel_data_10["mean"], 4) * 100)plt.figure(figsize=(12, 8)) sns.regplot(x=list(lead_cancel_data_10.index),y=y) plt.title("提前預定時長對取消的影響", fontsize=16) plt.xlabel("提前預定時長", fontsize=16) plt.ylabel("取消數 [%]", fontsize=16) # plt.savefig("F:/文章/提前預定時長對取消的影響")輸出:
可以明顯看到:不同的提前預定時長確定對旅客是否取消預定有一定影響;
通常,離入住日期越早約定,越不容易取消酒店房間預定。
3.3 市場細分
最后還可以查看以下不同市場細分下的預定分布情況。
segment_count = list(full_data_guests["market_segment"].value_counts()) segment_ls = list(full_data_guests["market_segment"].value_counts().index)# 查看市場細分分布 plt.figure(figsize=(12, 8)) fig = px.pie(values=segment_count, names=segment_ls, title="市場細分預訂分布(未取消預訂)", template="seaborn") fig.update_traces(rotation=90, textposition="inside", textinfo="label+percent+value")還可以查看一下,通過不同的市場預定兩家酒店的價格有何不同。
# 不同市場細分下的人均價格每晚 plt.figure(figsize=(6, 8)) # plt.subplot(121) sns.barplot(x="market_segment", y="adr_pp",hue="hotel",data=hb_new, ci="sd", errwidth=1, capsize=0.1, hue_order=["City Hotel", "Resort Hotel"]) plt.title("不同市場細分下人均每晚價格", fontsize=16) plt.xlabel("市場細分", fontsize=16) plt.ylabel("人均每晚價格", fontsize=16) plt.xticks(rotation=45) plt.legend(loc="upper left", facecolor="white", edgecolor="k", frameon=True)可以看到,人們大多通過線上旅行社完成預定,既因為其便捷性,也因為其價格合理;
而航空公司的價格則很高,同時通過其預定的人數也最少。
4. 做出結論
從以上三個維度的分析中可以看到:
上述分析數據來自kaggle——酒店預定需求;
有更好的角度分析的,歡迎大家來討論。
(部分圖形代碼參考自https://www.kaggle.com/marcuswingen/eda-of-bookings-and-ml-to-predict-cancelations)
歡迎關注微信,一起學習!
總結
- 上一篇: shiro学习(8):shiro连接数据
- 下一篇: 关于python3中的包operator