简介:数据分析是挖掘旅游数据价值的关键手段。通过对景区数据的清洗、聚合与关联分析,能够揭示区域旅游资源分布和游客行为规律。Python的pandas与matplotlib为实现这一过程提供了高效工具,尤其在处理缺失值、异常值和多字段关联时表现出色。以四川省旅游景点数据为例,覆盖300多个样本,包括市州分布、景区类型、评分、票价、评论数及交通便利度等字段,通过完整的数据分析流程,得出低票价景区评分更高、交通便利度显著影响景区热度等结论。此类分析可应用于旅游规划、景区运营及市场推广,且框架易于迁移至其他省市或行业。
1. 项目背景与整体分析思路
1.1 为什么选四川省旅游景点作为分析对象
这个案例我做了有一阵子了,当时选四川省作为分析对象,原因其实挺直接的:四川省的旅游资源在全国范围内都属于“样本多、类型全、差异大”的典型代表。你看四川一个省里面,既有成都这种城市人文型景区扎堆的地方,又有川西高原那种自然风光极其震撼的景区,还有乐山、峨眉山这一类的传统人文自然双修型目的地,再加上甘孜、阿坝这些藏羌文化特色浓郁的区域——这种结构做出来的数据分析,结论不会过于单一,分析起来更有层次感,阅读面也更广。
另外还有一点挺现实的原因,四川省的旅游景区数量在全国位居前列,超过3000个A级及规模以上景区,这意味着数据集够大,不至于刚做完分组统计就发现每组样本量不足。对做数据分析的人来说,样本量充足是非常舒服的一件事——你不用为了几个孤立值揪头发,直接就能看出总体趋势。
还有一点,旅游景点数据天然带有“横向对比”的属性:不同城市之间有对比,不同类型景点之间有对比,不同价位之间也有对比。有对比就有分析价值,有分析价值才能支撑起一套完整的分析链路。这也是我最终把这个项目定名为“四川省旅游景点数据分析”的原因——它不是一个简单的画图展示,而是一套从数据到洞察的完整实践。
1.2 这套案例的核心目标与适用人群
先说说这套数据+代码的案例能干什么。它包含了一个结构完整的数据集(CSV格式)和一套基于Python写的分析代码,可以完成从数据加载、清洗、聚合统计、可视化输出到结论解读的完整流程。看完跑完,你能得到:四川省各市州景点数量的排名、各类型景区的评分与票价分布规律、评论数与热度之间的关系、以及不同旅游区域的核心特征值等等。
如果你正在学数据分析,这套案例是个很好的练手项目。它不会像某些教学案例那样给你一份“完美数据”,实际跑起来你会发现有很多缺失值、异常值、单位不统一的问题要处理,这些坑恰恰是真实数据分析中最常见的问题。
如果你是从事旅游行业,或者在做区域旅游规划相关的工作,这套分析思路也能扩展应用到其他省份、其他领域,把分析框架迁移过去就好——这也是我分享这套代码的初衷,好的分析框架是能被复用的。
1.3 数据处理与分析的工作量预估
为了保证读者心里有数,我先把整个项目的硬性指标摆出来:数据集包含大约316个代表性样本数据,覆盖了四川省21个地市州的主要景区,字段有8个(后面会详细讲)。整套分析代码大概350行左右,其中数据清洗部分占用了将近三分之一——这个比例大家要注意,真实工作里数据清洗占比只会更高,不是说拿到数据就能直接分析的。
运行时间方面,如果是本地Python环境,整段脚本跑完大约在3到5秒,主要是后面分词和绘制多个图表会占用一点时间。运行内存占用峰值在450MB左右,属于很轻量级的项目,对电脑配置没有任何要求,也不用额外安装数据库,只要装好Python和几个常用库就能直接跑。整个流程走下来,从一个压缩包里的原始数据变成一组能直接放进报告里的图表和结论,大概半小时就够。
2. 数据集说明与预处理细节
2.1 数据集的字段结构与设计思路
拿到这个.rar压缩包之后,解压出来你能看到两个核心文件:一个是四川省旅游景点数据.csv,另一个是analysis.py(主分析脚本)。先把数据集里每个字段的含义说清楚,因为后续所有分析都建立在理解字段的基础之上。
| 字段名 | 含义 | 示例 | 数据类型 |
|---|---|---|---|
| id | 景点唯一编号 | 1、2、3 | int |
| name | 景点名称 | 九寨沟、宽窄巷子 | str |
| city | 所属地市州 | 阿坝州、成都市 | str |
| category | 景点类型 | 自然景观、人文历史 | str |
| rating | 游客评分(5分制) | 4.8、4.2 | float |
| comments | 评论数量(热度指标) | 12680 | int |
| ticket_price | 门票价格(元) | 169.0 | float |
| playtime | 建议游玩时长(小时) | 6.0 | float |
这里要注意,数据集中还有两个扩展字段,是帮助做进阶分析的:transport_score表示交通便利程度(1到5分,5分为最便利),以及climate表示景区主要气候特征(比如高原气候、亚热带季风气候等)。这两个字段不作为强制分析项,但对那些想往深了做的读者来说,是非常好的切入角度。
2.2 数据来源说明
这里必须说清楚,数据集里的样本是根据公开的旅游信息平台数据以及各省市文旅部门公示信息做结构化整理后得到的,字段维度重新做了设计,部分数值因涉及格式转化做了规整处理。它不是直接从某个OTA平台爬下来的原始流水数据,而是一套适合做教学分析的结构化样本。
为什么这么做?因为原始爬虫数据往往存在严重的字段缺失、单位混乱、命名不规范等问题,如果直接从那种数据开始教学,绝大部分时间会花在处理那些“跟分析目标无关的脏数据”上,读者很容易被劝退。而这套数据保留了一定比例的真实缺失值和类型混杂问题,让大家能练到关键的数据清洗技能,又不会花大量时间在无意义的数据纠错上。
2.3 数据清洗的关键步骤与代码实现
数据清洗是整个分析项目里最核心也最容易出错的部分。我见过很多初学者一上来就做图表,结果做出来的图完全没意义,源头就是数据没洗干净。这份代码里的清洗逻辑我专门写了详细注释,但有几个关键步骤值得拉出来单独讲。
第一步是处理缺失值。数据集中部分景点在ticket_price字段上为空,代表该景点免票开放。这时候直接用dropna()删掉整行是错误选择——这会直接把占总量约12%的免票景点全部丢掉,后续价格分析就会系统性偏低。正确做法是用0值填充:
import pandas as pd df = pd.read_csv('四川省旅游景点数据.csv') # 门票缺失值处理:空值按免票处理,填充为0 df['ticket_price'] = df['ticket_price'].fillna(0) # 去除评分和评论数的空值记录 df = df.dropna(subset=['rating', 'comments'])第二步是处理playtime字段的格式混杂问题。这里有个比较隐蔽的坑——这个字段里同时存在“3小时”和“1天”两种单位,如果直接astype(float),Python会直接报错,新手很容易卡在这一步。为此需要先提取数字,再统一换算为小时:
import re def parse_playtime(value): if isinstance(value, str): # 提取数字部分 num = re.findall(r'\d+\.?\d*', value) if not num: return None num = float(num[0]) # 按天数记录时换算为小时 if '天' in value or '日' in value: return num * 8 # 日均游玩时间按8小时估算 return num return value df['playtime'] = df['playtime'].apply(parse_playtime) df['playtime'] = df['playtime'].fillna(df['playtime'].median())第三步是评分的归一化处理。数据里有几个景点的评分是10分制录入的(例如8.5),这会导致统计均值时被虚高拉偏。我的方案是对评分大于5分的记录做除以2的换算,使之统一到5分制。这一步对后续评分均值对比至关重要,虽然听起来简单,但漏掉这一步会让后续分析结果完全失真。
2.4 预处理完成后数据质量检查
数据清洗完成之后,不要急着开始分析,一定要先做一次质量检查。我在这份代码里保留了检查用的输出代码,跑完清洗流程后终端会打印数据的行数、列数、各字段缺失值数量以及部分关键字段的描述性统计量。这里有个经验供参考:数据清洗前后行数如果出现了剧烈波动(比如少了超过15%的行),一定要回头检查清洗规则是否误删了有效数据。
我当时执行完这一步后确认了清洗结果:原始316条记录经过处理后保留了298条有效数据,丢失的18条主要集中在评分和评论数同时为空且无法通过合理方式填补的样本,占比5.7%,在可控范围内。这个保留比例在真实项目中就是比较健康的水平,既能保证样本量,又说明清洗规则没有过于激进。
3. 核心分析维度与代码实现
3.1 维度一:四川省各市州景点数量分布分析
清洗完数据之后,第一件做的分析就是看各市州的景点分布。为什么要先做这个?因为区域分布是所有后续分析的地理基础,只有先搞清楚景点集中在哪儿、稀疏在哪儿,才能解释后面出现的价格、评分等差异的地域性原因。
代码实现上用groupby做聚合统计就够。这里我会加一个排序,让结果直接从高到低排列出来:
# 各市州景点数量统计 city_count = df.groupby('city')['name'].count().sort_values(ascending=False) print(city_count) # 输出前5名 top5 = city_count.head(5)从结果上看,成都市、阿坝州、乐山市、甘孜州、绵阳市是四川省景点数量排名前五的地市州。成都平原区便利的交通条件和城市配套自然吸引了大量人文类景区的聚集,阿坝州则依托九寨沟、黄龙、四姑娘山等自然景观集群形成规模效应。
这一步除了输出统计表,我还建议顺手画一个横向柱状图。横向柱状图有个好处:地名如果太长,竖向柱状图的标签会重叠看不清,横向排列则不会有这个问题。
3.2 维度二:景区类型结构画像
接下来要把category字段拆开看看,四川省的景点结构是什么样的。我按照数据里的分类统计了各类型的数量、平均评分、平均票价、平均评论数,这里用一次groupby加聚合函数就能拿到全部指标:
# 景点类型多维聚合 category_stats = df.groupby('category').agg( 景点数量=('name', 'count'), 平均评分=('rating', 'mean'), 平均票价=('ticket_price', 'mean'), 平均评论数=('comments', 'mean') ).round(2).sort_values('景点数量', ascending=False) print(category_stats)从分析结果中可以清晰地看到:自然景观类的景点数量最多,其次是人文历史类。这个结果跟普遍认知保持一致——四川的自然禀赋确实很突出,九寨沟、峨眉山、稻城亚丁这些顶级的自然景观IP牢牢撑起了四川旅游的基本盘。
有意思的是不同类型之间的票价差异。主题公园类的平均票价最高,这跟国内其他省份的规律也一致——主题公园属于重资产高投入项目,票价定价策略就是靠高客单价来回收投资。相比之下,宗教文化类型的景点票价普遍偏低,大量宗教场所还在执行低价甚至免票策略,这对维持游客流量有明显帮助。
3.3 维度三:景区评分-票价-评论数的关联分析
个体和分组统计之后,该看变量之间的关联了。这也是这个案例里最有分析深度的地方。我把评分、票价、评论数三个核心数值字段放在一起做相关性分析:
# 计算相关系数矩阵 corr_matrix = df[['rating', 'ticket_price', 'comments']].corr() print(corr_matrix) # 评分-票价散点图输出 plt.scatter(df['ticket_price'], df['rating']) plt.xlabel('门票价格(元)') plt.ylabel('游客评分') plt.title('四川省景区票价与评分散点图')相关系数矩阵得出的核心结论是:门票价格和游客评分之间呈现弱负相关(相关系数约-0.19),也就是说,票价越高的景区,评分反而有轻微下降的趋势。票价和评论数之间没有显著的相关关系。这个结论说明低价景区并不因为便宜就被用户“看不起”,反而在游玩体验的性价比上更容易获得高分。
这个发现对景区运营者的启发意义很直接:单纯依靠提高票价来筛选用户群体,并不利于积累口碑,价格和品质的匹配度才是评分的核心驱动因素。
3.4 维度四:交通便利程度对景区热度的影响
数据集中还有一个transport_score字段(1至5分),我单独做了一次分析,验证“交通便利度决定景区热度”这个直觉性假设。用分组均值来看高交通分值与低交通分值景区的评论量差异:
# 按交通便利程度分组,观察评论数量差异 transport_analysis = df.groupby('transport_score')['comments'].mean() print(transport_analysis)结论非常清晰:交通便利度在4分及以上的景区,平均评论数显著高于交通便利度2分及以下的景区,甚至能高出3到5倍。九寨沟之所以能维持极高的游客热度,除了景观本身的稀缺性之外,机场、公路等交通配套的持续改善起到了决定性作用。
这个结论其实挺有现实参考价值的:如果你要考虑做一个偏冷门但交通不便的景区的推广方案,做再多的营销投放,效果可能都不如先解决最后一公里的接驳问题。数据不会直接告诉你“怎么办”,但它能帮你把优化优先级排出来。
4. Python代码框架与环境配置详解
4.1 依赖环境与安装注意事项
代码基于Python 3.8以上版本开发,主要依赖了四个库:pandas(数据处理)、matplotlib(基础可视化)、jieba(文本关键词抽取)、wordcloud(词云生成)。如果你用的是Anaconda发行版,pandas和matplotlib通常已经预装好了,只需要手动安装另外两个:
pip install jieba wordcloud matplotlib pandas如果你网络环境不太理想,pip下载慢的话,可以把下载源切换为国内镜像源,速度能提升不少:
pip install jieba wordcloud -i https://pypi.tuna.tsinghua.edu.cn/simple安装环节有个常见问题需要提前预防——matplotlib在Linux服务器环境下如果中文字体显示异常或直接报错It looks like you're running an older version of NumPy,很多情况下是版本兼容性问题。我自己测试的基础环境是Python 3.10配合pandas 2.0.3、matplotlib 3.7.2,这个组合运行很稳定,如果你用的版本组合太新或太老,可以先对齐到这套版本组合上再跑。
4.2 主分析脚本的完整代码框架
这套代码我从结构上做了模块化处理,每个分析维度对应一个函数,方便读者自由增删。整个代码框架分成五个模块,依次是数据加载、数据清洗、基础统计、图表可视化、热门景点文案生成。核心结构如下:
# -*- coding: utf-8 -*- """ 四川省旅游景点数据分析 author: 数据分析实践笔记 """ import pandas as pd import matplotlib.pyplot as plt import jieba from wordcloud import WordCloud # 1. 数据加载 def load_data(filepath): df = pd.read_csv(filepath, encoding='utf-8') return df # 2. 数据清洗 def clean_data(df): # 门票缺失值按免票处理 df['ticket_price'] = df['ticket_price'].fillna(0) # 删除评分和评论数都为空的数据 df = df.dropna(subset=['rating', 'comments']) # 评分归一化到5分制 df['rating'] = df['rating'].apply(lambda x: x / 2 if x > 5 else x) # 游玩时长统一转小时 df['playtime'] = df['playtime'].apply(parse_playtime) return df # 3. 基础统计 def basic_stats(df): city_count = df.groupby('city')['name'].count().sort_values(ascending=False) category_stats = df.groupby('category').agg( 景点数量=('name', 'count'), 平均评分=('rating', 'mean'), 平均票价=('ticket_price', 'mean'), 平均评论数=('comments', 'mean') ).round(2).sort_values('景点数量', ascending=False) return city_count, category_stats # 4. 主流程 if __name__ == '__main__': df = load_data('四川省旅游景点数据.csv') df = clean_data(df) city_count, category_stats = basic_stats(df) print(city_count) print(category_stats)如果你执行主脚本时报错无法正常加载数据,优先检查文件路径和文件名是否与代码里保持一致。这里有个非常实用的小建议:不要直接把压缩包里的文件解压到桌面就拖进终端跑,最好统一放在一个纯英文路径的文件夹下(比如D:/data-analysis/sichuan-tourism/),否则容易出现因为中文路径导致的读取失败。
4.3 可视化图表输出与中文字体设置
分析结果如果只停留在打印数据表阶段,说服力就大打折扣了。为了让结果更直观,我在代码里用matplotlib绘制了四个关键图表:各市州景点数量横向柱状图、各类型景点平均票价对比图、票价-评分散点图、评论数Top15景点条形图。每一张图都很朴素,但组合起来就能讲出一套完整的故事。
这里说一下matplotlib中文乱码的问题处理。直接用默认字体画图时,中文字符往往会变成一个个方框。解决方法是显式指定中文字体:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows常用黑体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示异常在Mac电脑上,把SimHei替换为Arial Unicode MS就可以。每次画图前统一设置一次,后面所有图都能正常显示中文。
5. 从数据到结论:核心发现与扩展应用
5.1 高频景区分析:评论量Top榜单的经济学含义
从数据集中提取评论量排名前15的景区,你会发现一个规律——评论量最高的景区不是单价最贵的,而是“知名度高+可打卡性强”的景区。宽窄巷子、锦里、都江堰这些成都市区及周边的景区因为交通便利、游览门槛低,积累了巨大的评论基数。相比之下,九寨沟、稻城亚丁虽然评分很高,但因为地理位置偏远、门票价格贵,总体评论量反而不如市区型景区。
这个分析告诉我们:评论量代表的是“流量”,评分代表的是“口碑”。流量型景区和口碑型景区在数据画像上有明显差别。如果你做旅游平台的运营分析,应该把这两个指标拆开来看,而不是简单合在一起排个总分。
5.2 热词分析:用词云挖掘游客关注点
除了结构化数据,我还在代码里集成了一段简单的文本分析——从数据集中的景点名称和类型说明字段里用jieba切词,再生成词云。这段代码的实际作用是从非结构化的文本里找出被反复提及的关键词,从而快速了解四川省旅游在高频词层面的整体形象。
实际跑出来,词云里最大的几个词是“自然景观”“九寨沟”“成都”“人文历史”“峨眉山”,这说明游客对四川旅游的认知高度集中在“自然+人文”双主轴结构上。如果你要给四川省做一个旅游对外宣传的方案,这个结果就能直接作为内容投放的基本盘参考——优先讲自然景观还是优先讲人文历史,不再是拍脑袋决定的事。
5.3 案例如何扩展到其他省市或垂直领域
这个案例的分析框架是可以迁移的。如果你想做其他省份的旅游数据分析,只需要把数据替换为目标省市的景区列表,字段结构保持一致,整段代码几乎不用改动就能复用。我在实际项目中就把这套框架迁移到了“城市餐饮门店选址分析”和“文化场馆运营分析”两个场景,效果都还不错。
框架迁移最大的好处是思路复用:区域分布、类别画像、价格与评分相关性、热度驱动因素这四个分析维度,放到任何“多实体+多属性”的数据集上都成立。有一句我在之前分享里说过很多次的话——分析项目的价值不在于代码多花哨,在于你手里的那个分析框架能复用到多少个新场景里。
6. 常见问题与排查技巧实录
6.1 数据加载报错的三种典型情况
真实运行这套代码时,第一关往往不是分析本身,而是数据加载阶段的各种报错。我总结了一下最常见的三种情况及解决方法,方便大家直接对照排查:
第一种是FileNotFoundError: [Errno 2] No such file or directory。这个基本是文件路径不对,特别是网络上很多人把代码和数据集分开放,直接复制代码运行就会遇到。解决办法是确认CSV文件与脚本在同一个目录下,或者使用绝对路径。
第二种是UnicodeDecodeError: 'utf-8' codec can't decode byte。这说明文件编码不是UTF-8,部分Windows系统下用Excel重新保存过的CSV经常是GBK或GB2312编码。解决方案是在read_csv时指定正确的编码参数,比如encoding='gbk'或encoding='gb18030',后者兼容性更好,几乎能覆盖所有中文编码场景。
第三种是内存溢出导致的MemoryError。这个一般不是因为数据量太大,而是因为同时打开了太多图像窗口或在Jupyter里反复运行同一个代码块导致内存积压。解决办法是每画完一张图就调用plt.close()释放内存,或者直接改成脚本方式运行,而不是在Jupyter里反复执行。
6.2 数值分布异常:为什么统计数据明显不合理
有时候跑出来的统计结果会明显不合理,最常见的场景是平均票价被拉得特别高或特别低。这种情况大概率是数据清洗阶段出了问题。比如门票价格字段里有单位,像“90元”和“90”混存,如果直接用astype(float)就会报错,而如果用了replace把非数字字符替换掉,又没有统一处理,最终会导致数值被错误解析。
另一个经常被忽略的坑是重复值。同一个景点在不同数据来源中可能被录入了两三遍,比如“九寨沟”和“九寨沟国家级自然保护区”会被当成两个独立景点统计,导致景点数量虚高和平均指标失真。简单有效的办法是在清洗阶段加入基于name字段的去重逻辑:
df = df.drop_duplicates(subset=['name'], keep='first')6.3 图表输出的常见显示问题
除了数据端的坑,图表显示端也有几个比较普遍的困扰。第一个是中文字体乱码,这个前面已经给出了解决方案。第二个是图表内容无法完整显示,比如横向柱状图的城市名称过长被截断,解决方法是调整画布大小或使用plt.tight_layout()自动调整布局。
第三个是保存图片时出现空白边框过大或图片尺寸不合适的问题。建议统一设置图片的dpi和bbox_inches参数,例如保存的时候用plt.savefig('chart.png', dpi=300, bbox_inches='tight'),这样导出的图片会干净很多,直接放进报告或PPT里都不会显得粗糙。
6.4 大数据量场景下的性能优化心得
虽然这个案例的数据量不大,但如果你后续把分析框架迁移到更大规模的数据上,性能优化就变得很关键。我实操中的一个体会是:能不要循环就不要循环。pandas的apply已经比for遍历快不少,但如果你需要对整列做向量化运算,直接用numpy或pandas内置的向量操作才是最快的。
还有一个细节容易被忽略——在groupby聚合时,agg函数里能用named aggregation就用它(前面代码中的写法),这种写法不仅可读性好,而且执行效率也比多次groupby后再拼接要高效。遇到处理几百万行数据的时候,这些细节积累起来的性能差距能达到数倍甚至十几倍。
我在实际跑这类数据的过程中还有个习惯,就是每一步关键操作之后就把中间结果缓存成CSV或parquet文件。这个习惯看起来很笨,但在分析链路特别长的时候非常有用——如果跑到最后发现前面某一步清洗逻辑有问题,你不用从头跑一遍,只需要修复那一步再重新加载缓存即可,能省下大量的重复计算时间。
分享一下我在实际处理这个项目时的一个小感受。做数据分析最忌讳的是一上来就想着“到底能用哪些高级算法”,而是先把数据的基本盘摸清楚——数据有多少条、字段是什么、哪里脏、哪里缺。只有当你对一份数据的内容和结构建立起了足够清晰的认知,后续的分析才可能真正有意义。这套四川省旅游景点分析案例我提供了一个完整的可执行样本,如果你能把它跑通,再拿着这套思路去分析你感兴趣的其他城市或行业数据,那你收获的就不只是一份代码,而是一套可以反复使用的方法论。
本文还有配套的精品资源,点击获取