一、前言

刷 B 站经常看到各类动态排名轮播柱状图(Bar Chart Race),人口、GDP 历年变迁动态排行视频播放量动辄几十万。本文基于Pandas+pyecharts完整复刻 B 站同款深色风动态人口排行,数据集覆盖1960–2024 全球各国人口,从原始脏数据清洗→数据重塑→单图测试→全量时间线轮播→B 站风格精细化美化全流程落地,生成可交互 HTML 动态排行,悬停查看人口、自动轮播、手动切换年份全部实现,新手可一键复现全代码。

项目最终效果

  1. 深色暗黑 B 站主题背景,柱子随机循环渐变彩色 + 圆角样式;
  2. 每年自动按人口降序取 Top20,人口越多国家排在图表最上方;
  3. 时间轴自动 600ms 切换年份、循环播放,支持暂停 / 拖拽切换年份;
  4. 鼠标悬浮单个国家,弹窗展示国家名称 + 精准人口数值;
  5. 导出 HTML 文件,浏览器直接打开,无需额外部署环境。

二、技术栈与环境准备

1. 依赖库安装

打开 cmd 执行 pip 安装依赖:

bash

pip install pandas pyecharts

2. 技术选型说明

表格

工具 用途
Pandas 原始数据读取、黑名单过滤清洗、宽表转长表、类型转换、空值剔除
pyecharts Bar 横向柱状图绘制、Timeline 时间线实现逐年轮播、图表样式配置
JsCode JS 自定义动态柱子配色,实现不同排名柱子自动换色
CDN 替换 替换 echarts 国内 CDN,解决 HTML 打开空白、资源加载失败问题

三、项目整体开发流程

整体分四大阶段:数据加载探查→黑名单精准清洗→数据格式重塑→可视化分步开发(单图→基础轮播→B 站美化完整版)

四、步骤 1:数据导入与全局配置(解决国内 HTML 空白坑)

4.1 全局导包 + 替换 CDN(关键!国内必写)

很多同学生成 HTML 打开空白,根源是 pyecharts 默认海外 CDN 无法访问,提前替换 jsdelivr 国内镜像:

python

# 1.导入核心工具
import pandas as pd
from pyecharts.charts import Bar, Timeline
from pyecharts import options as opts
from pyecharts.globals import ThemeType, CurrentConfig
from pyecharts.commons.utils import JsCode

# 修复CDN,解决国内网络HTML空白(必加配置)
CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/"

4.2 读取人口数据集

数据集:世界人口数据-中文版(1960-2024).csv,编码 gbk 避免中文乱码

python

# 读取中文版人口数据集
df = pd.read_csv('./data/世界人口数据-中文版(1960-2024).csv',encoding='gbk')
# 预览前5行
df.head()

4.3 原始数据探查

  • 原始数据:266 行、70 列,4 列基础属性(国家名、国家编码、指标名、指标编码),剩余 66 列为 1960–2025 年份人口列;
  • 2025 年全为空值 NaN,有效统计区间:1960~2024
  • 脏数据问题:包含全球、各大洲、收入分组、非主权地区(港澳台、海外领地)汇总数据,不能参与国家排行,需要黑名单过滤。

五、步骤 2:核心数据清洗(项目重难点:黑名单剔除无效数据)

5.1 定义黑名单,剔除非主权 / 汇总数据

黑名单包含:全球 / 大洲汇总、收入等级分类、世界银行分组、非主权领地 / 海外行政区,最终筛选出 195 个合法主权国家

python

