首页 / 资讯中心 / 文章详情

pyecharts实战:基于第七次人口普查的数据可视化全流程

pyecharts实战:基于第七次人口普查的数据可视化全流程 ★ FEATURED ARTICLE
查得到的是表格看不出的是趋势。我最近把一个练手项目翻出来整理——用 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, encodingutf-8-sig) # 过滤掉“全国合计”这类行 df df[~df[地区].str.contains(合计, naFalse)].copy() # 统一转成字符串后去掉千分位逗号再转数值类型 for col in [总人口, 男性人口, 女性人口]: df[col] ( df[col] .astype(str) .str.replace(,, , regexFalse) ) df[col] pd.to_numeric(df[col], errorscoerce) # 删除关键字段为空的行避免图表出现空位 df df.dropna(subset[总人口]) # 顺手看一眼结果 print(df.head())这里有几个细节值得解释。第一读取 CSV 时我用了encodingutf-8-sig而不是encodingutf-8因为从 Excel 另存的 CSV 经常带 BOM直接读会把列名解析成\ufeff地区导致后面列名匹配不上。第二str.replace(,, , regexFalse)里的regexFalse是刻意加的避免逗号被当成正则表达式去解析虽然这个场景里逗号没有特殊性但养成习惯可以减少很多莫名其妙的报错。第三to_numeric(..., errorscoerce)可以把无法解析的值变成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(总人口, ascendingFalse).head(10) bar ( Bar(init_optsopts.InitOpts( themeThemeType.LIGHT, width1200px, height600px )) .add_xaxis(top10[地区].tolist()) .add_yaxis( 总人口万人, [round(v) for v in top10[总人口].tolist()], label_optsopts.LabelOpts( is_showTrue, positiontop, formatter{c} ) ) .set_global_opts( title_optsopts.TitleOpts( title第七次人口普查人口总量前十省份, pos_leftcenter ), tooltip_optsopts.TooltipOpts( triggeraxis, formatter{b}br/人口{c} 万人 ), yaxis_optsopts.AxisOpts( splitline_optsopts.SplitLineOpts(is_showTrue) ), ) ) return bar这里有两个配置点容易忽略。一个是label_opts默认情况下柱状图顶部是不显示数值的要做汇报型图表建议把positiontop打开让读者不用鼠标悬停也能直接看到人口数。另一个是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_optsopts.InitOpts(themeThemeType.CHALK)) .add( series_name年龄构成, data_pairdata_pair, radius[40%, 70%], label_optsopts.LabelOpts(formatter{b}{d}%), ) .set_global_opts( title_optsopts.TitleOpts(title第七次人口普查全国人口年龄构成), tooltip_optsopts.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_optsopts.InitOpts(width1200px, height600px)) .add( series_name人口总量, data_pairdata_pair, maptypechina, is_map_symbol_showFalse, label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( title_optsopts.TitleOpts( title第七次人口普查各省人口分布, pos_leftcenter ), visualmap_optsopts.VisualMapOpts( min_0, max_12000, range_color[#e0f3f8, #abd9e9, #74add1, #4575b4, #313695], is_piecewiseFalse, ), ) ) 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的取值要理解清楚triggeraxis适合柱状图、折线图这种有坐标轴的情形悬停时显示整根轴线上的信息triggeritem适合饼图、地图这种离散项悬停时只显示当前扇区或当前区域的信息。用错之后表现很怪比如饼图里用axis触发可能鼠标移过去毫无反应。formatter是我最建议花时间研究的功能。它本质上是一段模板字符串{b}表示类目名{c}表示数值{d}表示百分比。合理组合这些占位符可以让提示框内容变得非常友好。比如地图里我希望悬停一个省时显示“人口总数XXX 万人”写作formatter{b}br/人口{c} 万人即可。如果你想显示更多字段比如同时展示男性和女性人口那就不只是配置的问题需要先组织好数据。4.2 系列配置与主题定制系列配置对应set_series_opts它控制的是每个数据系列本身的样式比如标签、颜色、线宽等。在一个图表有多个 y 轴系列时系列和全局配置的职责一定要分清楚。bar.set_series_opts( label_optsopts.LabelOpts(is_showFalse), markpoint_optsopts.MarkPointOpts( data[opts.MarkPointItem(type_max, name最大值)] ), )比如我希望柱状图顶部的数值标签只在特定情况下显示就可以在全局关掉、在系列里按需打开。ItemStyleOpts则用于更精细的颜色控制比如给单根柱子设置特殊颜色或者调整柱状图的圆角bar.add_yaxis( 总人口万人, values, itemstyle_optsopts.ItemStyleOpts( color#5B8FF9, border_radius4, ), )主题方面pyecharts 内置了多套主题通过InitOpts(themeThemeType.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, encodingutf-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_start0, range_end30), ], )这样柱状图在展示全量省份时不需要一次性把所有数据塞进视野用户可以通过下方的滚动条交互式查看阅读体验会好很多。还可以尝试把柱状图和折线图叠加在同一张图上比如 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 秒内看懂核心信息才算真正完成。如果对方需要反复追问“这图想表达什么”那就说明图表选择或排版有问题需要重新设计而不是继续堆图。最后再分享一个小技巧做一个项目前先问自己一句——“如果我只保留一张图我会留哪张”这个问题的答案通常就是你这个项目最核心的图。把它做精、做透比泛泛地做十张图有价值得多。我后续做任何可视化都是从这个提问开始七普这个项目也不例外。
阅读完成 · 觉得有帮助?
咨询建站