☰
pyecharts实战:基于第七次人口普查的数据可视化全流程
2026/10/21 9:57:38 网站建设 项目流程

查得到的是表格,看不出的是趋势。我最近把一个练手项目翻出来整理——用 pyecharts 把第七次人口普查的核心数据做成可视化页面。这篇文章不谈理论,直接讲我从数据整理、图表实现到踩坑排障的完整流程,代码都是能直接跑的。

做这个项目的初衷很实际:第七次人口普查数据是公开的,而且维度非常丰富,总量、年龄、性别、民族、城乡、受教育程度样样都有,恰好覆盖了 pyecharts 最常用的几种图表类型。把“地区 + 人口数 + 比例”这类冷冰冰的数字转换成柱状图、饼图、地图,看到的不只是结果,还有数据背后的结构变化。整个过程对于 Python 入门者、数据分析初学者来说,是一次完整的“数据获取 → 清洗 → 可视化 → 展示”演练。

如果你是刚接触 pyecharts 的新手,看到网上各种版本混乱的旧教程也不用慌,这篇文章基于 pyecharts 1.x 的新接口,里面每一步都有完整代码和配置讲解。老手也能直接从后面的“踩坑实录”里找到几处平时容易忽略的细节。

1. 项目定位与工具选型:为什么拿七普数据练手

1.1 七普数据作为练习素材的三个理由

做可视化项目,最怕的不是不会画图,而是找不到一组“值得画”的数据。第七次人口普查数据的好处是:公开、免费、覆盖面广。

公开意味着不需要写爬虫去采集,也不需要伪造数据;覆盖面广意味着可以同时练习多种图表。比如各省人口总量适合用柱状图做排序对比,年龄结构和性别比例适合用饼图看构成,各省人口密度和分布适合用地图做空间展示,城镇化率、受教育程度这类连续指标又可以用折线图或散点图表达。一套数据下来,基本把常用图形都练了一遍。

另外,人口数据本身自带“故事性”。把 60 岁及以上人口占比做成饼图、把不同省份人口规模做成地图,读者扫一眼就能看出老龄化程度、人口分布不均等事实。这种“图表自己能说话”的感觉,能极大提升做项目的成就感,也让作业或作品集更有说服力。

1.2 为什么选 pyecharts 而不是 matplotlib 或 Plotly

关于可视化工具,很多人第一反应是 matplotlib。坦白说,matplotlib 在统计图和论文配图里确实能打,但用来做人口数据这种偏“展示型”的需求,有几个很明显的问题:中文字体要折腾半天;图表是静态的,交互能力很弱;地图功能基本要靠 GeoPandas 和 shapefile 配合,入门成本不低。

pyecharts 的优点恰恰弥补了这些短板,我整理了一个简单的对比:

对比维度pyechartsmatplotlibPlotly
交互能力自带 Tooltip、缩放、图例选择弱强
地图支持内置中国地图,开箱即用需额外地理数据处理需要配置地图资源
中文显示无需额外处理需要设置字体需要设置字体
输出形式HTML,浏览器直接打开图片文件HTML / Jupyter 友好
上手成本低,接口接近前端习惯中中

pyecharts 的接口设计也非常符合数据清洗后的使用习惯:把 x 轴列表和 y 轴列表传给图表对象,再链式调用配置方法,代码写起来很流畅。而且它底层是 ECharts,图表渲染效果天然带着一层“产品感”,做出来的页面用来演示、汇报都很合适。

有一点必须强调:pyecharts 在 0.5.x 旧版本和 1.x 新版本之间接口变化非常大。网上搜到的大量旧教程还在用add() + render()的远古写法,很多方法在 1.x 已经被移除。这篇文章里的所有代码,我都是按 1.x 的from pyecharts.charts import Bar这样的写法来的。如果你照着旧教程跑不通,先确认一下自己装的版本,pip show pyecharts一眼就能看出来。

2. 数据准备与预处理:数据没洗干净,后面全是坑

2.1 原始数据结构长什么样

第七次人口普查的主要数据,官方都会按公报和分省表格的形式发布。下载下来的数据通常是一个包含多列统计指标的表格,最核心的字段大体如下:

字段含义常见问题
地区省级行政区名称可能带“合计”行
总人口该地区常住总人口单位是万人,可能是字符串
男性人口 / 女性人口分性别常住人口可能存在千分位逗号
0-14岁 / 15-59岁 / 60岁及以上年龄构成数值可能有百分比或万人两种口径
城镇化率城镇人口占比可能带百分号

