jk's notes
  • 第三部分 数据可视化

第三部分 数据可视化

ch15 生成数据

基本目标: 统计数据, 然后以统计图的形式将数据呈现出来.

这里介绍 Matplotlib, 一个集合了数据可视化工具的包 (就是绘制各种统计图).

jk: 总感觉不如直接看官网. 这个指南只适合入门. 一个感知而已. 细节太少.

jk: 不过符合本书的定位.

15.1 安装 Matplotlib

安装命令:

python -m pip install --user matplotlib

如果在 linux 等系统上, 使用 python3 代替 python.

关于 Matplotlib 可以绘制什么图形, 参考官网.

15.2 绘制简单的折线图

目标: 使用平方数序列来绘制折线图

步骤:

  1. 安装, 导入 matplotlib 中的 pyplot.
  2. 准备数据 (列表)
  3. 调用 pyplot 的 subplots() 方法来获得 图形 与 绘图 的引用.
  4. 调用绘图的 plot(数据) 方法.
  5. 调用 show() 方法展示.

逻辑上, 图形是整个图, 可以简单理解为容器. 而绘图可以看成是一个具体的图, 放在容器中.

示例代码:

import matplotlib.pyplot as plt
datas = [v ** 2 for v in range(1, 10)]
fig, ax = plt.subplots()
ax.plot(datas)
plt.show()

image-20240124094643818

下面看看如何配置显示.

15.2.1 修改标签文字和线条粗细

# 修改折线粗细
ax.plot(datas, linewidth=3)

# 设置图题, 与坐标上的标签
ax.set_title("平方数", fontsize=24)
ax.set_xlabel("值", fontsize=14)
ax.set_ylabel("序列", fontsize=14)

注意: 中文如果无法正常显示, 有两个方法处理:

  1. 设置 Matplotlib 使用中文字体.
  2. 使用字体库, 需要安装并导入一个支持中文字体的字体库.

方案1

plt.rcParams['font.sans-serif'] = ['SimSun'] # 指定默认字体为宋体 
plt.rcParams['axes.unicode_minus'] = False   # 解决保存图像是负号'-'显示为方块的问题

方案2

from matplotlib.font_manager import FontProperties 
font = FontProperties(fname="字体文件的路径") # 替换为你自己中文字体文件的路径 
plt.title('数的平方', fontproperties=font)

image-20240124095836302

15.2.2 矫正绘图

考虑数据源, 与绘制图, 坐标是错位的.

plot() 参数为一个数值列表时, 默认会认定第 一个 数据项的 x 坐标为 0.

要解决这个问题, 可以给 plot() 出入另一个参数, 被视为 x 坐标向量.

ax.plot(x向量, y向量, linewidth=3)

例如:

x_vector = list(range(1, 6))
y_vector = [v ** 2 for v in range(1, 6)]
fig, ax = plt.subplots()
ax.plot(x_vector, y_vector, linewidth=3)

显示结果:

image-20240124103848645

要修改配置除了使用 ax 的方法外, 还可以在调用 plot() 的时候作为参数传入.

jk: 细节在于有哪些属性可以用, 用来干什么.

15.2.3 使用内置样式

Matplotlib 内置了很多样式, 包括背景色, 网格线, 线条粗细, 字体, 字号等. 执行下面命令可以查看可用内置样式:

import matplotlib.pyplot as plt
for v in plt.style.available:
  print(v)

输出:

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

要使用这些样式, 可以在执行 subplot() 之前执行:

plt.style.use('内置样式名')

15.2.4 使用 scatter() 绘制散点图并设置样式

plot() 是绘制折线图, 使用 scatter() 绘制散点图.

ax.scatter(x_vector, y_vector, ...)

这里的参数可以是向量, 也可以是单个坐标值.

import matplotlib.pyplot as plt
x_vector = list(range(1, 6))
y_vector = [v ** 2 for v in range(1, 6)]
plt.style.use('ggplot')
fig, ax = plt.subplots()
ax.scatter(x_vector, y_vector, s=200)
ax.tick_params(labelsize=14)
plt.show()

image-20240124143658878

同样可以设置标题, 坐标轴标签, 以及刻度标记样式.

15.2.5 使用 scatter() 绘制一些列点 (略)

15.2.4 已说明了该方法, 就是使用向量作为参数.

15.2.6 自动计算 (略)

前面已经介绍了该方法, 就是不用一个个坐标的输入, 使用计算的方法获得 y 向量.

这里介绍一个方法, 可以设置 x, y 轴的取值范围

ax.axis([0, 1100, 0, 1_100_000])

15.2.7 定制刻度标记

默认会使用科学计数法, 如果修改可以使用: ax.ticklabel_format(style='plain')

15.2.8 定制颜色

