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
我们想要将新的数据框worlddata与realwage_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相匹配的国家。
这在下图中用红色阴影部分表示
我们还需要指定每个数据框中国家名称的位置,这将作为合并数据框的”键(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