79. 用Pandas处理面板数据#

79.1. 概述#

之前关于pandas的讲座中,我们学习了如何处理简单的数据集。

计量经济学家经常需要处理更复杂的数据集,比如面板数据。

常见的任务包括:

  • 导入数据、清理数据以及在多个轴上重塑数据。

  • 从面板数据中提取时间序列数据或横截面数据。

  • 对数据进行分组和汇总。

pandas(源自’panel’和’data’这两个词)包含强大且易用的工具,专门用于解决这类问题。

在接下来的内容中,我们将使用来自OECD的实际最低工资面板数据集来创建:

  • 数据在多个维度上的汇总统计

  • 数据集中各国平均最低工资的时间序列

  • 按大洲划分的工资核密度估计

首先,我们将从CSV文件中读取长格式面板数据,并使用pivot_table重塑生成的DataFrame来构建MultiIndex

使用pandas的merge函数将为我们的DataFrame添加额外的详细信息,并使用groupby函数对数据进行汇总。

79.2. 切片和重塑数据#

我们将读取来自OECD的32个国家的实际最低工资数据集,并将其赋值给realwage

数据集可通过以下链接访问:

url1 = 'https://raw.githubusercontent.com/QuantEcon/lecture-python/master/source/_static/lecture_specific/pandas_panel/realwage.csv'
import pandas as pd

# 为便于查看,显示数据集前6列
pd.set_option('display.max_columns', 6)

# 将小数点位数减少到2位
pd.options.display.float_format = '{:,.2f}'.format

realwage = pd.read_csv(url1)

让我们看看我们有什么可以使用的数据

realwage.head()  # 显示前5行
Unnamed: 0 Time Country Series Pay period value
0 0 2006-01-01 Ireland In 2015 constant prices at 2015 USD PPPs Annual 17,132.44
1 1 2007-01-01 Ireland In 2015 constant prices at 2015 USD PPPs Annual 18,100.92
2 2 2008-01-01 Ireland In 2015 constant prices at 2015 USD PPPs Annual 17,747.41
3 3 2009-01-01 Ireland In 2015 constant prices at 2015 USD PPPs Annual 18,580.14
4 4 2010-01-01 Ireland In 2015 constant prices at 2015 USD PPPs Annual 18,755.83

数据目前是长格式的,当数据有多个维度时,使用这种格式难以进行分析。

我们将使用pivot_table来创建宽格式面板,并使用MultiIndex来处理高维数据。

pivot_table的参数需要指定数据值(values)、索引和在结果数据框中我们所需的列名。

通过在columns参数中传入一个列表,我们可以在列轴上创建一个MultiIndex

realwage = realwage.pivot_table(values='value',
                                index='Time',
                                columns=['Country', 'Series', 'Pay period'])
realwage.head()
Country Australia ... United States
Series In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates ... In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates
Pay period Annual Hourly Annual ... Hourly Annual Hourly
Time
2006-01-01 20,410.65 10.33 23,826.64 ... 6.05 12,594.40 6.05
2007-01-01 21,087.57 10.67 24,616.84 ... 6.24 12,974.40 6.24
2008-01-01 20,718.24 10.48 24,185.70 ... 6.78 14,097.56 6.78
2009-01-01 20,984.77 10.62 24,496.84 ... 7.58 15,756.42 7.58
2010-01-01 20,879.33 10.57 24,373.76 ... 7.88 16,391.31 7.88

5 rows × 128 columns

为了更容易地筛选我们的时间序列数据,接下来我们将把索引转换为DateTimeIndex

realwage.index = pd.to_datetime(realwage.index)
type(realwage.index)
pandas.core.indexes.datetimes.DatetimeIndex

该数据框的列包含多层级索引,被称为MultiIndex,各层级按层次结构排序(Country > Series > Pay period)。

MultiIndex是在pandas中管理面板数据最简单且最灵活的方式。

type(realwage.columns)
pandas.core.indexes.multi.MultiIndex
realwage.columns.names
FrozenList(['Country', 'Series', 'Pay period'])

和之前一样,我们可以选择国家(MultiIndex中的最高层级)

