
简介基于Python与pyecharts的结婚离婚数据分析与可视化项目源码是面向数据可视化期末大作业或课程设计场景的完整参考项目。资源紧扣婚姻登记数据包含数据清洗、图表绘制与交互式页面搭建等环节代码附有注释结构清晰适合初学者模仿与二次开发。压缩包共31个文件涵盖app.py主程序、templates目录下的HTML模板、CSS样式、xlsx原始数据、pyecharts过程ipynb分析笔记及结果图表PNG等类型覆盖源码、文档与可视化产物整体仅1.1MB轻量易部署。目前已有268人学习浏览印证其作为高分作业参考的实用价值。项目自带婚姻登记数据文件与完整网页展示逻辑部署后即可查看交互图表还可快速替换数据用于其他社会统计主题对完成课程报告或答辩展示均有直接帮助。1. 交互式数据可视化期末大作业结婚离婚选题值不值得做交付物长什么样交互式数据可视化期末大作业最容易翻车的不是图表画不出来而是数据没洗干净、pyecharts 版本对不上、地图名字配不齐。基于 Pythonpyecharts 做结婚离婚数据分析与可视化是一个性价比很高的选题数据口径公开、指标直观图表类型能覆盖柱状图、折线图、地图和饼图恰好把一条数据分析项目的完整链路串起来——从 CSV 清洗到生成一份能双击打开、能缩放、能悬停看数值的交互式 HTML 报告。这个方案适合三类人要做期末大作业又不愿意写流水账的学生想拿现成源码改成自己数据的初学者以及需要快速交付一份带看板的数据分析项目的从业者。下面直接按这条链路把做法、参数和坑写清楚。2. 数据准备是可视化的地基把结婚离婚原始数据清洗成可用的 DataFrame2.1 拿到源码先确认三件事数据格式、pyecharts 版本、主脚本入口你手里那份源码包拿到以后先不要急着跑主程序。按我接手的这种大作业源码的常规结构它至少包含一个数据文件和一个主脚本。我第一次做类似项目时犯过一个低级错误直接双击主脚本结果文件编码不对、列名带 BOM、年份列是字符串一连串报错把半小时耗没了。用下面这段代码先探路三分钟就能确认数据能不能被 pandas 正确读取head -n 5 marriage_divorce.csvimport pandas as pd df pd.read_csv(marriage_divorce.csv) print(df.head(3)) print(df.dtypes)逻辑说明先用head看原始文本的字段分隔符和表头再用pd.read_csv试读。df.dtypes能立刻暴露两个典型问题——年份列是否被读成字符串数值列是否因为混入了“—”这类占位符而变成 object。参数说明read_csv默认按 UTF-8 解析。如果文件是 GBK 编码这里会直接抛UnicodeDecodeError如果文件是 UTF-8 with BOM第一列列名会多出一个看不见的\ufeff后续所有按列名的操作都会静默失效。后面会专门处理这两种情况。确认完数据再看一眼 pyecharts 主版本。期末大作业源码最大的分水岭就在这pyecharts 0.5.x 和 1.x 的 API 几乎不兼容旧教程里from pyecharts import Bar的写法在 1.x 里会直接报AttributeError。所以主脚本第一行如果是旧式导入先按后面的方式改掉再谈跑通。2.2 把字符串变成可计算数字编码、单位、缺失值处理民政口径的结婚离婚数据常见形态是一张“省份 × 年份 × 指标”的长表或者按年份拆分成的多张宽表。不管是哪种落到 pandas 里要做的第一件事都是统一编码、清洗列名、把数值列从字符串转成真正的数字。import pandas as pd df pd.read_csv(marriage_divorce.csv, encodingutf-8-sig) df.columns [c.strip() for c in df.columns] df[年份] df[年份].astype(int) df[结婚登记] pd.to_numeric(df[结婚登记], errorscoerce) df[离婚登记] pd.to_numeric(df[离婚登记], errorscoerce) df df.dropna(subset[结婚登记, 离婚登记]) print(df.groupby(年份)[[结婚登记, 离婚登记]].sum().head())逻辑说明encodingutf-8-sig同时解决 UTF-8 和带 BOM 两种情况GBK 文件则要换成encodinggbk我一般会写一个try...except轮流尝试但期末项目里手动指定一次就够了。pd.to_numeric(..., errorscoerce)的作用是原始表格里常见的“—” “/” “null” 这类占位符会被转成NaN而不是让整列变成字符串随后dropna把缺失行删掉图表阶段就不会出现“数据点突然断掉”的怪象。参数说明astype(int)前必须先保证年份列没有缺失值否则会报IntCastingNaNError。数值列如果原始单位是“件”而不是“万对”要在这一步统一除以 10000并且在整个项目里只用这一种单位不然后面折线图、柱状图互相引用时量纲对不上会非常难查。2.3 长表宽表互转melt 和 pivot_table 的两种使用场景pyecharts 的图表接口习惯于接收“ X 轴列表”和“ Y 轴列表”也就是宽表思维。而民政原始数据往往是长表思维一行是“2020 年 广东省 结婚登记”。所以要先想清楚画“某一年各省地图”需要把长表转宽画“历年趋势”则要保持长表按年聚合。# 宽表转长表多个指标列压成两列 long_df df.melt( id_vars[省份, 年份], value_vars[结婚登记, 离婚登记], var_name指标, value_name数值 ) # 长表转宽表某一年的指标摊开成列 wide_df long_df.pivot_table( index[省份, 年份], columns指标, values数值 ).reset_index() print(wide_df.head())逻辑说明melt里id_vars是保留不变的维度列value_vars是要折叠的数值列。转换后原来“结婚登记”“离婚登记”两列变成“指标”一列每个省份每年出现两行。pivot_table是反向操作index决定行维度columns决定新列名来源values是被聚合的数值列。参数说明pivot_table默认对重复行做聚合所以如果原始数据里同一省份同一年份出现了多条记录这里会用均值悄悄合并不会报错。写大作业时务必先drop_duplicates再去 pivot否则教师抽查数据时你答不清“为什么我这个省的值和统计公报对不上”。这也是数据清洗阶段最值得花时间的一步。2.4 快速校错用 groupby 和 describe 先替你看一遍图数据进图表之前我习惯先打印一轮分组统计。这一步看起来多此一举却能拦下大部分“图很好看但数据是错的”事故。print(df[df[年份] 2020].sort_values(离婚登记, ascendingFalse).head(10)) print(df.groupby(省份)[离婚登记].agg([sum, mean, count]).head()) print(df.describe())逻辑说明第一行看某一年数值最高的省份确认是否与常识大致吻合第二行看每个省份的记录条数count如果明显少于年份总数说明该省份有缺失第三行describe()给出的min/max/mean直接对应后面visualmap的max_设置也是答辩时老师最喜欢追问的点。参数说明agg([sum, mean, count])是三列结果不要只算 mean因为“均值异常高”很可能是一个小省份只有一年数据造成的count能暴露样本量问题。到这一步数据地基就稳了可以放心进入 pyecharts 画图阶段。3. 基于 pyecharts 把结婚离婚数据画成图版本、选型、参数与看板组装3.1 先用一条命令确认 pyecharts 版本与 API 风格pyecharts 1.x 和 0.5.x 的差别大到可以当成两个库来看。0.5.x 的写法是from pyecharts import Bar; bar Bar(标题)1.x 的写法是from pyecharts.charts import Bar再用options里的opts对象统一设置标题、图例、提示框。网上大量 python 教程和博客还停留在 0.5.x照着抄必然翻车。import pyecharts print(pyecharts.__version__) from pyecharts.charts import Bar, Line, Map, Pie, Page, Timeline from pyecharts import options as opts逻辑说明先打印版本号只要输出是 1.x 就可以继续如果输出 0.5.x先升级再回来跑这段导入。from pyecharts import options as opts是整个 1.x 风格的核心所有标题、颜色、提示框参数都通过opts.xxxOpts传入。pip install -U pyecharts参数说明升级后如果还残留 0.5.x 的缓存建议在虚拟环境里重装。装完导入不报错下面所有代码才成立。这里多说一句每个图对象在init_opts里单独设置宽高和主题最后用Page拼装时各图主题不一致会很突兀所以我在写每个图表前先把尺寸和主题统一成一套常量比如宽度 1000px、高度 600px、主题用light或dark。3.2 画图前先做一份“供图数据”聚合和派生指标画图代码本身不难难的是给图表喂对数据。假设清洗后的df已经是“省份、年份、结婚登记、离婚登记”的长表我先抽出全国历年总量和某一年份的省级数据yearly df.groupby(年份)[[结婚登记, 离婚登记]].sum().reset_index() year2020 df[df[年份] 2020].copy() year2020[离结比] (year2020[离婚登记] / year2020[结婚登记] * 100).round(2) print(yearly.tail()) print(year2020.head())逻辑说明yearly是折线图和柱状图的原料year2020带一列离结比用来做地图分级和后续派生分析。“离结比”是这类项目里最有分析价值的指标它表示每 100 对结婚对应多少对离婚能直接看出地区差异比单独看离婚登记量更有说服力。参数说明reset_index()必须加否则groupby的结果里“年份”是索引而不是列后面yearly[年份].tolist()会直接 KeyError。离结比保留两位小数四舍五入到 0.01 足够地图分段时不会出现一片同样颜色。3.3 四张核心图表与必调参数Bar、Line、Map、Pie这一节是能直接抄的部分。四张图覆盖四种最常见的表达柱状图看对比、折线图看趋势、地图看分布、饼图看占比。先看柱状图和折线图years yearly[年份].tolist() marry yearly[结婚登记].tolist() divorce yearly[离婚登记].tolist() bar ( Bar(init_optsopts.InitOpts(width1000px, height600px, themelight)) .add_xaxis(years) .add_yaxis(结婚登记, marry, gap10%) .add_yaxis(离婚登记, divorce, gap10%) .set_global_opts( title_optsopts.TitleOpts(title全国历年结婚与离婚登记量, subtitle单位万对), tooltip_optsopts.TooltipOpts(triggeraxis, axis_pointer_typecross), datazoom_opts[opts.DataZoomOpts(range_start20, range_end100)], ) .set_series_opts(label_optsopts.LabelOpts(is_showFalse)) ) line ( Line(init_optsopts.InitOpts(width1000px, height600px, themelight)) .add_xaxis(years) .add_yaxis(结婚登记, marry, is_smoothTrue) .add_yaxis(离婚登记, divorce, is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title全国历年结婚与离婚趋势), tooltip_optsopts.TooltipOpts(triggeraxis), ) .set_series_opts( label_optsopts.LabelOpts(is_showFalse), markline_optsopts.MarkLineOpts( data[opts.MarkLineItem(type_average, name均值)] ), ) )逻辑说明add_xaxis只接受列表所以前面要把 DataFrame 列转成tolist()。add_yaxis的第一个参数是系列名它同时作为图例文字和图例开关。柱状图里gap控制同一分组下两根柱子的间距默认是字符串写成10%表示相对柱宽的比例。折线图里is_smoothTrue把折线变成曲线视觉上更柔和但它只是贝塞尔插值不会改变数据点位置。参数说明datazoom_opts是交互式看板的关键——它生成一个可拖动的缩放条年份多的数据不必一屏挤满。答辩时演示“从 2008 年拖到 2020 年”这个动作比静态截图有说服力得多。markline_opts里的type_average会在图上画一条均值参考线这条线在期末答辩中经常被老师当作“你有没有理解数据”的加分项。然后是地图。地图的核心坑是省份名的匹配和visualmap的量程province_vals [(p, v) for p, v in zip(year2020[省份], year2020[结婚登记])] map_chart ( Map(init_optsopts.InitOpts(width1000px, height600px)) .add(结婚登记, province_vals, maptypechina) .set_global_opts( title_optsopts.TitleOpts(title2020年各省结婚登记分布), visualmap_optsopts.VisualMapOpts( max_year2020[结婚登记].max(), is_piecewiseTrue, orientvertical, pos_leftleft, ), ) )逻辑说明.add()的第二参数必须是“(名称, 数值)”的元组列表而不是两个平行列表这是 pyecharts 地图与普通图表最不同的地方。maptypechina使用内置中国地图数据里的省份名必须和地图 JSON 里的 name 字段一致否则该省不会渲染任何颜色。参数说明VisualMapOpts里max_必须手动设置为数据最大值。pyecharts 内部默认值不是自动适配的如果数据量纲到几十万而max_还是默认 100整个地图会只有一种颜色等于图白画了。is_piecewiseTrue把连续色带改成离散分段颜色差异更好读orient和pos_left控制图例条的位置避免挡住地图主体。最后是饼图。饼图最容易犯的错是把 31 个省全塞进去top10 year2020.nlargest(10, 离婚登记) pie_data [(p, v) for p, v in zip(top10[省份], top10[离婚登记])] pie ( Pie(init_optsopts.InitOpts(width800px, height500px)) .add(离婚登记, pie_data, radius[20%, 60%], center[50%, 55%]) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c})) .set_global_opts(title_optsopts.TitleOpts(title2020年离婚登记量Top10省份)) )逻辑说明nlargest(10, 离婚登记)先取出前十名再用列表推导式组装成元组列表。15 个以上扇区时饼图的可读性急剧下降31 个省全部放进饼图完全是灾难这是数据可视化里的基本取舍。参数说明radius[20%, 60%]是内外半径内半径留空形成环形饼图中间可以放标题或总数值观感比实心饼图专业。center[50%, 55%]控制圆心位置默认居中。如果想做玫瑰图在.add()里加rose_typeradius扇区角度相同时半径表达数值这种图型在答辩现场非常出效果。四张图的选型可以按下面这张表来定这也是我每次做数据看板的标准思路图表表达目的核心参数常见误区Bar对比多个类别的数值gap、datazoom_opts年份过多时柱子全部挤在一起Line表达时间趋势is_smooth、markline_opts数据点少却强行平滑Map表达地域分布visualmap_opts.max_、省份名匹配不设max_导致一片同色Pie表达占比构成radius、nlargest限数量扇区过多读不出信息3.4 把多张图组装成一个交互式看板Page、Tab 与 Timeline单张图表渲染成 HTML 容易但期末大作业的加分点在于“交互式看板”。pyecharts 提供三种组装方式Page顺序堆叠、Tab标签切换、Timeline时间轴播放。page Page(layoutPage.DraggablePageLayout, page_title结婚离婚数据看板) page.add(bar, line, map_chart, pie) page.render(dashboard.html)逻辑说明Page.DraggablePageLayout会生成一个可拖拽布局的 HTML图表可以随意移动位置。跑起来后第一次打开会把“默认布局”写进一个 JSON 文件下次渲染就用这份布局。对期末大作业来说这等于只写几行代码就免费获得一个“看板自定义”功能答辩时现场拖一下效果完全不一样。参数说明page_title是浏览器标签页的标题建议改成和项目一致的中文名。要注意Page混用多个主题时每张图的init_opts必须在创建时就统一否则页面里不同图表的背景和配色会打架。Timeline 适合做“按年份播放”的效果比如逐年展示结婚登记量排名tl Timeline(init_optsopts.InitOpts(width1200px, height600px)) tl.add_schema(pos_bottom0, is_auto_playTrue, play_interval2000) for year in [2015, 2016, 2017, 2018, 2019, 2020]: sub df[df[年份] year].nlargest(8, 结婚登记) b ( Bar() .add_xaxis(sub[省份].tolist()) .add_yaxis(结婚登记, sub[结婚登记].tolist()) .set_global_opts(title_optsopts.TitleOpts(titlef{year}年结婚登记量Top8)) ) tl.add(b, f{year}年) tl.render(timeline.html)逻辑说明Timeline像是一个容器每次tl.add(图表, 标签)就加入一帧。is_auto_playTrue让页面打开后自动轮播play_interval2000是每 2 秒切一年。这里我用nlargest(8, ...)只取前八名避免每帧柱子过多。参数说明Timeline 的每个子图都会共享同一个坐标系类型所以不要在一帧里放柱状图、下一帧放饼图那样切换会很突兀。最稳妥的用法是固定一种图型只换年份数据。4. 结婚离婚数据可视化项目常见问题排查5 个让图表翻车的真实案例4.1 页面白屏浏览器打开 HTML 后只有标题和一片空白现象bar.render()成功执行HTML 文件也生成了但双击打开后页面主体区域空白控制台报Failed to load resource或长时间转圈。原因pyecharts 渲染出的 HTML 默认从 CDN 加载echarts.min.js。演示环境没联网或者浏览器拦截了外链脚本时图表自然画不出来。这是期末答辩现场最常见的翻车点没有之一。解决提前把echarts.min.js下载到本地通过current_config.ONLINE_HOST指向本地路径。具体做法是在渲染脚本最前面加一段配置from pyecharts.globals import CurrentConfig, OnlineHostType CurrentConfig.ONLINE_HOST ./js/把下载好的echarts.min.js放进js目录重新 render之后整个 HTML 不再依赖外网。如果时间来不及至少要在答辩前用手机热点让笔记本浏览器完整加载一次页面并保持缓存。4.2 地图大片空白或只有零星省份有颜色现象Map 图能渲染出中国轮廓但只有几个省份有颜色大部分省份是浅灰空白或者图例显示正常地图区域全是一种颜色。原因两种可能。第一种是省份名不匹配map JSON 里叫“广东省”你的数据里写“广东”匹配不上就空白。第二种是visualmap的max_没有手动设置数据最大值与默认量程差距过大导致所有省份都落在同一档颜色上。解决先统一省份全名再做量程设置。省份名映射是绕不开的活儿常见写法如下province_full_name { 北京: 北京市, 天津: 天津市, 上海: 上海市, 重庆: 重庆市, 内蒙古: 内蒙古自治区, 广西: 广西壮族自治区, 西藏: 西藏自治区, 宁夏: 宁夏回族自治区, 新疆: 新疆维吾尔自治区, } df[省份] df[省份].replace(province_full_name)逻辑说明replace只替换键值对应项其余省份原名保留。替换顺序要在画图前完成最好和数据清洗放在同一段代码里。执行后打印df[省份].unique()检查一遍确认没有遗漏。参数说明write 黑龙江 河北这类本身不带“省”字的数据如果用黑龙江省匹配不到但黑龙江匹配上了反向情况也存在。最可靠的办法是打印一次内置地图名称对照着维护映射表不要凭感觉写。4.3 中文显示成方块、图例被截断现象柱状图的 X 轴中文省份名变成一个个方框或者标题和图例超出画布边缘看不到。原因中文字体缺失通常在服务器端渲染时才出现本地浏览器一般不会。但图例截断是真实高频问题——默认图例位于顶部中央系列名较长、图表宽度不足时图例自动换行会把标题区域挤掉。解决加宽画布并给图例明确位置bar.set_global_opts( title_optsopts.TitleOpts(title全国历年结婚与离婚登记量, pos_leftcenter), legend_optsopts.LegendOpts(pos_top8%, orienthorizontal), )逻辑说明pos_leftcenter把标题居中LegendOpts的pos_top下移图例避免与主标题重叠。如果系列名超过 5 个汉字建议用简短名称比如把“结婚登记万对”改成“结婚登记”。参数说明LegendOpts(orienthorizontal)是横向排布系列多的时候改成orientvertical加pos_right5%放到右侧能省下大量顶部空间。这个调整对 Map 和 Line 同样生效。4.4 折线图数据挤在底部一整条折线几乎贴地现象Line 图正常渲染x 轴年份也对但结婚登记和离婚登记两条线全部紧贴 y 轴底部高度差异肉眼几乎看不出来。原因传入add_yaxis的数值是字符串列表而不是数字。pandas 里df[结婚登记].tolist()如果原始列是 object 类型就会得到[567.8, 612.3]这样的字符串列表。ECharts 拿到字符串时把它当成离散类目数值大小关系丢失于是出现“被压扁”的视觉效果。解决回到清洗阶段用pd.to_numeric强制转换转换后再tolist()。排查时直接打印看一眼marry yearly[结婚登记].astype(float).tolist() print(marry[:5]) print(all(isinstance(x, (int, float)) for x in marry))逻辑说明astype(float)先做类型强制再用all(isinstance(...))快速验证列表里是不是全部为数值类型。第二条语句返回True才能放心进图表。这个坑在柱状图里同样存在只是因为柱状图把每个值都画成一根“柱子”问题没有折线图直观。参数说明astype(float)遇到NaN不会报错但会把整列变成 float折线会断点。如果序列里存在NaN先看总数确认是不是个别年份缺失不要强行补 0补 0 会改变统计学意义。4.5 按旧教程改写报错pyecharts 没有 Bar 元素现象import pyecharts不报错但from pyecharts import Bar或from pyecharts import Faker直接抛AttributeError: module pyecharts has no attribute Bar。原因安装的是 pyecharts 1.x但你抄的教程是 0.5.x 时代的写法。1.x 把图表类全部移到了pyecharts.charts子模块同时另起了一套opts参数体系。解决统一按下面的结构导入from pyecharts.charts import Bar, Line, Map, Pie, Page, Timeline from pyecharts import options as opts逻辑说明以后看到旧教程里bar Bar(标题)这种直接把标题传给构造函数的写法都要改成Bar()加.set_global_opts(title_optsopts.TitleOpts(...))。旧的add()方法在 1.x 里拆成了add_xaxis()和add_yaxis()但凡报unexpected keyword argument多半就是新旧混用。参数说明如果旧项目里大量用了pyecharts.Faker造数据1.x 里没有对应模块可以直接删掉换成自己数据。这个报错本身不可怕可怕的是项目里一半新语法一半旧语法报错位置在几十行之外排查起来最耗时间。5. 让大作业从“能跑”到“能答辩”交互增强、数据校验与演示收尾5.1 答辩前用三分钟做一次数据比对图和表都渲染出来以后我会花三分钟做一次“图数一致性”校验。方法是把图表里的最大值、最小值与describe()的结果对齐print(year2020[结婚登记].describe()) total_prov year2020[结婚登记].sum() total_yearly yearly[yearly[年份] 2020][结婚登记].iloc[0] print(省级合计:, total_prov) print(年度合计:, total_yearly) assert abs(total_prov - total_yearly) 0.01, 两口径对不上逻辑说明省级合计和年度合计算的是同一个东西如果两条链路有一处清洗错误这个断言会直接报错。这是答辩前最重要的一次自查比反复调图表样式有意义得多。参数说明abs(...) 0.01是为了容忍浮点精度。对不上的时候优先检查drop_duplicates是否执行过以及是否混入了非省份行比如“全国”“合计”这类汇总行。5.2 让评委动手玩dataZoom、tooltip 与图例开关答辩演示有一个反直觉的技巧别一口气把结论讲完留两个“能动手”的交互点给评委。第一是datazoom_opts的缩放条评委拖动时能感受到图是活的第二是图例点击——系列名上点一下可以隐藏对应数据这个功能 pyecharts 默认就有不需要额外参数。Tooltip 触发方式我用triggeraxis而不是默认的item因为折线图上鼠标悬停在坐标轴上比精准悬停在某一个点上容易得多。把这些交互点留到总结阶段再演示答辩节奏会从容很多。5.3 离线演示预案与字段命名规范如果你的演示场馆没有稳定网络除了前面提到的本地echarts.min.js还有一个办法把生成的 HTML 放进一个没有空格和中文的纯英文路径下再打开。路径中的中文在个别浏览器里会让资源加载失败这是最容易被忽视的环境问题。另一个我从上届学长那里学到的教训是文件命名规范数据文件、主脚本、输出 HTML 三者的命名要一致地带上项目名比如marriage_divorce_data.csv、main.py、dashboard.html。大作业源码包被反复拷贝之后很多翻车现场都是因为改错了文件、运行了旧的 py 脚本。我现在的习惯是任何一张图在报告里出现之前先自己打开生成的 HTML把每一个交互点亲手点一遍tooltip 数值和 DataFrame 打印结果不一致就回头改数据绝不带病上台。希望帮到你。本文还有配套的精品资源点击获取