
简介面向Python数据分析与可视化课程期末大作业的完整项目源码包聚焦2020年五一假期旅游网站公开数据覆盖数据清洗、统计分析、可视化图表生成与网页展示全流程适合需要参考高分大作业或快速搭建数据可视化项目的高校学生。压缩包共74个文件以36个HTML图表页面为主体配套JavaScript脚本、CSS样式、Python脚本、Jupyter Notebook、CSV数据源及docx说明文档整体约13.62MB目录按功能模块划分便于直接运行和二次修改。核心代码含app.py、function.py、picture.py另有多个ipynb分析过程文件可完整还原从数据处理到图表输出的逻辑可视化成果涵盖地图、词云图、漏斗图、水球图、条形图、扇形图等常见形式能较好体现课程评分中应用性与展示效果。项目经过严格调试评审分达95分以上目前已有616人学习下载适合作为期末大作业、课程设计或毕业设计数据分析模块的参考样板。1. 用 Python 拆一个旅游网站的数据这作业到底在做什么如果你正在为一门 Python 数据分析课的期末大作业发愁题目是“基于 Python 的旅游网站数据分析及可视化”那你大概已经见过这个带着 .zip 后缀的作业包了。它一般是把网站后台导出的游客订单、景区评分、点评文本和消费记录扔给你让你用 pandas 做清洗与聚合再用 pyecharts 或 ECharts 输出折线图、柱状图和可视化大屏。反直觉的是老师真正看重的不是图做得有多炫而是你能否从数据里抽出一条“哪类景区在什么季节最受欢迎、钱花在哪”的业务结论。这套流程适合信息管理、大数据、电子商务专业的学生也适合准备 Python 数据分析作品集的从业者。下面这套流程就是按“能答辩、能改、能复现”的标准来走的。2. 从 zip 到可运行项目先把环境和数据准备这一步踩平2.1 解压后的目录长什么样先认清四类文件再动手这类作业 zip 解压后一般有 4 类东西数据文件CSV/Excel/JSON分析脚本.py 或 .ipynb图表输出HTML 或图片以及一份写好的报告或答辩 PPT。因为是从“旅游网站”导出来的数据常见字段有 order_date下单日期、destination目的地、city、visitor_count、amount、rating、comment。先不要急着跑代码我的习惯是在终端列一遍目录确认数据文件的真实路径和编码顺便看看压缩包里是不是还有一份 README 说明。unzip tourism_site_analysis.zip -d tour_project cd tour_project find . -maxdepth 2 -type f | sortunzip 后面的 -d 参数把释放内容放到 tour_project 目录避免所有文件直接撒在当前目录。find 只列出两层以内的文件防止被一堆图表 html 刷屏。如果压缩包加了密码直接找提供者要不要花时间研究旁门左道这类作业包的时间应花在数据清洗上不在解压上。列完目录以后重点看三件事有没有 requirements.txtCSV 是放在 data 子目录还是根目录以及报告里提到的数据文件与你手上这份是否同名。这三个位置都是后面报错的高发区。如果发现 CSV 是 Excel 另存过来的大概率有 BOM 或者列名带空格这会直接导致后面 groupby 找错列名。如果手上只有数据文件没有代码也可以用 Python 快速判断文件编码下面这段代码能在读取失败之前给你答案with open(data/orders.csv, rb) as f: raw f.read(1024) print(raw[:20])输出里如果以 b\xef\xbb\xbf 开头说明是 UTF-8 带 BOM读的时候用 encodingutf-8-sig 最保险如果以 b\xff\xfe 开头说明是 UTF-16pandas 里要写 encodingutf-16。这个技能在处理旅游网站后台导出文件时特别实用很多旧系统导出的根本不是标准 UTF-8 文件。2.2 Python 环境安装与依赖pandas、pyecharts 这些库怎么配先说结论我不建议为了一个作业去硬上最新版 Python直接从 python 官网下 3.10 或 3.11 的 64 位安装包勾选 Add Python to PATH 是最省事的。装完以后在命令行跑 python --version 确认版本。接着用 venv 建一个干净环境不要往全局环境里一次性装二十个包期末交作业前卸载依赖真的很痛苦。python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install pandas numpy pyecharts flask openpyxl matplotlib seabornvenv 会把依赖隔离在项目目录避免和你平时写爬虫或别的作业用的包互相污染。pip install 后面这组包覆盖了读 Excelopenpyxl、数据清洗pandas/numpy、图表matplotlib/seaborn和网页大屏pyecharts/flask。如果 pip 下载慢可以换阿里云镜像pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/。装完以后建议把版本号记录进 requirements.txt这样换一台电脑也能一键复现pip freeze requirements.txt注意一个 pyecharts 特有的坑pyecharts 的地图数据包和主库是分开的做中国地图需要先装 echarts-countries-pypkg 这类包。但期末大作业如果只用柱状图、折线图、饼图和表格可以完全不用碰地图包省掉一堆版本兼容问题。我的建议是先用最小依赖跑通流程最后有时间再补地图或者直接把地图换成 Table 展示。2.3 数据读进来CSV/Excel/JSON 三种旅游数据的读取代码进入正题前有个容易被忽略的细节旅游网站后台导出的文件九成是 CSV但很可能不是纯 UTF-8 编码。用 pandas 读数据时Windows 下用 utf-8 可能直接报 UnicodeDecodeError正确的做法是多试一个 gbk 参数。Excel 文件需要 openpyxl 引擎JSON 则要看嵌套层级。下面这段是三种文件的读取模板import pandas as pd # CSV优先 utf-8-sig它会在读入时自动去掉 BOM df_orders pd.read_csv(data/orders.csv, encodingutf-8-sig) print(df_orders.head(), df_orders.columns.tolist()) # 如果上面报错说明文件可能是 GBK 编码 # df_orders pd.read_csv(data/orders.csv, encodinggbk) # Excel必须指定 sheet_nameopenpyxl 才能精准读 df_scores pd.read_excel(data/scenic_scores.xlsx, sheet_name0, engineopenpyxl) # JSON嵌套字典先用 json_normalize 打平 import json with open(data/hotel_orders.json, r, encodingutf-8) as f: raw json.load(f) df_hotels pd.json_normalize(raw, record_pathlist, meta[city])read_csv 的 encoding 参数是第一个坑utf-8-sig 比 utf-8 多处理一个 BOM 头能防止第一列列名变成 \ufefforder_date。GBK 在老旅游网站后台很常见遇到报错再切。read_excel 的 sheet_name 可以传 0 表示第一张表也可以直接传表名字符串。json_normalize 里 record_path 指向列表字段meta 是想要保留的上级字段这样能把酒店订单里的 list 数组打平成一行一行。读完以后统一做两步检查列名清洗和缺失值概览。Excel 编辑过的 CSV 经常在列名前后留空格代码里写 df[origin_city] 会直接 KeyError。# 列名去空格和 BOM转成小写避免一会儿大写一会儿小写 df_orders.columns ( df_orders.columns.str.strip() .str.replace(r\ufeff, , regexTrue) .str.lower() ) print(df_orders.isna().sum()) print(df_orders.dtypes) # 字符串列去首尾空格统一大小写 for col in df_orders.select_dtypes(includeobject).columns: df_orders[col] df_orders[col].str.strip().str.lower()isna().sum() 是判断缺失值分布最快的办法如果 visitor_count 有几百个 NaN后面求和会直接出错必须先决定是填充 0 还是删除行。dtypes 则是确认日期列是否为 datetime64 的关键如果 order_date 还是 object下面所有按时间聚合的操作都会翻车。数据准备阶段多花十分钟后面至少少熬夜两个晚上。3. 旅游数据分析五个必做的统计维度与实现代码3.1 游客量趋势分析时间序列聚合与月度/季度对比先讲为什么先做趋势。旅游网站订单天然带时间戳老师看到“某个月游客量冲到全年峰值”这种结论会立刻有兴趣。操作上不要拿原始日期直接画折线因为日粒度噪音太大先按周或月聚合。pandas 的 resample 是核心函数但 groupby 配 to_period 更直观适合调试df_orders[order_date] pd.to_datetime(df_orders[order_date], errorscoerce) df_orders df_orders.dropna(subset[order_date]) # 按月汇总游客量 df_orders[ym] df_orders[order_date].dt.to_period(M) monthly df_orders.groupby(ym, as_indexFalse)[visitor_count].sum() print(monthly.sort_values(visitor_count, ascendingFalse).head(6)) # resample 写法先把日期设为索引 ts df_orders.set_index(order_date)[visitor_count] monthly2 ts.resample(ME).sum() # pandas 2.x 推荐 MEpd.to_datetime 的 errorscoerce 把脏数据变成 NaT随后 dropna 直接丢掉非法日期。dt.to_period(M) 把每个日期归到月groupby 求每月总量这个写法比 resample 更直观且不容易踩到版本警告。resample 的 ME 在 pandas 2.x 里表示月结束旧代码常用 M新版会报警告直接用 ME 避免后续兼容性问题。对比季度时把 to_period(M) 换成 to_period(Q) 即可一眼看出旺季集中在 Q3 还是 Q1。这里要补一个细节to_period 生成的 ym 列是 Period 类型print 出来是 2024-05 这种格式但后续给 pyecharts 用之前必须 astype(str) 转成字符串否则 JSON 序列化会报错。业务端这个分析的价值是给景区排班和定价提供依据回答“全年客流是否均匀、淡季到底有多淡”这一类问题。3.2 热门目的地排名分组统计与 TopN 提取排名是旅游网站数据分析最容易拿分的一项结论一句话就能说清图表却是标准的横向条形图。别直接用 order by 再 head(10)应该先把统计口径定下来按“游客量”排还是按“成交金额”排两者结果经常不一样。我的经验是做一个 dest_stats 表同时算游客量、订单数、总金额和人均消费再按需求排序dest_stats ( df_orders.groupby(destination, as_indexFalse) .agg( visitor_sum(visitor_count, sum), order_count(order_id, nunique), amount_sum(amount, sum), ) ) dest_stats[per_capita] dest_stats[amount_sum] / dest_stats[visitor_sum] top10 dest_stats.nlargest(10, visitor_sum) print(top10[[destination, visitor_sum, amount_sum, per_capita]])groupby 统一走 as_indexFalse让 destination 保留成列而不是索引后面拼接图表时不用 reset_index少踩一次坑。agg 里用元组列表一次算多个指标比分开 groupby 再 merge 快得多。order_count 用 nunique 而不是 count因为订单表里同一单可能拆成多行nunique 才是真实订单数。nlargest 比 sort_values(...).head(10) 少一次全量排序对大表更友好。看排名表时我会刻意去找“游客量很高但人均消费低”的目的地这类数据常常引出“低价引流产品是否真的赚钱”的答辩亮点。注意 amount 字段如果有缺失或为 0per_capita 会出现 NaN 或无穷大需要在计算前过滤 amount 0这是分组统计里最常见的隐蔽错误。3.3 客源地与消费结构类别统计和占比计算旅游网站通常有客源城市、产品类型、支付方式这类分类字段。分类字段的分析不是简单 count而是占比和心理价位分布。pandas 的 value_counts 带 normalize 参数能直接出百分比再配合 crosstab 做“客源地 × 产品类型”的交叉表是老师比较认可的交叉分析。# 客源城市占比 origin_ratio df_orders[origin_city].value_counts(normalizeTrue).head(10) print((origin_ratio * 100).round(2)) # 客源地 × 消费区间交叉表 bins [0, 500, 1000, 3000, 10000] labels [0-500, 500-1000, 1000-3000, 3000] df_orders[amount_bucket] pd.cut(df_orders[amount], binsbins, labelslabels, rightFalse) cross pd.crosstab(df_orders[origin_city], df_orders[amount_bucket], normalizeindex) print(cross.head())value_counts 的 normalizeTrue 返回 0 到 1 的比例乘以 100 才是百分比。pd.cut 的 bins 和 labels 长度必须一致rightFalse 让区间左闭右开500 会被分进 500-1000 而不是 0-500避免边界歧义。crosstab 的 normalizeindex 表示每行归一到 100%看的是“上海来的游客更爱买贵产品还是便宜产品”而不是绝对人数。这里有个细节如果 amount 有负值退款订单要先过滤 amount 0否则分箱会出现 NaN比例直接失真。客源地分析反映的是渠道投放和区域偏好做完之后可以联动地图但前提是表里有城市字段。如果没有城市只做 top10 的表格也足够交差。3.4 景区评分与口碑评分分布与点评长度评分数据通常长得很漂亮全是 4.5、5.0直接画直方图会全部靠右。所以只看平均数没意义重点看低分段的样本量和评论长度。先做评分分布和描述统计rating_stats df_scores[rating].describe() print(rating_stats) # 分桶看分布bins 必须覆盖到实际最小值 buckets pd.cut(df_scores[rating], bins[1, 2, 3, 4, 5], rightTrue) rating_dist df_scores[rating].groupby(buckets, observedFalse).size() print(rating_dist) # 评论长度按目的地聚合 df_scores[comment_len] df_scores[comment].astype(str).str.len() print(df_scores.groupby(destination)[comment_len].mean().sort_values(ascendingFalse))describe 给出的 count/mean/std/min/25%/50%/75%/max 是答辩时被问到“数据质量怎么样”最好的素材。cut 把评分切成 1-2、2-3 等区间注意评分最低 1 分时bins 要写成 [1,2,3,4,5]写成 [0,1,2,3,4,5] 会把 1 分分进 (0,1] 导致 1 分落空。astype(str).str.len() 计算点评字数是把 NaN 评论安全转成字符串的技巧比 dropna 再算更稳。低分景区不一定是服务差也可能是样本量太少所以分析时要把评分均值、评分数量和点评长度三个指标一起看否则就会出现“0 条评论的景区均分 5.0”这种没意义的结果。我一般会把同时满足“评分低于 4.0 且评论数量大于 100”的景区列为重点改善对象这个口径写在报告里老师会觉得你有业务意识。3.5 相关分析与用户画像靠成交量/评分/消费额联动如果是 95 分级别的要求不能只做单列统计要把两个维度串起来。常见做法是探索评分与消费额、游客量与评论数量之间的相关性用 corr 计算皮尔逊系数再画热力图。但这里有个前提原始表是订单粒度评分表是景区粒度必须先按目的地聚合再 merge。dest_agg ( df_scores.groupby(destination, as_indexFalse) .agg(avg_rating(rating, mean), rating_count(rating, count)) ) merged dest_stats.merge(dest_agg, ondestination, howinner) print(merged.corr(numeric_onlyTrue).round(2)) # 按客源地和年龄段做简单用户画像 if age_group in df_orders.columns: profile df_orders.groupby([origin_city, age_group], as_indexFalse)[amount].mean() print(profile.sort_values(amount, ascendingFalse).head(10))merge 的 howinner 丢掉没有任何订单的景区避免评分表里出现 NaN 行。corr(numeric_onlyTrue) 在 pandas 2.x 里是必须的否则版本升级后会直接报错。相关系数只是一个方向参考不代表因果答辩时如果老师问“这俩变量强相关能说明什么”你要回答“只能说明两个变量同时波动不能说明谁影响谁”。用户画像部分依赖原始表里是否有年龄字段没有就别硬造。我当时是补了一段基于消费金额的 RFM 简化版最近一次消费、消费频次、消费金额数据不够时也可以只做“客源 × 人均消费”的二维交叉。重点是要让老师看到你有能力把多张表串起来分析而不是只会单独 describe。4. 可视化输出从 matplotlib 到 pyecharts 大屏的选型与参数4.1 图表选型什么数据配什么图别一张柱状图走天下先给一个选型底线规则这是落地时最容易被忽视的一环。数据关系可以分为趋势、排名、占比、分布、关联五类对应关系如下表数据关系推荐图表适用场景慎用场景时间趋势折线图月度游客量、销售额变化分类之间比较类别排名横向条形图Top10 目的地、热门客源地类别少于 3 个整体占比环形图/饼图支付方式、产品类型构成类别超过 5 类数值分布直方图/箱线图评分分布、消费金额分布样本量过小变量关联散点图/热力图评分与消费额关系没有业务解释时这样的选型理由在报告里逐条写很容易拉到印象分。排名数据超过 6 个类别时横向条形图一定比纵向柱状图好读因为景区名称比较长纵轴放不下会折行。饼图只在占比真的能对应“整体 100%”时才用且类别不要超过 5 类否则直接换环形图加图例。pyecharts 和 matplotlib 的选择也是有讲究的。日常调试用 matplotlib 最快因为它在 Jupyter 里直接出图不需要浏览器。但期末作业最好用 pyecharts因为它生成的是 html浏览器打开就能交互老师打开文件比装 Jupyter 环境容易得多。如果只有打印报告的需求matplotlib 的 dpi 控制更顺手。我的习惯是两者都装但交付物统一用 pyecharts。4.2 pyecharts 接入 Flask 输出网页最小可运行代码作业要求里的“可视化”通常指能交互的网页pyecharts 是对 Python 用户最友好的路线。不要一句一句在 Jupyter 里 render 然后截图交差应该用 Page 把多个图表放同一页生成一个 HTML。下面是直接改数据就能跑的最小版本from pyecharts.charts import Bar, Line, Pie, Page from pyecharts import options as opts # 假设 monthly 来自 3.1 节 bar ( Bar() .add_xaxis(monthly[ym].astype(str).tolist()) .add_yaxis(游客量, monthly[visitor_count].tolist()) .set_global_opts( title_optsopts.TitleOpts(title月度游客量趋势), yaxis_optsopts.AxisOpts(name人次), ) ) # 热门目的地 Top10 条形图反转让最大的在顶部 top10 top10[::-1] hbar ( Bar() .add_xaxis(top10[destination].tolist()) .add_yaxis(游客量, top10[visitor_sum].tolist()) .reversal_axis() .set_series_opts(label_optsopts.LabelOpts(positionright)) ) page Page(layoutPage.SimplePageLayout) page.add(bar, hbar) page.render(report/vis_tour.html) print(页面已生成report/vis_tour.html)add_xaxis 接收列表所以 DataFrame 的列必须用 tolist() 转出来。ym 是 Period 类型必须 astype(str) 转成字符串否则 pyecharts 序列化失败。reversal_axis() 是横向条形图的关键配合 label_opts positionright 让数值显示在条右侧。Page 的 SimplePageLayout 让所有图表从上到下排列不需要前端技能。如果老师明确要求“网页版”而不是静态 HTML再套 Flask 也不迟。常见做法是把 pyecharts 的 render_embed 输出到模板# app.pyFlask 集成最小示例 from flask import Flask, render_template from pyecharts.charts import Bar from pyecharts import options as opts app Flask(__name__) app.route(/) def index(): bar ( Bar() .add_xaxis(monthly[ym].astype(str).tolist()) .add_yaxis(游客量, monthly[visitor_count].tolist()) .set_global_opts(title_optsopts.TitleOpts(title月度游客量)) ) chart_html bar.render_embed() return render_template(index.html, chartchart_html) if __name__ __main__: app.run(debugTrue, port5000)render_embed 返回一段带 div 和 script 的 HTML模板里用{{ chart|safe }}输出。Flask 的 debugTrue 让修改代码后自动重启port5000 是默认端口。注意模板必须放在项目下的 templates 目录否则 render_template 找不到文件。这里我不建议为了作业硬上 Flask 在线服务因为答辩现场网络一断就全黑静态 HTML 反而最可靠。4.3 可视化大屏布局与配色参数让图表看起来像成品“大屏”这个词在期末作业里含金量高但不少学生误解成大屏等于很多图堆在一起。实际上老师期待的是“一眼能看见指标、图表和结论”。pyecharts 的 Page 支持拖拽布局可以在浏览器里手工拖动图表到理想位置再把布局保存下来。核心代码如下# 用拖拽布局生成大屏先运行一次在浏览器里拖动图表 page Page(layoutPage.DraggablePageLayout) page.add(bar, hbar, pie, line) page.render(report/dashboard.html) # 拖动完成后浏览器控制台会输出一段 JSON # 把它存成 chart_config.json然后重新生成最终页面 # Page.save_resize_chart(report/chart_config.json)DraggablePageLayout 模式下生成的 html 顶部有工具栏每个图表可以被鼠标拖动和缩放。浏览器控制台会打印当前布局的 JSON保存到 chart_config.json 后再用 Page.save_resize_chart 重放布局。这个机制是 pyecharts 自带的不需要写前端。注意拖拽布局要在同一台电脑上完成因为宽高和缩放比例跟屏幕分辨率强相关。配色上用 dark 主题加高对比色能最快出效果from pyecharts.globals import ThemeType bar ( Bar(init_optsopts.InitOpts(themeThemeType.DARK, width1200px, height500px)) .add_xaxis(monthly[ym].astype(str).tolist()) .add_yaxis(游客量, monthly[visitor_count].tolist()) .set_global_opts( title_optsopts.TitleOpts(title月度游客量趋势, subtitle来源订单表), legend_optsopts.LegendOpts(text_style{color: #eee}), ) )InitOpts 的 theme 参数决定图表整体风格DARK 主题适合深色大屏背景。width 和 height 建议显式指定否则缩放在不同分辨率下会变形。图例的 text_style 是容易被忽略的细节深色背景下默认黑色文字看不清要手动改成浅色。答辩时老师常问“为什么用深色主题”可以答“深色背景能减少屏幕反射、突出高亮数据适合展示场景”。最后把大屏整体的 background 统一成深色所有图表背景一致看起来才是成品而不是代码拼接。5. 避坑手册这个作业最容易翻车的 5 个细节5.1 中文乱码明明数据没问题图上一片方框现象CSV 读进来中文没问题但 matplotlib 图例和标题全是方框或者柱状图轴标签变问号。原因matplotlib 默认字体 DejaVu Sans 不含中文字形另一种情况是 CSV 编码读错字符串已经被替换成乱码后面无法恢复。解决第一层救编码读文件时先试 utf-8-sig 再试 gbk第二层设置 matplotlib 字体。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] FalseSimHei 和 Microsoft YaHei 是 Windows 自带字体Linux 服务器上要换成 Noto Sans CJK SC。axes.unicode_minus 不设置的话坐标轴负号会显示成方块。pyecharts 是浏览器渲染字体跟电脑系统字体走一般不乱码。判断乱码根源的办法是直接在终端 print 一列数据如果终端里是问号是数据源读错了如果终端正常、图里乱码才是字体问题。乱码问题一旦拖到答辩当天处理基本没有后悔药所以前置检查最重要。5.2 时间字段解析报错字符串里有“月”和“日”现象pd.to_datetime(df[order_date]) 抛 ValueError或者被解析成 1970-01-01 这类诡异日期。原因旅游网站后台导出的日期可能是“2024年5月1日”或“2024/5/1 10:30”pandas 默认格式只认识部分写法。解决手动指定 format同时加 errorscoerce。df_orders[order_date] pd.to_datetime( df_orders[order_date], format%Y年%m月%d日, errorscoerce )日期解析没有后悔药最好在第一步就统一类型。format 里 %Y 是四位年份%m 是月份%d 是日期%H 和 %M 对应小时和分钟。指定 format 的好处除了精准还能让解析速度提升一个数量级因为 pandas 不用去猜测格式。如果文件里同时存在多种格式先 df[order_date].astype(str).str.contains(月) 看看要不要分支处理或者直接把混合格式统一清洗成 ISO 格式再解析。5.3 分组后索引没重置导致后续合并错位现象groupby 出来的表看起来没问题但和另一个 DataFrame merge 时行对不上或报 “cannot reindex”。原因groupby 的默认行为是把分组字段变成索引索引和顺序都跟原始表不一致直接横向拼接就会错位。解决所有 groupby 都写 as_indexFalse或者每次都 reset_index()。# 反面写法destination 变成索引 tmp df.groupby(destination)[amount].sum() # 正面写法destination 保留成列 tmp df.groupby(destination, as_indexFalse)[amount].sum()分组后索引问题最容易在期末这几天集中爆发因为一个流程里有五六个 groupby中间哪个忘了重置索引最后 merge 时全都错乱。我还有个习惯尽量不用 inplaceTrue 到处改调试时很难回退。宁可写成 df df.dropna() 这种重新赋值的写法。排查方法是打印 df.index 和 df.columns看到 MultiIndex 就要警惕新手不建议碰复杂索引。5.4 图表不显示/没有数据df 列名带空格或 BOM现象pyecharts 生成了 html但打开页面空白或者提示 “data is empty”。原因CSV 的第一列列名带了 \ufeff 前缀BOM代码里写的是 df[origin_city]但实际列名是 df[\ufefforigin_city]于是取出来全是 NaN。另一个常见原因是列名前后有空格。解决读取时用 utf-8-sig读取后统一做列名清洗。df.columns df.columns.str.strip().str.replace(r\ufeff, , regexTrue)这行代码把列名里的空格和 BOM 一次清干净再跑 df.columns.tolist() 检查一遍。pyecharts 的 add_yaxis 如果传入的列表里有 NaN图表就会空转所以给图表喂数据前先 df df.dropna(subset[amount])。空页面的排查路径先 print 数据长度再打开 html 开发者工具看 console 有没有 JS 报错最后检查是不是浏览器限制了本地文件加载。file:// 协议下某些功能受限可以直接右键用浏览器打开而不是双击。5.5 答辩被追问“为什么用饼图不用条形图”现象图表数量很多老师只盯着其中一张问“这个饼图能看出什么”。原因很多人用饼图是因为好看但类别一多饼图小扇形面积很难比较三个相近的 20% 看起来没有差别。解决答辩前给每张图写一行选型理由。饼图只用于“整体构成”并且类别不超过 5 类超过 5 类就合并成“其他”再画。学生最容易踩的坑是把排名 Top10 画成饼图前十名的面积看得人头疼。正确的做法是排名用横向条形图占比用饼图。如果老师问“为什么同一份数据你画了柱状图和饼图”就答“柱状图看数值比较饼图看占比结构两者回答的问题不同”这个回答本身就是加分项。这五条是期末大作业里反复出现的翻车现场每条都值得在提交前原地跑一遍。6. 让作业从“能跑”到“95 分”三个加分项和一套自查清单先给自己一个判断标准能跑通只是及格线能回答“为什么”才是高分的分水岭。第一加分项是给每张图加“结论标注”。pyecharts 的 TitleOpts 里 subtitle 可以写“旺季出现在 Q37 月为峰值”或者在报告里把图和一句话结论放在同一页。老师不用从图里猜印象分会明显不一样。第二个加分项是动态展示。pyecharts 的 Timeline 组件能做“按月份播放的 Top10 变化”代码上只比普通 Bar 多一个时间轴包裹效果却像产品发布会的 demo。答辩时只要演示一次基本就能让老师记住这个项目。时间轴做不了的用 Page 拖拽大屏把指标卡片和图表排在同一块深色背景上视觉上已经超出“交作业”水平。第三个加分项是异常解释。数据清洗时必然会有缺失和脏值报告里不要藏着而是写“原数据 3.2% 订单缺少金额已按均值填充日期解析失败 45 条已剔除”。这种叙述证明你不是只会跑代码而是真懂数据质量。提交前最后用一套自查清单过一遍检查项怎么查有问题时怎么办列名是否带 BOM/空格df.columns.tolist()用 str.strip().str.replace 清洗日期是否全部解析df.dtypes 看 order_date指定 format 重新解析统计口径是否一致检查 visitor_count 是人数还是人次在报告里明确写单位缺失值是否处理isna().sum()按业务决定填充或删除图表标题和单位每张图看有没有标题/坐标轴单位补 TitleOpts 和 AxisOpts name交作业之前我一般会故意把数据里的一个单位改错然后看自己能不能在一分钟里找出来找不出来就说明图表的单位标注太含糊。我以前吃过这个亏大屏做得花哨答辩老师上来先问“订单金额单位是元还是千元”我盯着图表半天答不出来。从那以后坐标轴单位、图注和结论标注永远排在配色前面。作品能跑只是开始能不能把自己的分析讲明白才是这作业真正的目的。希望帮到你。本文还有配套的精品资源点击获取