Pandas是 Python 中广泛使用的数据操作库,它提供了处理各种类型数据的广泛功能。它的显着特性之一是能够使用MultiIndexes,也称为分层索引。在这篇博文中,我们将深入研究多索引的概念,并探索如何利用它们来处理复杂的多维数据集。
了解 MultiIndexes:分析运动表现数据
MultiIndex 是一种 pandas 数据结构,允许跨多个维度或级别索引和访问数据。它支持为行和列创建层次结构,提供一种灵活的方式来组织和分析数据。为了说明这一点,让我们考虑一个场景,您是一名私人教练或教练,您在运动员的体育活动中监测他们的健康参数。您想要在特定时间间隔内跟踪各种参数,例如心率、跑步配速和节奏。
综合健康表现数据
要处理此类数据,让我们首先编写模拟健康表现数据的 Python 代码,特别是心率和跑步节奏:
from __future__ import annotations
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
start = datetime(2023, 6, 8, 14)
end = start + timedelta(hours=1, minutes=40)
timestamps = pd.date_range(start, end, freq=timedelta(minutes=1), inclusive='left')
def get_heart_rate(begin_hr: int, end_hr: int, break_point: int) -> pd.Series[float]:
noise = np.random.normal(loc=0.0, scale=3, size=100)
heart_rate = np.concatenate((np.linspace(begin_hr, end_hr, num=break_point), [end_hr] * (100 - break_point))) + noise
return pd.Series(data=heart_rate, index=timestamps)
def get_cadence(mean_cadence: int) -> pd.Series[float]:
noise = np.random.normal(loc=0.0, scale=1, size=100)
cadence = pd.Series(data=[mean_cadence] * 100 + noise, index=timestamps)
cadence[::3] = np.NAN
cadence[1::3] = np.NAN
return cadence.ffill().fillna(mean_cadence)
提供的代码片段展示了体育活动期间心率和节奏的合成数据的生成。它首先导入必要的模块,例如 datetime、numpy 和 pandas。
体育活动的持续时间定义为 100 分钟,该 pd.date_range 函数用于以一分钟为间隔生成一系列时间戳以覆盖该时间段。
该 get_heart_rate 函数生成合成心率数据,假设心率线性增加到一定水平,然后在活动的剩余时间保持恒定水平。引入高斯噪声以增加心率数据的可变性,使其更加真实。
类似地,该 get_cadence 函数会生成合成节奏数据,假设在整个活动中节奏相对恒定。添加高斯噪声以创建步频值的可变性,噪声值每三分钟而不是每分钟更新一次,反映步频相对于心率的稳定性。
有了数据生成功能,现在可以为两个运动员 Bob 和 Alice 创建综合数据:
bob_hr = get_heart_rate(begin_hr=110, end_hr=160, break_point=20)
alice_hr = get_heart_rate(begin_hr=90, end_hr=140, break_point=50)
bob_cadence = get_cadence(mean_cadence=175)
alice_cadence = get_cadence(mean_cadence=165)
此时,我们有 Bob 和 Alice 的心率和节奏。让我们使用 matplotlib 绘制它们以更深入地了解数据:
from __future__ import annotations
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
date_formatter = mdates.DateFormatter('%H:%M:%S') # Customize the date format as needed
fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)
ax.xaxis.set_major_formatter(date_formatter)
ax.plot(bob_hr, color="red", label="Heart Rate Bob", marker=".")
ax.plot(alice_hr, color="red", label="Heart Rate Alice", marker="v")
ax.grid()
ax.legend()
ax.set_ylabel("Heart Rate [BPM]")
ax.set_xlabel("Time")
ax_cadence = ax.twinx()
ax_cadence.plot(bob_cadence, color="purple", label="Cadence Bob", marker=".", alpha=0.5)
ax_cadence.plot(alice_cadence, color="purple", label="Cadence Alice", marker="v", alpha=0.5)
ax_cadence.legend()
ax_cadence.set_ylabel("Cadence [SPM]")
ax_cadence.set_ylim(158, 180)

伟大的!对数据的初步分析提供了有趣的观察结果。我们可以很容易地区分 Bob 和 Alice 在最大心率和增加速率方面的差异。此外,Bob 的节奏似乎明显高于 Alice 的。
使用 Dataframes 实现可扩展性
bob_hr 但是,正如您可能已经注意到的那样,当前为每个健康参数和运动员使用单独变量( 、 alice_hr 、 bob_cadence 和)的方法 alice_cadence 不可扩展。在具有大量运动员和健康参数的现实场景中,这种方法很快变得不切实际且麻烦。
为了解决这个问题,我们可以通过使用 pandas DataFrame 来表示多个运动员和健康参数的数据,从而利用 pandas 的强大功能。通过以表格格式组织数据,我们可以轻松地同时管理和分析多个变量。
DataFrame 的每一行都可以对应一个特定的时间戳,每一列都可以代表特定运动员的健康参数。这种结构允许高效存储和操作多维数据。
通过使用 DataFrame,我们可以消除对单独变量的需要,并将所有数据存储在一个对象中。这增强了代码清晰度,简化了数据处理,并提供了整个数据集的更直观表示。
bob_df = pd.concat([bob_hr.rename("heart_rate"), bob_cadence.rename("cadence")], axis="columns")
这是 Bob 健康数据的 Dataframe 的样子:
|
心率 |
节奏 |
|
|
2026-03-16T19:57:44+00:00 |
112.359 |
175 |
|
2026-03-16T19:57:44+00:00 |
107.204 |
175 |
|
2026-03-16T19:57:44+00:00 |
116.617 |
175.513 |
|
2026-03-16T19:57:44+00:00 |
121.151 |
175.513 |
|
2026-03-16T19:57:44+00:00 |
123.27 |
175.513 |
|
2026-03-16T19:57:44+00:00 |
120.901 |
174.995 |
|
2026-03-16T19:57:44+00:00 |
130.24 |
174.995 |
|
2026-03-16T19:57:44+00:00 |
131.15 |
174.995 |
|
2026-03-16T19:57:44+00:00 |
131.402 |
174.669 |
引入分层数据框
最后一个数据框看起来已经更好了!但是现在我们仍然需要为每个运动员创建一个新的数据框。这是 pandas MultiIndex 可以提供帮助的地方。让我们来看看我们如何优雅地将多个运动员和健康参数的数据合并到一个数据框中:
from itertools import product
bob_df = bob_hr.to_frame("value")
bob_df["athlete"] = "Bob"
bob_df["parameter"] = "heart_rate"
values = {
"Bob": {
"heart_rate": bob_hr,
"cadence": bob_cadence,
},
"Alice": {
"heart_rate": alice_hr,
"cadence": alice_cadence
}
}
sub_dataframes: list[pd.DataFrame] = []
for athlete, parameter in product(["Bob", "Alice"], ["heart_rate", "cadence"]):
sub_df = values[athlete][parameter].to_frame("values")
sub_df["athlete"] = athlete
sub_df["parameter"] = parameter
sub_dataframes.append(sub_df)
df = pd.concat(sub_dataframes).set_index(["athlete", "parameter"], append=True)
df.index = df.index.set_names(["timestamps", "athlete", "parameter"])
此代码处理运动员 Bob 和 Alice 的心率和踏频数据。它执行以下步骤:
- 为 Bob 的心率数据创建一个 DataFrame,并为运动员和参数添加元数据列。
- 定义一个字典,用于存储 Bob 和 Alice 的心率和节奏数据。
- 生成运动员和参数的组合(Bob/Alice 和 heart_rate/cadence)。
- 对于每个组合,创建一个具有相应数据和元数据列的子数据框。
- 将所有子数据帧连接成一个数据帧。
- 设置索引以包括时间戳、运动员和参数的级别。这是创建实际 MultiIndex 的地方
这是分层数据框df的样子:
|
价值观 |
|
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
112.359 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
107.204 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
116.617 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
121.151 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
123.27 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
120.901 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
130.24 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
131.15 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃','heart_rate') |
131.402 |
至此,我们已经有了一个单一的数据框,其中包含任意数量的运动员和健康参数的所有信息。我们现在可以轻松地使用该.xs方法来查询分层数据框:
df.xs("Bob", level="athlete") # get all health data for Bob
|
价值观 |
|
|
(时间戳('2026-03-16T19:57:44+00:00'),'heart_rate') |
112.359 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'heart_rate') |
107.204 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'heart_rate') |
116.617 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'heart_rate') |
121.151 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'heart_rate') |
123.27 |
df.xs("heart_rate", level="parameter") *# get all heart rates*
|
价值观 |
|
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃') |
112.359 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃') |
107.204 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃') |
116.617 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃') |
121.151 |
|
(时间戳('2026-03-16T19:57:44+00:00'),'鲍勃') |
123.27 |
df.xs("Bob", level="athlete").xs("heart_rate", level="parameter") # get heart_rate data for Bob
|
时间戳 |
价值观 |
|
2026-03-16T19:57:44+00:00 |
112.359 |
|
2026-03-16T19:57:44+00:00 |
107.204 |
|
2026-03-16T19:57:44+00:00 |
116.617 |
|
2026-03-16T19:57:44+00:00 |
121.151 |
|
2026-03-16T19:57:44+00:00 |
123.27 |
用例:地球温度变化
为了展示分层数据框的强大功能,让我们探索一个真实世界的复杂用例: 分析过去几十年地球表面温度的变化 。对于此任务,我们将利用Kaggle 上可用的数据集,该数据集总结了美国国家航空航天局戈达德太空研究所 (NASA-GISS)分发的全球表面温度变化数据。
检查和转换原始数据
让我们从阅读和检查数据开始。此步骤对于在深入分析之前更好地了解数据集的结构和内容至关重要。以下是我们如何使用 pandas 完成此操作:
from pathlib import Path
file_path = Path() / "data" / "Environment_Temperature_change_E_All_Data_NOFLAG.csv"
df = pd.read_csv(file_path , encoding='cp1252')
df.describe()
|
区号 |
月份代码 |
元素代码 |
Y1961 |
Y1962 |
Y1963 |
Y1964 |
Y1965 |
Y1966 |
Y1967 |
Y1968 |
Y1969 |
Y1970 |
Y1971 |
Y1972 |
Y1973 |
Y1974 |
Y1975 |
Y1976 |
Y1977 |
Y1978 |
Y1979 |
Y1980 |
Y1981 |
Y1982 |
Y1983 |
Y1984 |
Y1985 |
Y1986 |
Y1987 |
Y1988 |
Y1989 |
Y1990 |
Y1991 |
Y1992 |
Y1993 |
Y1994 |
Y1995 |
Y1996 |
Y1997 |
1998年 |
Y1999 |
2000元 |
2001年 |
2002年 |
2003年 |
2004年 |
2005年 |
2006年 |
2007年 |
2008年 |
2009年 |
2010年 |
2011年 |
2012年 |
2013年 |
2014年 |
2015年 |
2016年 |
2017年 |
2018年 |
2019年 |
|
|
数数 |
9656 |
9656 |
9656 |
8287 |
8322 |
8294 |
8252 |
8281 |
8364 |
8347 |
8345 |
8326 |
8308 |
8303 |
8323 |
8394 |
8374 |
8280 |
8209 |
8257 |
8327 |
8290 |
8283 |
8276 |
8237 |
8205 |
8259 |
8216 |
8268 |
8284 |
8273 |
8257 |
8239 |
8158 |
8354 |
8315 |
8373 |
8409 |
8439 |
8309 |
8370 |
8324 |
8342 |
8241 |
8312 |
8390 |
8415 |
8424 |
8503 |
8534 |
8475 |
8419 |
8435 |
8437 |
8350 |
8427 |
8377 |
8361 |
8348 |
8366 |
8349 |
8365 |
|
意思是 |
821.806 |
7009.88 |
6674.5 |
0.402433 |
0.315527 |
0.317393 |
0.269382 |
0.217839 |
0.376419 |
0.263239 |
0.24487 |
0.382172 |
0.365322 |
0.240934 |
0.302553 |
0.427691 |
0.261849 |
0.314653 |
0.221112 |
0.422978 |
0.355488 |
0.442465 |
0.43827 |
0.437693 |
0.404857 |
0.503748 |
0.366971 |
0.365511 |
0.398096 |
0.535514 |
0.546662 |
0.469231 |
0.621797 |
0.499991 |
0.447798 |
0.439094 |
0.611078 |
0.635836 |
0.477239 |
0.617341 |
0.818264 |
0.704445 |
0.674191 |
0.741673 |
0.802509 |
0.769485 |
0.726237 |
0.777465 |
0.791795 |
0.842554 |
0.742614 |
0.814177 |
0.884504 |
0.768488 |
0.78893 |
0.829647 |
0.913872 |
1.01882 |
1.08149 |
1.00334 |
1.01083 |
1.0946 |
|
标准 |
1781.07 |
6.03825 |
596.531 |
0.701567 |
0.713777 |
0.853133 |
0.749216 |
0.739418 |
0.73737 |
0.725421 |
0.7549 |
0.725313 |
0.662412 |
0.727313 |
0.765895 |
0.677769 |
0.76885 |
0.723964 |
0.755587 |
0.677094 |
0.662228 |
0.670377 |
0.638585 |
0.70157 |
0.675693 |
0.749268 |
0.700364 |
0.765491 |
0.710202 |
0.756869 |
0.664038 |
0.758738 |
0.724396 |
0.656754 |
0.843925 |
0.81786 |
0.752511 |
0.754133 |
0.72521 |
0.741812 |
0.785076 |
0.723811 |
0.783754 |
0.781683 |
0.840545 |
0.794839 |
0.644364 |
0.720366 |
0.821489 |
0.854893 |
0.852562 |
0.694464 |
0.878303 |
0.750631 |
0.858586 |
0.713753 |
0.815933 |
0.840189 |
0.877399 |
0.8098 |
0.872199 |
0.853953 |
|
分钟 |
1个 |
7001 |
6078 |
-4.018 |
-5.391 |
-8.483 |
-7.309 |
-4.728 |
-8.147 |
-6.531 |
-8.407 |
-6.784 |
-5.847 |
-7.671 |
-7.722 |
-4.896 |
-4.732 |
-6.169 |
-4.263 |
-6.495 |
-8.228 |
-6.319 |
-5.784 |
-6.591 |
-4.55 |
-6.101 |
-5.437 |
-8.411 |
-6.345 |
-8.75 |
-8.963 |
-5.311 |
-4.03 |
-4.598 |
-5.414 |
-7.389 |
-5.099 |
-4.862 |
-4.027 |
-4.059 |
-6.031 |
-3.035 |
-3.596 |
-5.493 |
-6.17 |
-6.118 |
-5.025 |
-5.171 |
-4.981 |
-3.19 |
-9.334 |
-3.543 |
-6.072 |
-4.854 |
-5.785 |
-3.642 |
-5.367 |
-4.068 |
-3.306 |
-3.584 |
-2.216 |
-2.644 |
|
25% |
78 |
7005 |
6078 |
0.057 |
-0.033 |
0.03025 |
-0.1025 |
-0.214 |
0.055 |
-0.169 |
-0.164 |
0.171 |
0.094 |
-0.209 |
-0.028 |
0.201 |
-0.184 |
-0.115 |
-0.219 |
0.174 |
0.091 |
0.217 |
0.2455 |
0.205 |
0.168 |
0.268 |
0.077 |
0.123 |
0.164 |
0.293 |
0.285 |
0.186 |
0.298 |
0.264 |
0.18425 |
0.214 |
0.289 |
0.324 |
0.244 |
0.305 |
0.385 |
0.327 |
0.3 |
0.349 |
0.38 |
0.37525 |
0.361 |
0.373 |
0.371 |
0.378 |
0.337 |
0.381 |
0.392 |
0.365 |
0.37225 |
0.4085 |
0.418 |
0.437 |
0.457 |
0.443 |
0.434 |
0.455 |
|
50% |
153.5 |
7009 |
6674.5 |
0.366 |
0.333 |
0.355 |
0.326 |
0.303 |
0.36 |
0.313 |
0.312 |
0.385 |
0.367 |
0.305 |
0.346 |
0.413 |
0.305 |
0.325 |
0.309 |
0.388 |
0.35 |
0.406 |
0.424 |
0.409 |
0.39 |
0.468 |
0.373 |
0.374 |
0.38 |
0.504 |
0.478 |
0.404 |
0.49 |
0.448 |
0.419 |
0.427 |
0.493 |
0.539 |
0.45 |
0.528 |
0.715 |
0.548 |
0.514 |
0.596 |
0.668 |
0.658 |
0.597 |
0.676 |
0.646 |
0.667 |
0.589 |
0.693 |
0.771 |
0.64 |
0.651 |
0.719 |
0.745 |
0.858 |
0.949 |
0.865 |
0.81 |
0.939 |
|
75% |
226.25 |
7016 |
7271 |
0.6765 |
0.627 |
0.64775 |
0.609 |
0.584 |
0.66025 |
0.601 |
0.595 |
0.677 |
0.642 |
0.5885 |
0.628 |
0.709 |
0.586 |
0.625 |
0.586 |
0.695 |
0.633 |
0.69775 |
0.701 |
0.709 |
0.689 |
0.825 |
0.666 |
0.674 |
0.678 |
0.854 |
0.802 |
0.728 |
0.8485 |
0.758 |
0.777 |
0.748 |
0.874 |
0.905 |
0.777 |
0.938 |
1.188 |
0.98825 |
0.937 |
1.028 |
1.08425 |
1.063 |
0.991 |
1.104 |
1.082 |
1.096 |
1.0425 |
1.1345 |
1.2765 |
1.091 |
1.108 |
1.126 |
1.19 |
1.389 |
1.496 |
1.36475 |
1.341 |
1.508 |
|
最大限度 |
5873 |
7020 |
7271 |
5.771 |
4.373 |
4.666 |
5.233 |
5.144 |
5.771 |
4.768 |
4.373 |
4.411 |
4.373 |
4.373 |
9.475 |
6.304 |
6.912 |
6.15 |
7.689 |
4.875 |
5.956 |
5.483 |
4.519 |
6.144 |
5.411 |
6.513 |
9.303 |
5.948 |
6.845 |
4.516 |
4.982 |
6.841 |
9.73 |
6.11 |
6.017 |
5.989 |
6.477 |
7.221 |
7.27 |
5.637 |
6.816 |
7.017 |
5.836 |
6.092 |
8.719 |
6.171 |
7.461 |
7.651 |
11.331 |
7.655 |
8.298 |
6.415 |
7.19 |
6.531 |
10.826 |
6.738 |
11.759 |
7.59 |
10.478 |
7.389 |
9.228 |
7.215 |
从这个初步检查中,很明显数据是在一个数据框中组织的,不同的月份和国家有不同的行。但是,不同年份的值分布在数据框中的多个列中,并标有前缀“Y”。这种格式使得有效读取和可视化数据具有挑战性。为了解决这个问题,我们将数据转换为更加结构化和分层的数据帧格式,使我们能够更方便地查询和可视化数据。
from dataclasses import dataclass, field
from datetime import date
from pydantic import BaseModel
MONTHS = {
"January": 1,
"February": 2,
"March": 3,
"April": 4,
"May": 5,
"June": 6,
"July": 7,
"August": 8,
"September": 9,
"October": 10,
"November": 11,
"December": 12
}
class GistempDataElement(BaseModel):
area: str
timestamp: date
value: float
@dataclass
class GistempTransformer:
temperature_changes: list[GistempDataElement] = field(default_factory=list)
standard_deviations: list[GistempDataElement] = field(default_factory=list)
def _process_row(self, row) -> None:
relevant_elements = ["Temperature change", "Standard Deviation"]
if (element := row["Element"]) not in relevant_elements or (month := MONTHS.get(row["Months"])) is None:
return None
for year, value in row.filter(regex="Y.*").items():
new_element = GistempDataElement(
timestamp=date(year=int(year.replace("Y", "")), month=month, day=1),
area=row["Area"],
value=value
)
if element == "Temperature change":
self.temperature_changes.append(new_element)
else:
self.standard_deviations.append(new_element)
@property
def df(self) -> pd.DataFrame:
temp_changes_df = pd.DataFrame.from_records([elem.dict() for elem in self.temperature_changes])
temp_changes = temp_changes_df.set_index(["timestamp", "area"]).rename(columns={"value": "temp_change"})
std_deviations_df = pd.DataFrame.from_records([elem.dict() for elem in self.standard_deviations])
std_deviations = std_deviations_df.set_index(["timestamp", "area"]).rename(columns={"value": "std_deviation"})
return pd.concat([temp_changes, std_deviations], axis="columns")
def process(self):
environment_data = Path() / "data" / "Environment_Temperature_change_E_All_Data_NOFLAG.csv"
df = pd.read_csv(environment_data, encoding='cp1252')
df.apply(self._process_row, axis="columns")
此代码介绍了该类 GistempTransformer ,该类演示了如何处理 CSV 文件中的温度数据以及如何创建包含温度变化和标准差的分层 DataFrame。
该类 GistempTransformer 定义为数据类,包括两个列表 temperature_changes 和 standard_deviations ,用于存储处理后的数据元素。该 _process_row 方法负责处理输入 DataFrame 的每一行。它检查相关元素,例如“温度变化”和“标准偏差”,从“月份”列中提取月份,并创建该类的实例 GistempDataElement 。然后根据元素类型将这些实例附加到适当的列表中。
该 df 属性通过组合 temperature_changes 和 standard_deviations 列表返回一个 DataFrame。这个分层的 DataFrame 有一个 MultiIndex,其级别代表时间戳和区域,提供数据的结构化组织。
transformer = GistempTransformer()
transformer.process()
df = transformer.df
|
温度变化 |
标准偏差 |
|
|
(datetime.date(1961, 1, 1), '阿富汗') |
0.777 |
1.95 |
|
(datetime.date(1962, 1, 1), '阿富汗') |
0.062 |
1.95 |
|
(datetime.date(1963, 1, 1), '阿富汗') |
2.744 |
1.95 |
|
(datetime.date(1964, 1, 1), '阿富汗') |
-5.232 |
1.95 |
|
(datetime.date(1965, 1, 1), '阿富汗') |
1.868 |
1.95 |
分析气候数据
现在我们已经将所有相关数据整合到一个数据框中,我们可以继续检查和可视化数据。我们的重点是检查每个区域的线性回归线,因为它们提供了对过去几十年温度变化总体趋势的见解。为了促进这种可视化,我们将创建一个函数来绘制温度变化及其相应的回归线。
def plot_temperature_changes(areas: list[str]) -> None:
fig = plt.figure(figsize=(12, 6))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
for area in areas:
df_country = df[df.index.get_level_values("area") == area].reset_index()
dates = df_country["timestamp"].map(datetime.toordinal)
gradient, offset = np.polyfit(dates, df_country.temp_change, deg=1)
ax1.scatter(df_country.timestamp, df_country.temp_change, label=area, s=5)
ax2.plot(df_country.timestamp, gradient * dates + offset, label=area)
ax1.grid()
ax2.grid()
ax2.legend()
ax2.set_ylabel("Regression Lines [°C]")
ax1.set_ylabel("Temperature change [°C]")
在此函数中,我们在 pandas MultiIndex 上使用**get_level_values** 方法来有效地查询不同级别的分层 Dataframe 中的数据。让我们使用这个函数来可视化不同大陆的温度变化:
plot_temperature_changes(["Africa", "Antarctica", "Americas", "Asia", "Europe", "Oceania"])

