资讯详情

旅游景点数据分析实战:从口径定义到可视化看板

📅 2026/9/29 1:20:12 | 华诺云谱 👁 阅读
旅游景点数据分析实战:从口径定义到可视化看板
简介旅游景点数据分析实战是一套面向数据分析初学者与旅游业从业者的实操型资源围绕去哪儿网国庆期间景点公开数据完整演示了从数据清洗、统计分析到可视化呈现的业务流程。压缩包共七个文件包含五份HTML可视化报告如省份分布热力图、门票销售额柱状图、景区星级比例饼状图、一份Excel源数据与一个Python分析脚本整体约79KB结构轻量但环节齐全。目前已有1379人学习适合希望借助真实业务场景快速上手数据处理和图表绘制的读者。通过该资源读者既能学习Pandas、Matplotlib等工具的代码写法也能参照成品图表理解时间序列趋势、游客分布与销售排行从而将数据结论直接转化为景区运营和精准营销的决策依据。同时资源中清晰的流程拆分与可视化结果便于对比验证是入门旅游数据分析时不可多得的实践样例。1. 旅游景点数据分析先钉死口径再谈模型同一份景区数据有人算出国庆客流同比下降3%有人算出增长12%而且都对。差别不在算法在“同比”的口径一边取公历去年的同一天一边取调休后真正可比的节假日一边用售票数一边用闸机入园数。旅游景点数据分析的实战第一关从来不是Python或SQL而是把口径先钉死。这份资源是完整的“旅游景点数据分析实战”流程从多源数据汇聚、字段治理、清洗加工、指标定义到可视化看板与口径维护一起打通。面向刚入行的数据分析师也面向被Excel透视表折磨的景区运营。按资源的步骤走你能独立复现一套景点日常分析与节假日复盘的标准流程。2. 数据获取与字段治理把多源数据拼成一张能用的宽表2.1 数据来源的取舍与边界旅游景区数据天然分散。资源里用的是三类最常见来源景区票务系统导出的每日客流与收入、OTA平台的评分与评论量快照、维护景区主数据的字典表。实际项目里还会补地图热力和搜索指数但那类数据只有相对值没有绝对口径只能做辅助参考不建议和票务数据混进同一张指标表。先说边界。票务系统是最准的但往往拿不到实时接口OTA平台评分更新慢而且一个景区在多个平台都有页面评分需按景区维度先聚合主数据字典则要有人维护景区名称、等级、所属城市的对应关系否则后面所有维度下钻都会出错。资源里给的是脱敏样例数据和能直接跑通的脚本重点不在数据量大小而在把流程规范化。2.2 字段设计与口径注解我拿到一份数据第一件事不是跑describe而是先列字段口径表。同一个“日期”字段可能是售票日期、入园日期或OTA统计截止日期混用会让后续所有分析失真。字段类型口径说明来源date日期自然日含节假日标记票务导出调休日历scene_id字符串景区唯一ID主键组成部分主数据字典scene_name字符串景区名称主数据字典city / province字符串行政区域维度主数据字典level字符串5A / 4A / 4A以下文旅部门公示ticket_price浮点成人票挂牌价单位元票务系统visitor整型当日入园总人次含免票人群闸机系统revenue浮点当日门票二次消费收入单位元营收系统avg_score浮点OTA近30天平均评分0~5分OTA聚合快照comment_cnt整型OTA近30天新增评论数OTA聚合快照这里最容易被忽略的是“口径注解”。比如visitor字段必须写明“含免票”还是“只含购票”因为景区免票人群占比可以到三成后续算客单价时差异极大。我在实际项目里的习惯是字段表作为独立Markdown文档放进仓库和代码一起维护。2.3 多源合并与主键去重数据源之间靠scene_id和date关联。合并前一定先处理重复同一个景区同一天可能被闸机系统重复上报OTA页面也可能一天抓了多次不按主键去重后续聚合出来的数字就是错的。import pandas as pd # 读取三类数据源票务导出、OTA快照、景区主数据 sales pd.read_csv(data/sales_daily.csv, encodingutf-8-sig, parse_dates[date]) ota pd.read_json(data/ota_reviews.json) scene pd.read_excel(data/scene_dict.xlsx, sheet_namescenes) # 去重同一景区同一天可能重复上报保留最后一条 sales sales.drop_duplicates(subset[scene_id, date], keeplast) # OTA只保留每个景区最新一次评分快照 ota (ota.sort_values(date) .drop_duplicates(subset[scene_id], keeplast) .rename(columns{score: avg_score, count: comment_cnt})) # 宽表拼接票务为主表补景区维度和OTA评价 df (sales.merge(scene[[scene_id, scene_name, city, province, level]], onscene_id, howleft) .merge(ota[[scene_id, avg_score, comment_cnt]], onscene_id, howleft)) print(df.shape, df[scene_id].nunique())逻辑说明第一步drop_duplicates里keeplast前提是同一景区同一天的重复记录中最后一条数据最完整如果你们的业务特征是“第一次上报的才准”就改成keepfirst这是个业务决策不是技术默认值。第二步OTA聚合先按日期排序再保留每个景区最后一条避免一个景区多个OTA页面分数打架。第三步用left joinexcel文件里的主数据是维表不能丢行。encodingutf-8-sig在中文Windows环境下比utf-8稳主要防止CSV里的中文在Excel再次打开时乱码。合并完成后先检查匹配率。用left join后维表字段为空的通常是scene_id在主数据字典里没登记这种脏数据在清洗阶段要单独标记而不是静默丢失。3. 数据清洗实战时间、客流、价格里最常见的四类脏数据3.1 时间字段的标准化与节假日标注景区数据的时间字段永远在打架票务系统导出是“2024/5/1”ODS层可能是“2024-05-01 00:00:00”还有个把日期写成文本“20240501”的。多头格式不统一后面一切按日聚合都要出问题。# 混合格式统一解析解析失败置为 NaT 再人工复核 df[date] pd.to_datetime(df[date], formatmixed, errorscoerce)formatmixed在pandas较新版本里会自动尝试多格式解析老版本则要分批处理比如先pd.to_datetime(series, format%Y-%m-%d, errorscoerce)再对无法解析的部分试第二种格式。errorscoerce保证未知格式不会让整个任务崩掉。之后date为空的行单独导出人工复查大部分是导出的Excel单元格格式问题不是真的没有数据。节假日标注是旅游分析里必不可少的一步因为景区客流峰值几乎全在节假日。国庆、五一还要手动按当年调休补齐不能只标10月1日到7日有些年份放假安排有细微差别。这一列is_holiday直接影响后续所有黄金周对比和“工作日/节假日”分组统计。3.2 客流与营收的异常值识别客流的分布是右偏的。平时几千人五一当天冲上五万用均值加减三倍标准差做异常检测会把真实峰值当异常删掉。我在项目里一般用分位数做阈值。# 每个景区独立计算上限99分位数上浮50%超过则置空待审 df[visitor_cap] df.groupby(scene_id)[visitor].transform( lambda x: x.quantile(0.99) * 1.5) df.loc[df[visitor] df[visitor_cap], visitor] np.nan按景区分组计算是因为不同景区的容量差异巨大一个城市公园的2万人和故宫的2万人含义完全不同。quantile(0.99)*1.5这个倍数没有理论公式属于经验参数资源里默认1.5倍你换数据集后应该先画箱线图看分布再决定。超过阈值的先置空不要直接删行留到缺失值步骤统一处理方便后面追溯。营收字段还要做负值清洗票务系统常有退票记录导致当天营收为负数。业务上允许的但分析时必须先按景区日期聚合成净收入再进指标层否则算客单价会出现负值这种没法向业务解释的数字。3.3 价格与评分字段的校正ticket_price的脏数据集中在单位不统一有的景区接口返回“80”表示元有的返回“8000”表示分OTA评分偶尔解析出6.8分或“暂无评分”文本。这种问题写进清洗函数里做区间过滤。# 门票价格区间校验低于1元或高于500元的置空待审 df.loc[(df[ticket_price] 1) | (df[ticket_price] 500), ticket_price] np.nan # OTA评分必须是0~5分6.2那种直接置空 df.loc[(df[avg_score] 0) | (df[avg_score] 5), avg_score] np.nan区间的上下限不是拍脑袋1元以下基本是0元票或数据错误500元以上基本是套票或误填。注意这类过滤一定要把边界值保留比如刚好0元的免票政策在部分景区是存在的你会需要单独标记而不是一刀切置空。评分同理5分制是行业默认但有些平台用10分制接进来之前必须先做归一。3.4 缺失值的处理策略旅游数据的缺失最常见的三种下雨天闸机离线导致客流缺失、新景区没有OTA评分、非售票日没有营收数据。策略不能一刀切删行。# 日期和主键不能为空直接删除其余指标按景区中位数回填 df df.dropna(subset[date, scene_id]) fill_cols [visitor, revenue, ticket_price, avg_score, comment_cnt] for col in fill_cols: df[col] df.groupby(scene_id)[col].transform( lambda s: s.fillna(s.median()))先保主键完整再处理指标列。按景区中位数回填比全表中位数好因为不同景区的量级完全不同。雨天的visitor缺失不能直接用中位数填那样会掩盖天气对客流的影响更合理的做法是标记is_rainy后用同景区同星期类型工作日/周末/节假日的历史均值回填。资源里给的是默认中位数方案因为依赖的气象表不是每个项目都能方便拿到。4. 指标体系与SQL聚合把明细数据变成可交代的业务结论4.1 指标分层从北极星指标到诊断指标明细数据清洗完不能直接扔给业务看。我在实践里习惯把指标分成三层每一层对应不同汇报场景。层级指标示例计算口径使用场景L1 北极星日均客流量、景区总收入按自然周/月汇总管理层周报L2 运营层周环比增速、峰谷比、客单价、评论转化率基于L1派生运营复盘L3 诊断层单日承载率、区域TOP5、评分分布拆分维度后计算定位问题原因北极星指标不能多一个客流一个收入就够。L2的峰谷比是“节假日峰值客流÷平日均值”用来评估景区的承载力波动这个值越高说明淡旺季分化越严重直接影响营销节奏安排。客单价的定义要写清楚是“总收入÷总客流”还是“门票收入÷购票客流”前者是经营口径后者是票务口径两个数差了可能接近一倍。4.2 SQL聚合实战周环比、峰谷比、客单价清洗后的明细表命名为cleansed_visit_fact按城市维度和周维度做聚合。WITH daily_metric AS ( SELECT city, date, SUM(visitor) AS visitor, SUM(revenue) AS revenue, SUM(comment_cnt) AS comment_cnt FROM cleansed_visit_fact WHERE date DATE 2024-01-01 GROUP BY city, date ), weekly AS ( SELECT city, DATE_TRUNC(week, date) AS week_start, SUM(visitor) AS visitor, SUM(revenue) / NULLIF(SUM(visitor), 0) AS avg_spend FROM daily_metric GROUP BY city, DATE_TRUNC(week, date) ), with_prev AS ( SELECT city, week_start, visitor, avg_spend, LAG(visitor, 1) OVER (PARTITION BY city ORDER BY week_start) AS prev_visitor, LAG(avg_spend, 1) OVER (PARTITION BY city ORDER BY week_start) AS prev_spend FROM weekly ) SELECT city, week_start, visitor, prev_visitor, ROUND((visitor - prev_visitor) * 100.0 / NULLIF(prev_visitor, 0), 2) AS visitor_wow_pct, avg_spend FROM with_prev ORDER BY city, week_start;这段SQL是周报的核心拆开看三个CTE分别做了三件事。daily_metric把明细表聚合到城市日期这一步关于visitor用SUM合并如果之前清洗阶段没按景区去重这里就会多算一遍。weekly再聚合到周粒度并计算客单价avg_spendNULLIF(SUM(visitor), 0)防止除零——一旦某城市某周客流为0MySQL会返回NULL而不是报错这也是个细节。with_prev用LAG窗口函数取上一周数据PARTITION BY city保证每个城市自己和自己比不会被别的城市串数据。等于说一张周报上同时能看到当前值和上周环比。实际调参时要注意DATE_TRUNC在MySQL里的替代写法是DATE_FORMAT(date, %x-%v)PostgreSQL和ClickHouse的语义也有细微差别资源里按标准SQL给出落地时先拿一条数据验证周起始日是不是周一。峰谷比的计算不依赖SQL读取聚合结果后一行代码peak_valley_ratio df[visitor].max() / max(df.groupby(date)[visitor].mean().median(), 1)分母用客流中位数而不是均值因为节假日的极端峰值会把均值拉高导致峰谷比被低估。这里我一般会在代码里加注释否则三个月后回看没人记得这个分母为什么不用均值。4.3 指标口径如何防止下一次翻车口径光写在人的脑子里没用必须落地到代码仓库。我的做法是在项目根目录放一个metrics_catalog.yaml每个指标一行包含指标名、计算公式、数据来源、更新频率、负责人。分析报告里引用指标名的同时带上指标ID例如visitor_wow_pct任何人看到编号都能回查到定义。这份资源里同样包含了一套口径定义模板。实际操作中粒度要小哪怕“客单价”这一个指标都要写明分子分母来自哪张表用了哪个字段哪天开始生效。因为没有口径说明的指标就是给未来的自己挖坑。5. 可视化实践的常见问题与排查图表失真、坐标轴误导与口径错位5.1 图表选择与看板布局旅游景点分析里图表选择有相对固定的套路客流趋势用折线图区域对比用柱状图各景区评分分布用箱线图游客来源地分布用地图热力。看板则是给运营每天早上看一屏必须把三个问题顶到最上面昨天整体客流多少、对比上周同期涨还是跌、哪些景区异常。布局上我习惯采用“总分”结构顶部放核心KPI卡片中部放趋势和对比底部放明细表。KPI卡片数字后面永远跟着同比或环比箭头不让业务看绝对值猜趋势。刷新的策略要单独考虑数据源的底层表如果是T1批次看板做了分钟级刷新只会让用户反复看到同一个旧数字刷新频率和数据新鲜度保持一致比强行实时更有用。5.2 pyecharts核心配置与Excel交叉验证from pyecharts.charts import Line, Bar from pyecharts import options as opts line Line(init_optsopts.InitOpts(width1200px, height600px)) line.add_xaxis([str(d) for d in day_list]) line.add_yaxis(2023年, value_2023, is_smoothTrue, symbol_size6) line.add_yaxis(2024年, value_2024, is_smoothTrue, symbol_size6) line.set_global_opts( title_optsopts.TitleOpts(title国庆假期日客流同比), yaxis_optsopts.AxisOpts(min_0), # 明确定义Y轴从0开始 tooltip_optsopts.TooltipOpts(triggeraxis), legend_optsopts.LegendOpts(pos_top5%), ) line.render(compare_holiday_visitor.html)核心参数是yaxis_opts里的min_0这段代码第7行注释为什么必须写因为pyecharts默认会自动缩放Y轴2024年峰值是5万2023年峰值是4万2自动缩放后200%的差距会被视觉放大成翻倍。is_smoothTrue只是把折线变平滑不会改变任何数值语义。和Excel复核的方式是把同一份聚合结果导出CSV在Excel里插入图表对比两张图的数值是否一致。这个习惯能拦住一大部分“代码算错但图看着合理”的问题。5.3 四个高频踩坑记录坑一图表的“暴涨暴跌”业务不认账。现象看板上2024年客流曲线在10月2日显示直线拉升业务反馈当天没搞大活动数字夸张了。原因Y轴自动从8000开始而不是从0开始把正常的节假日爬坡放大成了爆发。解决所有柱状图和折线图的Y轴min_固定为0同时把同比数字直接标在图表上方让看图的人第一眼看到趋势和幅度而不是被坐标刻度带偏。我每次做完图都会把min_0作为默认值写进模板宁要平直真实不要刺激但失真。坑二同一个景区OTA显示4.6分客服却一天接到五个差评投诉。现象运营按4.6分判断“口碑良好”实际上近30天4000条评论里差评占比15%。原因avg_score用平均数个别极端一星差评被大量好评稀释而且这个字段在我这份资源的口径里已经明确是OTA近30天平均分平台本身可能做了筛选。解决不只报告均值还要同时看评分分布用箱线图或者“差评率评分≤2的比例”作为辅助指标把“平均分”改成“好评率/差评率”的口径。坑三今年中秋、去年国庆做同比数字失真。现象某景区中秋3天客流同比“下滑40%”业务当场质疑结果发现去年同期是国庆7天基数完全不在一个量级。原因节假日长度不同、调休补班日不同公历日期对不上农历节日。解决同比对比前先确认两个区间天数一致用“日均客流”而不是“总客流”做对比在图表标题注明“国庆(7天) vs 中秋(3天)日均对比”第一时间说清口径。坑四看板显示“今日客流”在上午9点还是昨天数据。现象领导在早会上看到的数字和昨天日报对不上。原因底层表每天凌晨2点才写入前一日数据看板却按小时刷新刷了半天刷的还是旧表。解决在看板右上角标注数据截止时间例如“数据截至2024-10-06 23:59”把可视化自动刷新频率和数据产出时间对齐T1的数据没必要做准实时刷新——除了给用户造成“数据在变”的错觉外没有其他价值。6. 进阶质量校验、自动更新与从“看数”到“用数”数据管道跑通只是第一步。我每次上线一套分析流程都要再加一道质量校验闸门强制在聚合入库前跑一遍。这个函数只输出结果不替你做决定def validate_report(df: pd.DataFrame) - dict: checks { 连续日期缺口: df[date].isna().sum(), 重复主键: df.duplicated(subset[scene_id, date]).sum(), 负客流: (df[visitor] 0).sum(), 负收入: (df[revenue] 0).sum(), 缺失率超5%的列: [c for c in df.columns if df[c].isna().mean() 0.05], } return checks四条检查覆盖了最致命的四类问题时间断裂、主键重复、业务负值、缺失扩散。校验结果超过阈值时脚本用非零退出码终止阻止脏数据进入下一步。从那以后我每次开发新分析流程都强制先写这四条校验再写业务逻辑这份资源里的脚本本身就是按照这个顺序组织的。自动更新的方式常见做法是把清洗脚本设计成幂等重跑先删临时表再写入或者按日期增量写入保证同一份脚本跑两次不产生重复数据。调度的落点可以是服务器计划任务也可以是本地定时触发核心约束只有一个——任务启动前检查上游数据是否已就绪否则会拿着一半的数据跑完整条链路。关于从“看数”到“用数”我的习惯是每份报告结尾必须回答三个问题涨了还是跌了、为什么、明天怎么办。只看第一个问题那是数据搬运工三个问题都落到具体的运营动作上数据分析才算闭环。比如发现某景区峰谷比超标下一步动作就是建议错峰票价和分时预约而不只是出一张趋势图。资源里提供的是一套可以改路径直接跑通的完整流程。你拿到手后先按原样复现一遍确认每个环节都有输出再替换成自己的数据。复用的时候重点改三处数据源文件的路径与字段名、quantile阈值、节假日日期列表。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