realwage['United States'].head()
Series In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates
Pay period Annual Hourly Annual Hourly
Time
2006-01-01 12,594.40 6.05 12,594.40 6.05
2007-01-01 12,974.40 6.24 12,974.40 6.24
2008-01-01 14,097.56 6.78 14,097.56 6.78
2009-01-01 15,756.42 7.58 15,756.42 7.58
2010-01-01 16,391.31 7.88 16,391.31 7.88

在本讲中,我们将经常使用MultiIndex的堆叠和取消堆叠来将数据框重塑成所需的格式。

.stack()将列MultiIndex的最低层级旋转到行索引(.unstack()执行反向操作 - 你可以试试看)

realwage.stack().head()
/tmp/ipykernel_11348/743219372.py:1: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  realwage.stack().head()
Country Australia Belgium ... United Kingdom United States
Series In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates In 2015 constant prices at 2015 USD PPPs ... In 2015 constant prices at 2015 USD exchange rates In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates
Time Pay period
2006-01-01 Annual 20,410.65 23,826.64 21,042.28 ... 20,376.32 12,594.40 12,594.40
Hourly 10.33 12.06 10.09 ... 9.81 6.05 6.05
2007-01-01 Annual 21,087.57 24,616.84 21,310.05 ... 20,954.13 12,974.40 12,974.40
Hourly 10.67 12.46 10.22 ... 10.07 6.24 6.24
2008-01-01 Annual 20,718.24 24,185.70 21,416.96 ... 20,902.87 14,097.56 14,097.56

5 rows × 64 columns

我们也可以传入一个参数来选择我们想要堆叠的层级

realwage.stack(level='Country').head()
/tmp/ipykernel_11348/1205496966.py:1: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  realwage.stack(level='Country').head()
Series In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates
Pay period Annual Hourly Annual Hourly
Time Country
2006-01-01 Australia 20,410.65 10.33 23,826.64 12.06
Belgium 21,042.28 10.09 20,228.74 9.70
Brazil 3,310.51 1.41 2,032.87 0.87
Canada 13,649.69 6.56 14,335.12 6.89
Chile 5,201.65 2.22 3,333.76 1.42

使用DatetimeIndex可以轻松选择特定的时间段。

选择一年并堆叠MultiIndex的两个较低层级,可以创建我们面板数据的横截面。

realwage.loc['2015'].stack(level=(1, 2)).transpose().head()
/tmp/ipykernel_11348/1065142626.py:1: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  realwage.loc['2015'].stack(level=(1, 2)).transpose().head()
Time 2015-01-01
Series In 2015 constant prices at 2015 USD PPPs In 2015 constant prices at 2015 USD exchange rates
Pay period Annual Hourly Annual Hourly
Country
Australia 21,715.53 10.99 25,349.90 12.83
Belgium 21,588.12 10.35 20,753.48 9.95
Brazil 4,628.63 2.00 2,842.28 1.21
Canada 16,536.83 7.95 17,367.24 8.35
Chile 6,633.56 2.80 4,251.49 1.81

在本讲后续内容中,我们将使用按国家和时间维度统计的每小时实际最低工资数据框(计量单位为2015年不变价美元)。

为了创建筛选后的数据框(realwage_f),我们可以使用xs方法:在保持更高层级索引(本例中为国家)的同时,选择多级索引中较低层级的值。

realwage_f = realwage.xs(('Hourly', 'In 2015 constant prices at 2015 USD exchange rates'),
                         level=('Pay period', 'Series'), axis=1)
realwage_f.head()
Country Australia Belgium Brazil ... Turkey United Kingdom United States
Time
2006-01-01 12.06 9.70 0.87 ... 2.27 9.81 6.05
2007-01-01 12.46 9.82 0.92 ... 2.26 10.07 6.24
2008-01-01 12.24 9.87 0.96 ... 2.22 10.04 6.78
2009-01-01 12.40 10.21 1.03 ... 2.28 10.15 7.58
2010-01-01 12.34 10.05 1.08 ... 2.30 9.96 7.88

5 rows × 32 columns

79.3. 合并数据框和填充空值(NaN 值)#

与SQL等关系型数据库类似,pandas内置了合并数据集的方法。

使用来自WorldData.info的国家信息,我们将使用merge函数将每个国家所属的大洲添加到realwage_f中。

可以通过以下链接访问数据集:

url2 = 'https://raw.githubusercontent.com/QuantEcon/lecture-python/master/source/_static/lecture_specific/pandas_panel/countries.csv'
worlddata = pd.read_csv(url2, sep=';')
worlddata.head()
Country (en) Country (de) Country (local) ... Deathrate Life expectancy Url
0 Afghanistan Afghanistan Afganistan/Afqanestan ... 13.70 51.30 https://www.laenderdaten.info/Asien/Afghanista...
1 Egypt Ägypten Misr ... 4.70 72.70 https://www.laenderdaten.info/Afrika/Aegypten/...
2 Åland Islands Ålandinseln Åland ... 0.00 0.00 https://www.laenderdaten.info/Europa/Aland/ind...
3 Albania Albanien Shqipëria ... 6.70 78.30 https://www.laenderdaten.info/Europa/Albanien/...
4 Algeria Algerien Al-Jaza’ir/Algérie ... 4.30 76.80 https://www.laenderdaten.info/Afrika/Algerien/...

5 rows × 17 columns

首先,我们将从worlddata中只选择国家和大洲变量,并将列名重命名为Country

worlddata = worlddata[['Country (en)', 'Continent']]
worlddata = worlddata.rename(columns={'Country (en)': 'Country'})
worlddata.head()
Country Continent
0 Afghanistan Asia
1 Egypt Africa
2 Åland Islands Europe
3 Albania Europe
4 Algeria Africa

我们想要将新的数据框worlddatarealwage_f合并。

pandas的merge函数可以通过行将数据框连接在一起。

我们的数据框将使用国家名称进行合并,这需要我们使用数据框realwage_f的转置,以便两个数据框中的行都对应于国家名称。

realwage_f.transpose().head()
Time 2006-01-01 2007-01-01 2008-01-01 ... 2014-01-01 2015-01-01 2016-01-01
Country
Australia 12.06 12.46 12.24 ... 12.67 12.83 12.98
Belgium 9.70 9.82 9.87 ... 10.01 9.95 9.76
Brazil 0.87 0.92 0.96 ... 1.21 1.21 1.24
Canada 6.89 6.96 7.24 ... 8.22 8.35 8.48
Chile 1.42 1.45 1.44 ... 1.76 1.81 1.91

5 rows × 11 columns

我们可以使用左连接(left)、右连接(right)、内连接(inner)或外连接(outer)来合并我们的数据集:

  • 左连接(left)只包含左侧数据集中的国家

  • 右连接(right)只包含右侧数据集中的国家

  • 内连接(inner)只包含左右数据集共有的国家

  • 外连接(outer)包含左侧和右侧数据集中的任一国家

默认情况下,merge将使用内连接(inner)。

在这个案例中,我们将传入how='left'以保留realwage_f中的所有国家,但丢弃worlddata中不能和realwage_f相匹配的国家。

这在下图中用红色阴影部分表示

_images/venn_diag.png

我们还需要指定每个数据框中国家名称的位置,这将作为合并数据框的”键(key)”。

我们的”左”(left)数据框(realwage_f.transpose())在索引中包含国家,所以我们设置left_index=True

我们的”右”(right)数据框(worlddata)在’Country’列中包含国家名称,所以我们设置right_on='Country'

merged = pd.merge(realwage_f.transpose(), worlddata,
                  how='left', left_index=True, right_on='Country')
merged.head()
2006-01-01 00:00:00 2007-01-01 00:00:00 2008-01-01 00:00:00 ... 2016-01-01 00:00:00 Country Continent
17.00 12.06 12.46 12.24 ... 12.98 Australia Australia
23.00 9.70 9.82 9.87 ... 9.76 Belgium Europe
32.00 0.87 0.92 0.96 ... 1.24 Brazil South America
100.00 6.89 6.96 7.24 ... 8.48 Canada North America
38.00 1.42 1.45 1.44 ... 1.91 Chile South America

5 rows × 13 columns

realwage_f 中出现但在 worlddata 中未出现的国家,其 Continent 列将显示 NaN

要检查是否发生这种情况,我们可以在 Continent 列上使用 .isnull() 并过滤合并后的数据框

merged[merged['Continent'].isnull()]
2006-01-01 00:00:00 2007-01-01 00:00:00 2008-01-01 00:00:00 ... 2016-01-01 00:00:00 Country Continent
NaN 3.42 3.74 3.87 ... 5.28 Korea NaN
NaN 0.23 0.45 0.39 ... 0.55 Russian Federation NaN
NaN 1.50 1.64 1.71 ... 2.08 Slovak Republic NaN

