资讯详情

旅游景点数据分析实战:从脏数据到可复现分析流水线的完整指南

📅 2026/11/2 15:43:49 | 华诺云谱 👁 阅读
旅游景点数据分析实战:从脏数据到可复现分析流水线的完整指南
简介面向数据分析初学者与旅游行业从业者的实战型项目资料围绕去哪儿网国庆景点数据展开覆盖从数据清洗、统计分析到可视化呈现的完整流程帮助读者理解如何用数据洞察游客分布、门票销量与景区星级偏好并支撑定价、营销等业务决策。压缩包共7个文件约79KB以5个html可视化页面、1个xlsx原始数据表和1个py分析脚本为主html用于呈现热力图、柱状图与饼状图结果xlsx存放结构化景点数据py脚本承担预处理与分析计算。已有1385人学习下载。读者可据此复现各省份景点分布热力图、门票销售额TOP20柱状图、景区星级比例饼状图等图表并参考脚本中的缺失值、异常值处理思路快速搭建一套可复用的旅游数据分析小项目。1. 旅游景点数据分析实战一份能直接跑通的分析包到底长什么样拿到一份标着“旅游景点数据分析实战”的资源包多数人第一反应是翻目录看有没有现成结论结果翻完发现全是脚本、字段说明和几张半成品图表心里就凉了半截。我一开始也这样直到把它当成一套可复用的分析骨架来拆才发现价值不在结论而在从原始字段到可解释指标的那条链路。这份资源面向的是想用真实业务口径练手的数据分析从业者尤其是手里有景点客流、票务、评价类数据却不知道怎么串起来的人。它解决的不是“教你画图”而是“给你一套能改参数、能换数据、能复现的景点分析流程”。数据分析这四个字在这里不是泛泛的口号而是落到字段清洗、指标定义、分组对比和异常排查上的具体动作。适合谁适合已经会写基础 pandas、但一遇到真实脏数据就卡住的新手也适合想快速搭一套景点分析模板的熟手。2. 拆开资源包字段口径与指标设计先立住2.1 先看清数据长什么样再谈分析景点类数据最常见的形态是“一行一条记录”但记录粒度差别很大有的按天按景点汇总客流有的按票种按订单明细有的干脆是点评文本加评分。资源包里的字段说明通常不会写得太细我一般先做一件事——把每个字段的取值分布和空值率拉出来判断它到底是维度字段还是度量字段。维度字段决定分组方式度量字段决定能算什么指标。这一步不做后面所有图表都是空中楼阁。import pandas as pd df pd.read_csv(scenic_raw.csv, encodingutf-8) # 先看整体规模和字段类型 print(df.shape) print(df.dtypes) # 空值率与唯一值数量快速区分维度/度量 profile pd.DataFrame({ null_rate: df.isna().mean().round(4), nunique: df.nunique(), sample: df.iloc[0] }) print(profile.sort_values(null_rate, ascendingFalse))这段代码的逻辑是先建立“数据体检表”null_rate告诉你哪些字段缺失严重、能不能用nunique帮你判断字段是低基数维度如景点名称、票种还是高基数标识如订单号。参数上encoding要按实际文件改遇到乱码先试gbksample取第一行只是给人看的真正判断要靠分布。常见翻车点是直接把订单号当维度去 groupby结果每个组只有一条记录图表全是噪点。2.2 指标不是拍脑袋先对齐业务口径景点分析里最容易吵架的三个指标是客流量、客单价和好评率。客流量到底算入园人次还是订单数客单价是含退票还是不含好评率的分母是全部评价还是有效评价——这些口径不先定死后面所有对比都不可信。我的做法是在脚本顶部集中写一份口径字典所有计算都引用它改口径只改一处。# 口径配置集中管理避免散落在各处 METRIC_CONFIG { visitor_col: ticket_count, # 入园人次字段 order_col: order_id, # 订单唯一标识 amount_col: paid_amount, # 实付金额 refund_flag: is_refund, # 退票标记 1退 rating_col: score, # 评分字段 valid_rating_min: 1, # 有效评分下限 } def calc_core_metrics(df, cfg): valid df[df[cfg[refund_flag]] 0] visitors valid[cfg[visitor_col]].sum() orders valid[cfg[order_col]].nunique() revenue valid[cfg[amount_col]].sum() rated valid[valid[cfg[rating_col]] cfg[valid_rating_min]] good_rate (rated[cfg[rating_col]] 4).mean() return { visitors: visitors, orders: orders, avg_ticket: revenue / visitors if visitors else 0, good_rate: round(good_rate, 4), }逻辑说明先剔除退票记录再算指标避免退款金额污染收入口径avg_ticket用人次做分母而不是订单数这是景点场景和电商场景的关键差别。参数上valid_rating_min控制有效评分下限有的数据用 0 分表示未评价不排除会把好评率拉低。这里没有写死任何景点名或平台名换一份同结构数据直接能跑。2.3 分组维度怎么选决定图表有没有信息量景点数据的分组维度通常有三类时间日/周/节假日、空间景区/区域/入口、属性票种/渠道/客群。资源包里如果只给了一个date字段别急着按天画折线先看时间跨度。跨度小于三个月按天画出来全是锯齿跨度超过一年按天画又太密。我一般先算时间跨度再决定粒度这一步用几行代码就能判断。df[date] pd.to_datetime(df[date]) span_days (df[date].max() - df[date].min()).days # 按跨度自动选粒度短跨度按天长跨度按月 if span_days 90: df[period] df[date].dt.date elif span_days 730: df[period] df[date].dt.to_period(M).astype(str) else: df[period] df[date].dt.to_period(Q).astype(str) trend df.groupby(period).agg( visitors(ticket_count, sum), orders(order_id, nunique) ).reset_index() print(trend.head())这段的关键是span_days这个判断阈值90 天和 730 天不是金科玉律但能避免“按天画一年”这种典型误用。to_period(M)转成月份字符串是为了后续排序稳定直接对 datetime 做 groupby 有时会遇到时区或格式问题。分组维度选错后面再漂亮的图也只是把噪声放大。3. 从清洗到可视化一条能复现的分析流水线3.1 脏数据的四类典型问题与处理顺序景点数据的脏通常集中在四类日期格式不统一、金额带货币符号、景点名称有别名、重复订单。处理顺序有讲究——先统一类型再处理文本最后去重。顺序反了去重时字段还没标准化会把同一景点当成两个。我习惯把清洗写成独立函数每步打印前后行数方便回溯。import re def clean_scenic_data(df): before len(df) # 1. 日期统一 df[date] pd.to_datetime(df[date], errorscoerce) df df.dropna(subset[date]) # 2. 金额去符号转数值 df[paid_amount] ( df[paid_amount].astype(str) .str.replace(r[^\d.], , regexTrue) .replace(, 0) .astype(float) ) # 3. 景点名称去空格、统一全半角 df[scenic_name] df[scenic_name].astype(str).str.strip() # 4. 按订单号去重保留最新记录 df df.sort_values(date).drop_duplicates( subset[order_id], keeplast ) print(f清洗前 {before} 行清洗后 {len(df)} 行) return df逻辑说明errorscoerce把无法解析的日期变成 NaT 再删掉比直接报错更稳金额正则[^\d.]会保留数字和小数点但要注意千分位逗号会被去掉这是符合预期的。去重保留last是因为后到的记录往往修正过。参数上subset用订单号而不是整行整行去重对字段顺序敏感容易漏。这一步跑完如果行数掉得离谱先别继续回头查日期解析失败率。3.2 客流趋势与同比环比怎么算才不误导趋势图谁都会画但景点场景有个坑节假日效应会让同比失真。比如今年春节在二月、去年在一月直接按月同比会把增长算错。稳妥做法是同时给“按月趋势”和“按节假日标记的趋势”后者需要一张节假日对照表。资源包里如果没有我一般用简化的周末/非周末标记先顶上至少能看出工作日和周末的差异。df[is_weekend] df[date].dt.weekday 5 # 按月汇总并计算环比 monthly df.groupby(df[date].dt.to_period(M)).agg( visitors(ticket_count, sum) ).reset_index() monthly[mom] monthly[visitors].pct_change().round(4) # 周末 vs 工作日对比 week_split df.groupby(is_weekend)[ticket_count].mean().round(2) print(monthly.tail(6)) print(week_split)pct_change()算环比第一个月会是 NaN这是正常的别用 0 填充否则图表会误导。is_weekend用weekday 5判断周一到周五是 0 到 4。如果数据里有明确的节假日字段优先用它周末标记只是退而求其次。这里要提醒一句环比在数据首月缺失时画图要么从第二个月开始要么在图注里说明别让读者以为首月零增长。3.3 可视化不是堆图表先定“一张图回答一个问题”资源包里的图表脚本往往一次画好几张我建议拆开用每张图只回答一个问题趋势图回答“整体在涨还是跌”箱线图回答“不同景点离散程度差多少”热力图回答“星期几和哪个入口最忙”。以景点间对比为例用箱线图比柱状图更能看出异常值。import matplotlib.pyplot as plt top_scenics df[scenic_name].value_counts().head(8).index subset df[df[scenic_name].isin(top_scenics)] fig, ax plt.subplots(figsize(10, 5)) subset.boxplot(columnticket_count, byscenic_name, axax, rot45) ax.set_title(Top 8 景点单日客流分布) ax.set_ylabel(入园人次) plt.suptitle() plt.tight_layout() plt.savefig(scenic_box.png, dpi150)value_counts().head(8)先筛出记录最多的景点避免小样本景点把箱线图拉花by指定分组列rot45防止景点名重叠。plt.suptitle()是去掉 pandas 默认加的冗余标题这个细节很多人不知道图出来总带一行莫名其妙的文字。保存用dpi150兼顾清晰度和文件大小。图表不是越多越好一张图能说清一个对比就够了。4. 避坑与排查五个真实会卡住你的地方4.1 日期解析静默失败趋势图直接少一半现象趋势图时间轴只到某个月就断了后面数据凭空消失。原因pd.to_datetime遇到混合格式如2024/1/5和2024-01-05混用时部分行变成 NaT被dropna悄悄删掉。解决解析后先算失败率超过 5% 就停下来看原始值必要时用formatmixed或分格式解析。raw_dates df[date].copy() parsed pd.to_datetime(raw_dates, errorscoerce) fail_rate parsed.isna().mean() print(f日期解析失败率: {fail_rate:.2%}) if fail_rate 0.05: print(raw_dates[parsed.isna()].head(10))4.2 金额字段混入“免费”字样求和直接报错现象paid_amount求和时报TypeError或结果异常大。原因字段里混了“免费”“-”这类文本astype(float)直接崩或者被错误转成 NaN 后求和忽略。解决先做正则清洗再转数值转换后用isna().sum()确认没有意外缺失别用fillna(0)掩盖问题。4.3 景点别名导致分组翻倍现象同一个景点在分组结果里出现两次比如“某湖景区”和“某湖景区东门”。原因名称字段没有标准化入口信息混进了景点名。解决建立别名映射表用replace统一映射表单独存一个字典别在代码里散落 if-else。ALIAS_MAP { 某湖景区东门: 某湖景区, 某湖景区西门: 某湖景区, } df[scenic_name] df[scenic_name].replace(ALIAS_MAP)4.4 好评率分母用错结论完全反过来现象某景点好评率算出来只有 60%但人工看评价明明大多是好評。原因把未评价记录评分为 0 或空算进了分母。解决先过滤有效评分再算比例有效下限写进口径配置别在多个脚本里各写各的。4.5 同比环比在首月出现 inf 或异常值现象环比列出现inf或极大值。原因上月值为 0 时做除法。解决计算前把 0 替换为 NaN或者用pct_change(fill_methodNone)并单独标注首月图表上对缺失月份留白而不是画成 0。提示这五条里日期解析和金额清洗是最容易“静默出错”的不会报错但结果全错建议每次换数据都先跑一遍体检代码。5. 进阶用法把一次性分析变成可复用模板走到这一步脚本能跑通、图能出来但离“下次换份数据还能用”还差一层。我的习惯是把整条链路收成一个配置驱动的入口数据路径、字段映射、口径参数全放一个字典主函数只负责按顺序调用清洗、指标、分组、出图。这样下次拿到新景点数据改配置就行不用翻脚本找硬编码。def run_pipeline(config): df pd.read_csv(config[path], encodingconfig.get(encoding, utf-8)) df clean_scenic_data(df) metrics calc_core_metrics(df, config[metric]) trend build_trend(df, config[time_granularity]) save_charts(trend, config[output_dir]) return {metrics: metrics, rows: len(df)} CONFIG { path: scenic_raw.csv, encoding: utf-8, metric: METRIC_CONFIG, time_granularity: auto, output_dir: ./out, } result run_pipeline(CONFIG) print(result)参数说明time_granularity设成auto时走前面按跨度判断的逻辑也可以强制day或monthoutput_dir统一管理图片输出避免散落在当前目录。这个入口的价值在于可验证——每次跑完打印行数和核心指标和上次对比行数突变或指标跳变就是信号。验证方法我一般用“双跑对比”同一份数据跑两次结果必须完全一致再拿一份手工算过的小样本比如 20 行跑一遍核对客流量和客单价是否对得上。小样本核对是最便宜的黑匣子检测比盯着大图看半天靠谱。验证项做法通过标准可复现性同数据跑两次指标完全一致口径正确20 行手工样本客流量/客单价对得上清洗完整性对比清洗前后行数掉行有合理解释图表可读换人看一张图能说出图在回答什么血泪经验是我曾经因为没做小样本核对把一个“退票未剔除”的口径错误带进了汇报图很漂亮但数字全偏。从那以后我每次换数据源都强制先跑一遍 20 行手工样本核对再跑全量。这个习惯比任何高级模型都值钱。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