这类数据的特点是:维度多,但“脏”的地方也很典型。比如“地区”列里往往混着一个“全国合计”行,必须删掉,否则画柱状图的时候会多出一根巨大的柱子;人口数字如果是从 PDF 或 Excel 里复制出来的,很可能是带千分位逗号的字符串,比如"12,601",直接丢给 pyecharts 会报错或者显示不正常。

我强烈建议在写任何可视化代码之前,先花 10 分钟把数据规整好。这一步做好了,后面所有图表的代码都是同一套逻辑,效率会高很多。

2.2 用 pandas 完成三大清洗动作

数据清洗在这个项目里主要做三件事:过滤掉合计行、清除字符串里的千分位、把数字列转成数值类型。这里有一段可以直接套用的代码:

import pandas as pd df = pd.read_csv("census_demo.csv", encoding="utf-8-sig") # 过滤掉“全国合计”这类行 df = df[~df["地区"].str.contains("合计", na=False)].copy() # 统一转成字符串后去掉千分位逗号,再转数值类型 for col in ["总人口", "男性人口", "女性人口"]: df[col] = ( df[col] .astype(str) .str.replace(",", "", regex=False) ) df[col] = pd.to_numeric(df[col], errors="coerce") # 删除关键字段为空的行,避免图表出现空位 df = df.dropna(subset=["总人口"]) # 顺手看一眼结果 print(df.head())

这里有几个细节值得解释。第一,读取 CSV 时我用了encoding="utf-8-sig"而不是encoding="utf-8",因为从 Excel 另存的 CSV 经常带 BOM,直接读会把列名解析成\ufeff地区,导致后面列名匹配不上。第二,str.replace(",", "", regex=False)里的regex=False是刻意加的,避免逗号被当成正则表达式去解析,虽然这个场景里逗号没有特殊性,但养成习惯可以减少很多莫名其妙的报错。第三,to_numeric(..., errors="coerce")可以把无法解析的值变成NaN,然后统一用dropna清掉,这样图表数据里就不会混入脏值。

顺便提一句,人口数据涉及到的“老龄化”“性别比”这些话题,在可视化时经常被拿来讨论。我的原则是:只做客观展示,不做主观解读,也不对政策方向做任何引申。图表的任务是让数字自己说话,而不是替数据下结论。

3. 三个核心图表的完整实现

3.1 柱状图:各省人口总量对比

做人口总量对比,柱状图是最直观的选择。下面这段代码会按总人口降序取前 10 个省份,画一根排名柱状图。取前 10 是为了避免地图和柱状图信息重复,也防止标签过于拥挤。

from pyecharts.charts import Bar from pyecharts import options as opts from pyecharts.globals import ThemeType def build_bar(df): # 选出人口总量前十的省份 top10 = df.sort_values("总人口", ascending=False).head(10) bar = ( Bar(init_opts=opts.InitOpts( theme=ThemeType.LIGHT, width="1200px", height="600px" )) .add_xaxis(top10["地区"].tolist()) .add_yaxis( "总人口(万人)", [round(v) for v in top10["总人口"].tolist()], label_opts=opts.LabelOpts( is_show=True, position="top", formatter="{c}" ) ) .set_global_opts( title_opts=opts.TitleOpts( title="第七次人口普查:人口总量前十省份", pos_left="center" ), tooltip_opts=opts.TooltipOpts( trigger="axis", formatter="{b}<br/>人口:{c} 万人" ), yaxis_opts=opts.AxisOpts( splitline_opts=opts.SplitLineOpts(is_show=True) ), ) ) return bar

这里有两个配置点容易忽略。一个是label_opts,默认情况下柱状图顶部是不显示数值的,要做汇报型图表,建议把position="top"打开,让读者不用鼠标悬停也能直接看到人口数。另一个是tooltip_opts里的formatter,它控制鼠标悬停时弹出框的文案,{b}代表 x 轴名称,{c}代表数值,这种模板字符串的书写方式在 pyecharts 里非常常用。

排序逻辑也值得说一句:如果不对 DataFrame 排序,图表会按数据表的原始顺序展示,人口最多的省可能被排到中间甚至最后,视觉冲击力大打折扣。先排序、再选前 10,是这类对比图的标准动作。

3.2 饼图:人口年龄构成

饼图最适合表达“整体中的占比”。第七次人口普查的年龄构成通常分为 0-14 岁、15-59 岁、60 岁及以上三段,可以直接用一个环形饼图来呈现。

