一、项目前言 & 效果展示

动态排序轮播图(Bar Chart Race)是当下短视频、数据分析平台非常热门的可视化形式,B 站上大量人口、GDP、榜单类动态排名视频均采用该样式。本次项目完整复刻 B 站深色风格动态人口排名轮播图,基于世界银行 1960-2024 年全球人口数据集,使用Pandas完成数据清洗、宽表转长表,结合pyechartsTimeline时间轴组件 +Bar柱状图,实现自动轮播、动态换色、圆角柱状图、自定义时间轴样式等商业级可视化效果,同时解决国内网络 Echarts 加载空白、数据格式错乱、无效汇总数据干扰等常见问题。

最终实现效果亮点

  1. 数据纯净:剔除大洲、收入分组、非主权地区,仅保留 195 个真实主权国家;
  2. 动态轮播:1960-2024 年自动循环播放,播放间隔 600ms,支持手动切换年份、暂停 / 播放;
  3. B 站风格美化:深色背景、渐变动态配色、圆角柱子、白色文字标签,视觉体验拉满;
  4. 交互完善:鼠标悬浮查看详细人口数值,时间轴节点、轴线自定义配色;
  5. 环境兼容:配置国内 CDN,解决 HTML 文件打开空白、加载失败问题。

二、技术栈与环境准备

1. 核心技术栈

表格

工具库 作用
Pandas 数据读取、缺失值清洗、黑名单过滤、宽表转长表、数据类型转换
pyecharts 绘制横向柱状图、Timeline 时间轴轮播、图表样式全局配置
JsCode 嵌入原生 JS 代码,实现柱子动态循环配色
CurrentConfig 配置国内 Echarts CDN,适配国内网络环境

2. 环境安装

打开终端 / CMD,执行以下命令安装依赖库:

bash

运行

# 安装数据处理库 + 可视化库
pip install pandas pyecharts -i https://pypi.tuna.tsinghua.edu.cn/simple

镜像源加速,避免国外源下载缓慢 / 超时;推荐 Python 3.8 及以上版本。

三、数据集介绍

1. 数据源

数据集名称:世界人口数据-中文版(1960-2024).csv 数据来源:世界银行公开数据,统计1960-2024 年全球各国总人口,共 266 行、70 列。

2. 数据结构探查

  • 固定属性列(4 列)Country Name(国家名称)、Country Code(国家代码)、Indicator Name(指标名称)、Indicator Code(指标代码);
  • 时间序列列(66 列)19601961……2024(每一年单独为一列,宽格式数据),2025 年数据全部为空,直接剔除;
  • 脏数据说明:原始数据包含大洲汇总(亚洲、非洲)、收入分组(高收入国家、低收入国家)、海外领地(中国香港、阿鲁巴等),这类非主权国家数据需要手动过滤。

3. 原始数据样例

表格

Country Name Country Code Indicator Name Indicator Code 1960 1961
阿鲁巴 ABW 人口,总数 SP.POP.TOTL 54922.0 55578.0
NaN AFE 人口,总数 SP.POP.TOTL 130075728.0 133534923.0
阿富汗 AFG 人口,总数 SP.POP.TOTL 9035043.0 9214083.0

四、完整代码分步解析(可直接复制运行)

整体流程:导入库 & 配置 CDN → 读取数据 → 数据清洗(空值 + 黑名单)→ 宽表转长表 → 数据类型转换 → 初始化时间轴 → 动态配色 JS 配置 → 循环绘制每年柱状图 → 时间轴样式配置 → 渲染输出

步骤 1:导入依赖库 + 修复国内 CDN(必加)

国内网络直接运行 pyecharts 生成的 HTML 会出现图表空白,核心原因是默认加载国外 Echarts CDN,这里手动替换为国内稳定镜像。

python

运行

# 1. 导入核心工具库
import pandas as pd
from pyecharts.charts import Bar, Timeline  # 柱状图、时间线轮播组件
from pyecharts import options as opts        # 图表全局配置项
from pyecharts.globals import ThemeType, CurrentConfig  # 内置主题、CDN配置
from pyecharts.commons.utils import JsCode  # 嵌入JS实现动态配色

# 关键:修复国内网络HTML空白问题,指定国内Echarts CDN
CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/"

步骤 2:读取 CSV 数据集

数据集包含中文,指定编码gbk,路径使用原始字符串r避免转义报错。

python

运行

# 2. 读取本地人口数据集,根据自己文件路径修改!
df = pd.read_csv(r"D:\zyx\code\data\世界人口数据-中文版(1960-2024).csv", encoding="gbk")

# 可选:查看数据前5行,验证读取是否成功
print("原始数据预览:")
print(df.head())
# 查看数据集基本信息(行列数、缺失值、数据类型)
print("\n数据集基本信息:")
df.info()

步骤 3:核心数据清洗(剔除脏数据)

这是本项目最关键的一步,分为 3 层清洗:删除国家名称空值、黑名单过滤非主权国家、筛选有效年份列。

python

运行

# 3.1 筛选属性列 和 年份列(自动识别纯数字年份列)
attr_cols = ['Country Name', 'Country Code', 'Indicator Name', 'Indicator Code']  # 固定属性列
year_cols = [col for col in df.columns if col.isdigit()]  # 自动提取1960-2024所有年份列

# 3.2 第一层清洗:删除国家名称为空的行(大洲汇总类数据)
df = df.dropna(subset=['Country Name'])

# 3.3 第二层清洗:黑名单过滤(剔除大洲、收入分组、海外领地等无效数据)
black_list = [
    # 全球 & 各大洲汇总
    "世界", "北美", "东亚与太平洋地区", "欧洲与中亚地区", "拉丁美洲与加勒比海地区",
    "中东、北非、阿富汗与巴基斯坦", "撒哈拉以南非洲地区", "南亚",
    # 收入等级、世界银行分组
    "高收入国家", "中高等收入国家", "中低等收入国家", "低收入国家", "中等收入国家",
    "欧洲联盟", "经合组织成员", "重债穷国 (HIPC)",
    # 非主权地区、海外领地
    "阿鲁巴", "美属萨摩亚", "百慕大", "中国香港特别行政区", "中国澳门特别行政区",
    "波多黎各", "格陵兰", "关岛", "科索沃", "法罗群岛"
]
# 反向过滤:保留不在黑名单内的数据
df = df[~df["Country Name"].isin(black_list)].reset_index(drop=True)
print(f"\n清洗后剩余国家数量:{df['Country Name'].nunique()}")  # 最终保留195个主权国家

步骤 4:宽表转长表(数据重塑)

原始数据是宽格式(一行 = 一个国家,多年份多列),无法直接循环绘图,使用pd.melt()转为长格式(一行 = 一个国家 + 一个年份 + 人口),适配时序可视化场景。

python

运行

# 4. 宽表转长表:拆分年份列,生成 Year(年份)、Population(人口) 新列
df_long = pd.melt(
    df,
    id_vars=attr_cols,       # 保持不变的基础字段
    value_vars=year_cols,    # 需要拆分的所有年份列
    var_name="Year",         # 拆分后新列名:年份
    value_name="Population"  # 拆分后新列名:人口数值
)

# 预览转换后的数据
print("\n宽表转长表后数据预览:")
print(df_long.head())

步骤 5:数据类型转换 & 最终清洗

统一字段类型,删除 2025 年空数据、人口为空的无效行,仅保留绘图所需 3 列。

python

运行

# 5.1 仅保留绘图核心三列:国家名称、年份、人口
df_clean = df_long[["Country Name", "Year", "Population"]]

# 5.2 类型转换:年份转为整数,人口转为数值型
df_clean["Year"] = df_clean["Year"].astype(int)
df_clean["Population"] = pd.to_numeric(df_clean["Population"], errors="coerce")

# 5.3 删除缺失值(剔除2025年全空数据、人口空值)
df_clean = df_clean.dropna()
# 过滤人口大于0的有效数据
df_clean = df_clean[df_clean["Population"] > 0]

print("\n最终清洗完成数据预览:")
print(df_clean.head())

步骤 6:初始化时间轴 & 配置动态配色 JS

  1. Timeline:时间轴容器,统一管理所有年份的柱状图,设置画布大小、B 站深色主题、纯黑背景;
  2. JsCode:编写 JS 代码实现排名不同、柱子颜色不同,颜色循环切换,提升动态视觉效果。

python

运行

# 6.1 初始化时间轴轮播图(B站深色风格,自定义画布大小+背景)
timeline = Timeline(init_opts=opts.InitOpts(
    width="1600px",    # 图表整体宽度
    height="850px",    # 图表整体高度
    theme=ThemeType.DARK,  # 内置深色主题(B站同款)
    bg_color="#080808"     # 极深黑色背景
))

# 6.2 JS代码:实现柱子动态循环配色
color_js = JsCode("""
function(params){
    // 自定义8种高饱和度配色:红、橙、黄、绿、蓝、靛、紫、青
    let colorList = ['#ff4757','#ffa502','#fffa65','#2ed573','#1e90ff','#3742fa','#a55eea','#48dbfb'];
    // 根据柱子索引循环取色,实现排名变色
    return colorList[params.dataIndex % colorList.length];
}
""")

步骤 7:循环绘制每年横向柱状图(核心绘图逻辑)

遍历所有年份,每年生成一张横向柱状图,按人口降序取 TOP20,排序逻辑适配横向柱状图展示规则。

python

运行

# 7. 获取所有唯一年份列表(1960-2024)
year_list = df_clean["Year"].unique().tolist()

# 遍历每一个年份,生成对应柱状图并添加到时间轴
for year in year_list:
    # 筛选当前年份的全部数据
    data_df = df_clean[df_clean["Year"] == year]
    # 按人口降序排序,取前20名国家
    data_df = data_df.sort_values(by="Population", ascending=False).head(20)
    # 再次升序排序:配合reversal_axis(),实现人口越多越靠上(符合阅读习惯)
    data_df = data_df.sort_values(by="Population", ascending=True)

    # 提取绘图数据:国家名称、人口数量
    country_list = data_df["Country Name"].tolist()
    population_list = data_df["Population"].tolist()

    # 绘制单年份横向柱状图
    bar = (
        Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
        .add_xaxis(country_list)  # X轴:国家名称
        .add_yaxis(
            series_name="人口",
            y_axis=population_list,
            # 柱子样式:动态配色、透明度、7px圆角
            itemstyle_opts=opts.ItemStyleOpts(
                color=color_js, opacity=0.85, border_radius=7
            ),
            # 标签样式:柱子右侧显示,格式为「数字+人」,字号11
            label_opts=opts.LabelOpts(
                position="right", formatter="{c}人", font_size=11
            )
        )
        .reversal_axis()  # 关键:柱状图横向展示
        .set_global_opts(
            # 图表标题
            title_opts=opts.TitleOpts(title=f"{year}年世界人口排名TOP20", pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False),  # 隐藏多余图例
        )
    )
    # 将当前年份柱状图添加到时间轴中
    timeline.add(bar, str(year))

步骤 8:时间轴样式 & 轮播规则配置

自定义轮播参数、时间轴轴线、节点、文字样式,打造完整交互体验。

python

运行

# 8. 配置时间轴轮播规则 + 视觉样式
timeline.add_schema(
    is_auto_play=True,        # 开启自动播放
    play_interval=600,        # 播放间隔:600毫秒(丝滑轮播)
    is_loop_play=True,       # 开启循环播放
    # 时间轴文字样式:白色、加粗、字号11
    label_opts=opts.LabelOpts(color="#ffffff", font_size=11, font_weight="bold"),
    # 时间轴轴线:亮蓝色、线宽4px
    linestyle_opts=opts.LineStyleOpts(color="#00a1ff", width=4),
    # 时间轴节点:蓝色底色+白色边框
    itemstyle_opts=opts.ItemStyleOpts(
        color="#00a1ff", border_color="#ffffff", border_width=2
    )
)

步骤 9:渲染输出(两种方式适配不同环境)

python

运行

# 9. 渲染生成HTML文件(所有Python环境通用,打开即可查看)
timeline.render("世界人口轮播排名图.html")
print("\n图表已生成:世界人口轮播排名图.html,请打开文件查看效果!")

# 可选:Jupyter Notebook/JupyterLab 专属渲染(网页内直接展示)
# timeline.render_notebook()

五、常见报错 & 解决方案(避坑指南)

结合实操中高频问题,整理对应的解决办法:

1. 问题 1:UnicodeDecodeError 编码错误

原因:CSV 中文编码不匹配,Pandas 默认utf-8无法读取中文 CSV。 解决:读取文件时指定编码 encoding="gbk",如本文代码所示。

2. 问题 2:生成 HTML 文件打开空白,无图表

原因:国内网络无法加载国外 Echarts CDN。 解决:添加 CDN 配置代码:

python

运行

from pyecharts.globals import CurrentConfig
CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/"

3. 问题 3:KeyError: xxx 列名不存在

原因:表格为宽格式,直接使用Year/Population列名报错。 解决:必须执行宽表转长表pd.melt()),转换后再使用对应列名。

4. 问题 4:数据包含大量大洲 / 地区汇总,排名异常

