2022年 11月 5日

16 使用Python下载数据

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

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

16.1 csv文件格式

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

# 16.1.1 分析CSV文件头

  1. filename = 'file/sitka_weather_07-2014.csv'
  2. with open(filename) as f:
  3. reader = csv.reader(f)
  4. hreader_row = next(reader)
  5. print(hreader_row)

运行结果

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

  1. filename = 'file/sitka_weather_07-2014.csv'
  2. with open(filename) as f:
  3. reader = csv.reader(f)
  4. hreader_row = next(reader)
  5. for index, colum_hreader in enumerate(hreader_row):
  6. print(index, colum_hreader)

运行结果

# 16.1.3 提取并读取数据

  1. # 知道哪些数据后,读取每天的的最高气温
  2. # 从文件中读取最高气温
  3. filename = 'file/sitka_weather_07-2014.csv'
  4. with open(filename) as f:
  5. reader = csv.reader(f)
  6. hreader_row = next(reader)
  7. # 创建一个highs的空列表,在遍历文件余下的各行
  8. highs = []
  9. # 遍历文件中余下的各行
  10. for row in reader:
  11. # 将字符串转为数字
  12. highs.append(row[1])
  13. print(highs)

运行结果

将字符串转换为数字

  1. ...
  2. for row in reader:
  3. # 将字符串转为数字
  4. high = int(row[1])
  5. highs.append(high)
  6. ...

运行结果

 # 16.1.4 绘制气温图表

  1. #从文件中获取最高气温
  2. ...
  3. # 根据数据绘制图形
  4. # figsize:指定figure的宽和高,单位为英寸;
  5. fig = plt.figure(dpi=128, figsize=(10, 6))
  6. plt.plot(highs, c='red')
  7. # 设置图形的格式
  8. plt.title("Dailly high temperatures July 2014")
  9. # 设置名称
  10. plt.xlabel('x', fontsize=16)
  11. plt.ylabel('Temperature(F)', fontsize=16)
  12. plt.tick_params(axis='both', which='major', labelsize=16)
  13. plt.show()

运行结果:

 

# 16.1.5模块datetime

 

  1. 注意:导入datetime时,不能省略from datetime
  2. from datetime import datetime
  3. # 我们首先导入了模块datetime中的datetime类,然后调用方法strptime(),并将包含所需日期的字符串作为第一个实参。
  4. # 第二个实参告诉Python如何设置日期的格式。在这个示例中,' %Y-,让Python将字符串中第一个连字符前面的部分视为四位的年份:
  5. # %m,让Python将 第s个连字符前面的部分视为表示月份的数字:而’%d’让Python将字符串的最后-部分视为月份中的一天(1~31) 。
  6. # 方法strptime()可接受各种实参,并根据它们来决定如何解读日期。
  7. first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')

运行结果

# 16.1.6在图表中添加日期

  1. filename = 'file/sitka_weather_07-2014.csv'
  2. with open(filename) as f:
  3. reader = csv.reader(f)
  4. hreader_row = next(reader)
  5. # 创建一个highs的空列表,在遍历文件余下的各行
  6. highs = []
  7. # 从文件中提取日期,
  8. dates = []
  9. # 遍历文件中余下的各行
  10. for row in reader:
  11. # 将字符串转为数字
  12. high = int(row[1])
  13. highs.append(high)
  14. current_date = datetime.strptime(row[0], '%Y-%m-%d')
  15. dates.append(current_date)
  16. print(highs)
  17. # 根据数据绘制图形
  18. # figsize:指定figure的宽和高,单位为英寸;
  19. fig = plt.figure(dpi=128, figsize=(10, 6))
  20. # 将日期和最高气温值传递给plot
  21. plt.plot(dates, highs, c='red')
  22. # 设置图形的格式
  23. plt.title("Dailly high temperatures July 2014")
  24. # 设置名称
  25. plt.xlabel('x', fontsize=16)
  26. plt.ylabel('Temperature(F)', fontsize=16)
  27. # 绘制斜的日期标签,以免他们重叠
  28. fig.autofmt_xdate()
  29. plt.tick_params(axis='both', which='major', labelsize=16)
  30. plt.show()

运行结果:

# 16.1.7 涵盖更长的时间

使用最新的数据文件sitka_weather-2014.csv(下载地址)

  1. filename = 'file/sitka_weather_2014.csv'
  2. with open(filename) as f:
  3. ...
  4. # 设置图形的格式
  5. plt.title("Dailly high temperatures - 2014")
  6. # 设置名称
  7. plt.xlabel('x', fontsize=16)

运行结果

# 16.1.8 再绘制一个数据系列

