Python系列17-数据可视化之下载数据

一.下载数据简介

在本博客中,你将从网上下载数据,并对这些数据进行可视化。网上的数据多得难以置信,且大多未经过仔细检查。如果能够对这些数据进行分析,你就能发现别人没有发现的规律和关联。

我们将访问并可视化以两种常见格式存储的数据:CSV 和JSON。我们将使用Python模块csv 来处理以CSV(逗号分隔的值)格式存储的天气数据,找出两个不同地区在一段时间内的最高温度和最低温度。然后,我们将使用matplotlib根据下载的数据创建一个图表,展示两个不同地区的气温变化:阿拉斯加锡特卡和加利福尼亚死亡谷。在本博客的后面,我们将使用模块json 来访问以JSON格式存储的人口数据,并使用Pygal绘制一幅按国别划分的人口地图。

阅读本博客后,你将能够处理各种类型和格式的数据集,并对如何创建复杂的图表有更深入的认识。要处理各种真实世界的数据集,必须能够访问并可视化各种类型和格式的在线数据。

1.1 CSV文件格式

要在文本文件中存储数据,最简单的方式是将数据作为一系列以逗号分隔的值 (CSV)写入文件。这样的文件称为CSV文件。例如,下面是一行CSV格式的天气数据:

2014-1-5,61,44,26,18,7,-1,56,30,9,30.34,30.27,30.15,,,,10,4,,0.00,0,,195

这是阿拉斯加锡特卡2014年1月5日的天气数据,其中包含当天的最高气温和最低气温,还有众多其他数据。CSV文件对人来说阅读起来比较麻烦,但程序可轻松地提取并处理其中的值,这有助于加快数据分析过程。

我们将首先处理少量锡特卡的CSV格式的天气数据,这些数据可在本书的配套资源(https://www.nostarch.com/pythoncrashcourse/ )中找到。请将文件sitka_weather_07-2014.csv复制到
存储本章程序的文件夹中(下载本书的配套资源后,你就有了这个项目所需的所有文件)。

1.1.1 分析CSV文件头

csv 模块包含在Python标准库中,可用于分析CSV文件中的数据行,让我们能够快速提取感兴趣的值。下面先来查看这个文件的第一行,其中包含一系列有关数据的描述

代码:
highs_lows.py

import csv

filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)
    print(header_row)

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/highs_lows.py
['AKDT', 'Max TemperatureF', 'Mean TemperatureF', 'Min TemperatureF', 'Max Dew PointF', 'MeanDew PointF', 'Min DewpointF', 'Max Humidity', ' Mean Humidity', ' Min Humidity', ' Max Sea Level PressureIn', ' Mean Sea Level PressureIn', ' Min Sea Level PressureIn', ' Max VisibilityMiles', ' Mean VisibilityMiles', ' Min VisibilityMiles', ' Max Wind SpeedMPH', ' Mean Wind SpeedMPH', ' Max Gust SpeedMPH', 'PrecipitationIn', ' CloudCover', ' Events', ' WindDirDegrees']

Process finished with exit code 0

reader处理文件中以逗号分隔的第一行数据,并将每项数据都作为一个元素存储在列表中。文件头AKDT 表示阿拉斯加时间(Alaska Daylight Time),其位置表明每行的第一个值都
是日期或时间。文件头Max TemperatureF 指出每行的第二个值都是当天的最高华氏温度。可通过阅读其他的文件头来确定文件包含的信息类型。

注意  文件头的格式并非总是一致的,空格和单位可能出现在奇怪的地方。这在原始数据文件中很常见,但不会带来任何问题。

1.1.2 打印文件头及其位置

为让文件头数据更容易理解,将列表中的每个文件头及其位置打印出来

代码:
highs_lows.py

import csv

filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)
    
    for index, column_header in enumerate(header_row):
        print(index, column_header)

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/highs_lows.py
0 AKDT
1 Max TemperatureF
2 Mean TemperatureF
3 Min TemperatureF
4 Max Dew PointF
5 MeanDew PointF
6 Min DewpointF
7 Max Humidity
8  Mean Humidity
9  Min Humidity
10  Max Sea Level PressureIn
11  Mean Sea Level PressureIn
12  Min Sea Level PressureIn
13  Max VisibilityMiles
14  Mean VisibilityMiles
15  Min VisibilityMiles
16  Max Wind SpeedMPH
17  Mean Wind SpeedMPH
18  Max Gust SpeedMPH
19 PrecipitationIn
20  CloudCover
21  Events
22  WindDirDegrees