原因:原始数据集自带汇总脏数据。 解决:使用黑名单过滤,剔除非主权国家数据,本文已封装完整黑名单。

5. 问题 5:人口数字显示科学计数法

解决:在数据清洗后添加一行代码,关闭科学计数法:

python

运行

pd.set_option('display.float_format', lambda x: '%.0f' % x)

六、项目总结 & 拓展方向

1. 项目总结

本项目完整实现了从原始数据→数据清洗→数据重塑→动态可视化的全流程数据分析案例,核心知识点总结:

  1. Pandas 实战:空值删除、黑名单过滤、melt()宽表转长表、数据类型转换,是时序数据处理的经典用法;
  2. pyecharts 进阶Timeline时间轴组件搭配Bar实现动态轮播,JsCode嵌入 JS 实现自定义样式,全局样式精细化配置;
  3. 工程化细节:适配国内 CDN、路径转义、脏数据过滤,代码可直接落地复用。

2. 功能拓展(进阶玩法)

  1. 增加数据标注:给 Top3 国家添加特殊颜色标记,突出头部国家;
  2. 切换图表类型:将柱状图改为面积图、折线图,实现人口趋势轮播;
  3. 数据筛选:单独筛选亚洲 / 欧洲国家,制作区域人口排名;
  4. 参数优化:调整轮播速度、柱子尺寸、配色方案,适配不同展示场景;
  5. 导出视频:结合selenium+ 剪辑工具,将 HTML 动态图转为短视频,直接发布至短视频平台。

七、完整项目源码(整合版)

将以上所有代码整合,复制即可直接运行(仅需修改 CSV 文件路径):

python

# ====================== 完整可运行源码 ======================
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/"

# 1. 读取数据(修改为你的文件路径)
df = pd.read_csv(r"D:\zyx\code\data\世界人口数据-中文版(1960-2024).csv", encoding="gbk")

# 2. 筛选列
attr_cols = ['Country Name', 'Country Code', 'Indicator Name', 'Indicator Code']
year_cols = [col for col in df.columns if col.isdigit()]

# 3. 数据清洗
df = df.dropna(subset=['Country Name'])
# 黑名单过滤
black_list = [
    "世界", "北美", "东亚与太平洋地区", "欧洲与中亚地区", "拉丁美洲与加勒比海地区",
    "中东、北非、阿富汗与巴基斯坦", "撒哈拉以南非洲地区", "南亚",
    "高收入国家", "中高等收入国家", "中低等收入国家", "低收入国家", "中等收入国家",
    "欧洲联盟", "经合组织成员", "重债穷国 (HIPC)",
    "阿鲁巴", "美属萨摩亚", "百慕大", "中国香港特别行政区", "中国澳门特别行政区",
    "波多黎各", "格陵兰", "关岛", "科索沃", "法罗群岛"
]
df = df[~df["Country Name"].isin(black_list)].reset_index(drop=True)

# 4. 宽表转长表
df_long = pd.melt(
    df, id_vars=attr_cols, value_vars=year_cols,
    var_name="Year", value_name="Population"
)

# 5. 最终数据清洗+类型转换
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"], errors="coerce")
df_clean = df_clean.dropna()
df_clean = df_clean[df_clean["Population"] > 0]

# 6. 初始化时间轴
timeline = Timeline(init_opts=opts.InitOpts(
    width="1600px", height="850px", theme=ThemeType.DARK, bg_color="#080808"
))

# 动态配色JS
color_js = JsCode("""
function(params){
    let colorList = ['#ff4757','#ffa502','#fffa65','#2ed573','#1e90ff','#3742fa','#a55eea','#48dbfb'];
    return colorList[params.dataIndex % colorList.length];
}
""")

# 7. 循环绘图
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_list = data_df["Country Name"].tolist()
    population_list = data_df["Population"].tolist()

    bar = (
        Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
        .add_xaxis(country_list)
        .add_yaxis(
            "人口", population_list,
            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}年世界人口排名TOP20", pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False)
        )
    )
    timeline.add(bar, str(year))

# 8. 时间轴配置
timeline.add_schema(
    is_auto_play=True, play_interval=600, is_loop_play=True,
    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)
)

# 9. 渲染输出
timeline.render("世界人口轮播排名图.html")
print("图表生成完成!")

本文基于真实世界人口数据集完成动态轮播图可视化,代码全开源、可直接复现。大家可以尝试修改配色、轮播速度、筛选区域数据,打造属于自己的可视化作品!如果运行代码遇到问题,欢迎评论区留言交流~

更多推荐