0%

数据可视化基础

生成数据

安装matplotlib

1
$ pip3 install --user matplotlib

测试matplotlib

1
2
$ python3
>> import matplotlib

如果没有出现任何错误消息,就说明你系统安装了matplotlib。

matplotlib画廊

要查看使用matplotlib可制作的各种图表,请访问http://matplotlib.org/的示例画廊。单击画廊中的图表,就可查看用于生成图表的代码。

绘制简单的折线图plot()

我们将使用平方数序列1、4、9、16和25来绘制这个图标。

1
2
3
4
5
import matplotlib.pyplot as plt

squares = [1,4,9,16,25]
plt.plot(squares)
plt.show()

2017-09-19.2.15.10

首先,导入了模块pyplot,并给它指定了别名plt。模块pyplot包含很多用于生成图标的函数。

我们创建了一个列表,在其中存储了前述平方数,再将这个列表传递给函数plot()。

plt.show()打开matplotlib查看器,并显示绘制的图形。

修改标签文字和线条粗细

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt

squares = [1,4,9,16,25]
plt.plot(squares,linewidth=5)

#设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)

#设置刻度标记的大小
plt.tick_params(axis='both',labelsize=14)

plt.show()

tick_params()设置刻度的样式,其中指定的实参将影响x轴和y轴上的刻度(axis=’both’),并将刻度标记的字号设置为14。

017-09-19.3.16.14

校正图形

我们发现没有正确的绘制数据:折线图的终点指出4.0的平方为25!下面来修复这个问题。

当你向plot()提供一系列数字时,它假设第一个数据点对应的x坐标值为0,但我们的第一个点对应的x值为1。为改变这种默认行为,我们可以给plot()同时提供输入值和输出值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 Numbers",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)

#设置刻度标记的大小
plt.tick_params(axis='both',labelsize=14)

plt.show()

屏幕快照 2017-09-19 下午3.30.45

使用scatter()绘制散点图

1
2
3
4
import matplotlib.pyplot as plt

plt.scatter(2,4)
plt.show()

屏幕快照 2017-09-19 下午3.56.57

向函数scatter()传递一对x和y坐标,它将在指定位置绘制一个点。

优化此图形:

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt

plt.scatter(2,4,s=200)

#设置图表标题并给坐标轴加上标签
plt.title("Square Numbers",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)

#设置刻度标记的大小
plt.tick_params(axis='both',which='major',labelsize=14)

plt.show()

Scatter()中的实参s设置了绘制图形时使用的点的尺寸。

屏幕快照 2017-09-19 下午4.05.05

使用scatter()绘制一系列点

要绘制一系列点,可向scatter()传递两个分别包含x和y的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

x_values = [1,2,3,4,5]
y_values = [1,4,9,16,25]

plt.scatter(x_values,y_values,s=100)

#设置图表标题并给坐标轴加上标签
plt.title("Square Numbers",fontsize=24)
plt.xlabel("Value",fontsize=14)
plt.ylabel("Square of Value",fontsize=14)

#设置刻度标记的大小
plt.tick_params(axis='both',which='major',labelsize=14)

plt.show()

将这些列表传递给scatter()时,matplotlib依次从每个列表中读取一个值来绘制一个点。

屏幕快照 2017-09-19 下午4.17.41

自动计算数据

手工计算列表要包含的值可能效率低下,需要绘制的点很多时尤其如此。可以不必手工计算包含点坐标的列表,而让Python循环来替我们完成这种事。下面绘制了1000个点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import matplotlib.pyplot as plt

x_values = list(range(1,1001))
y_values = [x**2 for x in x_values]

plt.scatter(x_values,y_values,s=40)

#设置图表标题并给坐标轴加上标签
plt.title("Square Numbers",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,1100,0,1100000])

plt.show()

函数axis()要求提供四个值:x和y坐标轴的最小最大值。

屏幕快照 2017-09-19 下午4.38.12

删除数据点的轮廓

matplotlib允许你给散点图中的各个点指定颜色。默认为蓝色点和黑色轮廓,在散点图不含数据不多时效果很好。但绘制很多点时,黑色轮廓可能会粘连在一起。删除轮廓,传递实参edgecolor。

1
plt.scatter(x_values,y_values,edgecolor='none',s=40)

自定义颜色

修改数据点的颜色,向scatter()传递参数c,如下:

1
plt.scatter(x_values,y_values,c='red',edgecolor='none',s=40)

