简介:面向交互式数据可视化期末大作业与课程设计场景,这套基于Python和pyecharts的结婚离婚数据分析项目,围绕婚姻登记数据展开,覆盖数据加载、整理到交互图表生成的全链路。主程序app.py、pyecharts过程Notebook、HTML页面模板与CSS样式齐备,代码附有详细注释,新手也能快速理解部署流程,直接运行即可展示完整可视化结果。压缩包共31个文件,主要包括8个HTML页面、7张PNG图表、2个Excel原始数据、Python脚本、ipynb过程文件及PyCharm工程配置等,整体仅1.1MB,目录划分清晰、轻量易用。已有269人学习浏览,图表模块涵盖结婚离婚趋势、登记量对比等交互展示,系统功能完整、界面简洁美观,可直接作为高分期末大作业或课程设计参考,实用价值较高。
1. 交互式数据可视化期末大作业:结婚离婚数据怎么变成可交互图表
交互式数据可视化期末大作业,听起来是个很宽泛的需求,但真正动手时你会发现,难点不在画图,而在怎么把“数据”“图表”“页面”三者串成一套能点、能筛、能演示的系统。这个基于Python+pyecharts实现的结婚离婚数据分析与可视化项目,就是为这个场景准备的:数据源是结婚登记(万对)和离婚登记(万对)两份Excel,后端用pandas读数、pyecharts生成图表,再用Flask把图表挂到网页上,最终呈现的是一个浏览器里能悬浮、能缩放、能切换图例的交互页面,而不是一张写死的静态图。对正在找数据分析案例模板、想用pyecharts做python数据分析与可视化实践、又担心期末答辩翻车的同学,这套源码的价值在于:它把echarts数据可视化的完整链路——数据读取、图表配置、页面承接、本地部署——压缩成了一个可以直接运行的包,下载下来改改数据就能变成自己的大作业。
2. 先拆包:项目文件结构与数据源长什么样
拿到源码包第一件事不是急着跑,而是把目录结构摸清楚。这份包里的文件看着多,但角色划分很明确:app.py是后端入口,templates里放页面模板,static里放静态资源,两份xlsx是原始数据,剩下的html和ipynb是开发过程的中间产物。搞懂谁负责什么,后面改代码才知道去哪改。
2.1 文件清单与角色分工
我把压缩包里出现的主要文件按职责分了个类,新手对照着看就不会在目录里迷路。
| 文件/目录 | 角色 | 说明 |
|---|---|---|
| app.py | Flask 后端入口 | 路由定义、数据读取、图表配置都在这里 |
| templates/index.html、home.html | 页面模板 | 承接 pyecharts 生成的图表配置,负责渲染 |
| static/ | 静态资源 | 项目里至少放了 home.css,页面样式的来源 |
| 结婚登记(万对).xlsx | 数据源一 | 结婚登记数据,单位是万对 |
| 离婚登记(万对).xlsx | 数据源二 | 离婚登记数据,单位是万对 |
| pyecharts过程.ipynb | 开发过程 | Notebook 形式的图表生成过程,适合学习和答辩演示 |
| a.html ~ e.html | 单图表页面 | Notebook 里生成的中间产物,直接用浏览器打开就能看单图 |
| images/*.png | 运行截图 | a.png、c.png、home.png 等,对应各页面效果 |
| README.md | 说明文档 | 启动方式、环境要求基本都写在里面 |
| .idea/、.gitattributes、finaltest.iml | 工程配置 | PyCharm 和 Git 的工程文件,不影响运行 |
这里有个容易忽略的点:a.html、b.html、c.html、d.html、e.html 这些文件在项目根目录而不是 templates 里,说明它们是 pyecharts 的 render 函数直接生成的独立页面,不走 Flask 模板。这在开发调试阶段很常用——先单图验证效果,再集成进 Flask。所以调试某个图表时,复制一份到根目录单跑,比每次重启 Flask 快得多。
2.2 Excel 数据读取:先确认列名再动手
这个项目的核心数据在 xlsx 里,所以 pandas 读取是第一道关卡。很多人拿到 Excel 第一反应是转成 CSV 再处理,其实没必要,pandas 直接就能读,但前提是 DataFrame 的列名要和你 chart 代码里的字段对得上。
import pandas as pd marriage_df = pd.read_excel("结婚登记(万对).xlsx") divorce_df = pd.read_excel("离婚登记(万对).xlsx") print(marriage_df.columns.tolist()) print(marriage_df.head()) print(divorce_df.columns.tolist())这段代码的价值在于:先把两份表的列名和头部几行打出来,确认年份列叫什么、数值列叫什么、单位是不是“万对”,再去写 pyecharts 的 add_xaxis。我见过太多人跳过了这一步,写代码时想当然用“年份”“结婚登记”当列名,结果 KeyError 连着报三四个才回头排查。head() 还有个作用,就是确认表头是不是在第一行——有的 Excel 前两行是标题说明,真正数据从第三行开始,这时候 read_excel 就要加 header=2 之类的参数,跳过无关行。这个项目默认按第一行表头读,如果你的数据表结构变了,先看 head() 再决定要不要调 header。
2.3 Flask 路由设计:一条页面一条路
从文件结构能看出来,这个项目有一个基本的路由框架,app.py 负责把 pyecharts 生成的图表配置传给模板。页面路由是 Flask 里最基础但也最容易写乱的部分,原则是“一个页面一个路由、一个图表一个函数”。
from flask import Flask, render_template import pandas as pd from pyecharts.charts import Line from pyecharts import options as opts app = Flask(__name__) marriage_df = pd.read_excel("结婚登记(万对).xlsx") divorce_df = pd.read_excel("离婚登记(万对).xlsx") @app.route("/") def index(): line = ( Line() .add_xaxis(marriage_df["年份"].tolist()) .add_yaxis("结婚登记", marriage_df["结婚登记"].tolist()) .set_global_opts(title_opts=opts.TitleOpts(title="结婚登记趋势")) ) return render_template("index.html", line_chart=line.dump_options()) @app.route("/home") def home(): return render_template("home.html") if __name__ == "__main__": app.run(debug=True, port=5000)这里的关键是 render_template 传参:pyecharts 1.x 的 dump_options() 会把图表配置序列化成 JSON 字符串,模板里再用 echarts 的 setOption 接住,页面结构完全由你自己控制。debug=True 的意思是改代码后服务自动重启,后端报错会在页面上直接显示,期末调试阶段非常有用。port=5000 对应浏览器访问 127.0.0.1:5000,如果你本机 5000 端口被别的进程占了,改成 5001 再访问对应端口即可。注意这份源码里同时存在 cpython-38.pyc 和 cpython-37.pyc,说明作者分别在 Python 3.7 和 3.8 下跑过,你本机只要装的是 3.7 及以上版本,基本不会遇到解释器不兼容的问题。
3. pyecharts 出图:折线、柱状、饼图与模板承接
pyecharts 是这套可视化方案的核心。它的写法和 echarts 前端配置很像,但直接用 Python 操作,意味着你可以把 pandas 处理好的 DataFrame 直接塞进图表,不需要手动拼 JSON。这个项目里最常见的就是三类图:折线图看趋势、柱状图做对比、饼图看占比。把这三类吃透,大作业的图表部分就稳了。
3.1 折线图:年份趋势是数据可视化的第一诉求
结婚离婚数据天然适合折线图,因为年份是连续变量,趋势变化是最直观的信息。pyecharts 的 Line 类写法很固定,核心就三步:x 轴、y 轴、全局配置。
from pyecharts.charts import Line from pyecharts import options as opts year_list = marriage_df["年份"].tolist() marriage_list = marriage_df["结婚登记"].tolist() line = ( Line() .add_xaxis(year_list) .add_yaxis( "结婚登记(万对)", marriage_list, is_smooth=True, is_symbol_show=False, ) .set_global_opts( title_opts=opts.TitleOpts(title="全国结婚登记数量年份趋势", pos_left="center"), tooltip_opts=opts.TooltipOpts(trigger="axis"), yaxis_opts=opts.AxisOpts(name="万对"), ) )add_yaxis 里的 is_smooth=True 把折线变成平滑曲线,视觉上比直线更舒服,答辩时也显得不那么“默认”;is_symbol_show=False 是隐藏数据点上的圆点,数据多时避免每个点都画个圆,页面看着干净。tooltip_opts 的 trigger="axis" 表示鼠标悬浮时触发提示框,显示的是整条竖线对应的所有系列数据,如果写成 trigger="item" 就只显示最近那个点,折线图用 axis 是最合适的。yaxis_opts 里的 name="万对" 是给 y 轴加单位,千万别省——老师看到图表没有单位,第一反应就是数据不严谨。
3.2 柱状图和饼图:对比与占比的两种表达
结婚和离婚两条线放一起看趋势,但要说“某年结婚多还是离婚多”,柱状图比折线图更直观;要说“整体结婚和离婚哪个占比高”,饼图直接给出比例结论。这个项目里的对比逻辑基本是这两类的组合。
from pyecharts.charts import Bar, Pie from pyecharts import options as opts bar = ( Bar() .add_xaxis(year_list) .add_yaxis("结婚登记", marriage_list) .add_yaxis("离婚登记", divorce_df["离婚登记"].tolist()) .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) .set_global_opts( legend_opts=opts.LegendOpts(pos_top="5%"), title_opts=opts.TitleOpts(title="结婚与离婚登记对比", pos_left="center"), ) ) pie = ( Pie() .add( "结婚/离婚", [("结婚登记", sum(marriage_list)), ("离婚登记", sum(divorce_list))], radius=["35%", "60%"], ) .set_global_opts( title_opts=opts.TitleOpts(title="结婚与离婚总量占比", pos_left="center"), ) )柱状图里我刻意把 label_opts 的 is_show 设成 False,数据多的时候柱顶全标数字会挤成一片,反而看不清对比关系。等答辩时候再把 is_show 改回 True 也不会花多少时间,对比几个关键年份时数字直接亮出来更有说服力。饼图的 radius=["35%", "60%"] 是画环形饼图,内环 35%、外环 60%,比默认的实心饼好看,这是课程设计里一个很实用的小优化。add 的第二个参数传的是 [(标签, 数值), ...] 这种二元组列表,pyecharts 1.x 会自动拆成名称和数据两列。
3.3 图表进模板:dump_options 和 render 的取舍
pyecharts 有两条落地路线,很多人在这里纠结:一条是 chart.render() 直接生成完整 html 文件,另一条是 chart.dump_options() 把配置序列化后交给 Flask 模板渲染。这个项目同时存在 a.html 等单图和 templates 下的页面,说明作者两条路都走过,最终集成交付用的是后者。
# Flask 视图函数里 return render_template( "index.html", line_option=line.dump_options(), bar_option=bar.dump_options(), pie_option=pie.dump_options(), )对应的模板里这样接:
<!-- templates/index.html 关键片段 --> <div id="lineChart" style="width:100%;height:400px;"></div> <div id="barChart" style="width:100%;height:400px;"></div> <div id="pieChart" style="width:100%;height:400px;"></div> <script src="{{ url_for('static', filename='echarts.min.js') }}"></script> <script> var lineChart = echarts.init(document.getElementById('lineChart')); lineChart.setOption({{ line_option|safe }}); var barChart = echarts.init(document.getElementById('barChart')); barChart.setOption({{ bar_option|safe }}); var pieChart = echarts.init(document.getElementById('pieChart')); pieChart.setOption({{ pie_option|safe }}); </script>这里有个必须强调的细节:template 里接收变量一定要加竖线 safe 过滤器,写成 {{ line_option|safe }}。不加的话,Jinja2 模板引擎会把 json 字符串里的引号全部转义,setOption 拿到的就不是一个对象而是一串带着 " 的字符串,图表直接白屏。这个问题是模板集成场景里翻车率最高的一个,后面避坑章节还会展开说。另外三个 div 的 id 必须各不相同,echarts 的 init 是按 id 找容器的,两个图表共用一个 id 只会显示最后一个。echarts.min.js 文件要放进 static 目录,因为 ret template 里用的是 url_for,Flask 默认只从这个目录静态文件。pyecharts 1.x 内置的 JS 依赖对应的是 echarts 5.x,版本不匹配也会出现图表初始化异常,这个下面排查章节再展开。
4. 本地跑起来:环境、依赖与部署验证
源码再好,跑不起来等于零。期末这个时间节点,大部分人不是被图表难住,是被环境折腾到心态崩溃。这一章把从零开始跑通项目的步骤拆开,每一步都说明为什么这么做,省得你把文档看完还卡在 import 阶段。
4.1 环境准备:先确认 Python,再装四个依赖
动手之前先确认解释器版本。我的习惯是先在命令行执行 python --version,python 3.8 和 3.9 跑这份代码都没压力,不用刻意装最新版。注意不要开好几个 Python 环境混着装依赖,最容易出问题的方式是“系统里既有 python 又有 python3,pip 装的包和 python 命令不对应”。
python --version pip install flask pandas openpyxl pyecharts四个依赖缺一不可:flask 是 Web 框架,pandas 负责读 Excel,openpyxl 是 pandas 读 xlsx 的底层引擎,pyecharts 是绘图库。这里最容易漏的是 openpyxl——你明明装了 pandas,但一执行 read_excel 就报错,就是因为 pandas 默认不带 xlsx 引擎,Excel 2003 的 xls 格式其实可以不带,但 xlsx 必须靠 openpyxl 兜底。安装结束后顺手验证一下版本,pyecharts 一定要是 1.x。如果你 import pyecharts 后 for 循环遍历图表,说明装成了 0.5 旧版,旧版的导入路径完全不同,后面怎么改都会报错。
4.2 启动 Flask:两种方式,一个结果
代码全齐之后,启动本身很简单。但从命令行跑还是从 IDE 跑,遇到问题时的排查路径不太一样。
cd 源码解压目录 python app.py把终端停在这个压缩包解压后的根目录,执行 python app.py,正常会看到提示 Running on http://127.0.0.1:5000(如果你改了端口就是对应的新地址)。然后在浏览器打开这个地址,能看到 index 页面;再加 /home 路径访问 home 页面。如果你用的是 PyCharm,可以直接右键 app.py 选 Run,效果一样,但要注意 PyCharm 的 Run Configuration 里如果设置了额外的环境变量或端口,以那个为准。还有一类常见情况是终端里敲 python 没反应或者报 ModuleNotFoundError,基本可以断定当前终端解释器和 pip 装的依赖不在同一个环境——同一台机器上装了 too many 个 Python 时尤其常见。这种错误翻车率很高,但不用慌:在终端里重启解释器,或者直接用 IDE 里的 Terminal 跑,环境问题就解决了。
4.3 验证交互:别让老师用鼠标点一下才发现是静态图
跑起来之后,建议花三分钟过一遍交互验证,把下面这张表当成检查清单。如果你的页面能逐项通过,答辩时基本不会在“是不是真交互”这个问题上丢分。
| 验证点 | 操作方式 | 预期结果 |
|---|---|---|
| 悬浮提示 | 鼠标停在曲线或柱子上 | 出现数据提示框,显示对应年份和数值 |
| 图例切换 | 点击图表右上角“结婚登记”/“离婚登记”图例 | 对应系列隐藏/恢复显示 |
| 页面布局 | 缩放浏览器窗口宽度 | 图表宽度自适应,不横向溢出 |
| 多图表独立 | 鼠标分别悬停三张图 | 每张图各自响应,互不干扰 |
| 路由切换 | 访问 / 和 /home 两个路径 | 不同页面分别正常渲染 |
有个大家容易忽略的点:直接把 a.html 这类文件拖进浏览器打开,也能看到图表,而且不需要启动 Flask。两类东西的关系是:a.html 是 pyecharts 直接 render 的静态产物,适合临时看单图;而 Flask 页面是完整的项目形态,数据能换、图表能配、页面能跳转,这才是大作业该有的完整度。很多同学答辩时只会打开 ipynb 给老师看过程,其实不如把 Flask 页面开好,让老师自己点两下悬浮和切换,交互感立刻就出来了。
5. 常见问题与排查:版本、编码、模板转义与端口占用
这个项目我前前后后帮人排查过不少次,出问题的点非常集中,翻来覆去就那五六个。把这一章当成排错清单来看,每一条都按“现象 → 原因 → 解决”写清楚。你要是能跑起来就直接跳到第 6 章,跑不起来就先对号入座。这些坑没有一个是玄学,全都和版本、转义、资源路径有关。
5.1 现象一:页面打开了,但图表区域是空白
这个问题出现的频率最高,十个白屏案例里八个是这个原因。现象是 Flask 页面正常显示标题和布局,但 div 区域什么都没有,浏览器的开发者工具 console 里可能挂着 Uncaught SyntaxError 之类的报错。
原因分两类。第一类是模板里 setOption 的参数没加 safe 过滤器,Jinja2 默认转义逻辑会把双引号转成 " ,setOption 拿到的不是 JS 对象而是字符串,图表自然起不来。第二类是 echarts.min.js 的引用方式问题——如果你引的是网络 CDN 地址而不是本地 static 文件,答辩教室的网络环境一旦不通,图表区域就是空的,页面其他部分却正常。
解决:把模板里所有图表变量统一改成 {{ 变量名|safe }},一个都不能漏;把 echarts.min.js 下载到 static 目录,用 url_for('static', filename='echarts.min.js') 引用。这里特别提醒,js 文件放进 static 后要重启一次 Flask,因为浏览器对静态资源有缓存,不强制刷新可能看到还是旧的。
5.2 现象二:read_excel 报错或列名读出来全是 Unnamed
现象是执行 pd.read_excel 直接抛异常,或者数据读出来了但列名是一堆 Unnamed: 0、Unnamed: 1,用“年份”根本取不到数据。
原因通常是两个:一是环境里没装 openpyxl,pandas 没有引擎去解析 xlsx 格式;二是 Excel 的标题行不在第一行,比如第一行是“全国结婚登记数据说明”合并单元格,第二行才是“年份”“结婚登记(万对)”,pandas 默认把第一行当表头,读出来自然全是 Unnamed。
解决:先 pip install openpyxl 补上引擎;再打印 columns 和 head(),确认表头行位置,如果表头在第二行甚至第三行,就调整 read_excel 的 header 参数,改成 header=1 或 header=2。另外一个容易翻车的细节:这份数据单位是万对,做聚合和对比时统一保持这个单位,不要在报告里一会儿说“万对”一会儿说“对”,答辩老师很容易抓到这种单位不自洽的问题。
5.3 现象三:from pyecharts.charts import Line 直接报 ModuleNotFoundError
现象就是你按照文章里的代码 import,终端或者页面报错说找不到 pyecharts.charts 这个模块。
原因大概率是 pyecharts 版本冲突。0.5.x 旧版的导入路径是 from pyecharts import Line、from pyecharts import Bar,而 1.x 用的是 pyecharts.charts 这种子模块方式。如果机器上有多个环境,或者 pip 装到了别的 Python 目录下,就会出现代码是 1.x 的写法、装的是 0.5 的库。
解决:强制重装 1.x 版本,执行 pip install pyecharts==1.9.1,装完再执行 pip show pyecharts 确认版本号。如果项目目录下有 requirements.txt 就按文件里的装。这个问题有个连带坑:1.9.1 版本对应的 echarts 依赖是 5.x,如果下载 echarts.min.js 时不小心下成了 4.x,图表里某些配置项会不生效,所以静态资源版本最好和 pyecharts 版本配套,别混搭。
5.4 现象四:三张图全挤在同一块区域,只显示最后一张
现象是多个图表都渲染了,但页面只显示其中一个,其余 div 是空的,或者所有图表都堆叠在同一个位置,布局完全错乱。
原因是多个图表共用了同一个 id。echarts.init 是按 document.getElementById 定位容器的,第一个图表用 init("chart") 初始化后,第二个图表又用 init("chart"),后者会接管同一个容器,前者就被顶掉了。
解决:确保每个图表的 div 有独立 id,且模板里的 init 目标一一对应。这是一个低级错误,但期末时间紧的时候很容易犯,而且页面不报错、控制台也干净,非常难排查。养成习惯:lineChart、barChart、pieChart 这种命名方式,一个图表对应一个 id,永远不要两个图表共享一个容器。
5.5 现象五:Flask 启动失败,提示 Address already in use
现象是执行 python app.py 后终端报错,说端口 5000 被占用了。
原因基本都是上次调试的 Flask 进程没退出,debug=True 模式下的监听进程常常被忽略,或者机器上其他服务占了 5000 端口。
解决:最简单的方式是把 app.run 的 port 改成 5001 再启动;也可以用命令找到占用进程并结束它。macOS/Linux 下执行 lsof -i:5000 能看到进程 PID,kill PID 即可;Windows 下执行 netstat -ano | findstr 5000 拿到 PID,再用 taskkill /PID 数字 /F 结束。我的建议是直接用改端口的方式更快,毕竟期末时间宝贵,至于 debug 模式,平时调试开着方便,答辩演示时再关掉也来得及,免得回调栈直接打在页面上一脸尴尬。
6. 进阶技巧:答辩前把大作业再升一档
如果项目已经跑通并且能正常交互,那这一章的内容就是锦上添花的部分。以下是三个我教研和实习阶段被老师问过最多的问题,以及对应的升级思路。
第一个是“你这个图能只看某一段年份吗”。默认的折线图把全部年份堆上去了,但真正想对比的往往是近十年。不用写任何前端 JS,在 pyecharts 里加一个 DataZoom 组件就行,这就是数据可视化的区间缩放交互。
line.set_global_opts( datazoom_opts=opts.DataZoomOpts( range_start=20, range_end=80, ), )range_start 和 range_end 的单位是百分比,表示初始状态下显示整个时间轴的某一区间。这个组件直接给折线图加了一个可拖动的缩放条,老师拖动时图表自动刷新,交互感和“我没做动态展示”是两个概念。实际答辩时,你先让老师看全部年份,再拖动缩放条聚焦到某一段,整体观感会好很多。
第二个是“图表怎么导出成图片放进报告”。pyecharts 的图表右上角自带工具栏,其中有一项是保存图片,答辩前把关键图表逐个右键保存成 png,插入到课程设计报告里。注意保存的图是当前页面渲染的完整快照,所以导出前先把数据缩放到你想展示的区间。如果你想要浏览器无头模式自动截图,可以装一个 snapshot_selenium,但期末场景手动保存就够用了,不用为了截图功能增加半小时的配置工作量。
第三个是提交前的一套固定动作。把那几个裸的 html 页面(a.html 这类)和 Flask 页面分别开一遍,验证单图独立打开没问题、Flask 集成页面没问题、图片导出没问题,三个环节都过一遍才算交付完。我当年交期末项目的前一晚,在教室设备上演示,发现模板里的 safe 过滤器漏加了一个,页面白屏,当场脑子也是嗡嗡的。后来每次交可视化作业,我都是强制自己走一遍完整流程:先命令行启动,再浏览器开页面,逐个图表悬浮确认数据,最后把关键图导出成 png 放进报告,确认无误才敢提交。这套流程花不了五分钟,但能给你省下一晚上的答辩翻车时间,希望帮到你。
本文还有配套的精品资源,点击获取