from pyecharts.charts import Pie def build_pie(age_data): # age_data 可以是 {"0-14岁": 25338, "15-59岁": 89438, "60岁及以上": 26402} data_pair = [ ("0-14岁", age_data["0-14岁"]), ("15-59岁", age_data["15-59岁"]), ("60岁及以上", age_data["60岁及以上"]), ] pie = ( Pie(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) .add( series_name="年龄构成", data_pair=data_pair, radius=["40%", "70%"], label_opts=opts.LabelOpts(formatter="{b}:{d}%"), ) .set_global_opts( title_opts=opts.TitleOpts(title="第七次人口普查:全国人口年龄构成"), tooltip_opts=opts.TooltipOpts( formatter="{b}<br/>人数:{c} 万人,占比:{d}%" ), ) ) return pie

环形饼图的关键在radius参数:["40%", "70%"]表示内半径与外半径,形成环形后视觉上比实心饼图更轻盈,也更方便在中心区域放一个总结性的文案。label_opts里的{d}%是 ECharts 的百分比占位符,不需要自己额外计算占比。

我实际做下来最深的感受是:饼图的扇区数量越少越好。年龄构成这种三段数据是完美场景,但如果把 31 个省份全塞进一张饼图,那就变成了灾难。遇到类别多的数据,优先想柱状图而不是饼图。

3.3 地图:各省人口分布

地图是这个项目里我最喜欢的部分,也是视觉冲击力最强的一张图。pyecharts 的Map可以直接使用中国地图,配合visualmap色阶,人口多寡一眼就能看出来。

from pyecharts.charts import Map def build_map(df): data_pair = [ [province, int(value)] for province, value in zip(df["地区"], df["总人口"]) ] m = ( Map(init_opts=opts.InitOpts(width="1200px", height="600px")) .add( series_name="人口总量", data_pair=data_pair, maptype="china", is_map_symbol_show=False, label_opts=opts.LabelOpts(is_show=False), ) .set_global_opts( title_opts=opts.TitleOpts( title="第七次人口普查:各省人口分布", pos_left="center" ), visualmap_opts=opts.VisualMapOpts( min_=0, max_=12000, range_color=["#e0f3f8", "#abd9e9", "#74add1", "#4575b4", "#313695"], is_piecewise=False, ), ) ) return m

使用地图时有一个极其重要、且最容易踩坑的点:省份名称必须和 pyecharts 地图内部的地理数据名称完全一致。比如数据文件里写“北京”,地图数据里可能要求“北京市”;数据文件里写“广西”,地图数据里要求“广西壮族自治区”。不一致时地图渲染不会报错,但那个省就是一片空白。

我的处理方式是加一个简单的名称映射函数:

def normalize_province(name): # 这里只列最常见的几种情况,按需扩充 if name == "北京": return "北京市" if name == "天津": return "天津市" if name == "广西": return "广西壮族自治区" if name == "内蒙古": return "内蒙古自治区" if name == "新疆": return "新疆维吾尔自治区" if name == "宁夏": return "宁夏回族自治区" if name == "西藏": return "西藏自治区" return name df["地区"] = df["地区"].apply(normalize_province)

在我自己的项目中,这个映射函数至少帮我解决了十几个地区的显示问题。建议在做地图之前先跑一遍,把没匹配上的名字打印出来,逐一补齐。

另外,如果你安装的是较老版本的 pyecharts,运行地图时可能会提示找不到地图数据。解决方法是单独安装地图扩展包:

pip install echarts-china-provinces-pypkg echarts-china-cities-pypkg echarts-china-misc-pypkg

安装后重启 Python 进程,地图一般就能正常渲染了。

3.4 把多个图表组合到同一个页面

单一图表做出来之后,最自然的想法是把柱状图、饼图、地图放到同一个 HTML 页面里。pyecharts 提供了Tab和Page两种方式:Tab会在页面顶部生成标签页,点击切换;Page则是多个图表从上到下排列,适合滚动阅读。

from pyecharts.charts import Tab def make_report(df, age_data): tab = Tab() tab.add(build_bar(df), "省份柱状图") tab.add(build_pie(age_data), "年龄构成饼图") tab.add(build_map(df), "人口地图") tab.render("第七次人口普查可视化.html")

我用Tab比较多,因为三个图单独拆开看更清爽,不需要一次加载太多图表导致页面卡顿。不过Page更适合给别人做整体汇报,滚动浏览时从总量到结构到空间分布,叙述逻辑是连贯的。两种方案没有绝对的优劣,看使用场景。