从这个图中,我们可以得出几个关键结论:
- 所有大陆的回归线都具有正梯度,表明地球表面温度升高的全球趋势。
- 与其他大陆相比,欧洲的回归线明显更陡峭,这意味着欧洲的气温上升更为明显。与其他地区相比,这一发现与欧洲加速变暖的观察结果一致。
- 与南极洲相比,导致欧洲温度升高的具体因素很复杂,需要进行详细的科学研究。然而,一个促成因素可能是洋流的影响。欧洲受到暖洋流的影响,例如墨西哥湾暖流,它将热量从热带输送到该地区。这些洋流在调节温度方面发挥着作用,并可能导致在欧洲观察到的相对较高的变暖。相比之下,南极洲被寒冷的洋流包围,其气候受南大洋和南极绕极流的严重影响,它们充当了温暖水域入侵的屏障,从而限制了变暖效应。
现在,让我们通过检查欧洲不同地区的温度变化,将分析重点放在欧洲本身。我们可以通过为每个欧洲区域创建单独的地块来实现这一点:
plot_temperature_changes(["Southern Europe", "Eastern Europe", "Northern Europe", "Western Europe"])

从欧洲不同地区的温度变化图中,我们观察到整个欧洲大陆的整体温度上升非常相似。虽然各地区之间回归线的陡度可能略有不同,例如东欧与南欧相比曲线略陡,但各地区之间没有显着差异。
受气候变化影响最大和最小的十个国家
现在,让我们将重点转移到确定自 2000 年以来平均气温上升幅度最大的前 10 个国家/地区。以下是我们如何检索国家/地区列表的示例:
df[df.index.get_level_values(level="timestamp") > date(2000, 1, 1)].groupby("area").mean().sort_values(by="temp_change",ascending=False).head(10)
|
区域 |
温度变化 |
标准偏差 |
|
斯瓦尔巴群岛和扬马延群岛 |
2.61541 |
2.48572 |
|
爱沙尼亚 |
1.69048 |
楠 |
|
科威特 |
1.6825 |
1.12843 |
|
白俄罗斯 |
1.66113 |
楠 |
|
芬兰 |
1.65906 |
2.15634 |
|
斯洛文尼亚 |
1.6555 |
楠 |
|
俄罗斯联邦 |
1.64507 |
楠 |
|
巴林 |
1.64209 |
0.937431 |
|
东欧洲 |
1.62868 |
0.970377 |
|
奥地利 |
1.62721 |
1.56392 |
为了提取自 2000 年以来平均气温增幅最高的前 10 个国家,我们执行以下步骤:
- 使用过滤数据框以仅包含年份大于或等于 2000 的行 df.index.get_level_values(level='timestamp') >= date(2000, 1, 1) 。
- 使用按“区域”(国家/地区)对数据进行分组 .groupby('area') 。
- 使用 计算每个国家/地区的平均温度变化 .mean() 。
- 选择平均温度变化最大的前 10 个国家/地区**.sort_values(by="temp_change",ascending=True).head(10)**。
这一结果与我们之前的观察结果一致,证实与其他大陆相比,欧洲经历了最高的温度上升。
继续我们的分析,现在让我们探索受气温上升影响最小的十个国家。我们可以使用与以前相同的方法来提取此信息。这是我们如何检索国家列表的示例:
df[df.index.get_level_values(level="timestamp") > date(2000, 1, 1)].groupby("area").mean().sort_values(by="temp_change",ascending=True).head(10)
|
区域 |
温度变化 |
标准偏差 |
|
皮特凯恩群岛 |
0.157284 |
0.713095 |
|
马绍尔群岛 |
0.178335 |
楠 |
|
南乔治亚岛和南桑威奇群岛 |
0.252101 |
1.11 |
|
密克罗尼西亚联邦 |
0.291996 |
楠 |
|
智利 |
0.297607 |
0.534071 |
|
威克岛 |
0.306269 |
楠 |
|
诺福克岛 |
0.410659 |
0.594073 |
|
阿根廷 |
0.488159 |
0.91559 |
|
津巴布韦 |
0.493519 |
0.764067 |
|
南极洲 |
0.527987 |
1.55841 |
我们观察到,这份名单中的大多数国家都是位于南半球的偏远小岛。这一发现进一步支持了我们之前的结论,即与其他地区相比,南部大陆,尤其是南极洲,受气候变化的影响较小。
夏季和冬季的温度变化
现在,让我们使用分层数据框深入研究更复杂的查询。在这个特定的用例中,我们的重点是分析冬季和夏季的温度变化。出于此分析的目的,我们将冬季定义为 12 月、1 月和 2 月,而夏季包括 6 月、7 月和 8 月。通过利用 pandas 的力量和分层数据框,我们可以轻松地可视化欧洲这些季节的温度变化。这是一个示例代码片段来实现这一点:
all_winters = df[df.index.get_level_values(level="timestamp").map(lambda x: x.month in [12, 1, 2])]
all_summers = df[df.index.get_level_values(level="timestamp").map(lambda x: x.month in [6, 7, 8])]
winters_europe = all_winters.xs("Europe", level="area").sort_index()
summers_europe = all_summers.xs("Europe", level="area").sort_index()
fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)
ax.plot(winters_europe.index, winters_europe.temp_change, label="Winters", marker="o", markersize=4)
ax.plot(summers_europe.index, summers_europe.temp_change, label="Summers", marker='o', markersize=4)
ax.grid()
ax.legend()
ax.set_ylabel("Temperature Change [°C]")

从这个图中,我们可以观察到与夏季的温度变化相比,冬季的温度变化表现出更大的波动性。为了量化这种差异,让我们计算两个季节的温度变化的标准差:
pd.concat([winters_europe.std().rename("winters"), summers_europe.std().rename("summers")], axis="columns")
|
冬天 |
夏天 |
|
|
温度变化 |
1.82008 |
0.696666 |
结论
总之,掌握 Pandas 中的 MultiIndexes 为处理复杂的数据分析任务提供了强大的工具。通过利用 MultiIndexes,用户可以以灵活直观的方式有效地组织和分析多维数据集。使用行和列的层次结构的能力提高了代码的清晰度,简化了数据处理,并支持同时分析多个变量。无论是跟踪运动员的健康参数还是分析地球温度随时间的变化,理解和利用 Pandas 中的 MultiIndexes 都可以释放图书馆处理复杂数据场景的全部潜力。
您可以在此处找到本文中包含的所有代码: https: //github.com/GlennViroux/pandas-multi-index-blog