在今晚,我将学习新的内容,我会下载JSON格式的人口数据,并使用json
模块来处理它们。
4.2 制作世界人口地图:
4.2.1 下载与提取世界人口数据:
将文件population_data.json复制到本章程序所在的文件夹中,这个文件包含全球大部分国家1960~2010年的人口数据。我们来研究一下population_data.json,看看如何着手处理这个文件中的数据:
这个文件实际上就是一个很长的Python列表,其中每个元素都是一个包含四个键的字典:国家名、国别码、年份以及表示人口数量的值。我们只关心每个国家2010年的人口数量, 因此我们首先编写一个打印这些信息的程序:
import json
# Load data into a list
filename = 'population_data.json'
with open(filename) as f:
pop_data = json.load(f)
# Print the population of each country in 2010
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = pop_dict['Value']
print(country_name + ':' + population)
我们首先导入了模块json
,以便能够正确地加载文件中的数据,然后,我们将数据存储在pop_data
中(见第6行)。函数json.load()
将数据转换为Python能够处理的格式, 这里是一个列表。在第9行处,我们遍历pop_data
中的每个元素。每个元素都是一个字典,包含四个键—值对,我们将每个字典依次存储在pop_dict
中。 在第10行处,我们检查字典的'Year'
键对应的值是否是2010
(由于population_data.json中的值都是用引号括起的,因此我们执行的是字符串比较)。如果年份为2010
,我们就将与'Country Name'
相关联的值存储到country_name
中,并将与'Value'
相关联的值存储在population
中(见第12行)。接下来,我们打印每个国家的名称和人口数量。
但我在这里遇到了一个问题,在我执行程序后出现了这样的情况:
可以看到,出现了No JSON object could be decoded这样的提示。在我上网搜索后找到的解决办法是将json
文件打开并以UTF8无BOM格式保存:
解决问题后,可以看到输出为一系列国家的名称和人口数量:
4.2.2 将字符串转换为数字值:
population_data.json中的每个键和值都是字符串。为处理这些人口数据,我们需要将表示人口数量的字符串转换为数字值,为此我们使用函数int()
:
import json
# Load data into a list
filename = 'population_data.json'
with open(filename) as f:
pop_data = json.load(f)
# Print the population of each country in 2010
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = int(pop_dict['Value'])
print(country_name + ':' + str(population))
在第12行处,我们将每个人口数量值都存储为数字格式。打印人口数量值时,需要将其转换为字符串(见第13行)。 然而,如上图所示,对于有些值,这种转换会导致错误。
原始数据的格式常常不统一,因此经常会出现错误。导致上述错误的原因是,Python不能直接将包含小数点的字符串’1127437398.85751’ 转换为整数(这个小数值可能是人口数据缺失时通过插值得到的)。为消除这种错误,我们先将字符串转换为浮点数,再将浮点数转换为整数:
import json
# Load data into a list
filename = 'population_data.json'
with open(filename) as f:
pop_data = json.load(f)
# Print the population of each country in 2010
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = int(float(pop_dict['Value']))
print(country_name + ':' + str(population))
函数float()
将字符串转换为小数,而函数int()
丢弃小数部分,返回一个整数。现在,我们可以打印2010年的完整人口数据,不会导致错误了:
每个字符串都成功地转换成了浮点数,再转换为整数。
4.2.3 获取两个字母的国别码:
制作地图前,还需要解决数据存在的最后一个问题。Pygal中的地图制作工具要求数据为特定的格式:用国别码表示国家,以及用数字表示人口数量。处理地理政治数据时,经常需要用到几个标准化国别码集。
population_data.json中包含的是三个字母的国别码,但Pygal使用两个字母的国别码。我们需要想办法根据国家名获取两个字母的国别码。 Pygal使用的国别码存储在模块i18n (internationalization的缩写)中。字典COUNTRIES
包含的键和值分别为两个字母的国别码和国家名。要查看这些国别码,可从模块i18n
中导入这个字典,并打印其键和值:
但出错了,我上网搜索后发现,pygal.i18n
已经不存在了,现在已经更改成了 pygal_maps_world
,需要单独通过pip3下载:
安装成功之后,将第1行代码改成如下所示,就可以正常调用国别码了:
from pygal_maps_world.i18n import COUNTRIES
for country_code in sorted(COUNTRIES.keys()):
print(country_code, COUNTRIES[country_code])
在上面的for
循环中,我们让Python将键按字母顺序排序(见第3行),然后打印每个国别码及其对应的国家。为获取国别码,我们将编写一个函数,它在COUNTRIES
中查找并返回国别码。我们将这个函数放在一个名为country_codes
的模块中,以便能够在可视化程序中导入它:
# -*- coding:utf-8 -*-
from pygal_maps_world.i18n import COUNTRIES
def get_country_code(country_name):
"""根据指定的国家,返回Pygal使用的两个字母的国别码"""
for code, name in COUNTRIES.items():
if name == country_name:
return code
# 如果没有找到指定的国家,就返回None
return None
print(get_country_code('United Arab Emirates'))
print(get_country_code('Andorra'))
print(get_country_code('Afghanistan'))
get_country_code()
接受国家名,并将其存储在形参country_name
中(见第5行)。接下来,我们遍历COUNTRIES
中的国家名—国别码对(见第7行);如果找到指定的国家名,就返回相应的国别码(见第8-9行)。在循环后面,我们在没有找到指定的国家名时返回None
(见第12行)。最后,我们使用了三个国家名来调用这个函数,以核实它能否正确地工作。与预期的一样,这个程序输出了三个由两个字母组成的国别码。
接下来,在world_population.py中导入get_country_code
:
import json
from country_codes import get_country_code
# Load data into a list
filename = 'population_data.json'
with open(filename) as f:
pop_data = json.load(f)
# Print the population of each country in 2010a
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = int(float(pop_dict['Value']))
code = get_country_code(country_name)
if code:
print(code + ':' + str(population))
else:
print(country_name + ':' + str(population))
有了国别码后,制作世界地图易如反掌。Pygal提供了图表类型Worldmap
,可帮助你制作呈现各国数据的世界地图。为演示如何使用Worldmap
,我们来创建一个突出北美、中美和南美的简单地图:
4.2.4 制作世界地图:
在第3行处,我们创建了一个Worldmap
实例,并设置了该地图的title
属性。在第6-8行处,我们使用了方法add()
,它接受一个标签和一个列表,其中后者包含我们要突出的国家 的国别码。每次调用add()
都将为指定的国家选择一种新颜色,并在图表左边显示该颜色和指定的标签。我们要以同一种颜色显示整个北美地区,因此第一次调用add()
时, 在传递给它的列表中包含'ca'
、'mx'
和'us'
,以同时突出加拿大、墨西哥和美国。接下来,对中美和南美国家做同样的处理。
但是却出现了错误,在一番搜索后,我发现可将代码的前两行改成如下所示:
import pygal_maps_world.maps
wm = pygal_maps_world.maps.World()
wm.title = 'North, Central, and. South America'
wm.add('North. America', ['ca', 'mx', 'us'])
wm.add('Central America', ['bz','cr', 'gt', 'hn' ,'ni', 'pa', 'sv'])
wm.add('South America', ['ar', 'bo', 'br', 'cl', 'co','ec', 'gf', 'gy',
'pe', 'py', 'sr', 'uy', 've'])
wm.render_to_file('americas.svg')
第13行处的方法render_to_file()
创建一个包含该图表的.svg文件,可以在浏览器中打开它。输出是一幅以不同颜色突出北美、中美和南美的地图,如下图所示: