数据可视化 指的是通过可视化表示来探索数据。它与数据分析 紧密相关,而数据分析指的是使用代码来探索数据集的规律和关联。数据集可 以是用一行代码就能表示的小型数字列表,也可以是数千兆字节的数据。
本章将首先使用Matplotlib来生成几个图表,为此需要使用 pip 来安装它。 pip 是 一个可用于下载并安装Python包的模块。请在终端提示符下执行如下命令:
python -m pip install --user matplotlib
在你的系统中,如果运行程序或启动终端会话时使用的命令不是 python , 而是python3 ,应使用类似于下面的命令:
python3 -m pip install --user matplotlib
下面使用Matplotlib绘制一个简单的折线图,再对其进行定制,以实现信息更丰富 的数据可视化效果。我们将使用平方数序列1、4、9、16和25来绘制这个图表。只需提供如下的数,Matplotlib将完成其他工作:
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(squares)
plt.show()
① 首先导入模块pyplot ,并为其指定别名 plt ,以免反复输入 pyplot。
② 我们创建了一个名为squares 的列表,在其中存储要用来制作图表的数据。
③ 调用函数subplots(),这个函数可在一张图片中绘制一个或多个图表。变量fig 表示整张图片。变 量ax 表示图片中的各个图表,大多数情况下要使用它。
④方法plot() ,它尝试根据给定的数据以有意义的方式绘制图表。
⑤ 函数 plt.show() 打开Matplotlib查看器并显示绘制的图表
图形表明数是越来越大的,但标签文字太小、线条太细,难以看清 楚。所幸Matplotlib让你能够调整可视化的各个方面。下面通过一些定制来改善这个图表的可读性,如下所示:
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(squares, linewidth=3)
# 设置图表标题并给坐标轴加上标签。
ax.set_title("平方数", fontsize=24)
ax.set_xlabel("值", fontsize=14)
ax.set_ylabel("值的平方", fontsize=14)
# 设置刻度标记的大小。
ax.tick_params(axis='both', labelsize=14)
plt.show()
①参数linewidth决定了plot() 绘制的线条粗细
②方法set_title() 给图表指定标题。
③方法set_xlabel() 和set_ylabel() 让你能够为每条轴设置标题。
④方 法tick_params()设置刻度的样式,其中指定的实参将影响 轴和 轴 上的刻度(axes='both' ),并将刻度标记的字号设置为14(labelsize=14 )。
1.2.2 校正图形
图形更容易看清后,我们发现没有正确地绘制数据:折线图的终点指出4.0的平方为 25!下面来修复这个问题。向 plot() 提供一系列数时,它假设第一个数据点对应的 坐标值为0,但这里第一 个点对应的值为1。为改变这种默认行为,可向plot() 同时提供输入值和输出值:
import matplotlib.pyplot as plt
input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(input_values, squares, linewidth=3)
# 设置图表标题并给坐标轴加上标签。
--snip
Matplotlib提供了很多已经定义好的样式,它们使用的背景色、网格线、线条粗 细、字体、字号等设置很不错,要获悉在你的系统中可使用哪些样式,可在终端会话中执行如下命令:
import matplotlib.pyplot as plt
plt.style.available
要使用这些样式,可在生成图表的代码前添加如下代码行:
import matplotlib.pyplot as plt
input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.style.use('seaborn')
fig, ax = plt.subplots()
--snip
方法plt.style.use(样式),置于生成图标的代码前,就可按照自己喜欢的样式来生成图标。
有时候,绘制散点图并设置各个数据点的样式很有用。要绘制单个点,可使用方法scatter() 。向它传递一对 坐标和 坐标,它将在指定位置绘制一个点:
import matplotlib.pyplot as plt
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.scatter(2, 4, s=200)
# 设置图表标题并给坐标轴加上标签。
ax.set_title("平方数", fontsize=24)
ax.set_xlabel("值", fontsize=14)
ax.set_ylabel("值的平方", fontsize=14)
# 设置刻度标记的大小。
ax.tick_params(axis='both', which='major', labelsize=14)
plt.show()
①调用scatter() 并使用参数 s 设置绘制图形时使用的点的尺寸。
要绘制一系列的点,可向 scatter() 传递两个分别包含 值和 值的列表,如下所 示:
import matplotlib.pyplot as plt
x_values = [1, 2, 3, 4, 5]
y_values = [1, 4, 9, 16, 25]
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.scatter(x_values, y_values, s=100)
# 设置图表标题并给坐标轴指定标签。
--snip
列表 x_values 包含要计算平方值的数,列表 y_values 包含前述数的平方值。将 这些列表传递给scatter() 时,Matplotlib依次从每个列表中读取一个值来绘制一个点。
手工计算列表要包含的值可能效率低下,需要绘制的点很多时尤其如此。我们不必 手工计算包含点坐标的列表,可以用Python循环来完成。
import matplotlib.pyplot as plt
x_values = range(1, 1001)
y_values = [x**2 for x in x_values]
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.scatter(x_values, y_values, s=10)
# 设置图表标题并给坐标轴加上标签。
--snip--
# 设置每个坐标轴的取值范围。
ax.axis([0, 1100, 0, 1100000])
plt.show()
使用方法axis() 指定了每个坐标轴的取值范围。方法axis() 要求提供4 个值: 和 坐标轴的最小值和最大值。这里将 坐标轴的取值范围设置为0~ 1100,并将 坐标轴的取值范围设置为0~1100 000。
要修改数据点的颜色,可向 scatter() 传递参数 c ,并将其设置为要使用的颜色 的名称(放在引号内),如下所示:
ax.scatter(x_values, y_values, c='red', s=10)
颜色映射 (colormap)是一系列颜色,从起始颜色渐变到结束颜色。在可视化中, 颜色映射用于突出数据的规律。例如,你可能用较浅的颜色来显示较小的值,并使 用较深的颜色来显示较大的值。
import matplotlib.pyplot as plt
x_values = range(1, 1001)
y_values = [x**2 for x in x_values]
ax.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues, s=10)
# 设置图表标题并给坐标轴加上标签。 --snip
我们将参数 c 设置成了一个 值列表,并使用参数 cmap 告诉 pyplot 使用哪个颜 色映射。这些代码将 值较小的点显示为浅蓝色,并将 值较大的点显示为深蓝色。
要让程序自动将图表保存到文件中,可将调用plt.show() 替换为调用 plt.savefig()
plt.savefig('squares_plot.png', bbox_inches='tight')
第一个实参指定要以什么文件名保存图表,这个文件将存储到scatter_squares.py 所在的目录。第二个实参指定将图表多余的空白区域裁剪掉。如果要保留图表周围多余的空白区域,只需省略这个实参即可。
本节将使用Python来生成随机漫步数据,再使用Matplotlib以引人瞩目的方式将这 些数据呈现出来。随机漫步 是这样行走得到的路径:每次行走都是完全随机的、没 有明确的方向,结果是由一系列随机决策决定的。
为模拟随机漫步,将创建一个名为 RandomWalk 的类,它随机地选择前进方向。这 个类需要三个属性:一个是存储随机漫步次数的变量,其他两个是列表,分别存储 随机漫步经过的每个点的 坐标和坐标。
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]
并在每次决策时都使用模块random 中的choice() 来决定使用哪种选择
我们将使用方法 fill_walk() 来生成漫步包含的点并决定每次漫步的方向,如下 所示。请将这个方法添加到random_walk.py中:
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值。
x = self.x_values[-1] + x_step
y = self.y_values[-1] + y_step
self.x_values.append(x)
self.y_values.append(y)
①使用choice([1, -1]) 给x_direction 选择一个值,结果要么是表示向右走的 1,要么是表示向左走的-1.
②choice([0, 1, 2, 3, 4]) 随机 地选择一个0~4的整数,告诉Python 沿指定的方向走多(x_distance )。
③将移动方向乘以移动距离,确定沿X轴和Y轴移动的距离。
下面的代码将随机漫步的所有点都绘制出来:
import matplotlib.pyplot as plt
from random_walk import RandomWalk
# 创建一个RandomWalk实例。
rw = RandomWalk()
rw.fill_walk()
# 将所有的点都绘制出来。
plt.style.use('classic')
fig, ax = plt.subplots()
ax.scatter(rw.x_values, rw.y_values, s=15)
plt.show()
将随机漫步包含的X值和Y传递给scatter() ,并选择合适的点尺寸 。
a. 给点着色
我们将使用颜色映射来指出漫步中各点的先后顺序,并删除每个点的黑色轮 廓,让其颜色更为明显。为根据漫步中各点的先后顺序来着色,传递参数c , 并将其设置为一个列表,其中包含各点的先后顺序。这些点是按顺序绘制的, 因此给参数c 指定的列表只需包含数0~4999,如下所示:
--snip--
while True:
# 创建一个RandomWalk实例。
rw = RandomWalk()
rw.fill_walk()
# 将所有的点都绘制出来。
plt.style.use('classic')
fig, ax = plt.subplots()
point_numbers = range(rw.num_points)
ax.scatter(rw.x_values, rw.y_values, c=point_numbers,
cmap=plt.cm.Blues,
edgecolors='none', s=15)
keep_running = input("Make another walk? (y/n): ")
--snip--
①使用range() 生成了一个数字列表,其中包含的数与漫步包含的点数量相同。
②并传递实参edgecolors='none'
③将参数c 设置为point_numbers ,指定使用颜色映射Blues
b. 重新绘制起点和终点
--snip--
while True:
--snip--
ax.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues,
edgecolors='none', s=15)
# 突出起点和终点。
ax.scatter(0, 0, c='green', edgecolors='none', s=100)
ax.scatter(rw.x_values[-1], rw.y_values[-1], c='red', edgecolors='none',
s=100)
plt.show()
--snip--
使用绿色绘制点(0, 0),并使其比其他点大(s=100 )。为突出终点,在漫步包含的最后一个 值和 值处绘制一个点,将其颜色设置为红 色,并将尺寸设置为100。务必将这些代码放在调用plt.show() 的代码前面,确保在其他点之上绘制起点和终点。
c. 隐藏坐标轴
# 隐藏坐标轴。
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
使用方法ax.get_xaxis() 和ax.get_yaxis() 将 每条坐标轴的可见性都设置为False 。 随着对数据可视化的不断学习和实践, 你会经常看到这种串接方法的方式。
d 调整尺寸以适合屏幕
--snip--
while True:
# 创建一个RandomWalk实例。
rw = RandomWalk(50_000)
rw.fill_walk()
# 将所有的点都绘制出来。
plt.style.use('classic')
fig, ax = plt.subplots(figsize=(15, 9))
--snip--
可传递参数figsize 以指定生成的图形的尺寸。需要给参数 figsize 指定一个元组,向Matplotlib指出绘图窗口的尺寸.
本节将使用Python包Plotly来生成交互式图表。
在终端输入:
python -m pip install --user plotly
即可安装plotiy
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)
①方法__init__() 接受一个可选参数。创建这个类的实例时,如果没有指定任何实
参,面数默认为6;
②方法roll() 使用函数randint() 来返回一个1和面数之间的随机数。这 个函数可能返回起始值1、终止值num_sides 或这两个值之间的任何整数。
有时我们使用这个类会出现错误,此时我们可以只定义roll函数,不去定义类。
使用这个类来创建图表前,先来掷D6,将结果打印出来,并确认结果是合理的:
from die import Die
# 创建一个D6。 ❶ die = Die()
# 掷几次骰子并将结果存储在一个列表中。
results = []
for roll_num in range(100):
result = die.roll()
results.append(result)
print(results)
为分析掷一个D6的结果,计算每个点数出现的次数:
--snip--
# 掷几次骰子并将结果存储在一个列表中。
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)
print(frequencies)
由于将使用Plotly来分析,而不是将结果打印出来,因此可将模拟掷骰子的次数增 加到1000 。为分析结果,我们创建空列表 frequencies ,用于存储每种 点数出现的次数。在 处,遍历可能的点数(这里为1~6),计算每种点数在 results 中出现了多少次。
有了频率列表,就可以绘制一个表示结果的直方图了。 直方图 是一种条形图,指出 了各种结果出现的频率。
from plotly.graph_objs import Bar, Layout
from plotly import offline
from die import Die
--snip--
# 分析结果。
frequencies = []
for value in range(1, die.num_sides+1):
frequency = results.count(value)
frequencies.append(frequency)
# 对结果进行可视化。
x_values = list(range(1, die.num_sides+1))
data = [Bar(x=x_values, y=frequencies)]
x_axis_config = {'title': '结果'}
y_axis_config = {'title': '结果的频率'}
my_layout = Layout(title='掷一个D6 1000次的结果',
xaxis=x_axis_config, yaxis=y_axis_config)
offline.plot({'data': data, 'layout': my_layout}, filename='d6.html')
①我们将可能出现的点数 (1到骰子的面数)存储在一个名为x_values 的列表中,Plotly不能直接 接受函数range() 的结果,因此需要使用函数list() 将其转换为列表。
②Plotly类 Bar() 表示用于绘制条形图的数据集,需要一个存储X值的列表和一个存储Y值的列表。这个类必须放在方括号内,因为数据集可能包含多个元素。
③类Layout() 返回一个指定图表布局和配置的对象 (见❹)。这里设置了图表名称,并传入了 轴和 轴的配置字典。
④这个函数需要一个包含 数据和布局对象的字典,还接受一个文件名,指定要将图表保存到哪里。这里将输 出存储到文件d6.html。
import csv
filename = 'data/sitka_weather_07-2018_simple.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
print(header_row)
将文件名赋给filename,这是打开文件时常用的做法。然后将用到至关重要的函数csv.reader(),括号里包含的是需要读取的文件对象,从而创建一个与该文件相关联的阅读器对象,在这里被赋给了reader。模块csv 包含函数next() ,调用它并传入阅读器对象时,它将返回文件中的下一 行。在上述代码中,只调用了next() 一次,因此得到的是文件的第一行,其中包含文件头。
注意:当阅读器经过next()函数以后,每当调用一次将会自动返回文件的下一行。
为了得到文件头下包含的数据,我们需要获取文件头的位置。于是接着上面代码的基础上我们再加几行代码:
import csv
filename = 'data/sitka_weather_07-2018_simple.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
for index,name in enumerate(header_row)
print(index,name)
在这里我们又引用了一个新的函数enumerate(),有点类似于对于字典使用的items()这个函数,不同的是这里返回的是索引和元素。由此我们获得了对应文件的开头,方便我们获得需要的数据。
知道了第一行里对应的数据名以及位置,我们可以回去剩下行里的数据。
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
# 从文件中获取最高温度。
highs = []
for row in reader:
high = int(row[5])
highs.append(high)
print(highs)
紧接着上面的代码我们获取每一行数据的第五个索引位置的数据(最高温度)。
为了将数据可视化,我们使用上面安装的用Matplotlib创建一个显示每日最高温度的简单图 形,如下所示:
import csv
import matplotlib.pyplot as plt
filename = 'data/sitka_weather_07-2018_simple.csv'
with open(filename) as f:
--_snip_—
# 根据最高温度绘制图形。
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.plot(highs, c='red')
# 设置图形的格式。
ax.set_title("2018年7月每日最高温度", fontsize=24)
ax.set_xlabel('', fontsize=16)
ax.set_ylabel("温度 (F)", fontsize=16)
ax.tick_params(axis='both', which='major', labelsize=16)
plt.show()
使用绘图函数plot(),并传递c="red",以便将数据点绘制为红色,其余函数在上一章均详细介绍过,
根据前面获取的第一行文件名,我们来获取详尽的时间,读取该数据时,获得的是一个字符串,因此需要想办法将字符串"2018-7-1" 转换 为一个表示相应日期的对象。为此我们再此使用新的函数,datetime模块中的的方法strptime(),下面演示此函数作用:
>>> from datetime import datetime
>>> first_date = datetime.strptime('2018-07-01', '%Y-%m-%d')
>>> print(first_date)
2018-07-01 00:00:00
实参 | 含义 |
%A | 星期几,,如Monday |
%B |
月份名,如January
|
%m |
用数表示的月份(01~12)
|
%d |
用数表示的月份中的一天(01~31)
|
% |
四位的年份,如2019
|
% |
两位的年份,如19
|
% |
24小时制的小时数(00~23)
|
% |
12小时制的小时数(01~12)
|
% |
am或pm
|
% |
分钟数(00~59)
|
import csv
from datetime import datetime
import matplotlib.pyplot as plt
filename = 'data/sitka_weather_07-2018_simple.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[2], '%Y-%m-%d')
high = int(row[5])
dates.append(current_date)
highs.append(high)
# 根据最高温度绘制图形。
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.plot(dates, highs, c='red')
# 设置图形的格式。
ax.set_title("2018年7月每日最高温度", fontsize=24)
ax.set_xlabel('', fontsize=16)
fig.autofmt_xdate()
ax.set_ylabel("温度 (F)", fontsize=16)
ax.tick_params(axis='both', which='major', labelsize=16)
plt.show()
--snip--
filename = 'sitka_weather_2018_simple.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[2], '%Y-%m-%d')
high = int(row[5])
low = int(row[6])
dates.append(current_date)
highs.append(high)
lows.append(low)
# 根据最高温度和最低温度绘制图形。
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.plot(dates, highs, c='red')
ax.plot(dates, lows, c='blue')
# 设置图形的格式。
ax.set_title("2018年每日最高温度", fontsize=24)
--snip--
--snip--
# 根据最低温度和最高温度绘制图形。
plt.style.use('seaborn')
fig, ax = plt.subplots()
ax.plot(dates, highs, c='red', alpha=0.5)
ax.plot(dates, lows, c='blue', alpha=0.5)
ax.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)
--snip--
我们再此引用函数方法fill_between() 。 它接受一个 值系列和两个 值系列,并填充两个 值系列之间的空间。实参alpha 指定颜色的透明度。alpha 值为0表示完全透明,为1(默认设置表示完全不透明。向fill_between() 传递一个 值系列(列表dates ),以及两个 值 系列(highs 和lows )。实参facecolor指定填充区域的颜色,还将alpha 设置成了较小的值0.1。
import json
# 探索数据的结构。
filename = 'data/eq_data_1_day_m1.json'
with open(filename) as f:
all_eq_data = json.load(f)
readable_file = 'data/readable_eq_data.json'
with open(readable_file, 'w') as f:
json.dump(all_eq_data, f, indent=4)
import json
# 探索数据的结构。
filename = 'data/eq_data_1_day_m1.json'
with open(filename) as f:
all_eq_data = json.load(f)
all_eq_dicts = all_eq_data['features']
print(len(all_eq_dicts))
--snip--
all_eq_dicts = all_eq_data['features']
mags = []
for eq_dict in all_eq_dicts:
mag = eq_dict['properties']['mag']
mags.append(mag)
print(mags[:10])
import plotly.express as px
fig = px.scatter(
x=lons,
y=lats,
labels={"x": "经度", "y": "纬度"},
range_x=[-200, 200],
range_y=[-90, 90],
width=800,
height=800,
title="全球地震散点图",
)
fig.write_html("global_earthquakes.html")
fig.show()
import pandas as pd
data = pd.DataFrame(
data=zip(lons, lats, titles, mags), columns=["经度", "纬度", "位置", "震级"]
)
data.head(
fig = px.scatter(
data,
x="经度",
y="纬度",
range_x=[-200, 200],
range_y=[-90, 90],
width=800,
height=800,
title="全球地震散点图",
size="震级",
size_max=10,
)
fig.write_html("global_earthquakes.html")
fig.show()
filename = 'data/eq_data_30_day_m1.json'
--snip--
fig = px.scatter(
data,
x="经度",
y="纬度",
range_x=[-200, 200],
range_y=[-90, 90],
width=800,
height=800,
title="全球地震散点图",
size="震级",
size_max=10,
color="震级
)