3 rows × 13 columns

我们有三个缺失值!

处理 NaN 值的一个方式是创建一个包含这些国家及其对应大洲的字典。

.map() 将会把 merged['Country'] 中的国家与字典中的大洲进行匹配。

注意那些不在我们字典中的国家是如何被映射为 NaN

missing_continents = {'Korea': 'Asia',
                      'Russian Federation': 'Europe',
                      'Slovak Republic': 'Europe'}

merged['Country'].map(missing_continents)
17.00        NaN
23.00        NaN
32.00        NaN
100.00       NaN
38.00        NaN
108.00       NaN
41.00        NaN
225.00       NaN
53.00        NaN
58.00        NaN
45.00        NaN
68.00        NaN
233.00       NaN
86.00        NaN
88.00        NaN
91.00        NaN
NaN         Asia
117.00       NaN
122.00       NaN
123.00       NaN
138.00       NaN
153.00       NaN
151.00       NaN
174.00       NaN
175.00       NaN
NaN       Europe
NaN       Europe
198.00       NaN
200.00       NaN
227.00       NaN
241.00       NaN
240.00       NaN
Name: Country, dtype: object

我们不想用这个映射覆盖整个序列。

.fillna() 只会用映射值填充 merged['Continent'] 中的 NaN 值,同时保持列中的其他值不变

merged['Continent'] = merged['Continent'].fillna(merged['Country'].map(missing_continents))

# 检查大洲是否被正确映射
merged[merged['Country'] == 'Korea']
2006-01-01 00:00:00 2007-01-01 00:00:00 2008-01-01 00:00:00 ... 2016-01-01 00:00:00 Country Continent
NaN 3.42 3.74 3.87 ... 5.28 Korea Asia

1 rows × 13 columns

我们把美洲合并成一个大洲 – 这样可以让我们后面的可视化效果更好。

为此,我们将使用.replace()并遍历一个包含我们想要替换的大洲的值的列表

replace = ['Central America', 'North America', 'South America']

for country in replace:
    merged['Continent'] = merged['Continent'].replace(
                                {country:'America'})

现在我们已经将所有想要的数据都放在一个DataFrame中,我们将把它重新整形成带有MultiIndex的面板形式。

我们还应该使用.sort_index()来确保对索引进行排序,这样我们之后可以高效地筛选数据框。

默认情况下,层级将按照从上到下的顺序排序

merged = merged.set_index(['Continent', 'Country']).sort_index()
merged.head()
2006-01-01 2007-01-01 2008-01-01 ... 2014-01-01 2015-01-01 2016-01-01
Continent Country
America Brazil 0.87 0.92 0.96 ... 1.21 1.21 1.24
Canada 6.89 6.96 7.24 ... 8.22 8.35 8.48
Chile 1.42 1.45 1.44 ... 1.76 1.81 1.91
Colombia 1.01 1.02 1.01 ... 1.13 1.13 1.12
Costa Rica NaN NaN NaN ... 2.41 2.56 2.63

5 rows × 11 columns

在合并过程中,我们丢失了DatetimeIndex,因为我们合并的列不是日期时间格式的

merged.columns
Index([2006-01-01 00:00:00, 2007-01-01 00:00:00, 2008-01-01 00:00:00,
       2009-01-01 00:00:00, 2010-01-01 00:00:00, 2011-01-01 00:00:00,
       2012-01-01 00:00:00, 2013-01-01 00:00:00, 2014-01-01 00:00:00,
       2015-01-01 00:00:00, 2016-01-01 00:00:00],
      dtype='object')

现在我们已经将合并的列设置为索引,我们可以使用.to_datetime()重新创建一个DatetimeIndex

merged.columns = pd.to_datetime(merged.columns)
merged.columns = merged.columns.rename('Time')
merged.columns
DatetimeIndex(['2006-01-01', '2007-01-01', '2008-01-01', '2009-01-01',
               '2010-01-01', '2011-01-01', '2012-01-01', '2013-01-01',
               '2014-01-01', '2015-01-01', '2016-01-01'],
              dtype='datetime64[ns]', name='Time', freq=None)

一般来说DatetimeIndex在行轴上运作起来更加顺畅,所以我们将对merged进行转置

