Python旅游推荐数据分析可视化:从CSV到交互看板实战

发布时间:2026/10/18 19:08:41

Python旅游推荐数据分析可视化:从CSV到交互看板实战 简介这是一套面向计算机相关专业毕设学生与Python项目实战学习者的旅游推荐数据分析可视化系统采用pythonDjangomysql协同过滤算法构建可直接作为毕业设计、课程设计或期末大作业使用。系统分为用户端与管理员端用户可完成登录注册、个人信息管理、景区浏览、评分收藏及个性化推荐管理员可对景区信息、景区类型分类及用户资料进行增删改查与行为记录管理功能链路完整。压缩包共422个文件约9.36MB以174个js、59个css、40个json、27个py、18个html及1个sql数据库脚本为主另含图片、字体与说明文档前端资源与后端逻辑分层清晰。项目经过严格调试确保可运行并附带数据库与部署说明便于快速搭建环境、理解协同过滤推荐流程与Django项目结构。目前已有135人学习下载适合需要完整源码参考与实战练习的读者。1. 旅游推荐数据分析可视化从一份 CSV 到能讲故事的看板手里拿到一份旅游景点数据字段无非是景点名称、城市、评分、热度、门票价格、评论数这些看起来平平无奇。但真正做过的人都知道把这份数据变成一张能回答“哪个城市值得去”“什么季节人少价低”“高评分景点是不是一定贵”的看板中间要跨过清洗、聚合、推荐打分、可视化四道坎。这个 python 项目实战之旅游推荐数据分析可视化核心就是用 pandas 做数据加工、用推荐算法给景点排个序、再用可视化把结论摆出来最后打包成能跑起来的工程。它适合两类人一类是刚学完 pandas 和 matplotlib、想找个完整项目练手的另一类是想把数据分析结果做成可交付看板、给非技术同事看的。下面我按自己实际搭这套流程的顺序把每一步的参数、坑和判断标准讲清楚。2. 数据清洗与字段设计脏数据不处理后面全是玄学旅游数据最大的特点是来源杂。爬来的、导出的、手工整理的混在一起同一个城市可能写成“杭州”“杭州市”“浙江杭州”评分列里混着“4.5分”“4.5”“暂无评分”。如果不在第一步统一后面 groupby 出来的结果会让你怀疑人生。我一般会先做字段盘点再定清洗规则最后落成一份干净的宽表。2.1 先看清字段类型和缺失分布拿到数据别急着 dropna先跑一遍 info 和缺失率心里有数再动手。下面这段是标准开场import pandas as pd import numpy as np # 读入原始数据注意编码旅游数据常见 utf-8 和 gbk 两种 df pd.read_csv(travel_raw.csv, encodingutf-8) # 看字段类型、非空数量 print(df.info()) # 缺失率排序优先处理缺失高的列 missing df.isnull().mean().sort_values(ascendingFalse) print(missing[missing 0]) # 看评分列到底长什么样决定怎么转数值 print(df[score].astype(str).str.strip().value_counts().head(20))逻辑说明info()告诉你哪些列是 object、哪些是 floatobject 类型的数值列基本都要转。缺失率排序是为了决定策略——缺失超过 60% 的列直接丢30% 到 60% 的考虑用中位数或众数填低于 30% 的按业务填。评分列先转成字符串再看取值分布是因为直接astype(float)遇到“暂无评分”会整列报错先看清有哪些异常值再写替换规则。参数说明encoding一定要试读出来乱码就换 gbk 或 utf-8-sig。value_counts()后面加head(20)是防止取值太多刷屏旅游数据里评分通常就几种写法看前 20 个足够。2.2 城市名和评分列的标准化城市名不统一是旅游数据的老毛病。我的做法是建一张映射表而不是写一堆 if-else。评分列则用正则把数字抠出来。# 城市名标准化去掉“市”“省”后缀统一到标准城市名 city_map { 杭州市: 杭州, 浙江杭州: 杭州, 苏州市: 苏州, 江苏苏州: 苏州, 成都市: 成都, 四川成都: 成都, } df[city] df[city].astype(str).str.strip() df[city] df[city].replace(city_map) # 兜底去掉末尾的“市”字 df[city] df[city].str.replace(r市$, , regexTrue) # 评分标准化抽出数字无法解析的置为 NaN df[score] pd.to_numeric( df[score].astype(str).str.extract(r(\d\.?\d*))[0], errorscoerce ) # 用中位数填充缺失评分比均值抗极端值 df[score] df[score].fillna(df[score].median()) # 门票价格同样处理注意“免费”要转成 0 df[price] df[price].astype(str).str.replace(免费, 0) df[price] pd.to_numeric( df[price].str.extract(r(\d\.?\d*))[0], errorscoerce ).fillna(0)逻辑说明replace(city_map)是精确替换适合已知的几种写法str.replace(r市$, )是正则兜底处理映射表没覆盖到的。评分用extract抽数字而不是replace去字符是因为评分里可能混着“分”“/5”等多种后缀正则更稳。errorscoerce让无法解析的值变成 NaN 而不是抛异常这是批量清洗的关键参数。门票价格里“免费”必须先转成 0 再抽数字否则会被当成缺失。参数说明fillna(median())用中位数而不是均值是因为旅游评分常有少量极低分拉偏均值。价格缺失填 0 要谨慎如果数据里缺失代表“未知”而非“免费”填 0 会低估均价这种情况我一般单独标记一列price_missing。2.3 派生字段热度分和性价比分原始字段往往不够用推荐和可视化都需要派生指标。热度分把评论数和评分揉在一起性价比分把评分和价格揉在一起。# 热度分评论数取对数后归一化再和评分加权 df[log_comments] np.log1p(df[comments]) df[heat] ( (df[log_comments] - df[log_comments].min()) / (df[log_comments].max() - df[log_comments].min()) ) * 0.6 (df[score] / 5) * 0.4 # 性价比分评分高且价格低得分高价格加 1 防止除零 df[value_score] df[score] / np.log1p(df[price] 1) # 保存清洗结果后续步骤直接读这份 df.to_csv(travel_clean.csv, indexFalse, encodingutf-8-sig)逻辑说明评论数跨度可能从几十到几十万直接归一化会被极端值压扁取log1p后再归一化分布更均匀。热度分给评论数 0.6 权重、评分 0.4是因为评论数更能反映真实到访量。性价比分用score / log(price1)价格取对数是为了让几十块和几百块的差距不被过度放大加 1 是防止价格为 0 时除零。参数说明权重 0.6 和 0.4 不是固定的如果数据里评分区分度低可以把评分权重降到 0.3。log1p等价于log(1x)专门处理含 0 的数据。保存时用utf-8-sig是为了 Excel 打开不乱码这是血泪经验。3. 推荐逻辑落地不用协同过滤也能做出可解释的推荐很多人一听推荐就想到协同过滤但旅游场景下用户行为数据往往拿不到硬上协同过滤只会得到一堆无法解释的结果。我的做法是基于内容的加权打分规则透明、可调、可解释给非技术同事看也能讲明白。这一章把打分函数、排序、Top-N 输出讲透。3.1 加权打分函数的设计推荐的核心是一个打分函数输入一个景点行输出一个分数。权重放在配置里方便调。# 推荐权重配置集中管理方便调参 WEIGHTS { score: 0.35, # 评分权重 heat: 0.30, # 热度权重 value_score: 0.20, # 性价比权重 price: -0.15, # 价格权重负号表示越贵分越低 } def normalize(series): min-max 归一化处理全等列的除零 rng series.max() - series.min() if rng 0: return series * 0 return (series - series.min()) / rng def recommend_score(df, weightsWEIGHTS): 对每个景点计算加权推荐分 score pd.Series(0.0, indexdf.index) for col, w in weights.items(): score normalize(df[col]) * w return score df[rec_score] recommend_score(df)逻辑说明normalize里判断rng 0是为了防止某一列所有值相同时除零返回全 0 而不是报错。权重用字典管理改权重不用动函数体。价格权重是负数因为价格越高推荐分应该越低归一化后乘负权重即可。pd.Series(0.0, indexdf.index)初始化保证索引对齐避免后续相加时错位。参数说明权重之和不必等于 1因为归一化后每列都在 0 到 1 之间权重只决定相对重要性。如果想让价格影响更大把 -0.15 改成 -0.25同时把评分权重降到 0.30 保持平衡。归一化用 min-max 而不是 z-score是因为推荐分需要落在固定区间方便比较。3.2 按城市和预算做条件推荐实际用的时候用户往往带着条件来想去某个城市、预算有限、要高分。这时候在打分基础上加过滤条件。def top_recommend(df, cityNone, max_priceNone, min_scoreNone, top_n10): 条件筛选 推荐分排序返回 Top-N sub df.copy() if city: sub sub[sub[city] city] if max_price is not None: sub sub[sub[price] max_price] if min_score is not None: sub sub[sub[score] min_score] # 按推荐分降序取前 N sub sub.sort_values(rec_score, ascendingFalse).head(top_n) return sub[[name, city, score, price, heat, rec_score]] # 示例杭州预算 200 以内、评分 4 分以上的前 10 个 result top_recommend(df, city杭州, max_price200, min_score4.0, top_n10) print(result.to_string(indexFalse))逻辑说明先 copy 再筛选避免修改原 DataFrame。三个条件都是可选的用is not None判断这样同一个函数能应对“只看城市”“只看预算”“全都要”多种场景。排序后head(top_n)取前 N最后只返回展示需要的列减少输出噪音。参数说明max_price用而不是因为预算通常包含边界。min_score同理用。top_n默认 10看板场景下一般展示 10 到 20 条太多用户看不完。如果筛选后结果为空函数返回空 DataFrame调用方要判断一下再展示否则看板会出现空白区域。3.3 城市维度的聚合推荐除了单景点推荐还常需要“哪个城市综合最值得去”。这就要按城市聚合再对城市打分。# 按城市聚合平均评分、平均价格、景点数、总热度 city_stats df.groupby(city).agg( avg_score(score, mean), avg_price(price, mean), spot_count(name, count), total_heat(heat, sum), ).reset_index() # 城市推荐分评分和热度为主景点数作为丰富度加分 city_stats[city_score] ( normalize(city_stats[avg_score]) * 0.4 normalize(city_stats[total_heat]) * 0.4 normalize(city_stats[spot_count]) * 0.2 ) city_rank city_stats.sort_values(city_score, ascendingFalse) print(city_rank.head(10).to_string(indexFalse))逻辑说明groupby后agg一次算出四个指标比多次 groupby 高效。城市推荐分里评分和热度各占 0.4景点数占 0.2是因为景点多的城市选择余地大但太多也可能意味着质量参差所以权重给低一些。reset_index()把 city 从索引变回列方便后续操作和导出。参数说明agg里用元组(score, mean)指定列和聚合函数这是 pandas 的标准写法。如果某城市只有一个景点avg_score就是该景点评分样本太少时结论不稳可以在展示时加一列spot_count让用户自己判断。城市推荐分同样用 min-max 归一化保证不同量纲的指标能相加。4. 可视化看板让图表替你说结论数据分析的终点是让人看懂。可视化不是把数据画出来就完事而是要回答具体问题哪些城市评分高、价格和评分什么关系、热度分布如何。这一章讲三类核心图表和它们的参数调优。4.1 城市评分与价格的对比图最常被问的就是“哪个城市好玩又不贵”。用横向条形图对比城市平均评分用颜色映射平均价格一张图回答两个问题。import matplotlib.pyplot as plt import matplotlib # 中文字体设置否则中文显示成方块 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False # 取评分前 15 的城市 top_city city_rank.head(15).sort_values(avg_score) fig, ax plt.subplots(figsize(10, 8)) # 颜色映射价格cmap 用 RdYlGn_r 表示价格越高越红 sc ax.barh( top_city[city], top_city[avg_score], colorplt.cm.RdYlGn_r(normalize(top_city[avg_price])) ) ax.set_xlabel(平均评分) ax.set_title(城市平均评分与价格分布颜色越红价格越高) # 加颜色条说明 sm plt.cm.ScalarMappable(cmapRdYlGn_r, normplt.Normalize(top_city[avg_price].min(), top_city[avg_price].max())) plt.colorbar(sm, axax, label平均价格) plt.tight_layout() plt.savefig(city_score_price.png, dpi150)逻辑说明barh画横向条形图城市名在 y 轴更好读。颜色用RdYlGn_r反向色带价格低是绿色、高是红色符合直觉。sort_values(avg_score)让条形从下到上递增视觉上更整齐。ScalarMappable是给颜色条用的因为条形图本身不直接生成 colorbar。参数说明figsize(10, 8)是 15 个城市的合适尺寸城市多了要加高。dpi150保证导出图片清晰看板用足够。字体必须设SimHei或系统里有的中文字体否则中文全是方块这是新手最常见的翻车点。tight_layout()防止标签被裁掉。4.2 评分与价格的散点图想看评分和价格到底有没有关系散点图最直接。再加一条趋势线结论一目了然。fig, ax plt.subplots(figsize(10, 6)) # 散点透明度 0.5 防止重叠点看不清 ax.scatter(df[price], df[score], alpha0.5, s20, csteelblue) # 拟合一条一次趋势线 z np.polyfit(df[price], df[score], 1) p np.poly1d(z) x_line np.linspace(df[price].min(), df[price].max(), 100) ax.plot(x_line, p(x_line), r--, labelf趋势线 斜率{z[0]:.4f}) ax.set_xlabel(门票价格) ax.set_ylabel(评分) ax.set_title(景点评分与价格关系) ax.legend() plt.tight_layout() plt.savefig(score_price_scatter.png, dpi150)逻辑说明alpha0.5让重叠的点颜色加深能看出密度。s20控制点大小太大糊成一片。np.polyfit做一次多项式拟合斜率正负直接告诉你价格和评分是正相关还是负相关。趋势线用红色虚线和蓝色散点区分开。参数说明斜率接近 0 说明价格和评分基本无关这时候“贵的一定好”就不成立。如果斜率明显为正说明高价景点评分普遍偏高但要注意可能是高价景点本身筛选过。np.linspace生成 100 个点画平滑线点太少线会折。4.3 热度分布直方图热度分布能看出数据是不是集中在少数头部景点这对推荐策略有影响。fig, ax plt.subplots(figsize(10, 6)) # 热度分 20 个区间加密度线 ax.hist(df[heat], bins20, colorskyblue, edgecolorwhite, densityTrue) ax.set_xlabel(热度分) ax.set_ylabel(密度) ax.set_title(景点热度分布) # 标出中位数 median_heat df[heat].median() ax.axvline(median_heat, colorred, linestyle--, labelf中位数{median_heat:.2f}) ax.legend() plt.tight_layout() plt.savefig(heat_dist.png, dpi150)逻辑说明densityTrue让 y 轴变成密度而不是计数方便比较不同样本量的分布。bins20是经验值数据量几千条时 20 个区间粒度合适。axvline标中位数比标均值更抗极端值。edgecolorwhite让柱子之间有分隔视觉更清晰。参数说明如果分布明显右偏大部分景点热度低、少数极高说明推荐要重点处理长尾不能只看头部。bins太少看不出形状太多锯齿明显20 到 30 之间试。中位数和均值差距大就说明有极端值这时候用中位数描述“典型景点”更准。5. 避坑与排查这些错我基本都犯过做这类项目代码写出来只是开始跑通和跑对是两回事。下面五条是我踩过的坑每条都按现象、原因、解决写清楚照着排查能省不少时间。5.1 中文显示成方块现象图表里所有中文标题、轴标签都变成一个个方块英文和数字正常。原因matplotlib 默认字体不含中文字形系统找不到就画方块。解决在画图前设置matplotlib.rcParams[font.sans-serif] [SimHei]如果系统没有 SimHei换成Microsoft YaHei或WenQuanYi Micro Hei。设完还不行就清一下 matplotlib 缓存缓存路径用matplotlib.get_cachedir()查。另外axes.unicode_minus要设成 False否则负号也会显示异常。5.2 归一化遇到全等列除零现象推荐分算出来全是 NaN或者报ZeroDivisionError。原因某一列所有值相同max - min等于 0归一化时除零。解决在 normalize 函数里判断rng 0就返回全 0 序列。这个坑在测试数据上不容易发现因为测试数据往往有差异真实数据里某一列全相同的情况不少见比如所有景点都来自同一个城市时城市列就没差异。5.3 价格缺失填 0 导致均价失真现象某城市平均价格算出来特别低点进去看发现很多景点价格是 0。原因原始数据里价格缺失被fillna(0)填成了 0但缺失其实代表“未知”而不是“免费”。解决区分“免费”和“未知”免费在清洗时显式转成 0未知保留 NaN聚合时用mean()会自动跳过 NaN。如果业务上必须填就单独加一列标记缺失展示时注明。这个坑的教训是填充值之前先想清楚缺失的含义。5.4 groupby 后索引丢失导致合并错位现象按城市聚合后想和原表合并结果数据对不上或者报索引错误。原因groupby后城市变成了索引直接合并时索引和列对不齐。解决聚合后加reset_index()把分组列变回普通列。如果忘了加可以用merge时指定left_on和right_indexTrue补救但不如一开始就 reset 干净。这个坑在链式操作里特别隐蔽因为中间结果不打印就看不出来。5.5 图片保存被裁切现象保存的图片边缘标签或标题被切掉一半。原因savefig默认按画布大小保存超出部分被裁。解决保存前调plt.tight_layout()自动调整边距或者在savefig里加bbox_inchestight参数。两个一起用最稳。如果图例放在图外tight_layout可能还是不够这时候用bbox_inchestight更可靠。导出看板图片时 dpi 至少 150屏幕上看 100 也行但打印或放大就糊了。6. 部署与交付让看板在别人机器上也能跑代码在自己机器上跑通不算完交付出去别人能跑起来才算。这一章讲依赖管理、目录结构和两个进阶技巧最后说一个我自己的习惯。6.1 依赖锁定与目录结构最常见的交付翻车是“在我机器上好好的”。根因是依赖版本不一致。我的做法是生成 requirements.txt 并锁定版本。# 生成依赖清单只导出实际用到的包 pip freeze requirements.txt # 别人拿到后一键安装 pip install -r requirements.txt逻辑说明pip freeze导出当前环境所有包及版本别人安装时版本一致行为就一致。如果环境里包太多可以只列核心依赖pandas、numpy、matplotlib。目录结构我一般这样组织data/放原始和清洗后数据src/放清洗、推荐、可视化三个脚本output/放图表根目录放main.py串起流程。这样别人一看就知道从哪进。参数说明requirements.txt 里版本号用锁定不用因为在不同时间装到的版本可能不同。如果某个包对版本特别敏感在文件里加注释说明。Python 版本也建议在 README 里写清楚3.8 和 3.11 在 pandas 行为上偶有差异。6.2 用 argparse 让脚本可配置硬编码路径和参数是交付大忌。用 argparse 把常用参数暴露出来别人不用改代码就能跑。import argparse def parse_args(): parser argparse.ArgumentParser(description旅游推荐数据分析) parser.add_argument(--input, defaultdata/travel_raw.csv, help原始数据路径) parser.add_argument(--output, defaultoutput/, help图表输出目录) parser.add_argument(--top-n, typeint, default10, help推荐展示条数) parser.add_argument(--city, defaultNone, help指定城市不填则全量分析) return parser.parse_args() if __name__ __main__: args parse_args() # 后续流程用 args.input、args.top_n 等 print(f读取 {args.input}输出到 {args.output})逻辑说明argparse把路径、条数、城市都变成命令行参数别人用python main.py --city 杭州 --top-n 20就能定制。default给合理默认值不传参也能跑。typeint保证 top-n 是整数传字符串会报错提示。help写清楚每个参数含义--help就能看到。参数说明--city默认 None代码里判断if args.city再过滤。--output结尾带斜杠拼接文件名时用os.path.join更稳。如果参数多可以分组用add_argument_group但四五个参数没必要。6.3 一个我坚持的习惯我做完任何数据分析项目都会留一个check.py里面就干一件事读清洗后的数据打印行数、列数、关键列的缺失率和取值范围。交付前跑一遍确认数据没在某个环节被意外过滤掉。这个习惯帮我抓到过好几次“筛选条件写错导致结果少了一半”的问题。数据分析的结论再漂亮数据本身错了就全白搭。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/18 19:08:41

