使用matplotlib制作图表
制作简单的折线图
import matplotlib.pyplot as plt
input_values = [1,2,3,4,5]
squares = [1,4,9,16,25]
plt.plot(input_values,squares,linewidth=5)
#设置图表标题,并给坐标轴加上标签
plt.title("Square Number",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)
#设置刻度标记的字体大小
plt.tick_params(axis='both',labelsize=14)
plt.show()
使用scatter()绘制散点图
import matplotlib.pyplot as plt
x_values = list(range(1,101))
y_values = [x**2 for x in x_values]
plt.scatter(x_values,y_values,c='red',edgecolor='none',s=4) #edgecolor删除数据点黑色轮廓
#设置图表标题并给坐标轴加上标签
plt.title("Square Number",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)
#设置刻度标记的大小
plt.tick_params(axis='both',which='major',labelsize=14)
plt.axis([0,110,0,12100])
plt.show()
可以使用RGB颜色模式自定义颜色。要指定自定义颜色,可传递参数c ,并将其设置为一个元组,其中包含三个0~1之间的小数值,它们分别表示红色、绿色和蓝色分量。值越接近0,指定的颜色越深,值越接近1,指定的颜色越浅。
plt.scatter(x_values, y_values, c=(0, 0, 0.8), edgecolor='none', s=40)
颜色映射 (colormap)是一系列颜色,它们从起始颜色渐变到结束颜色。在可视化中,颜色映射用于突出数据的规律
plt.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues,edgecolor='none', s=40)
参数c 设置成了一个 y 值列表,并使用参数cmap 告诉pyplot 使用哪个颜色映射。这些代码将 y 值较小的点显示为浅蓝色,并将y 值较大的点显示为深蓝色,
plt.savefig('squares_plot.png', bbox_inches='tight')
随机漫步
每次行走都完全是随机的,没有明确的方向,结果是由一系列随机决策决定的
random_walk.py
from random import choice
class RandomWalk():
"""一个生成随机漫步数据的类"""
def __init__(self,num_points = 5000):
"""初始化随机漫步的属性"""
self.num_points = num_points
#所有随机漫步都始于(0,0)
self.x_values = [0]
self.y_values = [0]
def fill_walk(self):
"""计算随机漫步包含的所有点"""
#不断漫步,直到列表达到指定的长度
while len(self.x_values) < self.num_points:
#决定前进方向以及沿这个方向前进的距离
x_step = self.get_step()
y_step = self.get_step()
#拒绝原地踏步
if x_step == 0 and y_step == 0:
continue
#计算下一个点的x和y的值
next_x = self.x_values[-1]+x_step
next_y = self.y_values[-1]+y_step
self.x_values.append(next_x)
self.y_values.append(next_y)
def get_step(self):
self.direction = choice([1,-1])
self.distance = choice([0,1,2,3,4])
self.step = self.direction * self.distance
return self.step
rw_visual.py
import matplotlib.pyplot as plt
from random_walk import RandomWalk
#只要程序处于活动状态,就不断地模拟随机漫步
while True:
#创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values,rw.y_values,s=5)
plt.show()
keep_running =input("Make another walk?(y/n):")
if keep_running == 'n':
break
给点着色、绘制起点终点
我们将使用颜色映射来指出漫步中各点的先后顺序,并删除每个点的黑色轮廓,让它们的颜色更明显。为根据漫步中各点的先后顺序进行着色,我们传递参数c ,并将其设置为一个列表,其中包含各点的先后顺序。由于这些点是按顺序绘制的,因此给参数c指定的列表只需包含数字1~5000
import matplotlib.pyplot as plt
from random_walk import RandomWalk
while True:
#创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
point_numbers =list(range(rw.num_points))
plt.scatter(rw.x_values,rw.y_values,c=point_numbers,cmap=plt.cm.Blues,edgecolor='none',s=5)#给点着色
#突出起点和重点
plt.scatter(0,0,c='green',edgecolors='none',s=100)
plt.scatter(rw.x_values[-1],rw.y_values[-1],c='red',edgecolors='none',s=100)
plt.show()
keep_running =input("Make another walk?(y/n):")
if keep_running == 'n':
break
隐藏坐标轴
--snip--
plt.scatter(rw.x_values[-1],rw.y_values[-1],c='red',edgecolors='none',s=100)
#隐藏坐标轴
plt.axes().get_xaxis().set_visible(False)
plt.axes().get_yaxis().set_visible(False)
plt.show()
--snip--
增加点数
rw = RandomWalk(50000)
图表适合屏幕大小时,更能有效地将数据中的规律呈现出来。为让绘图窗口更适合屏幕大小:
# 设置绘图窗口的尺寸
plt.figure(figsize=(10, 6))
函数figure() 用于指定图表的宽度、高度、分辨率和背景色。你需要给形参figsize 指定一个元组,向matplotlib指出绘图窗口的尺寸,单位为英寸。
Python假定屏幕分辨率为80像素/英寸,如果上述代码指定的图表尺寸不合适,可根据需要调整其中的数字。如果你知道自己的系统的分辨率,可使用形参dpi 向figure() 传递该分辨率,以有效地利用可用的屏幕空间,如下所示:
plt.figure(dpi=128, figsize=(10, 6))
使用Pygal模拟掷骰子
要了解使用Pygal可创建什么样的图表,请查看图表类型画廊:访问http://www.pygal.org/ ,单击Documentation,再单击Chart types。每个示例都包含源代码,让你知道这些图表是如何生成的。
掷一个骰子
die.py
from random import randint
class Die():
"""表示一个骰子的类"""
def __init__(self,num_sides=6):
"""骰子默认为6面"""
self.num_sides = num_sides
def roll(self):
"""返回一个位于1和骰子面数之间的随机值"""
return randint(1,self.num_sides)
die_visual.py
from die import Die
import pygal
#创建一个D6
die = Die()
#掷几次骰子,并将结果存储在一个列表中
results=[]
for roll_num in range(1000):
result = die.roll()
results.append(result)
#分析结果统计次数
frequencies =[]
for value in range(1,die.num_sides+1):
frequency = results.count(value)
frequencies.append(frequency)
#对结果进行可视化 直方图
hist = pygal.Bar()
hist.title = "Result of rolling one D6 1000 times."#标题
hist.x_labels=[x for x in range(1,die.num_sides+1)]#横坐标
hist.x_title = "Result"#横轴标题
hist.y_title = "Frequency of Result"#纵轴标题
hist.add('D6',frequencies)#将值添加到图表中
hist.render_to_file('die_visual.svg')#将图表渲染为svg文件
掷两个骰子
from die import Die
import pygal
#创建两个D6骰子
die_1 = Die()
die_2 = Die()
#掷多次骰子,并将结果存储在一个列表中
results=[]
for roll_num in range(1000):
result = die_1.roll()+die_2.roll()
results.append(result)
#分析结果统计次数
frequencies =[]
max_result = die_1.num_sides + die_2.num_sides
for value in range(2,max_result+1):
frequency = results.count(value)
frequencies.append(frequency)
#对结果进行可视化 直方图
hist = pygal.Bar()
hist.title = "Result of rolling two D6 1000 times."
hist.x_labels=[x for x in range(2,max_result+1)]
hist.x_title = "Result"
hist.y_title = "Frequency of Result"
hist.add('D6+D6',frequencies)
hist.render_to_file('dice_visual.svg')
处理csv文件
要在文本文件中存储数据,最简单的方式是将数据作为一系列以逗号分隔的值 (CSV)写入文件。这样的文件称为CSV文件。csv 模块包含在Python标准库中,可用于分析CSV文件中的数据行。
import csv
filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
print(header_row)
调用csv.reader() ,并将前面存储的文件对象作为实参传递给它,从而创建一个与该文件相关联的阅读器(reader )对象。模块csv 包含函数next() ,调用它并将阅读器对象传递给它时,它将返回文件中的下一行。
为让文件头数据更容易理解,将列表中的每个文件头及其位置打印出来:
for index, column_header in enumerate(header_row):
print(index, column_header)
对列表调用了enumerate() 来获取每个元素的索引及其值。(请注意,我们删除了代码行print(header_row))。得知日期和最高气温分别存储在第0列和第1列。
提取并读取数据
highs = [int(row[1]) for row in reader] #将字符串转换为数字,方便matplotlib读取
print(highs)
提取最高气温并添加到列表中
绘制气温图表
import csv
from matplotlib import pyplot as plt
# 从文件中获取最高气温
--snip--
# 根据数据绘制图形
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()
模块datetime
读取日期数据时,获得的是一个字符串,因此我们需要想办法将字符串'2014-7-1' 转换为一个表示相应日期的对象。为创建一个表示2014年7月1日的对象,可使用模块datetime 中的方法strptime()。在终端会话中看看strptime() 的工作原理:
>>> from datetime import datetime
>>> first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')
下表是模块datetime中设置日期和时间格式的实参
绘制一年时间的天气图
import csv
from matplotlib import pyplot as plt
from datetime import datetime
import matplotlib.dates as mdates
#从文件中获取最高气温
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)
#根据数据绘制图形
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)
#配置和横坐标
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%Y'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
fig.autofmt_xdate()#让日期标签倾斜
plt.ylabel("Temperature(F)",fontsize=16)
plt.tick_params(axis='both',which = 'major',labelsize=10)
plt.show()
问题1:横坐标刻度太少,自定义配置横坐标
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%Y'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
问题2:列表解析法分别读取文件两行内容,有一行是空的
highs = [int(row[1]) for row in reader]
dates = [datetime.strptime(row[0],"%Y-%M-%D") for row in reader]
print(len(highs))
print(len(dates))
第二行读取的内容是空的........为什么......
添加最低气温并给图表区域着色
在其中再添加最低气温数据,使其更有用。从数据文件中提取最低气温,并将它们添加到图表。通过着色来呈现每天的气温范围。为此,我们将使用方法fill_between() ,它接受一个 x 值系列和两个 y 值系列,并填充两个 y值系列之间的空间:
--snip--
#从文件中获取最高气温
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:
--snip--
low = int(row[3])
lows.append(low)
#根据数据绘制图形
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)
--snip--
实参alpha 指定颜色的透明度。Alpha 值为0表示完全透明,1(默认设置)表示完全不透明。通过将alpha 设置为0.5,可让红色和蓝色折线的颜色看起来更浅。
向fill_between() 传递了一个 x 值系列:列表dates ,还传递了两个 y 值系列:highs 和lows 。实参facecolor 指定了填充区域的颜色,我们还将alpha 设置成了较小的值0.1,让填充区域将两个数据系列连接起来的同时不分散观察者的注意力。
问题3:横轴坐标如何从y轴开始
plt.xlim(dates[0],dates[-1])
错误检查
我们应该能够使用有关任何地方的天气数据来运行highs_lows.py中的代码,但有些气象站会偶尔出现故障,未能收集部分或全部其应该收集的数据。缺失数据可能会引发异常,如果不妥善地处理,还可能导致程序崩溃。
加利福尼亚死亡谷的气温图
文件death_valley_2014.csv没有记录2014年2月16日的数据,表示最高温度的字符串为空。为解决这种问题,我们在从CSV文件中读取值时执行错误检查代码,对分析数据集时可能出现的异常进行处理,如下所示:
--snip--
for row in reader:
try:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except:
print(current_date,'missing data')
else:
dates.append(current_date)
highs.append(high)
lows.append(low)
--snip--
从中提取日期、最高气温和最低气温。只要缺失其中一项数据,Python就会引发ValueError 异常,打印一条错误消息,指出缺失数据的日期。打印错误消息后,循环将接着处理下一行。如果获取特定日期的所有数据时没有发生错误,将运行else 代码块,并将数据附加到相应列表的末尾
练习1:比较锡特卡和死亡谷的气温 :在有关锡特卡和死亡谷的图表中,气温刻度反映了数据范围的不同。为准确地比较锡特卡和死亡谷的气温范围,需要在y 轴上使用相同的刻度。为此,请修改y 轴设置,对锡特卡和死亡谷的气温范围进行直接比较(你也可以对任何两个地方的气温范围进行比较)。你还可以尝试在一个图表中呈现这两个数据集。
import csv
from datetime import datetime
from matplotlib import pyplot as plt
def get_weather_data(filename,dates,highs,lows):
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
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)
dates,highs,lows = [],[],[]
get_weather_data('sitka_weather_2014.csv',dates,highs,lows)
fig = plt.figure(dpi=128,figsize=(10,6))
plt.plot(dates,highs,c='red',alpha=0.6)
plt.plot(dates,lows,c='blue',alpha=0.6)
plt.fill_between(dates,highs,lows,facecolor='blue',alpha=0.15)
dates,highs,lows = [],[],[]
get_weather_data('death_valley_2014.csv',dates,highs,lows)
plt.plot(dates,highs,c='red',alpha=0.3)
plt.plot(dates,lows,c='blue',alpha=0.3)
plt.fill_between(dates,highs,lows,facecolor='blue',alpha=0.05)
title = "Daily high and low temperatures - 2014"
title += "\nSitka , AK and Death Valley, CA"
plt.title(title,fontsize=20)
plt.xlabel('',fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature(F)",fontsize=16)
plt.tick_params(axis='both',which = 'major',labelsize=16)
plt.ylim(10,120)
plt.xlim(dates[0],dates[-1])
plt.show()
制作世界人口地图:JSON格式
获取数据值
population_data.json文件是一个Python列表每个元素都是一个包含四个键的字典:国家名、国别码、年份以及表示人口数量的值。文件中的每个键和值都是字符串。为处理这些人口数据,我们需要将表示人口数量的字符串转换为数字值,为此我们使用函数int() ,但是由于原始数据的格式常常不统一,因此经常会出现错误。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']
#Python不能直接将包含小数点的字符串'1127437398.85751'转换为整数
population = int(float(pop_dict['Value']))
print(country_name + ": " + str(population))
获取两个字母的国别码
Pygal中的地图制作工具要求数据为特定的格式:用国别码表示国家,用数字表示人口数量。处理地理政治数据时,经常需要用到几个标准化国别码集。population_data.json中包含的是三个字母的国别码,但Pygal使用两个字母的国别码。我们需要想办法根据国家名获取两个字母的国别码。Pygal使用的国别码存储在模块i18n (internationalization的缩写)中。字典COUNTRIES 包含的键和值分别为两个字母的国别码和国家名。要查看这些国别码,可从模块i18n中导入这个字典,并打印其键和值:
from pygal.i18n import COUNTRIES
for country_code in sorted(COUNTRIES.keys()):
print(country_code, COUNTRIES[country_code])
问题1:执行程序时,报错
ModuleNotFoundError: No module named ‘pygal.i18n‘
经查找是由于
pygal.i18n
已经不存在了,现在已经更改成了
pygal_maps_world,需要单独通过pip下载
pip install pygal_maps_world
然而pip下载报错,只能从下载tar压缩包解压后进入解压目录 使用python setup.py install命令安装,安装成功之后,修改为
from pygal_maps_world.i18n import COUNTRIES
就可以正常调用国别码了
制作世界地图
from pygal_maps_world.maps import World
wm = World()
wm.title = 'North,Central,and South Amercia'
wm.add('North America', ['ca','mx','us'])
wm.add('Central America',['bz','cr','gt','hn','ni','pa','sv'])
wm.add('South Ameirca',['ar','bo','br','cl','co','ec','gf','gy','pe','py','sr','uy','ve'])
wm.render_to_file('americas.svg')
问题2:执行程序时报错
import pygal
wm = pygal.Worldmap()
AttributeError: module 'pygal' has no attribute 'Worldmap'
对于绘制世界地图的指令,也就是我遇到的第二个报错,语句相应地更改模块名称:
from pygal_maps_world.maps import World
wm = World()
绘制完整的世界人口地图
import json
from country_codes import get_country_code
from pygal_maps_world.maps import World
#将数据加载到一个列表中
--snip--
#打印每个国家2010年的人口数量
#创建一个包含人口数量字典
cc_populations = {}
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
#Python不能直接将包含小数点的字符串'1127437398.85751'转换为整数
population = int(float(pop_dict['Value']))
code = get_country_code(country_name)
if code:
cc_populations[code] = population
wm = World()
wm.title = 'World Population in 2010,by Country'
wm.add('2010',cc_populations)
wm.render_to_file('world_populations.svg')
创建了一个空字典,用于以Pygal要求的格式存储国别码和人口数量。如果返回了国别码,就将国别码和人口数量分别作为键和值填充字典cc_populations。创建了一个Worldmap 实例,并设置其title 属性。我们调用了add() ,并向它传递由国别码和人口数量组成的字典。
根据人口数量将国家分组
根据人口数量分成三组——少于1000万的85、介于1000万和10亿之间的69以及超过10亿的2:
--snip--
#根据人口数量将所有的国家分成三组
cc_pops_1,cc_pops_2,cc_pops_3 ={},{},{}
for cc,pop in cc_populations.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 = World()
wm.title = 'World Population in 2010,by Country'
wm.add('0-10m',cc_pops_1)
wm.add('10m-1bn',cc_pops_2)
wm.add('>1bn',cc_pops_3)
wm.render_to_file('world_populations.svg')
使用Pygal设置世界地图的样式
Pygal使用一种基色,但将指定该基色,并让三个分组的颜色差别更大:
import json
from country_codes import get_country_code
from pygal_maps_world.maps import World
from pygal.style import RotateStyle
--snip--
#根据人口数量将所有的国家分成三组
cc_pops_1,cc_pops_2,cc_pops_3 ={},{},{}
for cc,pop in cc_populations.items():
if pop < 10000000:
--snip--
wm_style = RotateStyle('#336699')
wm = World(style = wm_style)
wm.title = 'World Population in 2010,by Country'
--snip--
十六进制格式 的RGB颜色是一个以井号(#)打头的字符串,后面跟着6个字符,其中前两个字符表示红色分量,接下来的两个表示绿色分量,最后两个表示蓝色分量。每个分量的取值范围为00 (没有相应的颜色)~FF (包含最多的相应颜色).#336699混合了少量的红色33、多一些的绿色66和更多一些的蓝色99,它为RotateStyle 提供了一种淡蓝色基色。