merged = merged.transpose()
merged.head()
Continent America ... Europe
Country Brazil Canada Chile ... Slovenia Spain United Kingdom
Time
2006-01-01 0.87 6.89 1.42 ... 3.92 3.99 9.81
2007-01-01 0.92 6.96 1.45 ... 3.88 4.10 10.07
2008-01-01 0.96 7.24 1.44 ... 3.96 4.14 10.04
2009-01-01 1.03 7.67 1.52 ... 4.08 4.32 10.15
2010-01-01 1.08 7.94 1.56 ... 4.81 4.30 9.96

5 rows × 32 columns

79.4. 数据分组和汇总#

对于理解大型面板数据集来说,数据分组和汇总特别有用。

一种简单的数据汇总方法是在数据框上调用聚合方法,比如.mean().max()

例如,我们可以计算2006年至2016年期间每个国家的平均实际最低工资(默认是按行聚合)

merged.mean().head(10)
Continent  Country      
America    Brazil          1.09
           Canada          7.82
           Chile           1.62
           Colombia        1.07
           Costa Rica      2.53
           Mexico          0.53
           United States   7.15
Asia       Israel          5.95
           Japan           6.18
           Korea           4.22
dtype: float64

使用这个数据序列,我们可以绘制数据集中每个国家过去十年的平均实际最低工资

import matplotlib.pyplot as plt
import matplotlib as mpl
FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf"
mpl.font_manager.fontManager.addfont(FONTPATH)
plt.rcParams['font.family'] = ['Source Han Serif SC']

import seaborn as sns
sns.set_theme()
sns.set(font='Source Han Serif SC')

# 导入中文国家名称
map_url='https://raw.githubusercontent.com/QuantEcon/lecture-python.zh-cn/refs/heads/main/lectures/_static/country_map.csv'
country_map = pd.read_csv(map_url).set_index('English')['Chinese']
merged.T.groupby(level='Continent').mean()
Time 2006-01-01 2007-01-01 2008-01-01 ... 2014-01-01 2015-01-01 2016-01-01
Continent
America 2.80 2.85 2.99 ... 3.22 3.26 3.30
Asia 4.29 4.44 4.45 ... 4.86 5.10 5.44
Australia 10.25 10.73 10.76 ... 11.25 11.52 11.73
Europe 4.80 4.94 4.99 ... 5.17 5.48 5.57

4 rows × 11 columns

merged.mean().sort_values(ascending=False).plot(
          kind='bar',
          title="2006-2016年平均实际最低工资")

# 设置国家标签
country_labels = merged.mean().sort_values(
    ascending=False).index.get_level_values('Country').tolist()

# 将国家名称从英文转换为中文
country_labels_cn = [country_map[label] for label 
                     in country_labels]
plt.xticks(range(0, len(country_labels)), country_labels_cn)
plt.xlabel('国家')

plt.show()
_images/c237a728a98e652bcffd5a12b4ca9513deba835988d2353d56b54baf5e280515.png

通过向.mean()传入axis=1参数可以对列进行聚合(得到所有国家随时间变化的平均最低工资)

merged.mean(axis=1).head()
Time
2006-01-01   4.69
2007-01-01   4.84
2008-01-01   4.90
2009-01-01   5.08
2010-01-01   5.11
dtype: float64

我们可以将这个时间序列绘制成折线图

merged.mean(axis=1).plot()
plt.title('2006 - 2016年平均实际最低工资')
plt.ylabel('2015年美元')
plt.xlabel('年份')
plt.show()
_images/79fc87e41cb17e5fd36201214c27f6ccb02bdd0227ce4c11d87b7e1252a3431d.png

我们也可以指定MultiIndex的一个层级(在列轴上)来进行聚合

merged.T.groupby(level='Continent').mean().head()
Time 2006-01-01 2007-01-01 2008-01-01 ... 2014-01-01 2015-01-01 2016-01-01
Continent
America 2.80 2.85 2.99 ... 3.22 3.26 3.30
Asia 4.29 4.44 4.45 ... 4.86 5.10 5.44
Australia 10.25 10.73 10.76 ... 11.25 11.52 11.73
Europe 4.80 4.94 4.99 ... 5.17 5.48 5.57

4 rows × 11 columns