4. pyecharts 常用配置项详解:会画图不算完,能调好才算懂

4.1 全局配置:标题、图例与提示框

很多新人画完图后觉得“丑”,问题往往不出在数据,而是全局配置没有做。所谓“全局配置”,指的是set_global_opts里的那些控制整张图表外观和交互的选项。

常用配置项:

配置类典型参数作用
TitleOptstitle,subtitle,pos_left设置标题和副标题位置
LegendOptsis_show,pos_top,orient控制图例显示和位置
TooltipOptstrigger,formatter控制悬停提示框
VisualMapOptsmin_,max_,range_color控制地图色阶
DataZoomOptsrange_start,range_end控制缩放条

TooltipOpts里trigger的取值要理解清楚:trigger="axis"适合柱状图、折线图这种有坐标轴的情形,悬停时显示整根轴线上的信息;trigger="item"适合饼图、地图这种离散项,悬停时只显示当前扇区或当前区域的信息。用错之后表现很怪,比如饼图里用axis触发,可能鼠标移过去毫无反应。

formatter是我最建议花时间研究的功能。它本质上是一段模板字符串,{b}表示类目名,{c}表示数值,{d}表示百分比。合理组合这些占位符,可以让提示框内容变得非常友好。比如地图里我希望悬停一个省时显示“人口总数:XXX 万人”,写作formatter="{b}<br/>人口:{c} 万人"即可。如果你想显示更多字段,比如同时展示男性和女性人口,那就不只是配置的问题,需要先组织好数据。

4.2 系列配置与主题定制

系列配置对应set_series_opts,它控制的是每个数据系列本身的样式,比如标签、颜色、线宽等。在一个图表有多个 y 轴系列时,系列和全局配置的职责一定要分清楚。

bar.set_series_opts( label_opts=opts.LabelOpts(is_show=False), markpoint_opts=opts.MarkPointOpts( data=[opts.MarkPointItem(type_="max", name="最大值")] ), )

比如我希望柱状图顶部的数值标签只在特定情况下显示,就可以在全局关掉、在系列里按需打开。ItemStyleOpts则用于更精细的颜色控制,比如给单根柱子设置特殊颜色,或者调整柱状图的圆角:

bar.add_yaxis( "总人口(万人)", values, itemstyle_opts=opts.ItemStyleOpts( color="#5B8FF9", border_radius=4, ), )

主题方面,pyecharts 内置了多套主题,通过InitOpts(theme=ThemeType.XXX)切换。我常用的有ThemeType.LIGHT、ThemeType.DARK、ThemeType.CHALK。实际经验是:如果你做的页面要在投影仪上展示,CHALK这种深色主题往往比浅色主题更有质感;如果是放在网页正文里跟其他内容混排,浅色主题更安全。

颜色选择有一个原则:数据展示优先使用有连续语义的颜色,比如地图色阶用从浅到深的蓝色,比随意混搭红黄绿更好理解。人口密度与颜色深浅的对应关系,是地图的灵魂。

5. 实战中踩过的坑与排查清单

5.1 地图空白或部分省不显示

这是我见过最多的问题,也是我自己最开始跑的时候被卡住半小时的地方。现象很统一:页面能打开,地图轮廓也在,但要么整片白色,要么个别省份空心。

排查顺序建议是:先确认版本,再确认地图包,最后确认省份名。具体来说,pip show pyecharts看一下版本;没有安装地图扩展包的,安装最稳妥的方式是重新安装全套地图 pypkg;确认数据里的省份名是否和地图地理数据的标准名一致,尤其是“北京/北京市”“广西/广西壮族自治区”这类容易出现精简写法的名称。

一个快速验证名字是否匹配的小技巧:把df["地区"].tolist()打印出来,和地图图例渲染出来的城市名对照,找不到的省份一眼就能看出来。

5.2 中文乱码或 HTML 显示异常

pyecharts 生成的 HTML 文件默认使用 UTF-8 编码,浏览器打开一般不会乱码。真正容易出问题的是两个场景:一是 pandas 导入 CSV 后控制台 print 中文乱码,二是 Windows 的终端输出乱码让人误以为数据出问题。

控制台乱码绝大多数情况不影响图表,因为渲染完全在浏览器里进行。如果你的代码在某个环境里需要打印中文日志,可以临时设置:

import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")

但这个方法只解决当前终端,不要随便塞进正式代码。更规范的方案是让代码逻辑避免依赖终端打印,统一通过to_excel或to_csv落盘检查。

