前言

动态排序轮播图(Bar Chart Race)是当下 B 站、抖音等平台非常热门的数据可视化形式,凭借流畅的动画、直观的数据排名变化,能生动展现长时间维度下的数据变迁。本文将完整分享一套从数据清洗到商业级可视化的实战项目:使用pandas完成人口数据预处理,结合pyecharts打造 1960-2024 年世界各国人口动态排名轮播图,复刻 B 站主流深色风格交互图表,全程代码可直接运行,附踩坑解决方案与细节优化思路。

一、项目整体介绍

1.1 项目背景

本项目基于全球人口数据集,聚焦1960-2024 年世界主权国家人口变化,采用经典的动态排序轮播条形图形式,直观展示六十余年间各国人口排名、数量的动态演变。区别于基础可视化案例,本次项目对标 B 站专业数据视频的视觉风格与交互体验,兼顾实用性、美观性与交互性。

1.2 项目核心目标

  1. 数据纯净:剔除大洲、收入分组、非主权地区等无效数据,最终保留195 个真实主权国家数据;
  2. 动态排序:图表按年份自动轮播,每年依据人口总数降序排名,人口越多排名越靠前;
  3. 全交互功能:支持悬浮查看人口数值、播放 / 暂停、手动切换年份;
  4. 商业级美化:深色主题、动态配色、圆角柱状、丝滑轮播节奏,达到主流短视频平台可视化标准。

1.3 技术栈与运行环境

工具 / 库 作用
Python 3.x 主力编程语言
pandas 数据读取、清洗、宽表转长表、数据筛选
pyecharts 绘制柱状图、时间线轮播组件(Timeline)
JsCode 实现柱子动态配色逻辑
CDN 镜像配置 解决国内网络 ECharts 加载空白问题

前置依赖安装命令:

pip install pandas pyecharts

二、完整项目实现流程

2.1 环境初始化与库导入

首先导入项目所需全部依赖,重点配置国内 CDN 地址,这是解决 pyecharts 生成 HTML 页面空白的核心步骤(国内网络必加)。

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  # 动态JS配色工具

# 核心修复:配置国内CDN,解决HTML页面加载空白问题
CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/"
print("环境初始化完成!")

2.2 数据加载与基础探查

2.2.1 数据集说明
  • 数据源:本地 CSV 文件 世界人口数据-中文版(1960-2024).csv
  • 编码格式:gbk(中文数据集专用编码,避免乱码)
  • 原始结构:宽表结构,一行对应一个地区,1960-2025 年年份作为独立列,总计 70 列、266 行。
2.2.2 数据读取与预览

python

运行

# 读取本地人口数据集,指定gbk编码防止中文乱码
df = pd.read_csv('./data/世界人口数据-中文版(1960-2024).csv', encoding='gbk')

# 查看前5行数据
print("数据前5行预览:")
print(df.head())

# 探查数据集整体结构、缺失值、数据类型
print("\n数据集基础信息:")
df.info()

数据解读

  1. 特征列共 70 列:4 列基础属性(国家名称、国家代码、指标名称、指标代码)+ 66 列年份数据(1960-2025);
  2. 样本共 266 行:包含主权国家、大洲汇总、收入分组、海外领地等混合数据;
  3. 缺失值:2025 年所有人口数据均为NaN,后续需直接剔除,有效年份区间为1960-2024
  4. 无效数据:Country Name为空的行,多为非洲、美洲等大洲汇总数据,属于清洗重点。

2.3 核心数据清洗与预处理

原始数据混杂大量汇总数据、非主权地区,且宽表结构不适合动态排序绘图,需经过4 步清洗得到标准可用数据集。

2.3.1 拆分属性列与年份列

筛选出固定属性列和纯数字年份列,区分静态信息与时间序列数据:

python

运行

# 筛选所有列名为纯数字的列(年份列)
year_cols = [i for i in df.columns if i.isdigit()]
# 保留4个基础属性列
attr_cols = ['Country Name', 'Country Code', 'Indicator Name', 'Indicator Code']

print(f"有效年份数量:{len(year_cols)}")
print(f"年份范围:{year_cols[0]} - {year_cols[-1]}")
2.3.2 剔除无效数据(核心清洗)

分两步过滤:删除国家名称空值行 + 黑名单过滤汇总 / 非主权地区,最终保留 195 个主权国家。

python

运行

# 第一步:删除Country Name为空的行(大洲汇总类数据)
df = df.dropna(subset=['Country Name'])

# 第二步:定义黑名单,过滤大洲、收入分组、非主权领地、地区汇总
black_list = [
    # 全球&大洲汇总
    "世界","北美","东亚与太平洋地区","欧洲与中亚地区","拉丁美洲与加勒比海地区",
    "中东、北非、阿富汗与巴基斯坦","撒哈拉以南非洲地区","南亚","欧洲联盟",
    # 收入等级分类
    "高收入国家","低收入国家","中等收入国家","中高等收入国家","中低等收入国家",
    # 非主权地区/海外领地
    "阿鲁巴","美属萨摩亚","中国香港特别行政区","中国澳门特别行政区","波多黎各",
    "关岛","格陵兰岛","科索沃"
]

# 反向过滤:剔除黑名单内数据
df = df[~df["Country Name"].isin(black_list)].reset_index(drop=True)
print(f"清洗后剩余独立国家数量:{df['Country Name'].nunique()}")

运行结果输出:清洗后剩余独立国家数量:195,符合项目预期。

2.3.3 宽表转长表(数据重塑)

pyecharts 动态轮播图要求长表结构(一行 = 一个国家 + 一个年份 + 人口),使用pd.melt()完成宽表转长表:

python

运行

# 宽表转长表:拆分年份列,生成Year(年份)、Population(人口)新列
df_long = pd.melt(
    df,
    id_vars=attr_cols,    # 固定不变的属性列
    value_vars=year_cols,  # 需要拆分的年份列
    var_name="Year",       # 拆分后年份列名
    value_name="Population"# 拆分后人口数值列名
)

# 预览转换后数据
print("长表结构数据预览:")
print(df_long.head())
2.3.4 数据类型转换与最终去重

修正字段类型,剔除 2025 年无效空值,生成最终干净数据集:

python

运行

# 仅保留绘图所需三列:国家名称、年份、人口
df_clean = df_long[["Country Name", "Year", "Population"]].copy()

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

# 剔除2025年空值数据
df_clean.dropna(inplace=True)

print("最终清洗完成数据预览:")
print(df_clean.head())
print(f"有效数据总条数:{len(df_clean)}")

至此,数据预处理全部完成,数据集格式完全适配动态排序图表。

2.4 可视化分步实现

可视化分为三个阶段:单年份图表测试 → 基础轮播图 → B 站风格美化完整版,循序渐进排查问题。

2.4.1 阶段 1:单年份横向条形图测试

以 1990 年数据为例,测试横向柱状图渲染、排序、主题样式,验证基础逻辑:

python

运行

# 筛选1990年数据,按人口降序取前20名
test_df = df_clean[df_clean["Year"] == 1990]
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())         # X轴:国家名称
    .add_yaxis("人口", test_df["Population"].tolist())   # Y轴:人口数值
    .reversal_axis()  # 坐标轴反转 → 横向条形图
    .set_global_opts(
        title_opts=opts.TitleOpts(title="1990年世界人口TOP20排名", pos_left="center"),
        legend_opts=opts.LegendOpts(is_show=False)  # 隐藏图例,精简界面
    )
    .set_series_opts(label_opts=opts.LabelOpts(position="right"))  # 数值标签置于柱子右侧
)

# 渲染为HTML文件,浏览器打开查看
bar_test.render("单年份测试图.html")
print("单年份测试图生成完成!")

常见报错解决:代码缩进错误IndentationError,Python 严格区分缩进,统一使用 4 个空格缩进即可修复。

2.4.2 阶段 2:基础版时间线轮播图

基于Timeline时间线组件,遍历所有年份,实现自动轮播、循环播放的动态排名图:

python

运行

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

# 初始化时间线组件,设置画布尺寸
timeline = Timeline(init_opts=opts.InitOpts(width="1500px", height="820px", theme=ThemeType.DARK))

# 遍历每一个年份,逐个生成条形图并添加到时间线
for year in year_list:
    # 筛选当年数据,人口降序取TOP20
    data_df = df_clean[df_clean["Year"] == year]
    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()
    pop_list = data_df["Population"].tolist()

    # 构建单年份条形图
    bar = (
        Bar()
        .add_xaxis(country_list)
        .add_yaxis("人口", pop_list)
        .reversal_axis()
        .set_global_opts(
            title_opts=opts.TitleOpts(title=f"{year}年世界人口TOP20排名", pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False)
        )
        .set_series_opts(label_opts=opts.LabelOpts(position="right"))
    )
    # 将当年图表加入时间线
    timeline.add(bar, str(year))

# 配置轮播规则:自动播放、播放间隔600ms、循环播放
timeline.add_schema(
    is_auto_play=True,
    play_interval=600,
    is_loop_play=True
)

# 生成基础轮播图
timeline.render("基础人口轮播图.html")
print("基础版动态轮播图生成完成!")

关键排序逻辑解释: 先降序取 TOP20 → 再升序排列 + .reversal_axis()反转坐标轴,最终实现人口越多,条形图越靠上,符合大众阅读习惯。