React源码深度解析:调度器、Fiber工作循环与提交链路全景

读 React 源码的时候,很多人第一次产生"我好像懂了但又没完全懂"的感觉,就是在ensureRootIsScheduled到commitRoot这一段。这段代码横跨调度器和渲染器,把任务优先级、时间切片、Fiber 树遍历、副作用提交串成一条完整的链路。你可…

2026/10/18 19:08:41

厂商AI能力对比方法论:五维拆解与POC验证实战

1. 为什么这种对比每次都做得像走过场厂商AI能力对比这件事,几乎每个技术团队都做过一次。研发想接大模型接口,业务想上智能助手,管理层想要一份“到底选谁”的结论,于是排了个调研任务,收集各家公开材料,列…

2026/10/18 19:08:41

Transformers推理优化指南:从BERT到ONNX Runtime与量化部署

最近帮一位做内容的团队优化他们的情感分析服务,模型用的是 Transformers 里的 BERT-base,服务器是 Ubuntu 20.04,GPU 暂时只有一张,业务方天天抱怨"单条结果要 1 秒多"。其实模型本身没什么问题,就是裸装裸…

2026/10/18 20:08:50

AI生成+自动审核:医药检测报告协同编审方案实战

做了这么多年医药相关行业的质量管理工作,我最怕听到的一句话不是“检测失败了”,而是“报告在流转过程中被打回来了”。检测报告这件事,表面看只是把实验数据整理成一份文档,实际上它是我见过卡人流最狠的合规环节之一。一批样品…

2026/10/18 20:08:50

Delphi老项目使用Indy 10.2.3:安装配置、核心编程与避坑指南

简介:一份面向 Delphi 7~2007 开发者的 Indy 10.2.3 网络组件库完整源码包。Indy 是开源跨平台的网络通信组件集,它提供了 TCP/IP、HTTP、FTP、SMTP、POP3 及 SSL/TLS 支持,可直接用于构建客户端与服务端网络应用。资源共 839 个文…

2026/10/18 20:08:50

Git clone报错404:智能HTTP协议与info/refs路径解析

1. 项目概述:这不是网络问题,是 Git 请求路径被拒绝的明确信号“git clone 代码报错:error: RPC failed; result22, HTTP code 404”——这个报错我在过去三年里至少处理过87次,覆盖高校实验室、初创团队和中型技术部…

2026/10/18 20:03:44

IPTV直播源管理系统:从m3u管理到全端稳定播放的工程实践

简介:这是一套基于Python开发的IPTV直播源管理系统可运行源码,面向具备基础Python开发与Web部署能力的技术爱好者、DIYP影音定制用户及中小型IPTV服务搭建者,解决直播源集中管理、动态配置与前端对接缺失的问题。资源包共29个文件&#xff0c…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