
简介一个面向数据分析学习者与旅游业从业者的实战项目基于去哪儿网国庆期间真实旅游景点数据完整演示从数据采集清洗、统计建模到业务洞察的数据分析流程。压缩包内共七个文件整体仅七十九千字节包含五个网页可视化页面、一个表格数据源与一个脚本分析程序网页图表覆盖各省份景点分布热力图、景区门票销售额前二十柱状图、星级分布比例饼状图等核心视图脚本程序则承担数据预处理、缺失值处理与结果输出已有一千三百七十九人浏览学习。通过这套资料可掌握数据科学常用工具的真实用法理解时间序列趋势与游客偏好分析学习国庆旅游热门景点推荐排行的分析逻辑并可将可视化模板直接复用到个人项目中对于需提升旅游运营效率、开展数据驱动决策的从业者这是一份轻量而完整的参考也适合作为数据分析入门或课程设计的练手素材。1. 旅游景点数据分析实战与其凭感觉猜热门不如先把历史数据算明白做景区运营的人大概都经历过这种场面领导问下个月该把预算押在哪个景点上你翻出手机相册里去年同期的照片凭印象说“应该还是那几个老牌景区吧”。等真金白银投下去数据出来才发现周边两个新晋目的地早就把流量结构改写了。这个“旅游景点数据分析实战”资源就是把这事从拍脑袋变成看数字的一套完整数据集和代码包。它覆盖了数据采集清洗、客源结构分析、季节性波动测算、停留时长与消费行为建模最后用可视化图表把结论直接导出成报告。适合景区运营、旅行社计调、文旅行业数据分析师以及想拿真实业务数据练手的学生。这套东西的价值不在于代码多精妙而在于它整条链路是通的——从拿到原始数据到输出一张能汇报的图中间每一步都有对应脚本和参数说明。2. 先解决数据来源问题结构化CSV与公开接口的取舍做数据分析最耗时间的从来不是写分析代码而是把数据搞到手并确认它可信。这套实战资源里内置的数据不是爬虫现场抓的动态页面而是预先整理好的结构化CSV文件外加一部分通过公开接口拿到的景点客流与天气数据。我拿到资源后第一件事就是检查数据结构这里把选型和数据字典说清楚你后面跑脚本会顺手很多。2.1 结构化CSV的字段说明与数据字典资源内附带的主数据表是simulated_tourist_flow.csv这是一份模拟某省主要景点全年接待情况的记录表但它不是拍脑袋虚构的生成逻辑参考了文旅统计年鉴的常见口径。字段设计上兼顾了业务查询和建模需要具体列含义如下表所示字段名类型示例值说明spot_name字符串某古城景点名称已做脱敏处理city字符串某市景点所属地市visit_date日期2024-05-01游客到访日期精确到天visitor_count整数3200当日接待游客人次ticket_revenue浮点数128000.00当日门票收入单位元avg_stay_minutes整数187游客平均停留时长单位分钟ticket_type字符串联票票种结构大门票/联票/免票weather字符串晴当日天气状况用于交叉分析这份表最方便的地方在于日期字段已经做了规范化处理不需要再处理时间格式。数据量级在几十万行左右Pandas读进来后内存占用约几百兆普通办公电脑跑起来不会卡死。资源里还附带了hotel_occupancy.csv用于分析过夜游客对周边住宿的带动作用。2.2 读入数据并做初步探查拿到数据后别急着开始画图先跑一遍形状和缺失值检查确认这份数据是否值得继续投时间。我一般会在项目最开始就建一个数据探查脚本把字段类型、空值比例、重复记录数一次性看全。import pandas as pd df pd.read_csv(simulated_tourist_flow.csv, parse_dates[visit_date]) print(df.shape) print(df.info()) print(df.isnull().sum()) duplicate_rows df[df.duplicated(subset[spot_name, visit_date], keepFalse)] print(f重复记录数: {len(duplicate_rows)})逻辑说明第一行用read_csv读取文件parse_dates参数直接把visit_date列解析为Datetime类型后面做时间聚合时不用再转换。info()输出每列的非空数量和类型isnull().sum()按列统计缺失值。duplicated方法按景点和日期两个字段判断是否有重复记录keepFalse表示所有重复行都标记出来。参数说明如果本地内存紧张可以在read_csv时加usecols参数只读取需要的列比如usecols[spot_name, city, visit_date, visitor_count]能省下大约三成内存占用。另外如果原始CSV是gbk编码需要把encoding参数改成utf-8否则中文列名会乱码。3. 数据清洗的硬骨头缺失值、机构名与节假日标记这份资源里附带的清洗脚本是整套包里最值得读的部分。很多人拿到数据后习惯用dropna()把含空值的行直接删掉但在旅游数据里这么做会丢掉大量有效信息——比如免票景点的ticket_revenue本身就是空的这部分是业务含义上的“无收入”而不是数据缺失。资源里的清洗脚本针对这几个场景做了分层处理。3.1 缺失值分层处理与季节性填充清洗逻辑的核心思路是区分空值的业务含义。ticket_revenue为空但ticket_type是免票这是正常业务状态如果是大门票类型的景点收入为空那才是真正的数据缺失需要用该景点同月份的平均客单价来推算。import numpy as np free_ticket_mask df[ticket_type] 免票 df.loc[free_ticket_mask, ticket_revenue] 0 paid_mask df[ticket_type] ! 免票 monthly_avg_price df[paid_mask].groupby([spot_name, df[visit_date].dt.month])[ticket_revenue].transform(mean) df.loc[paid_mask df[ticket_revenue].isnull(), ticket_revenue] monthly_avg_price[paid_mask df[ticket_revenue].isnull()]逻辑说明先创建免票掩码把免票景点的收入置为0避免它被算进缺失值。然后对非免票记录按景点名称和月份分组用transform计算同组平均值再把这个平均值填进真正缺失的位置。transform和apply的区别在于transform保持原索引不变后面赋值时不会错位。参数说明groupby里的df[visit_date].dt.month是把日期列提取成月份数值这样季节性客单价差异就能反映在填充值里。如果你手里的数据覆盖多年最好把年份也加进分组条件即groupby([spot_name, df[visit_date].dt.year, df[visit_date].dt.month])避免跨年同比差异被平均掉。3.2 节假日标记的两种做法旅游分析里节假日是个天然分箱变量但很多原始数据里没有“是否是节假日”这一列需要根据日期自己判断。资源里推荐的做法是用工作日转换加法定节假日列表双重判断。from pandas.tseries.holiday import USFederalHolidayCalendar from pandas.tseries.offsets import CustomBusinessDay cn_holidays [2024-01-01, 2024-02-10, 2024-02-11, 2024-02-12, 2024-04-04, 2024-05-01, 2024-05-02, 2024-05-03, 2024-06-08, 2024-09-15, 2024-10-01] df[is_holiday] df[visit_date].dt.date.astype(str).isin(cn_holidays) df[day_of_week] df[visit_date].dt.dayofweek df[is_weekend] df[day_of_week].isin([5, 6]) df[date_type] 工作日 df.loc[df[is_weekend], date_type] 周末 df.loc[df[is_holiday], date_type] 节假日逻辑说明先用一个列表维护当年的法定节假日然后用isin判断每条记录的日期是否命中。dayofweek返回0到6的数字5和6对应周六和周日。最后用三次赋值构造date_type分类字段后续做聚合时直接按这个字段分组即可。参数说明这里的cn_holidays列表需要每年手动维护一次。你可以把补班日期也加进一个调休列表用于处理“周日上班但周一放假”这类特殊周。资源里把这个列表独立成了一个config.py文件方便所有脚本共用。如果数据时间跨度大更省事的做法是直接安装chinese_calendar库一行代码就能判断日期类型。4. 核心分析实战客源结构、季节性波动与停留时长建模数据洗完之后进入分析主流程。这一部分覆盖三类最常见的业务问题全年客流的时间分布规律、不同景点间的客源结构差异、以及停留时长对消费金额的影响。资源里给出的分析脚本不是单点计算而是把结果直接落成透视表和可视化参数方便后续生成报告。4.1 月度接待量趋势与同比增速先把整体趋势算出来。按月聚合游客量再计算环比增速和去年同期对比这是景区经营分析里最基础但最不能省的一步。monthly df.groupby(df[visit_date].dt.to_period(M))[visitor_count].sum().reset_index() monthly[month_str] monthly[visit_date].astype(str) monthly[mom_growth] monthly[visitor_count].pct_change() * 100 monthly[mom_growth] monthly[mom_growth].round(2) display(monthly.tail(12))逻辑说明to_period(M)把日期归一到月周期groupby求和后得到月度总接待量。pct_change计算环比变化率第一行因为没有上期数据自然是空值用round(2)保留两位小数。参数说明如果数据跨了多个年份这里最好把年份拆分后单独算同比。常见做法是先按spot_name和年份分列再在DataFrame里用groupby(spot_name)做透视这样每个景点自己跟去年同期比而不是所有景点混在一起算。4.2 景点客单价与停留时长散点关系接下来分析停留时长和客单价的关联度。直观上停留越久消费越多但不同景点类型差别很大比如自然风景区停留四小时但客单价不到五十主题公园停留三小时客单价却接近三百。用散点图加回归线能看出整体趋势还需要按城市做分面图才能看出差异。spot_stats df.groupby(spot_name).agg({ avg_stay_minutes: mean, ticket_revenue: sum, visitor_count: sum }).reset_index() spot_stats[avg_price_per_visitor] ( spot_stats[ticket_revenue] / spot_stats[visitor_count] ).round(2) top_spots spot_stats.nlargest(10, avg_price_per_visitor) print(top_spots[[spot_name, avg_stay_minutes, avg_price_per_visitor]])逻辑说明agg同时汇总平均停留时长、总收入和总人次再通过除法计算出人均客单价。nlargest(10, avg_price_per_visitor)直接取客单价Top10景点避免了自己排序再head的冗长写法。参数说明这里的客单价计算用的是总门票收入除以总人数整体均值会被大节假日的高客流拉高。更稳健的做法是先用日粒度算每天的客单价再对日客单价取中位数这样不受极端值影响。资源里同时提供了两种口径分别放在daily_price.py和overall_price.py里。4.3 客源结构分析省内与省外的占比透视景点数据里通常还有客源地字段这份资源也模拟了客源地信息。客源结构分析的价值在于判断一个景区的市场辐射半径如果省外游客占比超过四成说明这个景点具备跨省引流能力营销预算可以放在高铁沿线城市。source_pivot df.pivot_table( indexcity, columnsprovince_type, valuesvisitor_count, aggfuncsum, fill_value0 ) source_pivot[total] source_pivot.sum(axis1) source_pivot[outer_ratio] (source_pivot[省外] / source_pivot[total] * 100).round(1) top_cities source_pivot.sort_values(total, ascendingFalse).head(10) print(top_cities[[省内, 省外, total, outer_ratio]])逻辑说明pivot_table以城市为行、客源类型为列对游客量求和。fill_value0保证没有记录的组合显示为0而不是NaN。然后用sum横向求和得到每个城市的接待总量再计算省外游客占比。参数说明aggfunc默认是numpy.mean这里要显式改成sum否则算出来的是平均每天接待量而不是总量。如果客源类型不是二分类而是多级行政区可以把columns换成地级市名称再通过排序找出排前五的外地客源城市。5. 避坑清洗与分析过程四处常见的翻车现场数据分析的学习者最容易在清洗阶段以为自己已经会了实际上坑都埋在前面的细节里。这套资源用下来的过程中我整理了几条高频翻车记录每条都是“现象、原因、解决”三段式你跑代码的时候对照看看。5.1 缺失值不能一删了之聚合结果全错了现象用dropna()把含空值的行删除后月度总收入比预期少了大约一成怎么都跟台账对不上。原因免票景点的ticket_revenue本身就是空值这些行被当成缺失数据直接删掉了导致免票景点完全消失在分析结果里而它们恰恰贡献了一部分二次消费。解决清洗前先区分空值的业务含义。免票景点的收入置为0而不是删除真正缺失的数值型字段用同景点同月份的中位数填充。填充后务必做一次“填充前后总量对比”确认误差在2%以内再继续。5.2 景点名称不归一Top10榜单全是同一家景区的不同叫法现象排序后取客单价Top10景点发现前五名其实都是同一个地方只是原始数据里有的写“某古城”有的写“某古城景区”还有的写“古城文化旅游区”。原因数据来自多个渠道时景点名称没有统一的主数据标准。不同部门录入习惯不同简写和全称混用。解决建一个映射字典把所有别名映射到标准名称。最简单的方式是维护一个Excel映射表用pandas的replace()方法批量替换。替换后再次做分组统计确认不同名称下的记录都合并到了正确的目标景点下。5.3 节假日判断漏掉了调休补班日现象5月6日是工作日但实际是调休补班把这一天当周末处理导致当天的数据特征和同类型日期对比时出现明显偏差。原因只用“周六周日”判断休息日没有考虑法定节假日的调休规则。中国节假日体系里有些周末要上班有些工作日放假。解决维护一个全年补班日期列表判断逻辑改为“is_weekend且不在补班列表里才算休息日”。资源里把补班日期单独放在holiday_config.py里每年更新一次即可。5.4 时间聚合时索引错位pct_change算出了跨行增长率现象按月分组后直接对visitor_count计算环比增速发现第一行有数值后续行的数值明显不对。原因groupby后没有reset_index时间周期仍然是索引而不是普通列pct_change默认按索引顺序计算遇到非单调递增索引就会错位。解决groupby后紧跟reset_index()把时间周期列变成普通列再排序保证时间顺序正确最后计算pct_change。养成“先排序、后聚合、再算环比”的习惯能规避大部分索引错位问题。6. 进阶把分析结果导出成可视化看板与一键复现分析做完不算完能让别人看懂才是真本事。资源里封装了一个可视化脚本把前面几章的分析结果直接输出成HTML页面用Pyecharts画地图和折线图不需要前端基础也能做出能汇报的效果。6.1 用Pyecharts输出区域热度地图先看一个最有演示效果的场景把各地级市的游客接待量画成热力地图颜色越深代表接待量越大。这种图在汇报会上比表格直观得多领导一眼就能看出重点城市。from pyecharts import options as opts from pyecharts.charts import Map city_df df.groupby(city)[visitor_count].sum().reset_index() city_df.columns [city, visitor_count] map_chart ( Map() .add(series_name游客量, data_paircity_df.values, maptype河南) .set_global_opts( title_optsopts.TitleOpts(title各省辖市旅游接待量分布), visualmap_optsopts.VisualMapOpts(max_city_df[visitor_count].max()) ) ) map_chart.render(province_heatmap.html)逻辑说明先把城市和游客量配对成一个二维数组传给Map组件的data_pair参数。maptype河南指定地图区域需要安装对应的地图包才能正常显示。set_global_opts里的visualmap_opts控制颜色映射范围max_参数设置为总览最大值否则默认色阶可能最高只有一天的量。参数说明如果数据覆盖全国景区maptype换成china即可。VisualMapOpts还可以加is_piecewiseTrue把连续色阶切成分段色阶这样在PPT里展示时每一档的颜色和数值区间能直接体现分级思想。6.2 一键复现的工程化组织方式整套资源适合用Jupyter Notebook按章节顺序跑也可以直接用命令行一次跑完全流程。资源里提供了一个run_all.sh脚本把清洗、聚合、分析、可视化四步串成一条命令适合每天有固定批处理需求的运营场景。python 01_clean_data.py python 02_monthly_trend.py python 03_spot_ranking.py python 04_heatmap_render.py echo 分析完成输出文件在当前目录的output/文件夹下逻辑说明四个脚本依次执行01负责写入清洗后的临时文件02和03读取清洗结果生成趋势图和排名表04读取处理好的地理数据渲染HTML地图。参数说明如果临时文件不想覆盖原数据可以在01脚本里加output_dir参数把清洗结果写到独立的output目录下。批处理建议加try/except逻辑某一步失败时把错误日志写入err.log不中断整条链路。6.3 结论可视化时注意比例陷阱最后提一个汇报时的细节地图色阶默认是从0到最大值线性映射如果最大值和次大值差距巨大地图上除了最大城市外全部淡色视觉上会误导为其他城市都没有客流。我一般会在VisualMapOpts里手动指定split_number和max_或者用对数归一化处理。从那以后我每次做地图类可视化都会先看一眼数值分布如果发现中位数和最大值差了三个数量级就强制改成对数色阶再出图。分析数据最怕的不是算错而是算对了但画出来让人误读。这一点也是这套资源里脚本设计得比较周到的地方map组件的参数都留了手动调整的入口希望帮到你调出真正能说明问题的图。本文还有配套的精品资源点击获取