我们可以将每个大洲的平均最低工资绘制成时间序列图

continent_map = {'Asia':'亚洲', 
                 'Europe':'欧洲', 
                 'America':'美洲',
                 'Australia':'大洋洲'}

(merged.T
       .groupby(level='Continent') 
       .mean()
       .T.rename(columns=continent_map)                           
).plot()
plt.title('平均实际最低工资')
plt.ylabel('2015年美元')
plt.xlabel('年份')
plt.legend(title='大洲')
plt.show()
_images/6892024da3dd62be985756e88efbd100f3051290f363c7e5015897bb5d53285e.png

出于绘图目的,我们将去掉澳大利亚这个大洲

merged = merged.drop('Australia', level='Continent', axis=1)
(merged.T
      .groupby(level='Continent')
      .mean().T
      .rename(columns=continent_map)
      ).plot()
plt.title('平均实际最低工资')
plt.legend(title='大洲')
plt.ylabel('2015年美元')
plt.xlabel('年份')
plt.show()
_images/efee172cbaee14b97a6ead7d4d34f2100c97142de6d27ffb789da2b1f958e408.png

.describe() 可以快速获取一些常见的描述性统计量的汇总结果

merged.stack().describe()
/tmp/ipykernel_11348/4288984536.py:1: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  merged.stack().describe()
Continent America Asia Europe
count 69.00 44.00 200.00
mean 3.19 4.70 5.15
std 3.02 1.56 3.82
min 0.52 2.22 0.23
25% 1.03 3.37 2.02
50% 1.44 5.48 3.54
75% 6.96 5.95 9.70
max 8.48 6.65 12.39

让我们更深入地了解 groupby 的工作原理。

groupby 操作遵循一个称为”拆分-应用-合并”的模式:

  1. 首先,数据会按照指定的一个或多个键被拆分成多个组

  2. 然后,对每个组分别执行相同的操作或计算

  3. 最后,将所有组的结果合并成一个新的数据结构

这种模式使我们能够对数据子集进行灵活的分组分析。

groupby 方法实现了这个过程的第一步,创建一个新的 DataFrameGroupBy 对象,将数据拆分成组。

让我们再次按大洲拆分 merged,这次使用 groupby 函数,并将生成的对象命名为 grouped

grouped = merged.T.groupby(level='Continent')
grouped.keys

在对象上调用groupby方法时,该函数会被应用于每个组,运算结果会被合并到一个新的数据结构中。

例如,我们可以使用.size()返回数据集中每个大洲的国家数量。

在这种情况下,我们的新数据结构是一个Series

grouped.size()
Continent
America     7
Asia        4
Europe     19
dtype: int64

通过调用 .get_group() 来返回单个组中的国家,我们可以为每个大洲创建2016年实际最低工资分布的核密度估计。

grouped.groups.keys() 将返回 groupby 对象中的键

continents = grouped.groups.keys()

for continent in continents:
    sns.kdeplot(grouped.get_group(continent).T.loc['2015'].unstack(), 
                label=continent_map[continent], fill=True)

plt.title('2015年实际最低工资')
plt.xlabel('美元')
plt.ylabel('密度')
plt.legend()
plt.show()
_images/6fd8a90d32d9fc5abca3efc871215c1972168cf30a1a2221926cc1a5e7bf6213.png

79.5. 总结#

本讲座介绍了pandas的一些高级特性,包括多级索引、合并、分组和绘图。

在面板数据分析中,其他可能有用的工具包括xarray,这是一个将pandas扩展到N维数据结构的Python包。

79.6. 练习#

练习 79.1

在这些练习中,你将使用来自Eurostat的按年龄和性别划分的欧洲就业率数据集。

可以通过以下链接访问数据集:

url3 = 'https://raw.githubusercontent.com/QuantEcon/lecture-python/master/source/_static/lecture_specific/pandas_panel/employ.csv'

读取 CSV 文件会返回一个长格式的面板数据集。使用 .pivot_table() 构建一个带有 MultiIndex 列的宽格式数据框。

首先探究数据框和 MultiIndex 层级中可用的变量。

编写一个程序,快速返回 MultiIndex 中的所有值。

练习 79.2

筛选上述数据框,仅保留以’活动人口’百分比表示的就业数据。

使用seaborn绘制一个按年龄和性别分组的2015年就业率箱线图。