Process finished with exit code 0

1.1.3 提取并读取数据

知道需要哪些列中的数据后,我们来读取一些数据。首先读取每天的最高气温

代码:
highs_lows.py

import csv

# 从文件中获取最高气温
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    highs = []
    for row in reader:
        highs.append(row[1])

    print(highs)

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/highs_lows.py
['64', '71', '64', '59', '69', '62', '61', '55', '57', '61', '57', '59', '57', '61', '64', '61', '59', '63', '60', '57', '69', '63', '62', '59', '57', '57', '61', '59', '61', '61', '66']

Process finished with exit code 0

我们创建了一个名为highs 的空列表,再遍历文件中余下的各行。阅读器对象从其停留的地方继续往下读取CSV文件,每次都自动返回当前所处位置的下一行。由于我们已经读取了文件头行,这个循环将从第二行开始——从这行开始包含的是实际数据。每次执行该循环时,我们都将索引1处(第2列)的数据附加到highs 末尾.

我们提取了每天的最高气温,并将它们作为字符串整洁地存储在一个列表中。
下面使用int() 将这些字符串转换为数字,让matplotlib能够读取它们:

-- snip
    for row in reader:
        high = int(row[1])
        highs.append(high)
-- snip

1.1.4 绘制气温图表

为可视化这些气温数据,我们首先使用matplotlib创建一个显示每日最高气温的简单图形

代码:
highs_lows.py

import csv
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    highs = []
    for row in reader:
        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(highs, c='red')

# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

测试记录:

image.png

我们将最高气温列表传给plot() ,并传递c='red' 以便将数据点绘制为红色(红色显示最高气温,蓝色显示最低气温)。接下来,我们设置了一些其他的格式,如字体大小和标签。鉴于我们还没有添加日期,因此没有给x 轴添加标签,但plt.xlabel() 确实修改了字体大小,让默认标签更容易看清。上图:一个简单的折线图,显示了阿拉斯加锡特卡2014年7月每天的最高气温。

1.1.5 模块datetime

下面在图表中添加日期,使其更有用。在天气数据文件中,第一个日期在第二行

2014-7-1,64,56,50,53,51,48,96,83,58,30.19,--snip--

读取该数据时,获得的是一个字符串,因为我们需要想办法将字符串'2014-7-1' 转换为一个表示相应日期的对象。为创建一个表示2014年7月1日的对象,可使用模块datetime 中的方法strptime() 。我们在终端会话中看看strptime() 的工作原理

C:\>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from datetime import datetime
>>> first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')
>>> print(first_date)
2014-07-01 00:00:00
>>>

我们首先导入了模块datetime 中的datetime 类,然后调用方法strptime() ,并将包含所需日期的字符串作为第一个实参。第二个实参告诉Python如何设置日期的格式。在这个示例中,'%Y-' 让Python将字符串中第一个连字符前面的部分视为四位的年份;'%m-' 让Python将第二个连字符前面的部分视为表示月份的数字;而'%d' 让Python将字符串的最后一部分视为月份中的一天(1~31)。

方法strptime() 可接受各种实参,并根据它们来决定如何解读日期。表16-1列出了其中一些这样的实参。

实参 含义
%A 星期的名称,如Monday
%B 月份名,如January
%m 用数字表示的月份(01~12)
%d 用数字表示月份中的一天(01~31)
%Y 四位的年份,如2015
%y 两位的年份,如15
%H 24小时制的小时数(00~23)
%I 12小时制的小时数(01~12)
%p am或pm
%M 分钟数(00~59)
%S 秒数(00~61)

1.1.6 在图表中添加日期

知道如何处理CSV文件中的日期后,就可对气温图形进行改进了,即提取日期和最高气温,并将它们传递给plot()

