Data Visualizing
数据可视化指的是通过可视化表示来探索数据。它与数据分析紧密相关,而数据分析指的是使用代码来探索数据集的规律和关联
文章涉及两个常用的 Python package:
- Matplotlib v3.7.4 是一个数学绘图库,常被用作数据处理和可视化分析
- Plotly v5.18 包,它生成的图表非常适合在数字设备上显示。
Install matplotlib
通过命令 python -m pip install --user matplotlib 直接安装 matplotlib 即可
也可以通过创建一个 虚拟环境 ,在虚拟环境下直接执行 pip install matplotlib
或者 官方 列举的其他方法
matplotlib
Simple Line Chart
About matpltlib.pyplot
matplotlib.pyplot is a collection of functions that make matplotlib work like MATLAB. Each pyplot function makes some change to a figure: e.g., creates a figure, creates a plotting area in a figure, plots some lines in a plotting area, decorates the plot with labels, etc.
通过如下代码,我们可以通过使用 matpltlib 中的 pyplot 函数集,快速生成一个简单的折线图
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(squares)
plt.show()代码 plot.show() 用于新建一个图形实例,并展示图表。如果需要保存图标,可以参考 pyplot.savefig 将 plot.show() 替换为 plt.savefig('suqares_plot.png', bbox_inches='tight')。
WARNING
the variable fig in the code fig, ax = plt.subplots() can not be omitted because the function plt.subplots() returns a tuple containing a Figure and Axes object(s). tuple unpacking will make an assignment to fig and ax respectively with the Figure and Axes objects. Just like javascript, the args can not be omitted or the unpacked variables will mismatch.
If you only want the Axes object and don't care about the Figure, you could use _ to ignore the Figure
_, ax = plt.subplots()Set labels and Line Width
下面对代码进行标签和线条粗细的定制化
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(squares)
ax.plot(squares, linewidth=3)
ax.set_title("Square Numbers", fontsize=24)
# 标签最好使用英文,以防出现文字显示问题
ax.set_xlabel("Value", fontsize=14)
ax.set_ylabel("Square of Value", fontsize=14)
# Set size of tick labels.
ax.tick_params(axis='both', labelsize=14)
plt.show()Correcting the Figure
仔细观察此时的图形,我们会发现实际上 x轴 与 y轴 的数据是错位的。在 x=0 的位置上 y=1,而在最后一个点 x=4 的位置上 y=25
这是因为根据 ax.plot , plot 方法接收可选的 x轴 参数,当只传递了一组数据的时候,x轴 的数据为默认为 range(len(y))。因此当前图表数据的 x轴 数据是默认的 range(len(squares))
所以我们只需要指定对应的 x轴 数据即可
import matplotlib.pyplot as plt
input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
fig, ax = plt.subplots()
ax.plot(squares, linewidth=3)
ax.plot(input_values, squares, linewidth=3)
ax.set_title("Square Numbers", fontsize=24)
ax.set_xlabel("Value", fontsize=14)
ax.set_ylabel("Square of Value", fontsize=14)
# Set size of tick labels.
ax.tick_params(axis="both", labelsize=14)
plt.show()Set Plot Styles
可以通过 plt.style.available 获悉当前系统可使用那些样式,然后通过 plt.style.use('Solarize_Light2') 来使用对应的主题
import matplotlib.pyplot as plt
print(plt.style.available)
"""
output: ['Solarize_Light2', '_classic_test_patch', '_mpl-gallery', '_mpl-gallery-nogrid', 'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn-v0_8', 'seaborn-v0_8-bright', 'seaborn-v0_8-colorblind', 'seaborn-v0_8-dark',
'seaborn-v0_8-dark-palette', 'seaborn-v0_8-darkgrid', 'seaborn-v0_8-deep', 'seaborn-v0_8-muted', 'seaborn-v0_8-notebook', 'seaborn-v0_8-paper', 'seaborn-v0_8-pastel', 'seaborn-v0_8-poster', 'seaborn-v0_8-talk', 'seaborn-v0_8-ticks', 'seaborn-v0_8-white', 'seaborn-v0_8-whitegrid', 'tableau-colorblind10']
"""Code Example
import matplotlib.pyplot as plt
input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.style.use("Solarize_Light2")
fig, ax = plt.subplots()
ax.plot(input_values, squares, linewidth=3)
ax.set_title("Square Numbers", fontsize=24)
ax.set_xlabel("Value", fontsize=14)
ax.set_ylabel("Square of Value", fontsize=14)
# Set size of tick labels.
ax.tick_params(axis="both", labelsize=14)
plt.show()Scatter
当需要对点进行单独的设置时,散点图 Scatters 往往很有用。比如绘制大型数据集时。
要绘制单个点,可使用方法 scatter()。具体参数可参考matplotlib.pyplot.scatter
import matplotlib.pyplot as plt
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.scatter(2, 4)
plt.show()Set scatter style
随后进行图表样式的设置
import matplotlib.pyplot as plt
plt.style.use("seaborn")
fig, ax = plt.subplots()
# 设置散点图点的大小及颜色
ax.scatter(2, 4)
ax.scatter(2, 4, s=200, c="red")
# ax.scatter(1, 3, c="blue")
# 设置图表标题并给坐标轴加上标签
ax.set_title("Square Numbers", fontsize=24)
ax.set_xlabel("Value", fontsize=14)
ax.set_ylabel("Square of Value", fontsize=14)
# 设置刻度标记的大小
ax.tick_params(axis='both', which='major', labelsize=14)
plt.show()Set multiple scatters
可以通过传入 shape(n, ) 的 list 绘制多个点。同时可以利用自动计算与 列表推导 快速声明相关联的数据
import matplotlib.pyplot as plt
x_values = range(1, 1001)
y_values = [v**2 for v in x_values]
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.scatter(2, 4, s=200, c="red")
ax.scatter(x_values, y_values, s=10, c="red")
# 设置图表标题并给坐标轴加上标签
--snip--
# 设置每个坐标轴的取值范围
ax.axis([0, 1100, 0, 1100000])Set Color Map
除了手动设置颜色 c='red' 外,pyplot 内置了一组颜色映射,可以通过传入数组并指定颜色集,动态赋予散点图颜色。要使用这些颜色映射,需要告诉 pyplot 如何设置数据集中的颜色。
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, s=10, c="red")
ax.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Reds, s=10)
# 设置图表标题并给坐标轴加上标签。
--snip--Code Example
import matplotlib.pyplot as plt
x_values = range(1, 1001)
y_values = [v**2 for v in x_values]
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.scatter(x_values, y_values, s=10, c=y_values, cmap=plt.cm.Reds)
# 设置图表标题并给坐标轴加上标签
ax.set_title("Square Numbers", fontsize=24)
ax.set_xlabel("Value", fontsize=14)
ax.set_ylabel("Square of Value", fontsize=14)
# 设置刻度标记的大小
ax.tick_params(axis="both", which="major", labelsize=14)
# 设置每个坐标轴的取值范围
ax.axis([0, 1100, 0, 1100000])
# plt.show()
plt.savefig("test.png", bbox_inches="tight")Random Walk
随机漫步是这样行走得到的路径:每次行走都是完全随机的、没有明确的方向,结果是由一系列随机决策决定的。
首先我们新建一个随机漫步类。设置初始点为 (0, 0) ,传入一个目标值 num_points 模拟操作次数
随即声明一个随机步数工具函数 get_random_step 随机生成每一步的方向和距离。再通过在操作次数的限制内,进行 while 循环,生成 5000 次随机漫步
about random module
random.choice(seq) This module implements pseudo-random number generators for various distributions.
And random.choice(seq) return a random element from the non-empty sequence seq
pseudo-random means something that looks random, but is actually produced by a fixed and predictable process.
Update Styles
在 Scatter 散点图 那一章提到的相关样式也可以应用在随机漫步散点图中,通过如下代码去掉散点轮廓,并对 所有的点进行 num_points 维度的着色
ax.scatter(rw.x_values, rw.y_values, s=15)
ax.scatter(rw.x_values, rw.y_values, s=15, c=range(rw.num_points), cmap=plt.cm.Blues, edgecolors="none") 于此同时我们可能需要对随机漫步图的起点和重点进行特殊的样式设置,可以通过在 draw_plot 函数内声明一个强调点的函数,对特殊的点进行样式更新
def emphasize_point(idx, **scatter_args):
final_args = dict(s=100, edgecolors="none", c="red")
final_args.update(scatter_args)
ax.scatter(
rw.x_values[idx],
rw.y_values[idx],
**final_args,
)
emphasize_point(0, c="green")
emphasize_point(-1, c="red", s=120)Code Example
from random import choice
import matplotlib.pyplot as plt
def get_random_step(distance=[0, 1, 2, 3, 4]):
"""Determine the direction and distance for each step"""
direction = choice([-1, 1])
return direction * choice(distance)
class RandomWalk:
"""A class to generate random walks."""
def __init__(self, num_points=5000):
"""Initialize attributes of a walk"""
self.num_points = num_points
# All walks start at (0,0)
self.x_values = [0]
self.y_values = [0]
def fill_walk(self):
"""Calculate all the points in the walk"""
while len(self.x_values) < self.num_points:
# Decide which direction to go and how far to go in that direction
x_step = get_random_step()
y_step = get_random_step()
# Reject moves that go nowhere
if x_step == 0 and y_step == 0:
continue
# calculate the next x and y values
x = self.x_values[-1] + x_step
y = self.y_values[-1] + y_step
self.x_values.append(x)
self.y_values.append(y)
def draw_plot():
# Create a random walk instance and fill it
rw = RandomWalk()
rw.fill_walk()
# plot all the points in the walk
plt.style.use("classic")
fig, ax = plt.subplots()
ax.scatter(
rw.x_values,
rw.y_values,
s=15,
c=range(rw.num_points),
cmap=plt.cm.Blues,
edgecolors="none",
)
def emphasize_point(idx, **scatter_args):
"""Emphasize a point in the plot"""
final_args = dict(s=100, edgecolors="none", c="red")
final_args.update(scatter_args)
ax.scatter(
rw.x_values[idx],
rw.y_values[idx],
**final_args,
)
emphasize_point(0, c="green")
emphasize_point(-1, c="red", s=120)
plt.show()
if __name__ == "__main__":
draw_plot()Plotly
Plotly可以生成交互式的图表。并且其生成的图表还将自动缩放以适合观看者的屏幕
根据 官网 的描述可以得知, plotly 的可交互性是基于 Plotly JavaScript library 的。 因此我们可以根据使用 Plotly 模块的相关功能,导出可交互的 html
from plotly import offline
offline.plot({"data": data, "layout": my_layout}, filename="d6.html")这也是 version 4 推荐的图表展示方(Figure Display)。但在 version 5 可以使用 renderers framework 展示图表。renderers framework 是 plotly.offline.iplot 和 plotly.offline.plot 的一般化方案
Code Example
from plotly.graph_objects import Bar, Layout
# from plotly.graph_objects import Figure
import plotly.offline as offline
from random import randint
class Die:
"""A class representing a single die"""
def __init__(self, num_sides=6):
"""default to a six-sided die"""
self.num_sides = num_sides
def roll(self):
"""return a random value between 1 and number of sides"""
return randint(1, self.num_sides)
# def draw_plot():
# Create a D6
die = Die()
# make some rolls and save the results in a list
results = []
for roll_num in range(1000):
result = die.roll()
results.append(result)
# analyze the results
frequencies = []
for value in range(1, die.num_sides + 1):
frequencies.append(results.count(value))
x_values = list(range(1, die.num_sides + 1))
data = [Bar(x=x_values, y=frequencies)]
x_axis_config = {"title": "Result"}
y_axis_config = {"title": "Frequency of the result"}
my_layout = Layout(
title="Roll a D6 1000 times", xaxis=x_axis_config, yaxis=y_axis_config
)
# Figure(data=data, layout=my_layout).show()
offline.plot({"data": data, "layout": my_layout}, filename="d6.html")Mass Data Process
一般我们用于可视化的数据都不是自己生成的。是在网上通过爬取等方式总结汇总后,再进行二次处理得到的。
通过对可视化的数据进行存储与二次处理,就能够通过分析发现别人没有发现的规律和关联
CSV
CSV 文件通过将数据作为一系列以逗号分隔的值(comma-separated values)写入文件进行存储
例如 assets/data/sitka_weather_07-2018_simple.csv 这个文件中,通过如下的文本内容
"STATION","NAME","DATE","PRCP","TAVG","TMAX","TMIN"
"USW00025333","SITKA AIRPORT, AK US","2018-07-01","0.25",,"62","50"实际上表示了下表的数据
| STATION | NAME | DATE | PRCP | TAVG | TMAX | TMIN |
|---|---|---|---|---|---|---|
| USW00025333 | SITKA AIRPORT, AK US | 2018-07-01 | 0.25 | 62 | 50 |
Analyze CSV Data
Python 中内置了 CSV 模块,方便快速处理数据。结合 文件操作,可以
import csv
filename = 'assets/data/sitka_weather_07-2018_simple.csv'
with open(filename) as f:
reader = csv.reader(f)
# next 为内置函数读取文件的下一行
header_row = next(reader)
print(header_row) # ['STATION', 'NAME', 'DATE', 'PRCP', 'TAVG', 'TMAX', 'TMIN']也可以通过 enumerate 方法,将文件头及其索引更明确的表示出来
--snip--
# next 为内置函数读取文件的下一行
header_row = next(reader)
print(header_row)
for idx, col_header in enumerate(header_row):
print(idx, col_header) 类似的。通过遍历每一行的数据,可以获取到文件中最高温度的列表
--snip--
# next 为内置函数读取文件的下一行
header_row = next(reader)
for idx, col_header in enumerate(header_row):
print(idx, col_header)
highs = []
for row in reader:
high = int(row[5])
highs.append(high) 或使用列表推导式 highs = [int(row[5]) for row in reader] 快速生成数据 list
Draw temperature plot
参考 绘制折线图 等内容,可将上节中的 highs 设置为 y轴,得到一个基础的折线图
import matplotlib.pyplot as plt
with open(filename) as f:
--snip--
# 绘制图表
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.plot(highs, c="red")
# set chart title and label axes
ax.set_title("2018 Daily high temperatures for Sitka, AK", fontsize=24)
ax.set_xlabel("", fontsize=16)
ax.set_ylabel("Temperature(F)", fontsize=16)
ax.tick_params(axis="both", which="major", labelsize=16)再使用模块 datetime 将日期数据处理为对应的值。 strftime 和 strptime 是一对相对应的 API。前者可以实现日期的格式化 format,而后者可以实现日期的解析 parse
为什么不直接使用第二列作为 x轴 数据?
之所以不直接使用 row[2] 的日期数据作为 x轴 的数据,是因为要调用 Figure 对象的 autofmt_xdate 方法进行格式化,并自动倾斜
对代码进行如下修改
import csv
import matplotlib.pyplot as plt
from datetime import datetime
with open(filename) as f:
--snip--
# get temperature from the file
highs = []
dates, highs = [], []
for row in reader:
high = int(row[5])
current_date = datetime.strptime(row[2], '%Y-%m-%d')
dates.append(current_date)
highs.append(high)
--snip--
ax.set_xlabel("", fontsize=16)
fig.autofmt_xdate()
--snip--Draw another series
除了绘制最高温度,如果数据充足,我们还可以继续绘制最低温度
--snip--
header_row = next(reader)
dates, highs = [], []
dates, highs, lows = [], [], []
for row in reader:
current_date = datetime.strptime(row[2], '%Y-%m-%d')
dates.append(current_date)
high = int(row[5])
highs.append(high)
lows.append(int(row[6]))
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.plot(dates, highs, c="red")
ax.plot(dates, lows, c="blue")
ax.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1) 通过 plot.fill_between 方法,可以快速填充 data series 之间的空隙
Error process
理论上我们可以使用上一节中的代码,生成任何包含 date low high 三种数据的图表,但有时候 数据源 可能会出现问题,如
- 数据故障、缺失
- 数据格式错误
如果不妥善处理,可能导致程序崩溃。例如如果我们将文件替换为 assets/data/death_valley_2018_simple.csv,再将对应最高温度与最低温度的 idx 修改后,运行当前程序将会报一个 ValueError 的错误,因为在 int(row[4]) 的时候,某一行的 row[4] 数据为空,因此程序出错
解决方案很简单,使用 except 捕捉错误即可
try :
high = int(row[5])
low = int(row[6])
except ValueError:
print(f"Missing data for {current_date}")
else:
highs.append(high)
lows.append(low)
dates.append(current_date)Code Example
# sitka_highs.py
import csv
import matplotlib.pyplot as plt
from datetime import datetime
filename = "assets/data/sitka_weather_2018_simple.csv"
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
# get high temperature from the file
dates, highs, lows = [], [], []
for row in reader:
current_date = datetime.strptime(row[2], '%Y-%m-%d')
try :
high = int(row[5])
low = int(row[6])
except ValueError:
print(f"Missing data for {current_date}")
else:
highs.append(high)
lows.append(low)
dates.append(current_date)
plt.style.use("seaborn")
fig, ax = plt.subplots()
ax.plot(dates, highs, c="red")
ax.plot(dates, lows, c="blue")
# set chart title and label axes
ax.set_title("2018 Daily temperatures for Sitka, AK", fontsize=24)
ax.set_xlabel("", fontsize=16)
ax.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)
# format x axis according to date
fig.autofmt_xdate()
ax.set_ylabel("Temperature(F)", fontsize=16)
ax.tick_params(axis="both", which="major", labelsize=16)
plt.show()JSON
类似 CSV,Python 中同样内置了 JSON 模块,方便快速处理数据
Code Example
import json
# Explore the structure of the data
filename = "assets/data/json/eq_data_1_day_m1.json"
with open(filename) as f:
all_eq_data = json.load(f)
readable_file = "assets/data/json/readable_eq_data.json"
with open(readable_file, "w") as f:
json.dump(all_eq_data, f, indent=4)Process Json Response
Web API 是网站的一部分,用于使用具体 URL 请求特定信息。我们可以通过使用 Web API,请求外部数据源,对最新的数据进行可视化。通常返回值都是以易于处理的格式如 JSON 或者 CSV 返回
比如 https://api.github.com/search/repositories?q=language:python&sort=stars 。当前接口返回 Github 托管的 Python 项目信息,并且根据 stars 进行排序
为了能够请求对应接口,我们使用了外部模块 requests
import requests
# Make an API call and store the response
url = "https://api.github.com/search/repositories?q=language:python&sort=stars"
headers = {"Accept": "application/vnd.github.v3+json"}
r = requests.get(url, headers=headers)
print(f"Status code: {r.status_code}") # Status code: 200
# Store API response in a variable
response_dict = r.json()
# Process results
print(response_dict.keys()) # dict_keys(['total_count', 'incomplete_results', 'items'])使用 Plotly 图表化及数据处理过程,可以查看 Source Code