# 上图显示了大量意义深远的数据,但我们可以在其中再添加最低气温数据,使其更有用。
# 为此,需要从数据文件中提取最低气温,并将它们添加到图表

  1. import csv
  2. from datetime import datetime
  3. from matplotlib import pyplot as plt
  4. filename = 'file/sitka_weather_2014.csv'
  5. with open(filename) as f:
  6. reader = csv.reader(f)
  7. header_row = next(reader)
  8. dates, highs, lows = [], [], []
  9. for row in reader:
  10. current_date = datetime.strptime(row[0], '%Y-%m-%d')
  11. dates.append(current_date)
  12. high = int(row[1])
  13. low = int(row[3])
  14. highs.append(high)
  15. # 储存最低气温
  16. lows.append(low)
  17. # 根据数据绘制图形
  18. fig = plt.figure(dpi=128, figsize=(10, 6))
  19. plt.plot(dates, highs, c='red')
  20. # 绘制最低气温
  21. plt.plot(dates, lows, c='blue')
  22. # 设置图形的格式
  23. plt.title("Dailly high and low temperatures - 2014")
  24. # 设置名称
  25. plt.xlabel('x', fontsize=16)
  26. plt.ylabel('Temperature(F)', fontsize=16)
  27. # 绘制斜的日期标签,以免他们重叠
  28. fig.autofmt_xdate()
  29. plt.tick_params(axis='both', which='major', labelsize=16)
  30. plt.show()

运行结果

 

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

  1. 读取文件值
  2. ...
  3. # 根据数据绘制图形
  4. fig = plt.figure(dpi=128, figsize=(10, 6))
  5. # alpha 指定颜色透明度,Alpha值为0表示完全透明,1默认完全不透明
  6. plt.plot(dates, highs, c='red', alpha=0.5)
  7. # 绘制最低气温
  8. plt.plot(dates, lows, c='blue', alpha=0.5)
  9. # 我们向fill_between() 传递了一个 x 值系列:列表dates ,还传递了两个 y 值系列:highs 和lows 。
  10. # 实参facecolor 指定了填充区域的颜色,我们还将alpha 设置成了较小的值0.1,
  11. # 让填充区域将两个数据系列连接起来;
  12. plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

 

# 16.1.10 错误检查

# 我们应该能够使用有关任何地方的天气数据来运行highs_lows.py中的代码,
# 但有些气象站会偶尔出现故障,未能收集部分或全部其应该收集的数据。缺失数据可能会引发异常,
# 如果不妥善地处理,还可能导致程序崩溃。
# 例如,我们来看看生成加利福尼亚死亡谷的气温图时出现的情况。将文件death_valley_2014.csv(下载)复制到工程目录

  1. filename = 'file/death_valley_2014.csv'
  2. with open(filename) as f:
  3. reader = csv.reader(f)
  4. header_row = next(reader)
  5. dates, highs, lows = [], [], []
  6. for row in reader:
  7. current_date = datetime.strptime(row[0], '%Y-%m-%d')
  8. dates.append(current_date)
  9. high = int(row[1])
  10. low = int(row[3])
  11. highs.append(high)
  12. # 储存最低气温
  13. lows.append(low)

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

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

  1. filename = 'file/death_valley_2014.csv'
  2. with open(filename) as f:
  3. reader = csv.reader(f)
  4. header_row = next(reader)
  5. dates, highs, lows = [], [], []
  6. for row in reader:
  7. try:
  8. current_date = datetime.strptime(row[0], '%Y-%m-%d')
  9. high = int(row[1])
  10. low = int(row[3])
  11. except ValueError:
  12. print(current_date, 'missing data')
  13. else:
  14. dates.append(current_date)
  15. highs.append(high)
  16. # 储存最低气温
  17. lows.append(low)
  18. ...
  19. # 根据数据绘制图形
  20. ..

运行结果

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

16.2.1 下载世界人口数据(population_data下载)

16.2.2 提取2010年每个国家的人口数量

  1. filename = 'file/population_data.json'
  2. with open(filename) as f:
  3. pop_data = json.load(f)
  4. # 打印每个国家2010年的人口数量
  5. for pop_dict in pop_data:
  6. if pop_dict['Year'] == '2010':
  7. country_name = pop_dict['Country Name']
  8. population = pop_dict['Value']
  9. print("country_name:" + country_name + ":" + "population:" + population)

运行结果

# 16.2.3 将字符串转为数字值

  1. filename = 'file/population_data.json'
  2. with open(filename) as f:
  3.     pop_data = json.load(f)
  4. # 打印每个国家2010年的人口数量
  5. for pop_dict in pop_data:
  6. if pop_dict['Year'] == '2010':
  7. country_name = pop_dict['Country Name']
  8. # Python不能将字符串'1127437398.85751' 转换为整数,为了消除这种错误,
  9. # 我们先将字符串转换为浮点数,在将浮点数转换为整数
  10. population = int(float(pop_dict['Value']))
  11. print("country_name:" + str(country_name) + ":" + "population:" +
  12. str(population))