颜色可以使用名字, 或 rgb 色 (用 [0, 1] 中的数字来映射).

ax.scatter(x_values, y_values, color='red', s=10)
# 或者
ax.scatter(x_values, y_values, color=(0, 0.8, 0), s=10)

15.2.9 使用颜色映射

简单的说, 用颜色来描述下一维度的信息, 让数据用颜色值来呈现另一个信息.

这里演示将较小值用浅色, 较大值用深色的表示.

ax.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues, s=10)

这里的 c 就类似与 color, cmap 可以理解为 color map

image-20240124145216866

可用颜色映射可以参考文档.

15.2.10 自动保存绘图

不使用 show() 方法, 而使用:

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

即可保存到本地, 除此之外, 可以联合使用 Path, 可以保存在任意位置.

参数 bbox_inches='tight', 表示裁剪空白.

15.3 随机游走

简单的说就是生成随机数模拟布朗运动.

15.3.1 创建 RandomWalk 类

创建一个类, 来确定行走方向. 提供三个属性:

  1. 随机游走次数
  2. x 坐标
  3. y 坐标

整个类就两个方法, 一个初始化的构造函数. 另一个是生成随机数据的 fillwalk.

基本思路是, 根据传入的次数, 循环生成坐标 x, y. 生成的方式是, 随机生成一个方向, 然后随机生成一个步长 ([0, 4]), 然后加入到属性中. 然后利用绘制散点图的方式将其绘制出来.

注意, 这个类是用来生成数据的, 绘制不在类中.

这里引用的另一个方法:

ax.set_aspect('equal')

表示设置两个轴线的刻度比率相等.

jk: 这个代码可以自行写一写, 代码还是具有一定综合性的.

然后介绍了绘制多个图的方法. 基本思路:

写一个死循环, 和一个用户输入的阻塞, 在用户输入 q 的时候退出, 否则再次绘制.

while True:
  show() # 绘图
  
  key = input('输入 q 退出')
  if key == 'q':
    break

然后介绍了添加样式, 使得更为好看. 使用颜色映射来展示. 这样可以看出行走的路径. 越往后颜色越深.

然后突出显示起点和终点 (使用 (0, 0) 和 (x_vector[-1], y_vector[-1])).

最后隐藏坐标轴

ax.set_xaxis().set_visable(False)
ax.set_yaxis().set_visable(False)

调整屏幕尺寸

fig, ax = plt.subplots(figsize=(15, 9))

单位为英寸. 也可以传入 dpi 来设置分辨率

fig, ax = plt.subplots(figsize=(15, 9), dpi=128)

15.4 使用 Plotly 模拟掷骰子

使用 plotly 可以生成交互式图形. 可以在浏览器中呈现.

作者首先介绍 plotly express, 然后在配置属性.

plotly express 是 plotly 的子集, 旨在少写代码.

本节会模拟两个骰子投掷的点数, 然后统计绘图.

15.4.1 安装 plotly

python -m pip install --user plotly
python -m pip install --user pandas

plotly express 是依赖于 pandas 的, 所以还需要安装 pandas.

pandas 是一个高效的数据处理库.

15.4.2 创建 Die 类

该类用于描述一个骰子, 有 6 个面, 投掷依次可以生成 1, 6 的一个随机数.

from random import randint

class Die:
  def __init__(self, num_sides = 6):
    """骰子默认为 6 个面"""
    self.num_sides = num_sides
    
  def roll(self):
    """返回一个属于 [1, 6] 的随机数"""
    return randint(1, self.num_sides)

然后作者对代码进行了解释.

15.4.3 掷骰子

实例化, 然后调用 roll() 方法

from die import Die

die = Die()

results = []
for roll_num in range(100):
  result = die.roll()
  results.append(result)

然后解释了代码.

15.4.4 分析结果

就是对数据进行统计. 统计每一个数字出现的次数, 使用 列表.count(数字) 方法.

最终会得到一个形如 [155, 167, 168, 170, 159, 181] 的数据.

15.4.5 绘制直方图

使用 plotly.express 来绘制直方图:

  1. 导入 plotly.express 模块, 命名为 px
  2. 调用其 bar() 方法, 传入数据: 分量数据, 统计数据 (逻辑上就是 x 与 y 向量)
  3. 调用 show() 方法绘图
import plotly.express as px

x = [1, 2, 3, 4]
y = [123, 234, 213, 10]

fig = px.bar(x = x, y = y)
fig.show()

运行后会自动打开浏览器

image-20240124174945335

express 的意图在于, 先看看是否复合要求, 再深入其他配置. 所以显示的相对比较捡漏.

对于可用的图可以参考文档.

15.4.6 定制绘图

确定统计图类型后, 再进行配置标签, 样式等外观.

