简介这是一份面向计算机相关专业学生与项目实战学习者的Python数据分析可视化大作业完整源码聚焦机票价格数据分析与预测并借助PyQt5实现可视化界面展示适合正在准备课程设计、期末大作业或需要练手数据分析项目的人群。资源包共37个文件压缩后约1.33MB包含11个py源码文件、9个csv数据集、6个xml配置、6张png图表、2个ui界面文件及说明文档覆盖数据预处理、回归分析、回归可视化、数据可视化等核心模块目录结构清晰便于按功能模块查阅与二次开发。目前已有402人学习下载。项目经导师指导并认可评审分99分代码完整可运行读者可据此掌握从原始数据清洗到价格预测建模、再到PyQt5界面交互展示的完整流程同时获得可复用的分析脚本与图表产出适合作为课程设计参考或项目实战练习素材。1. 机票数据分析与可视化一份大作业怎么做出生产级质感机票价格是典型的时序数据却又比股票、气温难缠得多——同一条航线一天内可能被爬虫抓到十几个价位航司放票策略、舱位等级、中转方案、退改签规则全搅在一起。很多同学拿到「机票数据分析与可视化」这个大作业标题第一反应是找份 CSVdf.describe()跑一遍再plt.plot()画条折线就交差。结果答辩时老师一句「你这个价格波动为什么在周二凌晨出现低谷」就能问穿。这份作业真正的价值不在图表数量而在于你能不能把原始脏数据变成一条能自圆其说的分析链路清洗掉重复抓取、对齐时间粒度、拆出航线维度、再让可视化替结论说话。适合正在做 Python 数据分析课程设计、需要一份能拿高分且经得起追问的从业者和学生也适合想用真实业务数据练手 pandas 与 ECharts 的入门者。下面按我实际做过的顺序把选型、清洗、分析、可视化到避坑完整走一遍。2. 数据从哪来、字段怎么定先想清楚分析目标再动手2.1 机票数据的三个来源与取舍做机票分析数据来源直接决定后面能分析什么。常见做法有三类我一般会先评估再选来源典型字段优点坑公开数据集Kaggle/天池类航司、起降机场、价格、日期干净、可直接用字段少缺舱位和中转自己写爬虫抓取航班号、价格、抓取时间、舱位字段可控、时效新反爬、IP 封禁、数据重复航司开放接口/模拟数据结构化、含余票稳定需申请作业场景常拿不到大作业场景我建议优先用公开数据集打底再补一小段自己抓的实时数据做对比这样既有规模又有「新鲜感」。如果标题里强调「可视化大屏」那数据量至少要撑得起多图表联动几千条起步比较稳。2.2 字段设计别等分析时才发现缺列很多人抓完数据才发现没有「抓取时间」这一列导致无法分析价格随时间的变化。我一般会强制保留这几列flight_no航班号用于区分同一航线不同班次airline航司做航司维度对比dep_city/arr_city出发到达城市航线分析的基础dep_time/arr_time起降时间用于时段分析price价格核心指标cabin舱位等级经济舱/商务舱价格差异巨大crawl_time抓取时间戳时序分析的关键is_direct是否直飞中转会显著影响价格字段定好后用 pandas 读进来先看一眼结构和缺失情况import pandas as pd # 读取原始数据注意编码机票数据常含中文城市名 df pd.read_csv(flights_raw.csv, encodingutf-8) # 基础体检形状、类型、缺失、重复 print(df.shape) print(df.dtypes) print(df.isnull().sum()) print(df.duplicated(subset[flight_no, crawl_time]).sum())这段代码的作用是先摸清数据底细。shape看规模dtypes确认price是不是被读成了字符串常见翻车点isnull().sum()定位缺失列duplicated按「航班号抓取时间」判断重复抓取。参数上subset一定要选能唯一标识一条记录的字段组合否则会把正常的多条记录误判为重复。2.3 时间字段解析机票分析最容易翻车的地方机票数据里的时间格式五花八门2024/3/5 08:30、2024-03-05 8:30、甚至05-Mar-24都可能出现。统一解析是后续所有时序分析的前提# 统一时间格式errorscoerce 把解析失败的置为 NaT 而不是报错 df[dep_time] pd.to_datetime(df[dep_time], errorscoerce) df[crawl_time] pd.to_datetime(df[crawl_time], errorscoerce) # 解析失败的行要单独看不能直接丢 bad_rows df[df[dep_time].isnull()] print(f解析失败 {len(bad_rows)} 行) print(bad_rows[[flight_no, dep_time]].head()) # 派生字段出发小时、星期、是否周末 df[dep_hour] df[dep_time].dt.hour df[dep_weekday] df[dep_time].dt.weekday df[is_weekend] df[dep_weekday].isin([5, 6])errorscoerce是关键参数它让解析失败变成NaT而不是抛异常中断整个流程。派生出的dep_hour、dep_weekday是后面分析「什么时段机票便宜」「周末是否更贵」的基础。这一步做完数据才算真正可用。3. 清洗与特征工程把脏数据变成能分析的宽表3.1 去重、异常价与缺失值处理原始机票数据里重复抓取和异常价格是两大污染源。同一航班同一抓取时间被记录两次会让统计结果虚高价格出现 0 或 99999 这种极端值会毁掉均值。处理逻辑# 1. 按航班号抓取时间去重保留第一条 df df.drop_duplicates(subset[flight_no, crawl_time], keepfirst) # 2. 价格异常值处理用分位数截断避免个别极端值带偏 q_low df[price].quantile(0.01) q_high df[price].quantile(0.99) df df[(df[price] q_low) (df[price] q_high)] # 3. 关键字段缺失直接删非关键字段填充 df df.dropna(subset[price, dep_city, arr_city]) df[cabin] df[cabin].fillna(经济舱)分位数截断比直接设阈值更稳因为不同航线价格量级差异大固定阈值会误伤。quantile(0.01)和quantile(0.99)把最极端的 2% 去掉保留主体分布。这一步的参数要根据数据实际分布调如果数据本身很干净可以放宽到 0.005/0.995。3.2 航线维度与价格区间特征机票分析的核心维度是「航线」也就是出发城市到到达城市的组合。把两个字段拼成一个新字段后续 groupby 会方便很多# 构造航线字段 df[route] df[dep_city] - df[arr_city] # 价格分箱用于分布分析 bins [0, 500, 1000, 1500, 2000, 3000, 10000] labels [500以下, 500-1000, 1000-1500, 1500-2000, 2000-3000, 3000以上] df[price_level] pd.cut(df[price], binsbins, labelslabels) # 看热门航线 Top10 top_routes df[route].value_counts().head(10) print(top_routes)pd.cut做等距分箱labels让结果可读。分箱边界要结合数据实际价格范围定如果数据集中在 800-1200那 bins 就要相应加密。value_counts()快速看出哪些航线数据量大数据量太少的航线在后续分析里要谨慎下结论。3.3 按航线和时间聚合生成分析用的宽表清洗完的明细数据还不能直接画图需要聚合成「航线-日期-均价」这样的宽表# 按航线和出发日期聚合算均价、最低价、航班数 route_daily df.groupby([route, df[dep_time].dt.date]).agg( avg_price(price, mean), min_price(price, min), max_price(price, max), flight_count(flight_no, count) ).reset_index() route_daily.columns [route, dep_date, avg_price, min_price, max_price, flight_count] print(route_daily.head()) # 按航司聚合做航司对比 airline_stat df.groupby(airline).agg( avg_price(price, mean), total_flights(flight_no, count) ).sort_values(avg_price, ascendingFalse)groupby后接agg可以一次算出多个统计量比循环高效得多。reset_index()把分组键还原成列方便后续导出和画图。这一步产出的route_daily就是可视化阶段的主力数据源airline_stat用于航司对比图。4. 可视化落地从静态图到可交互看板4.1 用 Matplotlib 快速出探索性图表分析阶段先用 Matplotlib 快速看趋势不用追求美观重点是验证结论import matplotlib.pyplot as plt import matplotlib # 中文显示配置不做这步中文全是方块 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False # 选一条热门航线看价格趋势 sample route_daily[route_daily[route] 北京-上海].sort_values(dep_date) plt.figure(figsize(12, 5)) plt.plot(sample[dep_date], sample[avg_price], markero, label均价) plt.plot(sample[dep_date], sample[min_price], markers, label最低价, linestyle--) plt.xlabel(出发日期) plt.ylabel(价格元) plt.title(北京-上海航线价格趋势) plt.legend() plt.xticks(rotation45) plt.tight_layout() plt.savefig(route_trend.png, dpi150)font.sans-serif设成SimHei是中文环境的标准操作Linux 上可能要用WenQuanYi之类字体。tight_layout()防止标签被裁掉dpi150保证导出清晰度。这张图用来快速判断价格是否有周期性波动是后续深入分析的方向指引。4.2 用 ECharts 做可交互可视化大屏大作业如果要求「可视化大屏」ECharts 是性价比最高的选择。核心思路是后端把聚合数据转成 JSON前端用 ECharts 渲染。先准备数据接口import json # 把航线均价数据转成 ECharts 需要的格式 route_avg df.groupby(route)[price].mean().sort_values(ascendingFalse).head(15) chart_data { routes: route_avg.index.tolist(), prices: [round(p, 2) for p in route_avg.values.tolist()] } with open(route_avg.json, w, encodingutf-8) as f: json.dump(chart_data, f, ensure_asciiFalse)ensure_asciiFalse保证中文正常写入否则会变成\uXXXX转义。round(p, 2)控制小数位避免前端显示一长串。这个 JSON 就是前端 ECharts 的数据源。前端渲染部分// 读取后端生成的 JSON渲染柱状图 fetch(route_avg.json) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(routeChart)); chart.setOption({ title: { text: 热门航线均价 Top15 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.routes, axisLabel: { rotate: 45 } }, yAxis: { type: value, name: 均价元 }, series: [{ type: bar, data: data.prices, itemStyle: { color: #5470c6 } }] }); });echarts.init绑定容器setOption里xAxis的rotate: 45防止航线名重叠。tooltip的trigger: axis让鼠标悬停显示整列数据。这套结构可以复制到折线图、饼图只改series.type即可。4.3 时段热力图把「什么时候买便宜」讲清楚机票分析最有说服力的图表之一是「出发时段 vs 价格」的热力图。用 pandas 透视表生成矩阵再交给 ECharts 的 heatmap# 按出发小时和星期做透视值取均价 pivot df.pivot_table( valuesprice, indexdep_weekday, columnsdep_hour, aggfuncmean ).round(0) # 转成 ECharts heatmap 需要的 [x, y, value] 格式 heat_data [] for y, row in enumerate(pivot.values): for x, val in enumerate(row): if not pd.isna(val): heat_data.append([x, y, int(val)]) print(f热力图数据点{len(heat_data)})pivot_table的aggfuncmean算均价round(0)取整。转成[x, y, value]三元组是 ECharts heatmap 的标准输入格式。pd.isna过滤掉没有数据的格子否则前端会显示异常。这张图能直观回答「周二凌晨是不是真的便宜」这类答辩高频问题。5. 避坑与排查机票分析里那些血泪经验5.1 价格字段被读成字符串统计全错现象df[price].mean()报错或者返回奇怪结果describe()里 price 显示为 object 类型。原因原始 CSV 里价格带了货币符号如¥1200或千分位逗号1,200pandas 默认按字符串读入。解决读入时用dtype{price: float}强制转换或者读入后清洗df[price] df[price].astype(str).str.replace(r[¥,], , regexTrue).astype(float)str.replace用正则一次去掉货币符号和逗号再转 float。这一步不做后面所有价格统计都是错的。5.2 时区不统一导致时序分析错位现象价格趋势图在凌晨出现莫名其妙的尖峰或者同一天的数据被拆成两天。原因抓取时间用了 UTC而起降时间是本地时间两者混用导致时间对齐错误。解决统一到一个时区通常用出发地本地时间df[crawl_time] pd.to_datetime(df[crawl_time], utcTrue).dt.tz_convert(Asia/Shanghai).dt.tz_localize(None)tz_convert转时区tz_localize(None)去掉时区信息变成 naive 时间方便和起降时间对齐。时区问题不解决所有按天的聚合都不可信。5.3 重复抓取没去干净均价被拉偏现象某条航线均价明显高于预期检查发现同一航班被记录了多次。原因爬虫重试机制导致同一时间点抓了两次或者drop_duplicates的 subset 选错。解决用「航班号抓取时间价格」三个字段联合去重并检查去重前后行数变化before len(df) df df.drop_duplicates(subset[flight_no, crawl_time, price], keeplast) print(f去重前 {before} 行去重后 {len(df)} 行删除 {before - len(df)} 行)keeplast保留最新一条因为后抓的通常更准。打印删除行数是为了确认去重逻辑没有误删正常数据。5.4 中文乱码让图表标题变方块现象Matplotlib 图表里中文全是方框ECharts 里中文正常。原因Matplotlib 默认字体不含中文需要手动指定。解决除了前面设font.sans-serifLinux 服务器上还要确认字体已安装# 查看系统可用中文字体 fc-list :langzh如果没有输出需要安装中文字体包。Windows 上一般有SimHeiMac 上用Arial Unicode MS。这个坑不解决导出的图直接不能用。5.5 数据量太大导致前端卡死现象ECharts 渲染几万个点时页面卡顿甚至崩溃。原因把明细数据直接丢给前端没有做聚合。解决后端先聚合再传前端只渲染聚合后的结果。比如热力图最多 7×24168 个点柱状图 Top15 就 15 个点。明细数据留在后端做分析前端只负责展示结论。6. 让这份作业真正拿高分的两个进阶技巧第一个技巧是给分析加「对照组」。单独说「北京-上海均价 1200」没有说服力但如果说「北京-上海均价 1200比北京-广州高 18%且周末溢价达 25%」结论立刻立体。实现上就是多做几次 groupby 再算比值# 航线均价对比算相对基准的溢价 base df[df[route] 北京-上海][price].mean() compare df.groupby(route)[price].mean().reset_index() compare[premium_pct] ((compare[price] - base) / base * 100).round(1) compare compare.sort_values(premium_pct, ascendingFalse) print(compare.head(10))premium_pct就是相对基准航线的溢价百分比答辩时这种相对指标比绝对值更能体现分析深度。第二个技巧是加一个「预测」小模块。不用上复杂模型用statsmodels做个简单的季节性分解或移动平均就能让作业从「描述性分析」升级到「预测性分析」from statsmodels.tsa.seasonal import seasonal_decompose # 取一条航线的时间序列 ts route_daily[route_daily[route] 北京-上海].set_index(dep_date)[avg_price] ts ts.asfreq(D).interpolate() # 补齐缺失日期 # 季节性分解period7 表示按周周期 result seasonal_decompose(ts, modeladditive, period7) result.plot()asfreq(D)把时间序列对齐到每天interpolate()补缺失值period7假设一周一个周期。分解出的趋势项和季节项能直接支撑「价格有周期性」的结论。这个模块代码量不大但能让老师看到你懂时序分析的基本套路。我自己做这类作业最大的教训是别一上来就堆图表。先把数据清洗和时间对齐做扎实再想「这张图要回答什么问题」。图表是结论的载体不是装饰。一份能拿高分的机票分析核心是让每个数字都有出处、每个结论都能追溯到某段代码。希望帮到你。本文还有配套的精品资源点击获取