# 16.2.4 获取两个字母的国别码

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

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

运行结果

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

  1. # 为获取国别码,我们将编写一个函数,它在COUNTRIES 中查找并返回国别码。
  2. # 我们将这个函数放在一个名为country_codes 的模块中,以便能够在可视化程序中导入它
  3. def get_country_code(country_name):
  4. """根据指定的国家,返回Pygal使用的两个字母的国别码"""
  5. for code, name in COUNTRIES.items():
  6. if name == country_name:
  7. return code
  8. # 如果没有找到指定的国家,就返回NONE
  9. return None
  10. 提取国家名和人口数量
  11. filename = 'file/population_data.json'
  12. with open(filename) as f:
  13. pop_data = json.load(f)
  14. for pop_dict in pop_data:
  15. if pop_dict['Year'] == '2010':
  16. country_name = pop_dict['Country Name']
  17. population = int(float(pop_dict['Value']))
  18. code = get_country_code(country_name)
  19. if code:
  20. print(code + ":" + str(population))
  21. else:
  22. print("Error-" + country_name)

运行结果

 

16.2.5 制作世界地图

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

  1. import pygal_maps_world.maps
  2. wm = pygal_maps_world.maps.World()
  3. wm._title = 'North,Central, and South America'
  4. wm.add('North America', ['ca', 'mx', 'us'])
  5. wm.add('Central America', ['bz', 'cr', 'gt', 'hn', 'ni', 'pa', 'sv'])
  6. wm.add('South America', ['ar', 'bo', 'br', 'cl', 'co', 'ec', 'gf', 'gy',
  7. 'pe', 'py', 'sz', 'uy', 've'])
  8. wm.render_to_file('americas.svg')

运行结果

# 16.2.6 在世界地图上呈字

  1. wm = pygal_maps_world.maps.World()
  2. wm.title = 'North,Central, and South America'
  3. wm.add('North America', {'ca': 34126000, 'us': 309349000, 'mx': 113423000})
  4. # 首先,创建了一个Worldmap 实例并设置了标题。接下来,使用了方法add() ,
  5. # 但这次通过第二个实参传递了一个字典而不是列表。这个字典将两个字母的Pygal国别码作为键
  6. # 将人口数量作为值。Pygal根据这些数字自动给不同国家着以深浅不一的颜色(人口最少的国家颜色最浅,人口最多的国家颜色最深),
  7. wm.render_to_file('americas.svg')

运行结果

# 16.2.7 绘制完整的世界人口地图

  1. 将数据加载到列表中
  2. ...
  3. cc_populations = {}
  4. for pop_dict in pop_data:
  5. if pop_dict['Year'] == '2010':
  6. country_name = pop_dict['Country Name']
  7. population = int(float(pop_dict['Value']))
  8. code = get_country_code(country_name)
  9. if code:
  10. cc_populations[code] = population
  11. wm = pygal_maps_world.maps.World()
  12. wm.title = 'World population in 2010,by Country'
  13. wm.add('2010', cc_populations)
  14. wm.render_to_file("world_population.svg")

运行结果

# 16.2.8 根据人口数量将国家分组

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

  1. 创建一个人口的字典
  2. # 根据人口数量将所有的国家分成三组
  3. cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
  4. for cc, pop in cc_populations.items():
  5. if pop < 10000000:
  6. cc_pops_1[cc] = pop
  7. if pop < 1000000000:
  8. cc_pops_2[cc] = pop
  9. else:
  10. cc_pops_3[cc] = pop
  11. # 看看每组包含多少个国家
  12. print(
  13. "每组包含多少个国家:" + str(len(cc_pops_1)) + " " + str(len(cc_pops_2)) + " " + str(
  14. len(cc_pops_3)))
  15. wm = pygal_maps_world.maps.World()
  16. wm.title = 'World Population in 2010, by Country'
  17. wm.add('0-10m', cc_pops_1)
  18. wm.add('10m-1bn', cc_pops_2)
  19. wm.add('>1bn', cc_pops_3)
  20. wm.render_to_file('world_population.svg')

运行结果

在本章中,你学习了:如何使用网上的数据集;如何处理CSV和JSON文件,以及如何提取你感兴趣的数据;如何使用matplotlib来处理以往的天气数据,包括如何使用模
块datetime ,以及如何在同一个图表中绘制多个数据系列;如何使用Pygal绘制呈现各国数据的世界地图,以及如何设置Pygal地图和图表的样式;

在下一章,你将编写自动从网上采集数据并对其进行可视化的程序