你还可以使用RGB颜色模式自定义颜色。要指定自定义颜色,可传递参数c,并将其设置为一个元组,其中包含三个0~1之间的小数值,它们分别表示红色、绿色和蓝色分量。例如,下面代码将创建一个由淡蓝色点组成的散点图:

1
plt.scatter(x_values,y_values,c=(0,0,0.8),edgecolor='none',s=40)

值越接近0,指定的颜色越深,越接近1,指定的颜色越浅。

使用颜色映射

颜色映射(colormap)是一系列颜色,它们从起始颜色渐变到结束颜色。在可视化中,颜色映射用于突出数据的规律,例如,你使用颜色深的表示数值大的,浅的表示小的。

模块pyplot内置了一组颜色映射。要使用这些颜色映射,你需要告诉pyplot如何设置数据集中每个点的颜色。下面演示如何根据每个点的y值来设置颜色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt

x_values = list(range(1,1001))
y_values = [x**2 for x in x_values]

#change
plt.scatter(x_values,y_values,c=y_values,cmap=plt.cm.Blues,
edgecolor='none',s=40)

#设置图表标题并给坐标轴加上标签
plt.title("Square Numbers",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,1100,0,1100000])

plt.show()

我们将参数c设置成了一个y值列表,并使用参数cmap告诉pyplot使用哪个颜色映射。将较小的点设置成浅蓝色,较深的点设置成深蓝色。

屏幕快照 2017-09-19 下午4.58.54

自动保存图表

让程序自动将图表保存到文件中,可将对plt.show()的调用替换为对plt.savefig()的调用:

1
plt.savefig('squares_plot.png',bbox_inches='tight')

第一个实参指定要以什么样的文件名保存图表,这个文件存储到当前程序所在的目录中。第二个实参指定将图表多余的空白区域裁剪掉。如果要保留图表周围空白的区域,可省略这个实参。

例:画Sigmoid函数图

参考链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import matplotlib.pyplot as plt
import numpy as np
import mpl_toolkits.axisartist as axisartist

# 创建画布
fig = plt.figure(figsize=(8, 8))
# 使用axisartist.Subplot方法创建一个绘图区对象ax
ax = axisartist.Subplot(fig, 111)
# 将绘图区对象添加到画布中
fig.add_axes(ax)

# 通过set_visible方法设置绘图区所有坐标轴隐藏
ax.axis[:].set_visible(False)

# ax.new_floating_axis代表添加新的坐标轴
ax.axis["x"] = ax.new_floating_axis(0, 0)
# 给x坐标轴加上箭头
ax.axis["x"].set_axisline_style("->", size=1.0)
# 添加y坐标轴,且加上箭头
ax.axis["y"] = ax.new_floating_axis(1, 0)
ax.axis["y"].set_axisline_style("-|>", size=1.0)
# 设置x、y轴上刻度显示方向
ax.axis["x"].set_axis_direction("top")
ax.axis["y"].set_axis_direction("right")
# 生成x步长为0.1的列表数据
x = np.arange(-15, 15, 0.1)
# 生成sigmiod形式的y数据
y = 1 / (1 + np.exp(-x))
# 设置x、y坐标轴的范围
plt.xlim(-12, 12)
plt.ylim(-1, 1)
# 绘制图形
plt.plot(x, y, c='b')
plt.show()

DataView_Sigmoid

随机漫步

我们使用python来生成随机漫步数据,再使用matplotlib以引人瞩目的方式将这些数据呈现出来。

随机漫步数据是这样行走得到的路径:每次行走都是完全随机的,没有明确的方向,结果是由一系列随机决策决定的。在自然界、物理学、生物学、化学和经济领域,随机漫步都有其实际用途。例如,漂浮在水面上的花粉因不断受到水分子的挤压而在水面上移动。花粉在水面上的运动轨迹犹如随机漫步。

创建RandomWalk()类

为模拟随机漫步,我们将创建一个名为RandomWalk的类,它随机的选择前进方向。

这个类需要三个属性,其中一个是存储随机漫步的次数的变量,其他两个是列表,分别存储随机漫步经过的每个点的x和y坐标。包含两个方法:_init(),fill_walk(),其中后者计算随机漫步经过的所有点。

下面是init方法。(random_walk.py)

1
2
3
4
5
6
7
8
9
10
11
12
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]

我们将所有的可能都存储在一个列表中,并在每次做决策时都使用choice()来决定使用哪种选择。我们将随机漫步包含的默认点数设置为5000。

选择方向

使用fill_walk()来生成漫步包含的点,并决定每次漫步的方向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def fill_walk(self):
"""计算随机漫步包含的所有的点"""