定制也有两个方法:

  1. 调用 bar() 这类绘图函数时传入配置参数
  2. 调用绘图函数后, 在显示 (show()) 前, 调用配置方法进行配置.
title = '统计显示的结果'
labels = { 'x': '结果', 'y': '频率' }
fig = px.bar(x=..., y=..., title=title, labels=labels)
fig.show()

然后作者对代码进行了一定解释.

15.4.7 同时投两个骰子

基本逻辑是创建两个 Die 对象, 将 roll() 的结果加起来, 存储, 再统计.

这里有几个扩展

  • 默认情况下, x 轴不会为每一个条形图添加 label, 使用 fig.update_layout(xaxis_dtick = 1) 进行配置.
  • 然后又模拟了不同面的骰子进行投掷 (通过构造函数传入面数), 从而抽象了骰子的意义, 可以将其应用到任意概率均衡的选择游戏中. 并利用这个程序可以清晰的模拟出不同频次, 不同点数的结果.
  • 最后将统计图呈现出来, 也可以使用 fig.write_html('文件名') 来保存 html 文件.

从统计的结果来看, 也是满足正态分布特征的.

ch16 下载数据

对网上下载的数据进行可视化处理, 解析两种格式, 一个 csv, 一个 json.

16.1 csv 文件格式

首先解释了 csv 格式的样式 (用逗号分隔数据).

16.1.1 解析 csv 文件头

标准库中的 csv 模块提供解析 csv 功能.

csv 格式的文件第一行为表格头, 从第二行开始才是数据.

基本逻辑:

  1. 导入 path 中的 Path() 函数, 以及 csv 模块.
  2. 利用 Path() 函数返回的对象调用 read_text() 方法, 读取文件文本.
  3. 利用文本的 splitlines() 方法获得多行数据.
  4. 利用 csv.reader(多行数据) 来解析 csv 数据, 获得迭代器.
  5. 利用内置的 next() 函数来遍历迭代器. 获取每一行数据.

next() 方法是内置方法. 用法有二:

  1. next(iterator) 迭代返回. 逻辑与 C# 中的 next() 方法一样. 遇到结尾会抛出 StopIteration 异常.
  2. next(iterator, default), 迭代返回, 在结束时返回 default.

因此解析 csv 代码可以写成:

from pathlib import Path
import csv

lines = Path('./name.csv').read_text().splitlines()
rows = csv.reader(lines)

while True:
  res = next(rows, False)
  if res == False:
    print('遍历结束')
    break
  print(res)

然后作者介绍了 enumerate() 函数, 对列表调用该函数, 可以返回索引+项

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

单纯的遍历 列表, 仅会返回项, 不会返回列表索引.

然后将数据解析提取出来, 这里对于数据需要转换的会使用到 int() 等类型转换函数.

https://docs.python.org/3/library/functions.html#int

细节还是需要看官方文档.

然后利用 matplotlib 绘制折线图.

以上内容与之前介绍的绘图方式逻辑一样 (细节略).

然后作者介绍 datetime 模块, 来格式化日期.

datetime 是内置模块, 作用与 JS 中 dayjs 一样, 用来格式化日期.

文档: https://docs.python.org/3/library/functions.html#int

from datetime import datetime
date = datetime.strptime('2024-01-25', '%Y-%m-%d')
print(date)

这里使用了 strptime 函数, 即 string parse to time, 还有一个函数 strftime, 即 string from time. 简单这么理解.

这里 %Y 4 位年份, %m 2 位月份, %d 从 1 开始的日期天数.

详细用法可以参考: https://docs.python.org/zh-cn/3/library/datetime.html#strftime-and-strptime-behavior

然后构建两个向量:

  • x 轴使用日期时间作为值的向量
  • y 轴记录对应时间气温的数据向量

然后绘制折线图

image-20240125182659600

然后作者又使用了一个更多数据的文件, 绘制了一年的气温变化数据.

最后作者在图中绘制了两组数据, 一个最高温统计数据, 一个最低温统计数据.

实际上就是在一个图中绘制两条折线. 绘制使用的是 ax 变量

fig, ax = plt.subplots()

这里只需要使用 ax.plot(...) 绘制两次即可. 每调用依次就绘制一条折线.

即可以在 一个图中绘制多个折线.

16.1.9 给图中区域着色

ax.plot() 方法可以带有参数 color 用来给绘制的折线着色.

ax.fill_between() 方法提供一组 x 向量, 两组 y 向量, 然后配置颜色, 可以给两组折线间的区域着色.

ax.fill_between(datas, heights, lows, facecolor='blue', alpha=0.1)

其中 datas 是 x 向量, lows 和 heights 为两组 y 向量.

Last Updated:
Contributors: jk