代码:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs = [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')

# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

测试记录:

image.png

1.1.7 涵盖更长的时间

设置好图表后,我们来添加更多的数据,以成一幅更复杂的锡特卡天气图。请将文件sitka_weather_2014.csv复制到存储本章程序的文件夹中,该文件包含Weather Underground提供的整年的锡特卡天气数据。

现在可以创建覆盖整年的天气图了:
代码:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs = [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')

# 设置图形的格式
plt.title("Daily high temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

测试记录:

image.png

1.1.8 再绘制一个数据系列

上图改进后的图表显示了大量意义深远的数据,但我们可以在其中再添加最低气温数据,使其更有用。为此,需要从数据文件中提取最低气温,并将它们添加到图表中,如下所示:
代码:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

        low = int(row[3])
        lows.append(low)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')
plt.plot(dates ,lows , c='blue')

# 设置图形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

测试记录:

image.png

1.1.9 给图表区域着色

添加两个数据系列后,我们就可以了解每天的气温范围了。下面来给这个图表做最后的修饰,通过着色来呈现每天的气温范围。为此,我们将使用方法fill_between() ,它接受一个 x 值系列和两个 y 值系列,并填充两个 y 值系列之间的空间

代码:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

        low = int(row[3])
        lows.append(low)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red', alpha=0.5)
plt.plot(dates ,lows , c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

# 设置图形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

测试记录:

image.png

通过着色,让两个数据集之间的区域显而易见。

1.1.10 错误检查

我们应该能够使用有关任何地方的天气数据来运行highs_lows.py中的代码,但有些气象站会偶尔出现故障,未能收集部分或全部其应该收集的数据。缺失数据可能会引发异常,如果不妥善地处理,还可能导致程序崩溃。

例如,我们来看看生成加利福尼亚死亡谷的气温图时出现的情况。将文件death_valley_2014.csv复制到本章程序所在的文件夹,再修改highs_lows.py,使其生成死亡谷的气温图
highs_lows.py

--snip--
# 从文件中获取日期、最高气温和最低气温
filename = 'death_valley_2014.csv'
with open(filename) as f:
--snip--

运行这个程序时,出现了一个错误,如下述输出的最后一行所示:

Traceback (most recent call last):
File "highs_lows.py", line 17, in 
high = int(row[1])
ValueError: invalid literal for int() with base 10: ''

该traceback指出,Python无法处理其中一天的最高气温,因为它无法将空字符串(' ' )转换为整数。只要看一下death_valley_2014.csv,就能发现其中的问题

2014-2-16,,,,,,,,,,,,,,,,,,,0.00,,,-1

其中好像没有记录2014年2月16日的数据,表示最高温度的字符串为空。为解决这种问题,我们在从CSV文件中读取值时执行错误检查代码,对分析数据集时可能出现的异常进行处理

highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 从文件中获取最高气温
filename = 'death_valley_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        try:
            current_date = datetime.strptime(row[0], "%Y-%m-%d")
            high = int(row[1])
            low = int(row[3])
        except ValueError:
            print(current_date, 'missing data')
        else:
            dates.append(current_date)
            highs.append(high)
            lows.append(low)

    # print(highs)

# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red', alpha=0.5)
plt.plot(dates ,lows , c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

# 设置图形的格式
plt.title("Daily high and low temperatures - 2014\nDeath Valley, CA", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

对于每一行,我们都尝试从中提取日期、最高气温和最低气温。只要缺失其中一项数据,Python就会引发ValueError 异常,而我们可这样处理:打印一条错误消息,指出缺失数据的日期。打印错误消息后,循环将接着处理下一行。如果获取特定日期的所有数据时没有发生错误,将运行else 代码块,并将数据附加到相应列表的末尾。鉴于我们绘图时使用的是有关另一个地方的信息,我们修改了标题,在图表中指出了这个地方。

测试记录:
如果你现在运行highs_lows.py ,将发现缺失数据的日期只有一个

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/highs_lows.py
2014-02-16 00:00:00 missing data
image.png

1.2 制作世界人口地图:JSON格式

在本节中,你将下载JSON格式的人口数据,并使用json 模块来处理它们。Pygal提供了一个适合初学者使用的地图创建工具,你将使用它来对人口数据进行可视化,以探索全球人口的分布情况。

1.2.1 下载世界人口数据

将文件population_data.json复制到本章程序所在的文件夹中,这个文件包含全球大部分国家1960~2010年的人口数据。Open Knowledge Foundation(http://data.okfn.org/ )提供了大量可以免费使用的数据集,这些数据就来自其中一个数据集。

1.2.2 提取相关的数据

我们来研究一下population_data.json,看看如何着手处理这个文件中的数据:
population_data.json

[
{
"Country Name": "Arab World",
"Country Code": "ARB",
"Year": "1960",
"Value": "96388069"
},
{
"Country Name": "Arab World",
"Country Code": "ARB",
"Year": "1961",
"Value": "98882541.4"
},
--snip--
]

这个文件实际上就是一个很长的Python列表,其中每个元素都是一个包含四个键的字典:国家名、国别码、年份以及表示人口数量的值。我们只关心每个国家2010年的人口数量,因此我们首先编写一个打印这些信息的程序.
代码:
world_population.py

import json

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家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)

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/world_population.py
Arab World: 357868000
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
--snip--
Uzbekistan: 28228000
Vanuatu: 240000
Venezuela, RB: 28834000
Vietnam: 86928000
Virgin Islands (U.S.): 110000
West Bank and Gaza: 4152000
Yemen, Rep.: 24053000
Zambia: 12927000
Zimbabwe: 12571000

Process finished with exit code 0

1.2.3 将字符串转换为数字值

population_data.json中的每个键和值都是字符串。为处理这些人口数据,我们需要将表示人口数量的字符串转换为数字值,为此我们使用函数int()
代码:
world_population.py

import json

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家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))

测试记录:
对于有些值,这种转换会导致错误:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/world_population.py
Arab World: 357868000
Traceback (most recent call last):
  File "E:/python/learn_python1/数据可视化/world_population.py", line 12, in 
    population = int(pop_dict['Value'])
ValueError: invalid literal for int() with base 10: '1127437398.85751'
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
--snip--

原始数据的格式常常不统一,因此经常会出现错误。导致上述错误的原因是,Python不能直接将包含小数点的字符串'1127437398.85751' 转换为整数(这个小数值可能是人口数据缺失时通过插值得到的)。为消除这种错误,我们先将字符串转换为浮点数,再将浮点数转换为整数。
代码:

import json

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家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))

每个字符串都成功地转换成了浮点数,再转换为整数。以数字格式存储人口数量值后,就可以使用它们来制作世界人口地图了。

1.2.4 获取两个字母的国别码

制作地图前,还需要解决数据存在的最后一个问题。Pygal中的地图制作工具要求数据为特定的格式:用国别码表示国家,以及用数字表示人口数量。处理地理政治数据时,经常需要用到几个标准化国别码集。population_data.json中包含的是三个字母的国别码,但Pygal使用两个字母的国别码。我们需要想办法根据国家名获取两个字母的国别码。

Pygal使用的国别码存储在模块i18n (internationalization的缩写)中。字典COUNTRIES 包含的键和值分别为两个字母的国别码和国家名。要查看这些国别码,可从模块i18n 中导入这个字典,并打印其键和值:

pygal.i18n 已经不存在了,现在已经更改成了 pygal_maps_world ,需要单独通过pip下载

C:\>pip install pygal_maps_world
Collecting pygal_maps_world
  Downloading pygal_maps_world-1.0.2.tar.gz (270 kB)
     |████████████████████████████████| 270 kB 598 kB/s
Requirement already satisfied: pygal>=1.9.9 in c:\users\administrator\appdata\local\programs\python\python36\lib\site-packages (from pygal_maps_world) (2.4.0)
Using legacy 'setup.py install' for pygal-maps-world, since package 'wheel' is not installed.
Installing collected packages: pygal-maps-world
    Running setup.py install for pygal-maps-world ... done
Successfully installed pygal-maps-world-1.0.2

代码:

from pygal_maps_world.i18n import COUNTRIES

for country_code in sorted(COUNTRIES.keys()):
    print(country_code, COUNTRIES[country_code])

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/countries.py
ad Andorra
ae United Arab Emirates
af Afghanistan
al Albania
--snip--
yt Mayotte
za South Africa
zm Zambia
zw Zimbabwe

Process finished with exit code 0

为获取国别码,我们将编写一个函数,它在COUNTRIES 中查找并返回国别码。我们将这个函数放在一个名为country_codes 的模块中,以便能够在可视化程序中导入它
代码:

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

print(get_country_code('Andorra'))
print(get_country_code('United Arab Emirates'))
print(get_country_code('Afghanistan'))

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/countries.py
ad
ae
af

Process finished with exit code 0

接下来,在world_population.py中导入get_country_code
代码:
world_population.py

import json

from country_codes import get_country_code

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家2010年的人口数量
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(country_name + ": " + str(population))
        else:
            print('ERROR - ' + country_name)

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/world_population.py
ERROR - Arab World
ERROR - Caribbean small states
ERROR - East Asia & Pacific (all income levels)
ERROR - East Asia & Pacific (developing only)
ERROR - Euro area
ERROR - Europe & Central Asia (all income levels)
--snip--
Zambia: 12927000
Zimbabwe: 12571000

Process finished with exit code 0

1.2.5 制作世界地图

有了国别码后,制作世界地图易如反掌。Pygal提供了图表类型Worldmap ,可帮助你制作呈现各国数据的世界地图。为演示如何使用Worldmap ,我们来创建一个突出北美、中美和南美的简单地图

代码:
americas.py

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')

测试记录:

image.png

1.2.6 在世界地图上呈现数字数据

为练习在地图上呈现数字数据,我们来创建一幅地图,显示三个北美国家的人口数量

代码:
na_populations.py

import pygal_maps_world.maps

wm = pygal_maps_world.maps.World()
wm.title = 'Populations of Countries in North America'
wm.add('North America',{ 'ca': 34126000, 'us':309349000, 'mx':113423000})

wm.render_to_file('na_populations.svg')

测试记录:

image.png

1.2.7 绘制完整的世界人口地图

要呈现其他国家的人口数量,需要将前面处理的数据转换为Pygal要求的字典格式:键为两个字母的国别码,值为人口数量。为此,在world_population.py中添加如下代码
代码:
world_population.py

import json
import pygal_maps_world.maps
from country_codes import get_country_code

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 创建一个包含人口数量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

wm = pygal_maps_world.maps.World()
wm.title = 'World Population in 2010, by Country'
wm.add('2010', cc_pupulations)

wm.render_to_file('world_population.svg')

测试记录:

image.png

1.2.8 根据人口数量将国家分组

印度和中国的人口比其他国家多得多,但在当前的地图中,它们的颜色与其他国家差别较小。中国和印度的人口都超过了10亿,接下来人口最多的国家是美国,但只有大约3亿。下面不将所有国家都作为一个编组,而是根据人口数量分成三组——少于1000万的、介于1000万和10亿之间的以及超过10亿的

代码:
world_population.py

import json
import pygal_maps_world.maps
from country_codes import get_country_code

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 创建一个包含人口数量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

# 根据人口数量将所有的国家分为三组
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_pupulations.items():
    if pop < 10000000:
        cc_pops_1[cc] = pop
    elif pop < 1000000000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

# 看看每组分别包含多少个国家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm = pygal_maps_world.maps.World()
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bm', cc_pops_2)
wm.add('>1bn', cc_pops_3)


wm.render_to_file('world_population.svg')

测试记录:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/数据可视化/world_population.py
85 69 2

Process finished with exit code 0

image.png

1.2.9 使用Pygal设置世界地图的样式

在这个地图中,根据人口将国家分组虽然很有效,但默认的颜色设置很难看。例如,在这里,Pygal选择了鲜艳的粉色和绿色基色。下面使用Pygal样式设置指令来调整颜色。

我们也让Pygal使用一种基色,但将指定该基色,并让三个分组的颜色差别更大.

代码:
world_population.py

import json
import pygal_maps_world.maps
from pygal.style import RotateStyle
from country_codes import get_country_code

# 将数据加载到一个列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 创建一个包含人口数量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

# 根据人口数量将所有的国家分为三组
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_pupulations.items():
    if pop < 10000000:
        cc_pops_1[cc] = pop
    elif pop < 1000000000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

# 看看每组分别包含多少个国家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm_style = RotateStyle('#336699')
wm = pygal_maps_world.maps.World(style=wm_style)
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bm', cc_pops_2)
wm.add('>1bn', cc_pops_3)


wm.render_to_file('world_population.svg')

测试记录:

image.png

1.2.10 加亮颜色主题

Pygal通常默认使用较暗的颜色主题。为方便印刷,我使用LightColorizedStyle 加亮了地图的颜色。这个类修改整个图表的主题,包括背景色、标签以及各个国家的颜色。要使用这个样式,先导入它.

from pygal.style import LightColorizedStyle

然后就可独立地使用LightColorizedStyle 了,例如:

wm_style = LightColorizedStyle

然而使用这个类时,你不能直接控制使用的颜色,Pygal将选择默认的基色。要设置颜色,可使用RotateStyle ,并将LightColorizedStyle 作为基本样式。为此,导入LightColorizedStyle 和RotateStyle

from pygal.style import LightColorizedStyle, RotateStyle

再使用RotateStyle 创建一种样式,并传入另一个实参base_style:

wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)

这设置了较亮的主题,同时根据通过实参传递的颜色给各个国家着色。使用这种样式时,生成的图表与本书的屏幕截图更一致。
尝试为不同的可视化选择合适的样式设置指令时,在import 语句中指定别名会有所帮助:

from pygal.style import LightColorizedStyle as LCS, RotateStyle as RS

这样,样式定义将更短:

wm_style = RS('#336699', base_style=LCS)

通过使用几个样式设置指令,就能很好地控制图表和地图的外观。

参考

1.Python编程:从入门到实践
2.https://github.com/ehmatthes/pcc/tree/master/chapter_16

你可能感兴趣的:(Python系列17-数据可视化之下载数据)