2.4.3 阶段 3:B 站风格完整版(深度美化)

在基础版之上完成全维度美化,实现动态配色、圆角柱子、定制提示框、高亮时间轴,达到 B 站专业可视化效果,也是本项目最终成品代码:

python

运行

# 初始化时间线,设置超大画布+极深灰背景(B站经典深色)
timeline = Timeline(
    init_opts=opts.InitOpts(
        width="1600px",
        height="850px",
        theme=ThemeType.DARK,
        bg_color="#080808"  # 极致深色背景,提升沉浸感
    )
)

# 定义JS动态配色规则:8种高饱和颜色循环切换
color_js = JsCode("""
function (params){
    let colorArr = ['#ff4757','#ffa502','#fffa65','#2ed573','#1e90ff','#3742fa','#a55eea','#22d2d0'];
    return colorArr[params.dataIndex % colorArr.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_list = data_df["Country Name"].tolist()
    pop_list = data_df["Population"].tolist()

    # 构建美化版条形图
    bar = (
        Bar()
        .add_xaxis(country_list)
        .add_yaxis(
            "人口",
            pop_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,
                color="#ffffff"
            )
        )
        .reversal_axis()
        .set_global_opts(
            title_opts=opts.TitleOpts(
                title=f"{year}年世界人口TOP20排名",
                pos_left="center",
                title_textstyle_opts=opts.TextStyleOpts(color="#ffffff", font_size=18)
            ),
            legend_opts=opts.LegendOpts(is_show=False),
            # 定制悬浮提示框
            tooltip_opts=opts.TooltipOpts(
                trigger="axis",
                formatter="<b>{b}</b><br/>人口:{c} 人"
            )
        )
    )
    timeline.add(bar, str(year))

# 时间轴专属美化:亮蓝色进度条、白色边框节点、加粗字体
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)
)

# 生成最终成品图表
timeline.render("B站风格_世界人口动态轮播图.html")
print("【最终版】B站风格人口动态排序轮播图生成完毕!")

三、项目亮点与核心优化点

3.1 数据层面亮点

  1. 精准数据过滤:通过「空值删除 + 黑名单」双重过滤,彻底剔除大洲、地区、海外领地等无效数据,保证数据严谨性;
  2. 宽表转长表:使用melt完成数据重塑,是时序动态图表的通用数据处理思路,可复用在 GDP、销量等同类项目中;
  3. 异常值处理:自动剔除 2025 年全空数据,避免图表出现空白条目。

3.2 可视化美化亮点(对标 B 站风格)

  1. CDN 适配:配置国内 JSdelivr 镜像,彻底解决 pyecharts 国内加载空白的经典问题;
  2. 动态配色:通过JsCode实现柱子颜色循环切换,视觉效果更丰富;
  3. 细节打磨:圆角柱状、半透明效果、定制悬浮提示框、高亮时间轴节点,贴合短视频平台审美;
  4. 交互完善:支持自动播放、暂停、手动切年份、悬浮看数据,满足全交互需求。

3.3 踩坑总结(新手必看)

  1. 中文乱码:CSV 文件读取必须指定encoding='gbk',中文数据集通用方案;
  2. HTML 空白:必须手动修改CurrentConfig.ONLINE_HOST为国内 CDN,否则 ECharts 资源加载失败;
  3. 排序颠倒:横向条形图需「先降序取 TOP,再升序排列」,配合reversal_axis才能保证排名正常;
  4. 缩进报错:Python 代码严格缩进,所有链式调用代码保持统一缩进格式;
  5. SettingWithCopyWarning:数据切片时使用.copy()生成新 DataFrame,消除警告。

四、项目拓展方向

基于本项目框架,可快速拓展出更多同类可视化案例:

  1. 替换数据集:改为各国 GDP、城市房价、影视票房,复刻同款动态排名图;
  2. 功能升级:增加人口增长率计算,在图表中附加增长率标签;
  3. 样式拓展:切换 pyecharts 内置主题,制作复古风、科技风等不同风格图表;
  4. 数据筛选:单独筛选亚洲 / 欧洲国家,制作区域人口排名轮播图。

五、完整项目总结

本项目完整走完数据探查→清洗重塑→可视化测试→美化上线全流程,是 Python 数据分析 + 可视化的经典练手项目。核心知识点总结:

  1. pandas:数据读取、空值处理、黑名单过滤、宽长表转换;
  2. pyecharts:横向条形图、Timeline 时间线轮播、主题 / 样式 / 交互配置;
  3. 实战技巧:国内 CDN 适配、动态 JS 配色、排序逻辑优化、前端可视化细节打磨。

更多推荐