#不断漫步,直到列表达到指定的长度
while len(self.x_values) < self.num_points:
#决定前进的方向以及沿这个方向前进的距离
x_direction = choice([-1,1])
x_distance = choice([0,1,2,3,4])
x_step = x_direction * x_distance

y_direction = choice([-1,1])
y_distance = choice([0,1,2,3,4])
y_step = y_direction * y_distance

#拒绝原地踏步
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)

方法choice([-1,1])将在-1,1表示在两者之间随机选择一个数。choice([0,1,2,3,4])是在这5个数中间随机选择一个数字,之所以包含0是因为可能点要沿垂直的方向行动。x_step与y_step是最终的步长,x_values[-1],Y_values[-1]取到列表的最后一个元素,它们也用来保存最新行动的点,即append()方法。

绘制随机漫步图

(rw_visual.py)

1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt

from  random_walk import RandomWalk

#创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values,rw.y_values,s=15)
plt.show()

创建RandomWalk的实例rw,再调用fill_walk()。然后将实例中产生的x_values和y_values赋值给scatter()函数。

下图是包含5000个点的随机漫步图。

屏幕快照 2017-09-21 上午10.29.43

设置随机漫步图样式

我们将定制图表,以突出每次漫步的重要特征,并让分散注意力的元素不那么显眼。最终的结果是简单的可视化表示,清楚地指出了每次漫步经过的路径。

给点着色

我们使用颜色映射来指出漫步中各点的先后顺序,并删除每个点的黑色轮廓。我们传递参数c,并将其设置为一个列表,其中包含各点的先后顺序。由于这些点是按照顺序绘制的,因此给参数c指定列表只需包含数字1~5000。

1
2
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=15)

屏幕快照 2017-09-21 上午11.14.58

重新绘制起点和终点

除了给随机漫步的各个点着色,以指出它们的先后顺序外,如果还能呈现随机漫步的起点和终点就更好了。为此,可在绘制随机漫步图后重新绘制起点和终点。

1
2
3
4
#突出起点和终点
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)

屏幕快照 2017-09-21 下午12.33.14

隐藏坐标轴

1
2
3
#隐藏坐标轴
plt.axes().get_xaxis().set_visible(False)
plt.axes().get_yaxis().set_visible(False)

增加点数

1
rw = RandomWalk(50000)

调整屏幕以适应屏幕

1
2
#设置绘图窗口的尺寸
plt.figure(dpi=227,figsize=(10,6))

函数figure()用于指定图表的宽度、高度、分辨率和背景色。你需要给figsize指定一个元组,单位为英寸。Python假定屏幕的分辨率为80像素/英寸,可利用形参dip向figure()传递该分辨率,有效的利用屏幕空间。

屏幕快照 2017-09-21 下午1.00.20 1

使用Pygal模拟掷骰子

我们将使用Python可视化包Pygal来生成可缩放的矢量图形文件。对于需要在尺寸不同的屏幕上显示的图表,这很有用,它们会自动缩放。如果你打算以在线方式使用图表,请考虑使用Pygal来生成它们,这样它们在任何设备上显示时都会很美观。

如果掷两个骰子,为确定哪些点数出现的可能性最大,我们将生成一个表示掷骰子结果的数据集,并根据结果绘制出一个图形。

安装Pygal

1
$ pip3 install --user pygal==1.7

Pygal画廊

要了解使用Pygal可创建什么样的图表,可查看图表类型画廊, http://www.pygal.org/,单击Documentation,再单击Chart types。每个示例都包含源代码。

掷单个骰子

  • 创建Die类

    下面的类模拟掷一个骰子。die.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)

    创建这个类的实例时,骰子默认面为6,如果指定了参数,这个值将用于设置骰子的面数,6面为D6,8面为D8,以此类推。方法roll()使用函数randint()来返回一个1和面数之间的随机数。这个函数可能返回起始值1,终止值num_sides或者这两个值之间的任何整数。(choice()返回列表列出的随机数)

  • 掷骰子

    掷D6骰子,将结果打印出来,并检查结果是否合理。die_visual.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from die import Die
    #创建一个D6
    die = Die()

    #掷100次骰子,并将结果存储在一个列表中
    results = []
    for roll_num in range(100):
    result = die.roll()
    results.append(result)

    print(results)

    [6, 4, 4, 4, 5, 1, 5, 3, 5, 1, 5, 6, 1, 4, 1, 4, 4, 4, 3, 6, 2, 4, 6, 5, 3, 4, 3, 4, 3, 2, 2, 2, 2, 2, 4, 5, 1, 5, 3, 1, 5, 2, 6, 3, 1, 4, 4, 3, 6, 5, 1, 2, 3, 6, 4, 1, 4, 5, 3, 3, 4, 1, 5, 4, 6, 4, 1, 3, 3, 1, 1, 1, 6, 4, 5, 4, 5, 5, 2, 2, 5, 2, 5, 5, 4, 2, 3, 3, 6, 6, 5, 5, 2, 5, 3, 5, 6, 2, 2, 5]
  • 分析结果

    分析掷一个D6骰子的结果,我们计算每个点数出现的次数。range()为1~num_sides

    1
    2
    3
    4
    5
    6
    7
    #分析结果
    frequencies = []
    for value in range(1,die.num_sides+1):
    frequency = results.count(value)
    frequencies.append(frequency)

    [14, 18, 23, 12, 18, 15]
  • 绘制直方图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import pygal

    from die import Die
    --snip--

    #对结果进行可视化
    hist = pygal.Bar()

    hist.title = "Results of rolling one D6 100 times."
    hist.x_labels = ['1','2','3','4','5','6']
    hist.x_title = 'Result'
    hist.y_title = 'Frequency of Result'

    hist.add('D6',frequencies)
    hist.render_to_file('die_visual.svg')

    add()将一系列值添加到图表中(向它传递要给添加的值指定的标签,还有一个列表),最后,我们将这个图表渲染为一个SVG文件,这种文件的扩展名必须为.svg。

    要查看生成的直方图,使用浏览器,新建标签页,在其中打开文件die _visual.svg,它位于其所在的文件夹内

屏幕快照 2017-09-21 下午2.24.19

同时掷两个骰子

同时掷两个骰子时,得到的点数更多,结果分布也不同。每次掷两个骰子时,我们将两个骰子点数相加,并将结果存在results中。(dice_visual.py)(以下是部分代码,其他自行修改)

1
2
3
for roll_num in range(100):
result = die_1.roll() + die_2.roll()
results.append(result)

屏幕快照 2017-09-21 下午3.06.28

下载数据

在本章中,将从网上下载数据,并对这些数据进行可视化。两种常见格式存储的数据:CSV 和 JSON。我们将使用Python模块csv来处理CSV数据(逗号分隔的值)。然后,使用matplotlib根据下载的数据创建一个图表。这两者我们用来分析天气数据,找出不同地区在同一时间内的最高温和最低温。

使用模块json来访问以JSON格式存储的人口数据,并使用Pygal绘制一幅按国别划分的人口地图。

CSV文件格式

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

1
2014-1-5,13,15,14,23,34,6,32,18,19,30,29

CSV文件对人来说阅读起来比较麻烦,但程序可轻松地提取并处理其中的值,这有助于加快数据分析过程。

将少量锡特卡的CSV格式的天气预报数据(sitka_weather_07-2014.csv)下载到当前文件夹中。

分析CSV文件头

csv模块包含在Python标准库中,可用于分析CSV文件中的数据行,让我们能快速提取感兴趣的值。

1
2
3
4
5
6
7
8
9
import csv

filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
print(header_row)

['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']

调用csv.reader()将前面存储的文件对象作为实参传递给它,从而创建一个与该文件相关联的阅读器reader对象。将阅读器存储在reader中。模块cav包含函数next(),调用它将返回文件中的下一行。

reader处理文件中以逗号分隔的第一行数据,并将每个数据项都作为一个元素存储在列表中。

打印文件头及其位置

我们将文件头及其位置打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import csv

filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

for index,colum_header in enumerate(header_row):
print(index,colum_header)

0 AKDT
1 Max TemperatureF
2 Mean TemperatureF
3 Min TemperatureF
4 Max Dew PointF
.......

我们对列表调用了enumerate()来获取每个元素的索引及其值。从中可知,日期和最高气温分别存储在第0列和第一列。我们将处理文件中的每行数据,并提取其中索引为0和1的值。

提取并读取数据

首先读取每天的最高气温。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

['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']

阅读器对象从其停留的地方继续往下读取csv文件,每次都自动返回当前所处位置的下一行。由于我们之前已经读取了文件头,这个循环将从第二行开始。

我们使用int()将这些字符串转化为数字,让matplotlib能够读取它们。

