背景
随着生活水平提升,旅游成为普遍的休闲方式,但面对众多景点,选择成为一大挑战。本项目通过清洗和分析全国旅游景点数据,聚焦游客偏好和景区价格,旨在借助可视化工具提供直观指导,帮助游客根据自身条件轻松选择理想目的地。
数据预处理
import pandas as pd
import geopandas as gpd
import contextily as ctx
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import plotly.graph_objects as go
import plotly.express as px
from scipy.stats import gaussian_kde
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
df = pd.read_excel(r'\Users\21399\旅游景点.xlsx',index_col=0)
print('-'*50)
print('数据集存在重复值个数:')
print(df.duplicated().sum())
print('-'*50)
print('数据集存在缺失值个数:')
print(df.isna().sum())
print('-'*50)
print('数据集信息:')
print(df.info())
print('-'*50)
print('数据集预览:')
display(df.head())
Grade、Price、Sales应该为数值型,但实际上为object类型,可能含有非数值型数据,因此我们需要先强制转换为数值型。
df['Grade'] = pd.to_numeric(df['Grade'],errors='coerce')
df['Price'] = pd.to_numeric(df['Price'],errors='coerce')
df['Sales'] = pd.to_numeric(df['Sales'],errors='coerce')
# 把Sales转换为int类型,要先把Nan填为0
df['Sales'].fillna(0,inplace=True)
df['Sales'] = df['Sales'].astype(int)
print(df.info())
print('-'*50)
print('数据集描述统计:')
display(df.describe())
接下来,我们先假设下:当前采集的数据是全国热门景点的,因此所采集到的销量一定不为0。即门票免费也会产生销量,比如需要领取/兑换门票。
# 通过描述统计可知:
# 1.Grade有0-50%数据为0
# 2.Price最小值为0,可以检查下isFree为True时Price是否为0,再看看当Price为0时,isFree的唯一值是否只有True一个值(双重保险)
free_price = df.query('isFree == True').Price.sum() # 0 ,由此判断sFree为Ture时Price一定为0
price_free = df.query('Price == 0').isFree.unique() # [True,False]且只有两条数据,需要删除False这一条记录
# 删除满足此条件的行索引的所在行
del_index = df.query('Price == 0 & isFree == False').index
df.drop(index=del_index,inplace=True)
# 3.Sales最小值为0,先检查下当isFree为True时Sales是否非0
free_sales = df.query('isFree==True').Sales.sum() # 1202,当isFree为0时销量总和是非0
df = df.query('Sales!=0') # 剔除销量0的数据
# 4.Price最大值为23888,可能存在异常,画个散点图看看
plt.figure(figsize=(10,6))
plt.scatter(range(len(df['Price'])),df['Price'],color='#B2B2FF')
plt.title('全国景点价格分布',fontsize=15,pad=20)
plt.show()
# 其他省份的景点价格普遍在五千以下,我个人认为这是一个异常数值,故需要删除。
del_index = df['Price'].idxmax()
df.drop(index=del_index,inplace=True)
# 将Coordinate拆分为两列latitude和longitude,便于后续的数据分析和绘画操作
df[['Longitude','Latitude']] = df['Coordinate'].str.split(',',expand=True)
df[['Longitude','Latitude']] = df[['Longitude','Latitude']].astype(float)
# 合并为一个类型为zip的二维结构,便于绘画。
# 由于zip()生成的是迭代器,dataframe不可直接存储迭代器,故需要再转换为list类型
df['Coordinate1'] = list(zip(df.Longitude, df.Latitude))
# 预览效果
display(df.head())
# 考虑到本数据集侧重销量情况而不是地理位置,因此没有剔除坐标为0的数据块
# oo_df = df.query('Longitude==0 & Latitude==0') # shape:(21,24)
全国景点分布
# 将df转换为gdf,gdf是gpd(geopandas)库提供的特殊类型的数据结构,专为处理带有空间信息的数据设计。
# 即带有地理坐标的dataframe是geodataframe(gdf)
# gpd.points_from_xy()将每一对对应的经度和纬度合成一个point(几何对象),每一个point代表地理位置。即按照地理坐标系统设置CRS
gdf = gpd.GeoDataFrame(df,geometry=gpd.points_from_xy(df.Longitude,df.Latitude),crs='EPSG:4326')
# 创建地图
fig,ax = plt.subplots(figsize=(15,15))
# 绘图点位
gdf.plot(ax=ax,color='#B2B2FF',markersize=25)
# 添加地图背景图
ctx.add_basemap(ax,zoom=1,source=ctx.providers.Stamen.Terrain)
# 在地图上添加标签
for _,row in gdf.iterrows():
try:
ax.annotate(row.Name,xy=row.Coordinate1,xytext=(5,5),textcoords='offset points')
except (AttributerError,TypeError) as e:
print(f"Error annotating row{row.index}:{e}")
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.rcParams['font.family'] = ['Arial']
plt.title('全国景点地理分布',size=15,pad=20)
plt.show()
# 全国省级景点数量分布
province_df = df.State.value_counts().reset_index()
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.figure(figsize=(18,8))
sns.barplot(x='index',y='State',data=province_df,alpha=0.4,color='#B2B2FF')
for x,y in zip(range(len(province_df)),province_df['State']):
plt.text(x,y,y,ha='center',va='bottom')
plt.title('全国省级景点数量分布',fontsize=14,pad=20)
plt.show()
国民出游分析
#全国各省景点门票销量总和
# 按省级统计销量
sales_by_state = df.groupby('State').Sales.sum().reset_index()
sales_by_state = sales_by_state.sort_values('Sales',ascending=False,ignore_index=True)
# display(sales_by_state)
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.figure(figsize=(18,8))
sns.barplot(x='State',y='Sales',data=sales_by_state,alpha=0.4)
plt.title('全国各省景点门票销量总和',fontsize=15,pad=20)
for x,y in zip(range(len(sales_by_state.State)),sales_by_state.Sales):
plt.text(x,y,y,ha='center',va='bottom',fontsize=9)
plt.show()
小结:景点门票销量最高的是北京,其次是上海,江苏位居第三,四川、陕西、广东不相上下。可以看出一二线大城市交通、娱乐、服务等各方面比较完善,是人们出游的首选。
# 全国门票销量TOP10景点
sales_by_name = df[['State','Name','Sales']].sort_values(by = 'Sales',ascending=False)
sales_by_name['StateName'] = sales_by_name['State']+sales_by_name['Name']
# display(sales_by_name.head(10))
plt.figure(figsize=(15,6))
sns.barplot(x='StateName',y='Sales',data=sales_by_name.head(10),alpha=0.2,color='#B2B2FF')
plt.title('全国门票销量TOP10景点',fontsize=15,pad=20)
plt.xticks(rotation=15)
for x,y in zip(range(len(sales_by_name.head(10).StateName)),sales_by_name.head(10).Sales):
plt.text(x,y,y,ha='center',va='bottom')
plt.show()
小结:全国门票销量TOP10景点,主要是一二线城市的一些热门且著名景点,故宫、秦始皇帝陵博物馆、颐和园、八达岭长城等。
# 全国各省评分平均数
# 先把Grade缺失值填为nan,boxplot会自动过滤掉这些缺失值
df.loc[df['Grade']==0] = np.nan
plt.figure(figsize=(18,8))
sns.boxplot(x='State',y='Grade',data=df,width=0.5)
plt.title('全国各省评分平均分布',fontsize=16,pad=20)
plt.show()
小结:发现北上广等一线城市评分相比其他城市要偏低许多,上海、北京评分中位数分别约为3.6,3.6,3.7,3.7、3.7,这可能与多种因素相关联。根据个人经验和听闻来猜测:这些一线大城市游客密度高,尤其是在节假日和旅行旺季人流量往往极大,如果有景点需要长时间排队,容易导致整体的旅行体验下降;高物价也是原因之一,往往是一线大城市的通病之一,旅行中餐饮、住宿、交通等方面消费会比较高,会让游客感觉整体性价比不高,进而影响评分;还有期望值与显示差异也是影响评分的因素之一。
# 探索销量和价格的相关性,分析是否存在物美价廉效应
correlation_matrix=df[['Sales','Price']].corr(method='pearson')
print(f'销量与价格的相关性:{correlation_matrix.iloc[0,1].round(3)}')
销量与价格的相关性:-0.05
小结:销量与价格相关性为-0.025,接近于0,说明两者相关强度非常微弱。人们选择景点可能更多是受到景点的特色、文化等因素的吸引,而非单纯受门票价格的影响。
# 门票价格与评分的关系
plt.figure(figsize=(15,8))
sns.scatterplot(data=df[['Grade','Price']],x='Price',y='Grade',alpha=0.5,color='#B2B2FF')
plt.title('门票价格与评分的关系',fontsize=15,pad=20)
plt.show()
# 门票价格与评分的相关系数
correlation = df['Price'].corr(df['Grade'])
print(f'门票价格与评分的相关性:{correlation.round(3)}')
门票价格与评分的相关性:-0.065
再次证明了无论是评分还是销量等都不必然与价格呈强相关性。
# 门票价格与评分精选的景点指南
price_grade_df = df.query('Price<=500 and Grade in (4,5)')
price_grade_df = price_grade_df.sort_values(by=['Grade','Price'],ascending=[False,True]).reset_index()
display(price_grade_df.iloc[:,1:-3].head(20))
小结:评分维度反映了游客对景点质量的主观评价例如体验、环境、服务等多个方面。想要在旅途中享受经济实惠,又不牺牲游览体验的游客可以参考以上门票价格与评分精选的景点指南。从排名(这里只截了前部分数据表)来看,价格实惠但评分很高的景点有澳门水舞间演出、厦门大学、神农顶景、皇城相府、成吉思汗陵旅游景区等。
# 价格与销量的相关性
correlaton = df['Price'].corr(df['Sales'])
print(f'价格与销量的相关性:{correlation.round(3)}')
价格与销量的相关性:-0.065
# 门票价格与销量精选的景点指南
price_sales_df = df.query('Price<=500')
price_sales_df = price_sales_df.sort_values(by=['Sales','Price'],ascending=[False,True]).reset_index()
display(price_sales_df.iloc[:,1:-3].head(20))
小结:销量维度更多体现景点的市场表现和知名度,但不一定直接对应于游客的满意度。想要在旅途中享受经济实惠且轻松打卡知名景点的游客可以参考以上门票价格与销量精选的景点指南。从排名(这里只截了前部分数据表)来看,价格实惠但销量高的景点有故宫、秦始皇帝陵博物馆、程度大熊猫繁育研究基地、颐和园、八达岭长城等等。
景区价格分析
# 全国各省景点价格平均
df_nofree = df.query('isFree == False')
plt.figure(figsize=(15,8))
sns.boxplot(x='State',y='Price',data=df_nofree,color='#B2B2FF')
plt.title('全国各省景点价格分布',fontsize=15,pad=20)
plt.show()
小结:相较于大城市,西部地区旅行消费往往显得相对高,这一个现象部分原因在于西部地域辽阔,景点分散,由于景点之间距离较远,自驾或包车成了人们的首选。因此相应地推高了旅行中的交通成本。
总结
- 除港澳台之外,所有省份的景点数量不相上下,基本有70个左右。尤其是华北南部、华南、华东、华中等地理城市的景点比较密集,像西北、东北等地理城市由于地方辽阔,景点之间距离就较远。
- 如果希望交通、娱乐、服务等方面较完善的可以考虑一二线城市比较多的省份,例如北京、上海、江苏、浙江、福建、广西等。有个别游客倾向有良好的旅行体验,比如不用看人头、也不用长时间排队等,可以考虑二线城市,因为一线城市尤其是北上广,人流量往往比较大,旅行消费相对高,可能会导致整个体验下降。
- 想要在旅途中享受经济实惠,又不牺牲游览体验的游客可以考虑以下景点:澳门水舞间演出、厦门大学、神农顶景、皇城相府、成吉思汗陵旅游景区等。
- 想要在旅途中享受经济实惠且轻松打卡知名景点的游客可以参考以下景点:故宫、秦始皇帝陵博物馆、程度大熊猫繁育研究基地、颐和园、八达岭长城等。
- 像新疆、西藏、内蒙古、青海这些省份由于地理特殊,地域相对其他城市要辽阔很多,景点之间距离比较远,交通设施也不太完善,因此游客最好选择自驾或者包车出行旅行,但是旅行消费成本并不会比北上广消费的低,甚至高出几千。不过人流量也不会很大,被分散在各个景区,因此如果您有足够的预算和时间又想体验不一样的旅程,可以选择这些地方。