#黑名单:剔除大洲、收入等级、发展区域汇总、非主权地区
black_list = [
# 全球 & 大洲汇总
"世界", "北美", "东亚与太平洋地区", "欧洲与中亚地区",
"东亚与太平洋地区(不包括高收入)", "欧洲与中亚地区(不包括高收入)",
"拉丁美洲与加勒比海地区", "拉丁美洲与加勒比海地区(不包括高收入)",
"中东、北非、阿富汗与巴基斯坦", "中东与北非地区(不包括高收入)",
"撒哈拉以南非洲地区", "撒哈拉以南非洲地区(不包括高收入)",
"南亚", "小国", "加勒比小国", "太平洋岛国", "其他小国",
"未分类国家", "阿拉伯联盟国家", "欧洲联盟", "欧洲货币联盟",
"经合组织成员", "重债穷国 (HIPC)", "脆弱和受衝突影響的情況下",
# 收入等级
"高收入国家", "低收入国家", "中等收入国家",
"中高等收入国家", "中低等收入国家", "中低收入国家",
#人口红利阶段
"早人口紅利", "後期人口紅利", "預人口紅利", "人口紅利之後",
#发展水平分类
"最不发达国家:联合国分类",
# 世界银行分组
"IBRD与IDA", "只有IBRD", "只有IDA", "IDA總", "IDA混合",
"东亚与太平洋地区 (IBRD与IDA)", "欧洲与中亚地区 (IBRD与IDA)",
"拉丁美洲与加勒比海地区 (IBRD与IDA)", "中东与北非地区 (IBRD与IDA)",
"南亚 (IBRD与IDA)", "撒哈拉以南非洲地区 (IBRD与IDA)",
# 地区/领地/非主权地区
"阿鲁巴", "美属萨摩亚", "百慕大", "库拉索", "开曼群岛",
"海峡群岛", "法罗群岛", "直布罗陀", "格陵兰", "关岛",
"中国香港特别行政区", "中国澳门特别行政区", "圣马丁(法属)",
"圣马丁(荷属)", "北马里亚纳群岛", "新喀里多尼亚", "波多黎各",
"约旦河西岸和加沙", "法属波利尼西亚", "特克斯科斯群岛",
"英屬維爾京群島", "美属维京群岛", "科索沃"
]
#反向过滤:不在黑名单的数据保留
df = df[~df["Country Name"].isin(black_list)].reset_index(drop=True)
print("过滤后剩余主权国家数量:",df["Country Name"].nunique())

5.2 宽表转长表(melt 重塑数据格式)

原始是宽格式:一行国家、多列年份;绘图需要长格式:一行 = 某国某一年人口,使用pd.melt拆分行:

python

#筛选属性列(固定字段)和年份列(纯数字列)
attr_cols = ["Country Name","Country Code","Indicator Name","Indicator Code"]
year_cols = [col for col in df.columns if col.isdigit()]

# melt:宽表转为长表
df_long = pd.melt(
    df,
    id_vars=attr_cols,        # 不变字段:国家信息
    value_vars=year_cols,     # 需要拆分的所有年份列
    var_name="Year",          # 拆分后新列:年份
    value_name="Population"   # 拆分后新列:人口数值
)

5.3 数据类型转换 + 空值清理

python

# 只保留绘图需要三列
df_clean=df_long[["Country Name","Year","Population"]]
#年份转为整数
df_clean["Year"]=df_clean["Year"].astype(int)
#人口转为数值类型
df_clean["Population"]=pd.to_numeric(df_clean["Population"])
#删除人口空值(剔除2025无效空数据)
df_clean.dropna(inplace=True)
#查看清洗后数据
df_clean.head()

清洗后:有效数据 12675 条,仅保留 195 个主权国家 1960–2024 人口数据。

六、步骤 3:可视化开发(分 3 个版本迭代)

版本 1:单年份静态横向柱状测试(1990 年 Top20 人口排行)

先单年绘图验证逻辑,reversal_axis()实现横向柱状,深色主题:

python

# 筛选1990年数据
test_df=df_clean[df_clean["Year"]==1990]
#人口降序取前20
test_df=test_df.sort_values(by="Population",ascending=False).head(20)

#绘制单年横向柱状图
bar_test=(
    Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
    .add_xaxis(test_df["Country Name"].tolist())
    .add_yaxis("人口",test_df["Population"].tolist())
    .reversal_axis() #坐标轴反转→横向柱状
    .set_global_opts(
        title_opts=opts.TitleOpts(title="1990年世界人口排名",pos_left="center"),
        legend_opts=opts.LegendOpts(is_show=False), #隐藏图例
    )
    .set_series_opts(label_opts=opts.LabelOpts(position="right")) #数值在柱子右侧
)
bar_test.render_notebook() #jupyter预览
# bar_test.render("1990人口排行.html") #导出html

版本 2:基础版 TimeLine 全年份自动轮播

遍历所有年份,逐年生成柱状图加入时间轴,实现自动轮播:

python

year_list=df_clean["Year"].unique().tolist()
#初始化时间线
timeline = Timeline(init_opts=opts.InitOpts(width="1500px",height="820px",theme=ThemeType.DARK,bg_color="#0a0a0a"))

#循环每一年绘图
for year in year_list:
    data_df=df_clean[df_clean["Year"]==year]
    #降序取top20,再升序排序(配合反转坐标轴,大数在上)
    data_df=data_df.sort_values(by="Population",ascending=False).head(20)
    data_df=data_df.sort_values(by="Population",ascending=True)
    country=data_df["Country Name"].tolist()
    population=data_df["Population"].tolist()

    bar=(
        Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
        .add_xaxis(country)
        .add_yaxis("人口",population)
        .reversal_axis()
        .set_global_opts(
            title_opts=opts.TitleOpts(title=f"{year}年世界人口排名",pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False),
        )
        .set_series_opts(label_opts=opts.LabelOpts(position="right"))
    )
    timeline.add(bar,str(year))

