Python实战:构建历史领土面积排行榜与动态可视化
2026/8/10 7:04:17 网站建设 项目流程

之前在做历史数据可视化项目时,想找一个能直观展示全球主要国家领土面积历史变迁的数据集,发现网上资料要么零散,要么只聚焦现代。本文将整合一套从公元前3500年到公元2026年的全球主要国家/政权领土面积估算数据,并手把手教你如何用Python进行数据处理、分析、排序与可视化。无论你是历史爱好者、数据科学初学者,还是想做一个酷炫的数据可视化项目,都能从本文中找到完整的代码和清晰的思路。

1. 背景与核心概念

在历史学和地理信息系统中,一个国家的“领土面积”是一个动态且复杂的概念。它受到战争、条约、自然变迁、测量技术等多种因素的影响。本文所讨论的“领土面积排行榜”,并非指精确到平方公里的现代测绘数据,而是基于历史研究、学术论文和权威资料(如《世界历史地图集》、各国历史疆域研究)的综合估算。

为什么需要这样的数据?

  1. 宏观视角:帮助我们理解文明兴衰、帝国扩张与收缩的宏观趋势。
  2. 数据分析练习:这是一个绝佳的、跨学科的数据分析实战项目,涉及时间序列处理、数据清洗、排序和可视化。
  3. 可视化需求:生成动态排名图或地图,能让枯燥的历史数据变得生动直观。

核心概念界定:

  • 国家/政权:指在特定历史时期具有相对稳定统治疆域的政治实体,如古埃及、罗马帝国、蒙古帝国、明清中国、现代美国等。
  • 领土面积:指该政权有效控制或宣称主权的陆地面积(通常不包括广阔的海洋专属经济区),单位为万平方公里或百万平方公里。
  • 时间点:数据通常以特定年份(如公元元年、1500年)或世纪为单位。本文示例将采用多个关键历史时间节点。

重要提示:历史疆域面积存在多种估算体系,数据本身具有争议性。本文的重点在于提供一套完整的数据处理与可视化方法学,示例数据为演示目的而构建,在实际研究中应引用更权威的学术数据源。

2. 环境准备与版本说明

我们将使用Python作为主要工具,因为它拥有丰富的数据处理和可视化库。以下是本次实战的环境配置。

操作系统:Windows 10/11, macOS, 或 Linux (Ubuntu) 均可。Python版本:建议使用 Python 3.8 及以上版本。主要依赖库

  • pandas: 数据处理与分析的核心。
  • numpy: 数值计算支持。
  • matplotlib: 基础绘图库,用于制作静态排名图。
  • seaborn: 基于matplotlib的统计图形库,让图表更美观。
  • plotly(可选但推荐): 用于制作交互式动态图表,效果更炫酷。

IDE或工具:Jupyter Notebook, JupyterLab, VS Code, 或 PyCharm 均可。本文示例代码在Jupyter Notebook中编写和测试。

安装依赖: 打开终端(或Anaconda Prompt),使用pip安装所需库:

# 安装核心数据处理和可视化库 pip install pandas numpy matplotlib seaborn # 安装交互式可视化库(用于制作动态条形图) pip install plotly

项目结构建议

territory_ranking_project/ │ ├── data/ │ ├── raw_data.csv # 原始数据文件 │ └── processed_data.pkl # 处理后的数据文件 │ ├── notebooks/ │ └── territory_analysis.ipynb # 主要的分析笔记本 │ ├── scripts/ │ └── data_processor.py # 数据清洗脚本(可选) │ └── outputs/ ├── static_plots/ # 存放静态图片 └── interactive_plots/ # 存放HTML交互图表

3. 核心数据处理与分析方法拆解

在开始完整案例前,我们先拆解几个关键技术点。

3.1 数据结构设计

历史领土数据通常是“长格式”或“宽格式”。

  • 长格式:每一行是一个“国家-年份-面积”的记录。适合pandas进行时间序列分析和plotly制作动画。
  • 宽格式:每一行是一个国家,每一列是一个年份的面积数据。适合直观查看某个国家面积随时间的变化。

我们将以长格式作为核心数据结构,因为它更灵活。

示例数据结构(DataFrame)

countryyearterritory_sqkm
Roman Empire1175000000
Han Dynasty1006000000
Mongol Empire127924000000

3.2 关键操作:按年份分组与排序

这是生成排行榜的核心。我们需要对每个年份,按territory_sqkm降序排列,并可能添加排名列。

import pandas as pd # 假设df是包含上述结构的长格式DataFrame # 为每个年份的数据按面积降序排序,并添加排名 df['rank'] = df.groupby('year')['territory_sqkm'].rank(method='min', ascending=False).astype(int) # 查看公元117年的排行榜 df_117 = df[df['year'] == 117].sort_values(by='rank') print(df_117[['rank', 'country', 'territory_sqkm']].head(10))

3.3 数据插值与平滑

历史数据点可能很稀疏(如每100年一个点)。为了制作平滑的动画,有时需要对缺失年份的数据进行插值。注意:这属于数据模拟,会引入误差,仅适用于可视化演示。

# 为每个国家创建完整的时间序列索引(例如,从公元前3500年到公元2026年,每年) all_years = range(-3500, 2027) # 注意:公元前年份用负数表示 all_countries = df['country'].unique() # 创建多索引 index = pd.MultiIndex.from_product([all_countries, all_years], names=['country', 'year']) df_full = pd.DataFrame(index=index).reset_index() # 合并原始数据 df_full = df_full.merge(df, on=['country', 'year'], how='left') # 对国家分组,使用线性插值填充面积数据(向前/向后填充边界值) df_full['territory_sqkm'] = df_full.groupby('country')['territory_sqkm'].apply( lambda x: x.interpolate(method='linear').ffill().bfill() )

4. 完整实战案例:构建与可视化历史领土面积排行榜

现在,我们从一个模拟数据集开始,完成从数据创建到可视化展示的全流程。

4.1 创建模拟数据集

我们创建一个包含几个主要帝国/国家在数个关键年份面积数据的模拟数据集。

import pandas as pd import numpy as np # 定义关键年份 years = [-3500, -2000, -500, 0, 200, 500, 750, 1000, 1200, 1500, 1700, 1800, 1900, 1950, 2000, 2026] # 定义国家/政权 countries = [ 'Ancient Egypt', 'Akkadian Empire', 'Roman Empire', 'Han Dynasty', 'Byzantine Empire', 'Islamic Caliphate', 'Mongol Empire', 'Ming Dynasty', 'Spanish Empire', 'British Empire', 'Russian Empire', 'Qing Dynasty', 'United States', 'India', 'Brazil' ] # 手动为每个国家在不同年份赋予一个模拟的面积值(单位:万平方公里) # 这是一个简化的示例,实际数据应来自研究 np.random.seed(42) # 固定随机种子,确保结果可复现 data = [] for country in countries: # 为每个国家生成一个大致的历史面积趋势(峰值和衰减) base_area = np.random.uniform(50, 300) # 随机基础面积 peak_year = np.random.choice(years[5:12]) # 随机选择鼎盛年份 for year in years: # 模拟一个围绕鼎盛年份先增后减的趋势,并加入一些噪声 time_factor = -((year - peak_year) / 500) ** 2 area = base_area * np.exp(time_factor) + np.random.uniform(-20, 50) area = max(area, 10) # 确保面积不为负或过小 data.append([country, year, round(area, 2)]) # 创建DataFrame df = pd.DataFrame(data, columns=['country', 'year', 'territory_10k_sqkm']) print(df.head()) print(f"数据集形状: {df.shape}")

4.2 数据清洗与排名计算

对数据进行基本清洗,并计算每年的面积排名。

# 1. 检查缺失值 print("缺失值统计:") print(df.isnull().sum()) # 2. 按年份分组,计算排名 # 使用‘min’方法处理并列情况, ascending=False表示面积越大排名越靠前(数值越小) df['rank'] = df.groupby('year')['territory_10k_sqkm'].rank(method='min', ascending=False).astype(int) # 3. 查看公元1500年的排行榜 df_1500 = df[df['year'] == 1500].sort_values('rank') print("\n公元1500年领土面积排行榜(前10名):") print(df_1500[['rank', 'country', 'territory_10k_sqkm']].head(10))

4.3 静态可视化:特定年份的排行榜

使用matplotlibseaborn绘制一个静态的条形图,展示某一年(如1500年)的领土面积排名。

import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体(如果需要显示中文标签) # plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] # Windows # plt.rcParams['axes.unicode_minus'] = False # 选择要可视化的年份 target_year = 1500 df_target = df[df['year'] == target_year].sort_values('territory_10k_sqkm', ascending=True).tail(15) # 取面积最大的15个国家 # 创建图表 plt.figure(figsize=(12, 8)) bars = plt.barh(df_target['country'], df_target['territory_10k_sqkm'], color=sns.color_palette('viridis', len(df_target))) # 添加数据标签 for bar in bars: width = bar.get_width() plt.text(width + 1, bar.get_y() + bar.get_height()/2, f'{width:.0f}', ha='left', va='center', fontsize=10) plt.xlabel('领土面积 (万平方公里)', fontsize=14) plt.title(f'公元 {target_year} 年全球主要政权领土面积排行榜', fontsize=16, fontweight='bold') plt.grid(axis='x', linestyle='--', alpha=0.7) plt.tight_layout() # 保存图片 plt.savefig('./outputs/static_plots/ranking_1500.png', dpi=300) plt.show()

4.4 动态可视化:历史排名变迁(动画)

这是最精彩的部分。我们将使用plotly库创建一个交互式动态条形图,展示排名随时间的变化。

import plotly.express as px import plotly.graph_objects as go # 为了动画效果更好,我们需要更密集的时间数据。 # 这里我们对原始稀疏数据进行插值(仅用于演示可视化技巧)。 # 首先将数据转换为宽格式,便于插值 df_wide = df.pivot(index='year', columns='country', values='territory_10k_sqkm') # 重新索引,生成连续的年份(例如每50年一个点) all_years = range(-3500, 2027, 50) # 步长50年 df_wide = df_wide.reindex(all_years) # 线性插值 df_wide_interpolated = df_wide.interpolate(method='linear') # 转回长格式 df_long_for_animation = df_wide_interpolated.reset_index().melt(id_vars='year', value_name='territory_10k_sqkm', var_name='country') # 再次计算排名 df_long_for_animation['rank'] = df_long_for_animation.groupby('year')['territory_10k_sqkm'].rank(method='first', ascending=False).astype(int) # 筛选出每年排名前10的国家 df_top10_each_year = df_long_for_animation[df_long_for_animation['rank'] <= 10] # 创建动态条形图 fig = px.bar( df_top10_each_year.sort_values(['year', 'rank'], ascending=[True, True]), x='territory_10k_sqkm', y='rank', orientation='h', animation_frame='year', animation_group='country', color='country', hover_name='country', range_x=[0, df_top10_each_year['territory_10k_sqkm'].max() * 1.1], range_y=[10.5, 0.5], # 反转Y轴,让第1名在最上面 labels={'territory_10k_sqkm': '领土面积 (万平方公里)', 'rank': '排名'}, title='全球主要政权领土面积排名变迁 (公元前3500年 - 公元2026年)', height=600 ) # 优化布局和动画速度 fig.update_layout( showlegend=False, # 图例在动态图中可能太乱,可以关闭 xaxis_title='领土面积 (万平方公里)', yaxis_title='排名', yaxis=dict(tickmode='array', tickvals=list(range(1, 11)), autorange='reversed') # 确保排名1在顶部 ) fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 100 # 每帧100ms fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 50 # 过渡50ms # 显示图表(在Jupyter Notebook中) fig.show() # 将交互式图表保存为独立的HTML文件,方便分享 fig.write_html('./outputs/interactive_plots/territory_ranking_animation.html')

运行上述代码后,你将得到一个HTML文件。用浏览器打开它,你可以看到一个随着时间轴滑动而动态变化的条形排名图,非常直观地展示了帝国疆域的兴衰更迭。

4.5 结果分析与解读

通过生成的图表,我们可以进行一些简单的分析:

  1. 趋势观察:可以看到某些帝国(如蒙古帝国)在特定年代迅速崛起并占据榜首,然后又快速衰落。
  2. 长期稳定性:有些文明(如以“Ancient Egypt”模拟的文明)在早期占据主导,但后期逐渐退出前十。
  3. 现代格局:在接近现代的年份(如2000年、2026年),排行榜前列的国家逐渐稳定为现代领土大国。

请注意:由于我们使用的是随机生成的模拟数据,上述趋势仅用于演示分析流程。真实的历史数据分析需要基于严谨的学术数据。

5. 常见问题与排查思路

在实际操作中,你可能会遇到以下问题:

问题现象常见原因解决思路
KeyError或数据合并失败列名拼写错误或数据类型不一致。使用df.columnsdf.dtypes检查列名和类型。确保合并键(如country,year)完全一致。
排名计算错误(如并列处理不当)rank()函数的method参数使用不当。根据需求选择method‘min’(并列取较低排名)、‘max’(并列取较高排名)、‘dense’(并列排名相同,且后续排名连续)。
plotly动画不显示或报错数据格式不对,或animation_frame/animation_group设置错误。确保用于animation_frame的列(如year)是数值型或能被正确排序。animation_group应设置为标识同一对象的列(如country),确保动画中条形正确过渡。
图表过于拥挤,看不清同时显示的国家太多。在动画或静态图中,只筛选出每个时间点排名前N(如前10或前15)的国家进行展示。
插值后数据出现异常值(如负数)插值方法不适合边界或数据跨度太大。在插值后使用.clip(lower=0)将面积限制为非负。考虑使用更稳健的插值方法(如‘time’),或在插值前进行更合理的填充(.ffill().bfill())。
运行速度慢,特别是生成动画时数据量过大(国家多、年份密)。1. 减少数据粒度(增大年份步长)。2. 在生成动画前,先聚合或筛选数据。3. 使用更高效的数据结构,确保countryyear列为合适的类型(如分类、整数)。

6. 最佳实践与工程建议

要将这个项目从演示提升到准生产或研究级别,需要注意以下几点:

  1. 数据源管理

    • 建立数据字典:创建一个metadata.csv文件,记录每个数据点的来源(如引用哪本书、哪篇论文、哪个数据库)、估算方法、可信度评分。这是学术严谨性的基础。
    • 版本控制:使用Git管理你的数据文件和清洗脚本。任何对原始数据的修正都应记录在案。
  2. 代码可维护性

    • 模块化:将数据加载、清洗、插值、可视化等功能拆分成独立的函数或类,放在scripts/目录下的.py文件中。在Jupyter Notebook中主要进行调用和探索。
    • 配置文件:将关键参数(如时间范围、排名数量、颜色方案、输出路径)写入一个config.yamlconfig.py文件,避免硬编码。
  3. 可视化优化

    • 颜色映射:为每个国家分配一个固定的颜色,并在整个动画中保持一致,方便观众追踪。可以使用plotly.expresscolor_discrete_map参数。
    • 添加注释:在动画的关键帧(如某个帝国面积达到峰值时),可以添加文本注释,解释历史事件。
    • 多视图联动:除了动态条形图,可以尝试用plotly创建联动视图,比如一个动态排名图,旁边配一个显示该国疆域变化趋势的折线图。
  4. 性能考量

    • 对于超长时间序列(如每年一个点)和大量国家,plotly动画可能会卡顿。可以考虑:
      • 输出为视频(使用plotlywrite_html后再用工具转换,或使用imageio等库逐帧生成)。
      • 使用更底层的图形库(如matplotlib.animation)进行更精细的控制,但代码会更复杂。
  5. 生产环境注意事项

    • 如果将此可视化部署为Web应用(如使用Dash),需要考虑后端API设计、数据缓存、前端加载性能。
    • 确保使用的历史地图和疆域数据不涉及现代领土争议表述,避免不必要的政治风险。学术研究应聚焦历史事实本身。

7. 总结与学习路线

通过本文,我们完成了一个从模拟数据生成、处理、分析到高级可视化的完整项目流程。你掌握了:

  • 核心技能:使用pandas进行时间序列分组、排序、排名计算和数据透视。
  • 可视化技巧:使用matplotlib/seaborn制作静态排名图,以及使用plotly创建强大的交互式动态条形图。
  • 项目思维:从环境搭建、数据结构设计、代码编写到结果解读的全链条实践。

下一步可以深入探索的方向:

  1. 寻找真实数据:尝试从Our World in DataGapminder或学术数据库(如ICEWS)寻找相关历史数据集,或从《剑桥世界历史地图集》等权威著作中手动数字化数据。
  2. 结合地理信息:使用geopandasfolium/kepler.gl库,将面积数据与历史地图结合,真正实现“变动疆域”的可视化。
  3. 深化分析:计算帝国扩张/收缩的速度,分析面积与人口、经济产出的相关性,建立简单的兴衰模型。
  4. 构建仪表盘:使用Plotly DashStreamlit,将你的分析做成交互式Web应用,允许用户选择年份、国家,查看详细趋势。

历史数据的量化分析是一个充满挑战但极具魅力的领域。希望本文提供的这套“方法工具箱”,能帮助你开启自己的探索之旅。动手修改代码,替换上你感兴趣的真实数据,创造出独一无二的历史洞察可视化作品吧。如果在实践过程中遇到问题,欢迎在评论区交流讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询