1
2
3
 highs.append(int(row[1])

[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]

绘制气温图表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import csv

from matplotlib import pyplot as plt

file_name = 'sitka_weather_07-2014.csv'
with open(file_name) as f:
reader = csv.reader(f)
header_row = next(reader)

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

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

#设置图形的格式
plt.title("Daily high temperature,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()

屏幕快照 2017-09-23 下午12.06.32

模块datetime

在天气数据文件中,第一个日期在第二行。地区该行时,获取的是一个字符串,因为我们需要将字符串“2014-7-1“转换为一个表示相应日期的对象。可使用模块datetime中的方法strptime()。

1
2
3
4
5
6
from datetime import datetime

first_date = datetime.strptime('2014-7-1','%Y-%m-%d')
print(first_date)

2014-07-01 00:00:00

“%Y-“让Python将字符串中第一个连字符前面的部分视为四位的年份。strptime()可接受实参,并根据其解读日期。

实 参 含 义
%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~59)

在图表中添加日期

调用fig.autofmt_xdate()来绘制斜的日期标签,以免它们彼此重叠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import csv

from datetime import datetime

from matplotlib import pyplot as plt

file_name = 'sitka_weather_07-2014.csv'
with open(file_name) 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)
highs.append(int(row[1]))

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

#设置图形的格式
plt.title("Daily high temperature,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()

屏幕快照 2017-09-23 下午2.02.28

最高与最低气温

导入”sitka_weather_2014.csv”数据,它记录了sitka地区整年的气温变化,我们观察最高与最低气温。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
xiaimport csv

from datetime import datetime

from matplotlib import pyplot as plt

file_name = 'sitka_weather_2014.csv'
with open(file_name) 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)
highs.append(int(row[1]))
lows.append(int(row[3]))

#根据数据绘制图形
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 temperature,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()

屏幕快照 2017-09-23 下午2.28.54

下面给图表区域着色。使用fill_between(),它接受一个x值与y值系列,并填充两个y值系列之间的空间。alpha指定颜色的透明度。Alpha=0表示完全透明,1完全不透明。fill_between()传递了一个x值系列:列表dates,还传递了两个y值系列:highs和lows。facecolor指定了填充区域的颜色。

1
2
3
4
5
#根据数据绘制图形
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)

屏幕快照 2017-09-23 下午2.39.56

错误检查

数据的缺失可能引发异常,例如我们导入死亡谷的气温数据“death_valley_2014.csv”,并且运行上述代码。

1
ValueError: invalid literal for int() with base 10: ''

结果显示,无法处理文件中的一行数据,因为它无法将空字符串转化为整数。我们打开文件,发现其中没有记录2014-2-16的数据,最高气温的字符串为空。

我们在csv文件中读取值时执行错误检查代码,对分析数据集时可能出现的异常进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--snip--
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)
--snip--

屏幕快照 2017-09-23 下午3.04.05

可以看到,有一个数据没有读到。其他正常显示。

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

Pygal提供了一个适合初学者使用的地图创建工具,使用它对人口数据进行可视化,以探索全球人口的分布情况。

文件population_data.json中包含全球大部分国家1960~2010的人口数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"Country Name": "Arab World",
"Country Code": "ARB",
"Year": "1960",
"Value": "96388069"
},
{
"Country Name": "Arab World",
"Country Code": "ARB",
"Year": "1961",
"Value": "98882541.4"
},
......

这个文件实际上就是一个很长的Python列表,其中每个元素都是一个包含4个键的字典。国家名、国别码、年份、以及人口数量。

我们只关心人口数量。因此先将这些信息打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import json

#将数据加载到一个列表中
file_name = 'population_data.json'
with open(file_name) 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)

Arab World:357868000
Caribbean small states:6880000
East Asia & Pacific (all income levels):2201536674
East Asia & Pacific (developing only):1961558757
Euro area:331766000
......

首先导入了json模块,以便正确的加载文件中的数据。然后将数据存储在pop_data中。函数pop_data 将数据转换为Python能够处理的格式,这里是一个列表。

可以看到,捕捉的数据中,并非每个都是国家名,现在将数据转换为Pygal能够处理的格式。

注意:将str转换为int时,若使用str(),如果数字(人口数量)为小数,则会发生错误,因为Python不能将包含小数点的字符串(如:“10.984958”)转化为整数。

应该先将其字符串转换为浮点数float(),再将浮点数转化为整数int()。Python会自动抛弃小数部分。

1
2
population = int(float(pop_dict['Value']))
print(country_name + ":" + str(population))

获取两个字母的国别码

