一、项目前言 & 开发背景

Bar Chart Race(动态条形竞赛轮播图)是 B 站数据区爆款可视化形式,人口变迁排行更是常年热门选题。市面上大多使用在线 Flourish 工具一键生成,但不利于学习数据清洗 + Python 可视化底层逻辑。

本项目基于世界银行 1960-2024 全球人口 CSV 数据集,使用Pandas做全量数据清洗,Pyecharts+Timeline实现逐年自动轮播排行,最终落地深色 B 站商业级美化成品:支持暂停 / 播放、悬浮查看人口、年份手动切换、柱子动态换色、圆角柱状样式,完美复刻 B 站同款动态人口排行榜效果。

项目开发目标

  1. 数据纯净:剔除大洲、收入分组、港澳台及海外领地等非主权国家数据,最终筛选 195 个合法主权国家;
  2. 动态排行:每年按人口降序 TOP20 自动排序,配合反转坐标轴实现 “人口越多越靠上”;
  3. 全交互:时间轴可手动跳转年份、自动循环播放、悬浮 tooltip 展示详细人口;
  4. 美化落地:深色极黑背景、循环渐变配色、圆角柱子、白色标签,对标 B 站专业数据视频画质。

二、环境与依赖安装

2.1 所需技术栈

表格

工具库 作用
pandas 数据读取、筛选、清洗、宽表转长表重塑
pyecharts 柱状图 + Timeline 时间轮播核心可视化
JsCode 实现柱子动态循环配色(JS 内嵌)

2.2 一键安装依赖

bash

运行

pip install pandas pyecharts

注意:国内网络 pyecharts 默认 CDN 容易白屏,项目中手动替换 jsdelivr CDN 地址解决空白问题。

三、数据集探查与原始数据说明

3.1 数据源说明

数据源:世界人口数据-中文版(1960-2024).csv,原始数据 266 行、70 列:

  • 4 列属性字段Country Name(国家名)Country Code(国家编码)Indicator NameIndicator Code
  • 66 列年份字段:1960~2025,其中2025 年全为空 NaN,有效统计区间 1960-2024;
  • 脏数据问题:原始包含全球汇总、各大洲、收入分级(高 / 低收入国家)、非主权领地(中国香港、阿鲁巴、关岛等),必须黑名单过滤。

3.2 数据加载代码(编码 GBK 适配中文)

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/"

# 读取CSV,中文gbk编码
df = pd.read_csv('./data/世界人口数据-中文版(1960-2024).csv',encoding='gbk')
print(df.head())
print(df.info())

原始数据样例:部分行Country Name为空(AFE/AFW 大洲汇总编码),这是后续要删除的无效数据。

四、核心:数据清洗全流程(项目重难点)

原始数据是宽格式(一行一国,年份横向平铺),无法直接做时序轮播,清洗分 4 大步骤:空值删除→黑名单过滤→宽表转长表→数据类型规整,最终精简为国家-年份-人口三列标准长数据。

4.1 步骤 1:删除国家名为空的汇总行

python

运行

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

4.2 步骤 2:黑名单剔除非主权数据(核心过滤)

自定义黑名单,一次性剔除:全球 / 大洲汇总、收入等级分类、非主权地区 / 海外领地,最终保留 195 个主权国家:

python

运行

# 黑名单:汇总区、收入分类、非主权领地全过滤
black_list = [
    # 全球&各大洲汇总
    "世界","北美","东亚与太平洋地区","欧洲与中亚地区","拉丁美洲与加勒比海地区","撒哈拉以南非洲地区","南亚","欧洲联盟",
    # 收入等级
    "高收入国家","低收入国家","中等收入国家","中高等收入国家","中低等收入国家",
    # 非主权领地/特别行政区
    "阿鲁巴","中国香港特别行政区","中国澳门特别行政区","关岛","波多黎各","格陵兰","法罗群岛"
]
# 反向筛选,不在黑名单的数据保留
df = df[~df["Country Name"].isin(black_list)].reset_index(drop=True)
print("过滤后主权国家数量:",df["Country Name"].nunique()) # 输出195

4.3 步骤 3:宽表转长表(melt 重塑,时序必备)

原始年份横向铺开,使用pd.melt拆分出Year、Population字段,由宽→长:

python

运行

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

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

4.4 步骤 4:数据类型转换 + 空值剔除

python

运行

# 只保留绘图需要三列
df_clean = df_long[["Country Name","Year","Population"]].copy()
# 年份转int、人口转数值
df_clean["Year"] = df_clean["Year"].astype(int)
df_clean["Population"] = pd.to_numeric(df_clean["Population"])
# 删除空值(2025无效数据被清理)
df_clean.dropna(inplace=True)
# 最终干净数据
print(df_clean.head())