#设置自动播放、循环切换
timeline.add_schema(is_auto_play=True,play_interval=600,is_loop_play=True)
timeline.render_notebook()
# timeline.render("基础版人口轮播.html")

排序关键知识点: 先降序取 Top20,再升序 +reversal_axis(),人口最多的国家会固定在图表最上方,符合观看习惯。

版本 3:B 站高级美化完整版(最终成品代码)

增加JS 动态变色、圆角柱子、自定义时间轴样式、标签格式化,对标 B 站视频可视化风格:

python

# 初始化最终时间线画布
timeline_final = Timeline(init_opts=opts.InitOpts(
    width="1600px",
    height="850px",
    theme=ThemeType.DARK,
    bg_color="#080808" #极深黑B站背景
))

# JS代码:实现柱子循环随机配色
color_js = JsCode("""
function(params){
    let c = ['#ff4757','#ffa502','#fffa65','#2ed573','#1e90ff','#3742fa','#a55eea','#48dbfb'];
    return c[params.dataIndex % c.length];
}
""")

year_list=df_clean["Year"].unique().tolist()
#循环逐年绘图
for year in year_list:
    data_df=df_clean[df_clean["Year"]==year]
    data_df=data_df.sort_values(by="Population",ascending=False).head(20)
    data_df=data_df.sort_values(by="Population",ascending=True)
    country=data_df["Country Name"].tolist()
    population=data_df["Population"].tolist()

    bar=(
        Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
        .add_xaxis(country)
        .add_yaxis("人口",population,
            #圆角、透明度、动态颜色
            itemstyle_opts=opts.ItemStyleOpts(color=color_js,opacity=0.85,border_radius=7),
            #标签格式化:数字+人
            label_opts=opts.LabelOpts(position="right",formatter="{c}人",font_size=11)
        )
        .reversal_axis()
        .set_global_opts(
            title_opts=opts.TitleOpts(title=f"{year}年世界人口排名",pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False),
        )
    )
    timeline_final.add(bar,str(year))

#精细化配置时间轴样式(蓝色进度条、白色节点、加粗年份文字)
timeline_final.add_schema(
    is_auto_play=True,
    is_loop_play=True,
    play_interval=600, #600ms切换一年
    is_timeline_show=True,
    pos_left="center",
    width="95%",
    label_opts=opts.LabelOpts(color="#ffffff", font_size=11,font_weight="bold"),
    linestyle_opts=opts.LineStyleOpts(color="#00a1ff", width=4),
    itemstyle_opts=opts.ItemStyleOpts(color="#00a1ff",border_color="#ffffff", border_width=2),
)

#导出最终HTML文件
timeline_final.render("B站同款人口动态排行.html")
timeline_final.render_notebook()

七、项目成果总结

数据层面:剔除全球汇总、地区、非主权地区脏数据,精准保留 195 个主权国家 1960–2024 人口数据; ✅ 交互层面:自动轮播、暂停播放、手动拖拽年份、悬浮查看人口全部实现; ✅ 视觉层面:深色 B 站暗黑风格、圆角渐变彩色柱子、精细化时间轴,对标 B 站数据视频; ✅ 落地成果:生成独立 HTML 文件,脱离 Python 环境,任意浏览器打开即可运行。

八、后续优化拓展方向

  1. 国旗优化:X 轴国家名称前嵌入各国国旗 Base64 图标,视觉效果更强;
  2. 多指标切换:增加下拉框,一键切换人口总数 / 人口增长率 / GDP 排行;
  3. 性能优化:百万级大数据提前预计算各年份 Top20 缓存,减少前端渲染压力;
  4. 导出视频:通过 selenium 自动截图 HTML 轮播,拼接生成 MP4 排行视频。

九、踩坑总结(避坑指南)

  1. HTML 空白:必须替换CurrentConfig.ONLINE_HOST国内 CDN,否则 echarts 资源加载失败;
  2. 排序颠倒:排行榜必须「降序取 TOP→升序 + 反转坐标轴」两步,否则排名倒置;
  3. 中文乱码:csv 读取指定encoding="gbk",国内中文版数据集通用;
  4. 空值报错:原始数据 2025 全为空,dropna()提前剔除无效数据。

更多推荐