population_data.json中包含的是三个字母的国别码,但Pygal使用两个字母的国别码。我们需要根据国家名获取两个字母的国别码。Pygal使用的国别码存储在模块i8n中。字典COUNTRIES包含的键和值分别是两个字母的国别码和国家名。要查看,可导入。

1
2
3
4
5
6
7
8
9
10
from pygal.i18n import COUNTRIES

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

ad Andorra
ae United Arab Emirates
af Afghanistan
al Albania
......

为获取国别码,编写一个函数,它在COUNTRIES中查找并返回国别码。(country_codes.py)

1
2
3
4
5
6
7
8
from pygal.i18n import COUNTRIES

def get_country_code(country_name):
"""根据指定的国家,返回Pygal使用的两个字母的国别码"""
for code,name in COUNTRIES.items():
if name == country_name:
return code
return None

在之前的代码中导入。(world_population.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import json

from country_codes import get_country_code

#将数据加载到一个列表中
file_name = 'population_data.json'
with open(file_name) 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']
code = get_country_code(country_name)
if code:
print(code + ":" + str(population))
else:
print('ERROR -' + country_name)

......
ERROR -Algeria
ERROR -American Samoa
ad:84864
......

制作世界地图

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

我们创建了一个Worldmap实例,并设置了该地图的title属性。

使用add方法,它接受一个标签和一个列表,后者包含我们要突出的国家的国别码。每次调用add都将为指定的国家选择一种新颜色,并在左边显示该颜色和指定的标签。我们要以同一种颜色显示整个北美地区。

1
2
3
4
5
6
7
8
9
10
11
import pygal

wm = pygal.Worldmap()
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','sr','uy','ve'])

wm.render_to_file('americas.svg')

屏幕快照 2017-09-23 下午4.41.59

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

1
2
3
4
5
6
7
import pygal

wm = pygal.Worldmap()
wm.title = 'Population of Countries in North America'
wm.add('North America',{'ca':34126000,'us':30934900,'mx':113423000})

wm.render_to_file('na_populations.svg')

屏幕快照 2017-09-23 下午4.52.41

这次的add方法,第二个参数传递的是一个字典而不是列表。这个字典将两个字母的Pygal国别码作为键,将人口数量作为值。Pygal根据这些数字自动给不同国家着以深浅不一的颜色(人口最少的国家颜色最浅,反之最深)

绘制完整的世界人口地图

在之前的world_population.py中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import json

import pygal

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

#创建一个包含人口数量的字典
cc_populations = {}

#每个国家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:
cc_populations[code] = population

wm = pygal.Worldmap()
wm.title = 'World Population in 2010,by Country'
wm.add('2010',cc_populations)

wm.render_to_file('world_population.svg')

屏幕快照 2017-09-23 下午5.09.55

我们将根据人口数量将国家分组,再分别给每个组着色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
--snip--
from pygal.style import LightColorizedStyle as LCS, RotateStyle as RS
--snip--

if code:
cc_populations[code] = population

# Group the countries into 3 population levels.
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

# See how many countries are in each level.
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm_style = RS('# ', base_style=LCS)
wm = pygal.Worldmap(style=wm_style)
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_population.svg')

85 69 2

屏幕快照 2017-09-23 下午5.31.20

用Pygal设置世界地图样式

我们让Pygal使用设置指令来调整颜色。让三个分组的颜色差别更大。

1
2
3
4
5
6
7
8
9
--snip--

from pygal.style import RotateStyle

--snip--
wm_style = RotateStyle('# ')
wm = pygal.Worldmap(style=wm_style)

--snip--

Pygal样式存储在模块style中,我们从中导入了RotateStyle样式。创建这个类的实例时,需要提供一个参数:16进制的RGB颜色;Pygal将根据指定的颜色为每组选择颜色。前两个字符表示红色分量,然后是绿色,蓝色。取值00~FF。建议搜索hex color chooser(16进制颜色选择器)

加亮颜色主题

我们使用LightColorizedStyle加亮了地图。

1
from pygal.style import LightColorizedStyle

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

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

再使用RotateStyle创建一种样式

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

这设置了较亮的主题,通过实参传递的颜色给各个国家着色。

使用API

编写一个独立的程序,并对其获取的数据进行可视化。这个程序将使用Web应用编程接口(API)自动请求网站的特定信息而不是整个网页,再对这些信息进行可视化。由于这样编写的程序始终使用最新的数据来生成可视化,因此即便数据瞬息万变,它呈现的信息也都是最新的。

我们将使用GitHub的API来请求有关该网站中Python项目的信息,然后使用Pygal生成交互式可视化,以呈现这些项目的受欢迎程度。这个程序将自动下载GitHub上星级最高的Python项目的信息,并对这些项目进行可视化。

使用Web API

GitHub的API让你能够通过API调用来请求各种信息。要知道API调用是什么样的,输入以下网址:

https://api.github.com/search/repositories?q=language:python&sort=stars

这个调用返回GitHub当前托管了多少个Python项目,还有有关最受欢迎的Python仓库的信息。仔细研究这个调用,第一部分(https://api.github.com/)将请求发送到GitHub网站中响应API调用的部分;接下来一部分(search/repositories)让API搜索GitHub上的所有仓库。

repositories后面的问号指出我们要传递一个实参。q表示查询,而等号能让我们开始指定的查询。通过使用language:python,我们指出只想获取主要语言为python仓库的信息,最后一部分查询&sort=stars指定项目将按其获得的星级进行排序。

1
2
3
4
5
6
7
8
9
{
"total_count": 1968718,
"incomplete_results": false,
"items": [
{
"id": 21289110,
"name": "awesome-python",
"full_name": "vinta/awesome-python",
"owner": {

上面显示了响应的前几行。从中可知,GitHub总共有1968718个python项目。”incomplete_results”的值为false,据此我们知道请求是成功的(它并非不完整的)。倘若GitHub无法全面处理该API,它返回的这个值将为true。接下来的列表中显示了返回的“items”,其中包含GitHub上最受欢迎的Python项目的详细信息。

安装requests

requests包让python程序能够轻松地向网站请求信息以及检查返回的响应,要安装requests,执行类似于下面的命令:

1
$ pip3 install --user requests

处理API响应

下面的程序,它执行API调用并处理结果,找出GitHub上星级最高的Python项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

#执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url)
print("Status code:",r.status_code)

#将API响应存储在一个变量中
response_dict = r.json()

#处理结果
print(response_dict.keys())

Status code: 200
dict_keys(['total_count', 'incomplete_results', 'items'])

1.我们导入模块request。

2.使用requests来执行调用url。调用get()并将url传递给它,再将响应对象存储在变量r中。

3.响应对象包含了一个名为status_code的属性,它让我们知道请求是否成功了。(状态码200表示成功了)

4.这个API返回一个json格式的信息,因此使用方法json()将这些信息转换为一个Python字典存储在response_dict

5.打印response_dict中的键。

处理响应字典

将API调用返回的信息存储到字典中后,就可以处理这个字典中的数据了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests

#执行API调用并存储响应
url = "https://api.github.com/search/repositories?q=language:python&sort=stars"
r = requests.get(url)
print("status code",r.status_code)

#将API响应存储在一个变量中
response_dict = r.json()
print("Total repositories returned:",response_dict['total_count'])

#搜索有关仓库的信息
repo_dict = response_dict['items']
print("Repositories returned:",len(repo_dict))

#研究第一个仓库
repo_dict = repo_dict[0]
print("\nKeys:",len(repo_dict))
for key in sorted(repo_dict.keys()):
print(key)

status code 200
Total repositories returned: 1970122
Repositories returned: 30

Keys: 70
archive_url
.......

与item相关联的值是一个列表,其中包含很多字典,而每个字典都包含有关一个Python仓库的信息。

我们将这个字典列表存储在repo_dicts中。

repo_dict[0]提取第一个字典,并将其存储在repo_dict中。

接下来打印第一个仓库的字典中很多键相关的值。

1
2
3
4
5
6
7
8
9
10
11
--snip--
#研究第一个仓库
repo_dict = repo_dict[0]

print("\nSelected information about first repository")
print("Name:",repo_dict['name'])
print("Stars:",repo_dict['stargazers_count'])

Selected information about first repository
Name: awesome-python
Stars: 38871

概述最受欢迎的仓库

1
2
3
4
5
6
7
8
9
10
11
12
--snip--

#搜索有关仓库的信息
repo_dict = response_dict['items']
print("Repositories returned:",len(repo_dict))

print("\nSelected information about first repository")
for repo_dict in repo_dicts:
print("Name:",repo_dict['name'])
print("Stars:",repo_dict['stargazers_count'])
print("Repository:",repo_dict['html_url'])
print("Description:",repo_dict['description'])

监视API的速率限制

大多数API都存在速率限制,即你在特定时间内可执行的请求数存在限制。要获悉你是否接近GiHub的限制,请输入https://api.github.com/rate_limit

1
2
3
4
5
6
7
8
9
10
11
12
{
"resources": {
"core": {
"limit": 60,
"remaining": 60,
"reset": 1506236519
},
"search": {
"limit": 10,
"remaining": 10,
"reset": 1506232979
},

search中,limit代表每分钟10个请求,而在当前这一分钟,我们还可以执行10个请求。若到达配置,你必须等待配额重置。

使用Pygal可视化仓库

现在进行可视化,呈现GitHub上Python项目最受欢迎程度。条形的高低代表获得了多少颗星。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests 
import pygal
from pygal.style import LightColorizedStyle as LCS,LightenStyle as LS

#执行API调用并存储响应
url = "https://api.github.com/search/repositories?q=language:python&sort=stars"
r = requests.get(url)
print("status code",r.status_code)

#将API响应存储在一个变量中
response_dict = r.json()
print("Total repositories returned:",response_dict['total_count'])

#搜索有关仓库的信息
repo_dicts = response_dict['items']

names,stars = [],[]
for repo_dict in repo_dicts:
names.append(repo_dict['name'])
stars.append(repo_dict['stargazers_count'])

#可视化
my_style = LS('# ',base_style=LCS)
chart = pygal.Bar(style=my_style,x_label_rotation=45,show_legend=False)
chart.title = "Most-Starred Python Projects on GitHub"
chart.x_labels = names

chart.add('',stars)
chart.render_to_file('python_repos.svg')

屏幕快照 2017-09-24 下午2.19.13

x_label_rotation=45让标签绕x轴旋转45度,并隐藏了图例show_legend=False

改进Pygal图表

下面来改进这个图表的样式,创建一个配置对象,在其中包含要传递给Bar()的所有配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--snip--
#可视化
my_style = LS('# ',base_style=LCS)
my_config = pygal.Config()
my_config.x_label_rotation = 45
my_config.show_legend = False
my_config.title_font_size = 14
my_config.major_label_font_size = 18
my_config.truncate_label = 15
my_config.show_y_guides = False
my_config.width = 1000

chart = pygal.Bar(my_config,style=my_style)
chart.title = "Most-Starred Python Projects on GitHub"
chart.x_labels = names

chart.add('',stars)
chart.render_to_file('python_repos.svg')

屏幕快照 2017-09-24 下午2.32.56

我们创建了一个Pygal类Config的实例,并将其命名为my_config,通过修改my_config,可定制图表的外观。truncate_label将较长的项目名缩短为15个字符。show_y_guides为False将隐藏图表中的水平线。width为自定义宽度,让图表更充分地利用浏览器中的可用空间。

添加自定义工具提示

在Pygal中,将鼠标指向条形将显示它表示的信息,这通常称为工具提示。在这个示例中,当前显示的是项目获得了多少个星。我们向add()传递字典列表,而不是值列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pygal
from pygal.style import LightColorizedStyle as LCS,LightenStyle as LS

my_style = LS('# ',base_style=LCS)
chart = pygal.Bar(style=my_style,x_label_rotation=45,show_legend=False)

chart.title = 'Python Project'
chart.x_labels = ['httpie','django','flask']

plot_dicts = [
{'value':16101,'label':'Description of httpie.'},
{'value':15028,'label':'Description of django.'},
{'value':14798,'label':'Description of flask'}
]

chart.add('',plot_dicts)
chart.render_to_file('bar_description.svg')

屏幕快照 2017-09-24 下午3.06.59

每个字典都包含两个键:“value”和“label”。Pygal根据与键“value”相关联的数字来确定条形的高度,并使用“label”相关联的字符串给条形创建工具。

根据数据绘图

为根据数据绘图,我们将自动生成plot_dicts,其中包含API调用的30个项目的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--snip--
#搜索有关仓库的信息
repo_dicts = response_dict['items']

names,plot_dicts = [],[]
for repo_dict in repo_dicts:
names.append(repo_dict['name'])

plot_dict = {
'value':repo_dict['stargazers_count'],
'label':repo_dict['description'],
}
plot_dicts.append(plot_dict)

--snip--

chart.add('',plot_dicts)

--snip--

屏幕快照 2017-09-24 下午3.23.21

在图表中添加可单击的链接

Pygal允许你将图表中的每个条形用作网站的相应链接。

1
2
3
4
5
6
7
8
9
--snip--

plot_dict = {
'value':repo_dict['stargazers_count'],
'label':repo_dict['description'],
'xlink':repo_dict['html_url'],
}

--snip--