解决SettingWithCopyWarning:使用.copy()生成新 DataFrame 规避副本告警。

五、可视化分步实现:从单图测试→基础轮播→B 站美化完整版

5.1 阶段 1:单年份静态柱状测试(以 1990 年 TOP20 为例)

先验证单年绘图逻辑,横向柱状 + 深色主题,标签放柱子右侧:

python

运行

# 筛选1990年数据,人口降序取TOP20
test_df = df_clean[df_clean["Year"]==1990].sort_values("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() # 反转XY→横向柱状
    .set_global_opts(title_opts=opts.TitleOpts(title="1990年世界人口TOP20"))
    .set_series_opts(label_opts=opts.LabelOpts(position="right"))
)
bar_test.render("1990单年测试.html")

5.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,先降序取前20、再升序排列(反转后大数在上)
    data_year = df_clean[df_clean["Year"]==year].sort_values("Population",ascending=False).head(20)
    data_year = data_year.sort_values("Population",ascending=True)
    bar = (
        Bar()
        .add_xaxis(data_year["Country Name"].tolist())
        .add_yaxis("人口",data_year["Population"].tolist())
        .reversal_axis()
        .set_global_opts(title_opts=opts.TitleOpts(title=f"{year}年全球人口TOP20排行"),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")

关键排序逻辑:先降序取 TOP20→再升序,配合reversal_axis()实现人口越多、图表位置越靠上,符合观看习惯。

5.3 阶段 3:B 站深色商业美化完整版(最终成品代码)

实现动态循环配色、圆角柱子、极黑背景、蓝色高亮时间轴、自定义标签格式,对标 B 站可视化风格:

python

运行

# JS定义多色数组,柱子循环变色
color_js = JsCode("""
function(params){
    let c = ['#ff4757','#ffa502','#fffa65','#2ed573','#1e90ff','#3742fa','#a55eea'];
    return c[params.dataIndex%c.length];
}
""")

# 初始化时间线,深色黑底
timeline = Timeline(init_opts=opts.InitOpts(width="1600px",height="850px",theme=ThemeType.DARK,bg_color="#080808"))
year_list = df_clean["Year"].unique().tolist()

for year in year_list:
    data_year = df_clean[df_clean["Year"]==year].sort_values("Population",ascending=False).head(20).sort_values("Population",ascending=True)
    bar = (
        Bar()
        .add_xaxis(data_year["Country Name"].tolist())
        .add_yaxis(
            "人口",data_year["Population"].tolist(),
            itemstyle_opts=opts.ItemStyleOpts(color=color_js,opacity=0.85,border_radius=7) # 圆角+透明度+动态色
        )
        .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",formatter="{c}人",font_size=11,color="#ffffff"))
    )
    timeline.add(bar,str(year))

# 美化时间轴:蓝线+白边节点+白色加粗年份文字
timeline.add_schema(
    is_auto_play=True,play_interval=600,is_loop_play=True,
    label_opts=opts.LabelOpts(color="#fff",font_size=11,font_weight="bold"),
    linestyle_opts=opts.LineStyleOpts(color="#00a1ff",width=4),
    itemstyle_opts=opts.ItemStyleOpts(color="#00a1ff",border_color="#ffffff")
)
# 生成最终成品HTML
timeline.render("B站风格_世界人口动态排行.html")

五、项目成果 & 数据洞察

  1. 产出文件B站风格_世界人口动态排行.html,直接浏览器打开即可交互,无需额外环境;
  2. 历史人口变化看点
    • 1960 年:中、印、美稳居前三,印尼、俄罗斯紧随其后,欧美老牌强国人口体量领先;
    • 2000 年后:尼日利亚、巴基斯坦、孟加拉国人口增速爆发,逐步挤进全球 TOP10;
    • 近 10 年:印度人口持续高速增长,逐步赶超中国成为全球人口第一大国;
  3. 技术收获:熟练掌握 pandas 宽长表转换、黑名单数据过滤、pyecharts Timeline 动态图表、JS 内嵌动态配色方案。

六、常见踩坑总结(避坑指南)

  1. HTML 打开空白:必须手动替换CurrentConfig.ONLINE_HOST为 jsdelivr 国内 CDN,默认国外 CDN 国内加载失败;
  2. 中文乱码:读取 csv 指定encoding="gbk"(中文版数据源通用编码);
  3. 排序颠倒:牢记降序取TOP20→升序+reversal_axis三步组合,否则排行上下颠倒;
  4. SettingWithCopy 警告:数据切片后修改字段,用.copy()新建 DataFrame。

更多推荐