一、项目概述

1.1 项目背景

动态排序轮播图(Bar Chart Race)是 B 站数据区热门可视化形式,直观展示指标随年份变迁的排名变化。本项目基于世界银行全球人口数据集,用 Python 完成数据清洗→宽长格式转换→动态可视化全流程,复刻高质量世界各国人口年度动态排名图,时间跨度 1960~2024 年。

1.2 项目目标

  1. 数据纯净:剔除全球汇总、各大洲、收入分组、海外领地等非主权数据,最终留存 195 个独立主权国家;
  2. 动态轮播:图表自动按年份轮播,人口数值越高,国家在图表越靠上;
  3. 交互完整:悬浮查看人口、暂停 / 自动播放、手动切换指定年份;
  4. 视觉美化:深色 B 站风格、循环配色圆角柱状、自定义标签格式。

1.3 技术栈

  • 编程语言:Python3.x
  • 数据处理:pandas(数据读取、清洗、宽表转长表)
  • 可视化:pyecharts(Bar 柱状图 + Timeline 时间轴轮播)
  • 辅助:JsCode实现柱子动态循环配色、自定义 CDN 解决国内 HTML 加载空白

二、环境准备与依赖安装

2.1 安装依赖包

python运行

# 安装pyecharts可视化库
!pip install pyecharts

2.2 导入全部依赖 + 配置国内 CDN(关键,解决网页白屏)

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,解决echarts国内加载失败空白问题
CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/"

三、数据加载与原始数据探查

3.1 读取 CSV 数据集

数据集:世界人口数据-中文版(1960-2024).csv,原始为宽表结构,年份横向平铺为列,编码使用gbk适配中文。

python运行

# 读取本地csv文件
df = pd.read_csv('D:\wxl\code\数据查询\世界人口数据-中文版(1960-2024) (1).csv',encoding="gbk")
# 预览前5行数据
df.head()

原始数据结构说明:

  • 属性固定列:Country Name(国家名称)、Country Code(国家代码)、Indicator Name、Indicator Code
  • 年份列:1960~2025 共 66 列,2025 年全为空值,有效数据 1960-2024
  • 原始 266 行数据:包含全球汇总、各大洲、非主权地区、主权国家,需要过滤无效行

3.2 数据基本信息查看

python运行

df.info()

总数据:266 行 ×70 列;object 类型 4 列(属性),float64 类型 66 列(年份人口)。

四、数据清洗与预处理(核心步骤)

4.1 筛选属性列、年份列

python运行

# 筛选列名全为数字的年份字段
year_cols = [i for i in df.columns if i.isdigit()]
# 固定属性字段列表
attr_cols = ['Country Name','Country Code','Indicator Code','Indicator Name']

4.2 清洗无效数据:空值删除 + 黑名单过滤

① 删除国家名称为空的行(大洲汇总行大多无国家名)

python运行

# 删除Country Name为空的汇总数据
df = df.dropna(subset=['Country Name'])
② 黑名单剔除非主权地区(全球 / 大洲 / 收入分组 / 海外领地)

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())
# 输出:过滤后剩余主权国家数量:195

4.3 宽表转长表(pd.melt 重塑数据,绘图必备)

原始数据一行一个国家、多列年份(宽表),melt拆分后变成一行 = 一个国家单年人口(长表)

python运行

# 宽表转长格式
df_long = pd.melt(
    df,
    id_vars=attr_cols,      # 保留不变的属性列
    value_vars=year_cols,   # 需要拆分的所有年份列
    var_name="Year",        # 拆分后新列:年份
    value_name="Population" # 拆分后新列:人口数值
)

4.4 字段精简 + 类型转换 + 缺失值清理

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)
# 预览清洗后数据
df_clean.head()

五、可视化分步实现

5.1 单年份柱状测试(1990 年 TOP20 人口,验证绘图逻辑)

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())
    .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"))
)
# Jupyter预览
bar_test.render_notebook()
# 导出HTML文件
# bar_test.render("1990人口柱状.html")

5.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:
    # 筛选当年数据、降序取前20、升序排序适配横向反转
    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)
        .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))

# 配置轮播参数:自动播放、间隔600ms、循环播放
timeline.add_schema(is_auto_play=True,play_interval=600,is_loop_play=True)
# 渲染输出
timeline.render("基础轮播图.html")
# timeline.render_notebook()

5.3 B 站美化完整版(动态变色 + 圆角柱子 + 自定义时间轴样式)

python运行

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

year_list = df_clean["Year"].unique().tolist()
# 初始化深色背景时间轴
timeline = Timeline(init_opts=opts.InitOpts(
    width="1600px",
    height="850px",
    theme=ThemeType.DARK,
    bg_color="#080808" # 极深黑背景,B站风格
))

# 遍历年份绘图
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}年世界人口TOP20排名",pos_left="center"),
            legend_opts=opts.LegendOpts(is_show=False)
        )
    )
    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)
)

# 最终生成成品HTML
timeline.render("B站风人口动态轮播图.html")
# timeline.render_notebook()

六、项目成果与效果说明

  1. 数据成果:原始 266 条数据过滤后保留195 个主权国家,时间跨度 1960-2024,共 195×65=12675 条有效人口数据;
  2. 可视化效果
    • 自动轮播:600ms 切换一个年份,支持暂停 / 手动点击年份跳转;
    • 视觉:深色黑底、7 色循环圆角柱状、人口数值标注在柱子右侧;
    • 交互:鼠标悬浮可查看对应国家人口详情;
  3. 拓展方向:可筛选单个大洲、单独分析中印人口增速、导出 GIF 动态图。

七、踩坑总结

  1. HTML 页面空白:必须配置CurrentConfig.ONLINE_HOST国内 CDN 地址,默认国外 CDN 国内无法加载;
  2. KeyError 列名报错:原始数据列名中英文 / 空格不一致,提前用df.columns核对字段;
  3. 排序颠倒:先降序取TOP20升序+reversal_axis(),实现人口越多越在图表上方;
  4. SettingWithCopyWarning:字段赋值使用.copy()生成新 DataFrame 消除警告。

更多推荐