5.3 render 生成的 HTML 打不开

render()默认会在当前工作目录生成文件,如果你用 IDE 运行,文件往往生成在项目根目录而不是脚本所在目录。页面打不开时,先去控制台看一下输出的路径,确认文件确实存在。

另一个细节是文件路径里尽量别带中文和空格。某些浏览器或 Windows 环境下,中文文件名可能导致双击无法打开,虽然不是 pyecharts 本身的问题,但排查起来非常绕。我一般把输出文件名写成census_report.html,部署展示前再手动重命名成中文。

5.4 数据看着不对,但代码没报错

这属于最隐蔽的一种坑。举例:柱状图出来之后发现某根柱子特别高或特别矮,饼图百分比加起来不是 100%,地图某个区域颜色明显失真。

出现这类问题,我 90% 的可能性会去检查两个地方:一是 pandas 读入的列是不是被识别成了字符串,导致sort_values按字典序而不是数字序排列;二是 DataFrame 里是否混入了“合计”“全国”这种汇总行。解决办法就是前面 2.2 节写的清洗流程,用pd.to_numeric强制转换,再明确过滤汇总行。数据可视化项目里,“图错了”的终极原因往往不是 pyecharts,而是数据没洗干净。

我整理了一份问题速查表,方便你排障:

现象可能原因解决动作
地图整体空白地图包未安装安装地图 pypkg 扩展包
部分省空白省名不匹配使用名称映射函数统一标准名
柱状图顺序混乱列是字符串类型先to_numeric再排序
饼图总和不是 100%数据里有缺失或重复行检查并删除汇总行/重复行
HTML 打不开中文路径或文件未生成改用英文路径,确认 render 输出
控制台中文乱码终端编码问题换 UTF-8 终端,不影响图表

6. 进阶思路:从“能出图”到“能读图”

6.1 让图表再“聪明”一点:交互与组合

基础图表做出来之后,可以给页面加更多交互能力。比如在柱状图里加入DataZoomOpts,在数据量很大时提供滚动缩放:

bar.set_global_opts( datazoom_opts=[ opts.DataZoomOpts(range_start=0, range_end=30), ], )

这样柱状图在展示全量省份时,不需要一次性把所有数据塞进视野,用户可以通过下方的滚动条交互式查看,阅读体验会好很多。

还可以尝试把柱状图和折线图叠加在同一张图上,比如 x 轴都是省份,柱状图表示人口总量,折线图表示城镇化率,两者量纲不同时用双 y 轴。pyecharts 的Overlap或者直接在一个Bar上追加Line都能实现。这类组合图在讲“总量和比例关系”的时候非常好用。

6.2 把流程封装成可复用模块

等到图表类型稳定之后,可以把整个流程封装成一个脚本,输入 CSV 路径,输出完整 HTML 报告。这是我认为项目从“一次性作业”升级到“可复用工具”的关键一步。

def main(csv_path): df = read_and_clean(csv_path) age_data = extract_age_data(df) make_report(df, age_data) if __name__ == "__main__": main("census_demo.csv")

以后官方数据更新了,或者想换成其他城市的公开数据,只需要替换输入文件,重新运行一次脚本即可。数据可视化的价值有很大一部分体现在“可复用”上,写一次脚本,长期生效。

6.3 用可视化讲清楚一个事实

做可视化项目,最常见的心态是“把所有能画的图都画出来”,结果页面塞了 20 张图,读者不知道先看哪张。我自己经历过这种阶段,后来总结出来的原则是:一个页面只讲清楚一个核心结论,其它图表都是辅助证据。

放到七普项目里,如果核心结论是“人口向重点区域集聚”,地图就是主角,柱状图是支撑数据;如果核心结论是“老龄化程度在加深”,饼图或堆叠柱状图就是主角,其它图只能当配角。图表数量少一点,信息密度高一点,讲解时的叙事线才会清晰。

我在实际使用中发现,一个页面能让对方在 30 秒内看懂核心信息,才算真正完成。如果对方需要反复追问“这图想表达什么”,那就说明图表选择或排版有问题,需要重新设计,而不是继续堆图。

最后再分享一个小技巧:做一个项目前,先问自己一句——“如果我只保留一张图,我会留哪张?”这个问题的答案,通常就是你这个项目最核心的图。把它做精、做透,比泛泛地做十张图有价值得多。我后续做任何可视化,都是从这个提问开始,七普这个项目也不例外。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询