12 Makie包作图
这一部分介绍用Julia的Makie包作图方法。
参考:
- Jose Storopoli, Rik Huijzer, Lazaro Alonso(2022) Julia Data Science. https://cn.julialang.org/JuliaDataScience/
- Makie: https://makie.juliaplots.org/stable/index.html
using DataFrames, DataFramesMeta
using CategoricalArrays
using Statistics
12.1 Makie包
Makie是Julia语言的一个作图扩展包, 特点是高性能富,可扩展性强。 另外与另外常用的Plots扩展包相比, Plots包依赖于不同的后端, 不同的后端支持的功能有多有少, 所以Plots只能选择其中的最小部分。 Makie也有不同后端, 但更强调不同后端的增强功能。
参考:
12.1.1 Makie后端
Makie有如下后端:
- CairoMakie:主要是各种二维图,非动态。 能制作出版质量图形。
- GLMakie:利用OpenGL图形引擎功能, 性能强大,可以制作三维图形, 可以制作动态交互图形。 但不擅长制作矢量图。
- WGLMakie:支持在网页中的交互图形。
- RPRMakie:使用RadeonProRender引擎, 可以制作光线追踪图像。
12.1.2 安装和运行
安装任一后端的同时可以安装Makie。如:
using Pkg; Pkg.add("CairoMakie")
用某个后端作图前,
用using
调用包并调用该后端的activate!()
函数,如:
using CairoMakie
activate!() CairoMakie.
在最基本的REPL环境, CairoMakie后端无法提供图形窗口。 但是VSCode、Jupyter、Pluto等集成编辑环境或笔记本软件都可以支持显示其结果。
GLMakie在REPL也可以提供自己的图形窗口显示。
12.1.3 样例数据
12.1.3.1 学生身高体重年龄
设当前工作目录有class19.csv文件内容如下:
name,sex,age,height,weight
Sandy,F,11,130,23
Karen,F,12,143,35
Kathy,F,12,152,38
Alice,F,13,144,38
Becka,F,13,166,44
Tammy,F,14,160,46
Gail,F,14,163,41
Sharon,F,15,159,51
Mary,F,15,169,51
Thomas,M,11,146,39
James,M,12,146,38
John,M,12,150,45
Robert,M,12,165,58
Jeffrey,M,13,159,38
Duke,M,14,161,46
Alfred,M,14,175,51
William,M,15,169,51
Guido,M,15,170,60
Philip,M,16,183,68
读入为数据框:
using DataFrames, DataFramesMeta, CSV
using CategoricalArrays
= CSV.read("class19.csv", DataFrame)
dclass transform!(dclass,
:sex => (s -> categorical(s)),
= false)
renamecols transform!(dclass, :sex => (x -> levelcode.(x)) => :sexi,
:age => categorical => :agec)
19 rows × 7 columns
name | sex | age | height | weight | sexi | agec | |
---|---|---|---|---|---|---|---|
String7 | Cat… | Int64 | Int64 | Int64 | Int64 | Cat… | |
1 | Sandy | F | 11 | 130 | 23 | 1 | 11 |
2 | Karen | F | 12 | 143 | 35 | 1 | 12 |
3 | Kathy | F | 12 | 152 | 38 | 1 | 12 |
4 | Alice | F | 13 | 144 | 38 | 1 | 13 |
5 | Becka | F | 13 | 166 | 44 | 1 | 13 |
6 | Tammy | F | 14 | 160 | 46 | 1 | 14 |
7 | Gail | F | 14 | 163 | 41 | 1 | 14 |
8 | Sharon | F | 15 | 159 | 51 | 1 | 15 |
9 | Mary | F | 15 | 169 | 51 | 1 | 15 |
10 | Thomas | M | 11 | 146 | 39 | 2 | 11 |
11 | James | M | 12 | 146 | 38 | 2 | 12 |
12 | John | M | 12 | 150 | 45 | 2 | 12 |
13 | Robert | M | 12 | 165 | 58 | 2 | 12 |
14 | Jeffrey | M | 13 | 159 | 38 | 2 | 13 |
15 | Duke | M | 14 | 161 | 46 | 2 | 14 |
16 | Alfred | M | 14 | 175 | 51 | 2 | 14 |
17 | William | M | 15 | 169 | 51 | 2 | 15 |
18 | Guido | M | 15 | 170 | 60 | 2 | 15 |
19 | Philip | M | 16 | 183 | 68 | 2 | 16 |
12.1.3.2 Cleveland心脏病数据
这是UCI网站的一个机器学习用样例数据, 关注的因变量是num,是一个取0,1,2,3,4数值的变量, 需要区分0与非0。 变量有:
- age
- sex,1为男,0为女
- cp, 胸痛类型:
- 1:典型心绞痛
- 2:非典型心绞痛
- 3:非心绞痛类型
- 4:无症状
- trestbps: 收缩压
- chol: 胆固醇
- fbs: 快速血糖是否超标,1为超标,否则0
- restecg:心电图,
- 0: 正常
- 1: T-ST波异常
- 2: 提示心室肥大
- thalach: 最大心率
- exang: 是否锻炼引发心绞痛,1有,0无
- oldpeak: 锻炼引发的ST波降幅
- slope: 锻炼ST波峰斜率,
- 1: 向上
- 2:平缓
- 3: 向下
- ca: 造影检查显示大血管数,取0,1,2,3
- thal:
- 3: 正常
- 6:固化缺陷
- 7: 可恢复缺陷
- num: 0表示不诊断心脏病, 1,2,3表示血管造影诊断有心脏病, 主要血管50%以上狭窄
using Downloads
= "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
urlf = CSV.read(Downloads.download(urlf), DataFrame,
dht =0)
headerrename!(dht, ["age", "sex", "cp", "trestbps", "chol",
"fbs", "restecg", "thalach", "exang", "oldpeak",
"slope", "ca", "thal", "num"])
303×14 DataFrame
Row │ age sex cp trestbps chol fbs restecg th ⋯ │ Float64 Float64 Float64 Float64 Float64 Float64 Float64 Fl ⋯─────┼───────────────────────────────────────────────────────────────────── 1 │ 63.0 1.0 1.0 145.0 233.0 1.0 2.0 ⋯ 2 │ 67.0 1.0 4.0 160.0 286.0 0.0 2.0
3 │ 67.0 1.0 4.0 120.0 229.0 0.0 2.0
4 │ 37.0 1.0 3.0 130.0 250.0 0.0 0.0
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱ 300 │ 68.0 1.0 4.0 144.0 193.0 1.0 0.0 ⋯ 301 │ 57.0 1.0 4.0 130.0 131.0 0.0 0.0
302 │ 57.0 0.0 2.0 130.0 236.0 0.0 2.0
303 │ 38.0 1.0 3.0 138.0 175.0 0.0 0.0
7 columns and 295 rows omitted
12.2 简单一次性完成的图形
12.2.1 折线图
考虑如下的简单数据:
= DataFrame(
dline01 = 1:5,
x = [11, 13, 18, 15, 14]) y
5 rows × 2 columns
x | y | |
---|---|---|
Int64 | Int64 | |
1 | 1 | 11 |
2 | 2 | 13 |
3 | 3 | 18 |
4 | 4 | 15 |
5 | 5 | 14 |
= DataFrame(x = [0,1,3,6,7,8], y=[15, 13, 14, 11, 10, 9]) dline02
6 rows × 2 columns
x | y | |
---|---|---|
Int64 | Int64 | |
1 | 0 | 15 |
2 | 1 | 13 |
3 | 3 | 14 |
4 | 6 | 11 |
5 | 7 | 10 |
6 | 8 | 9 |
lines(dline01[:, :x], dline01[:, :y])
上面的例子是假设在交互环境下运行,
所以运行lines(dline01[:, :x], dline01[:, :y])
,
实际是运行了display(lines(dline01[:, :x], dline01[:, :y]))
,
所以图形在REPL、Jupyter、Pluto、VSC等交互环境中能够自动显示。
如果在编程环境中,
需要调用display
函数。
12.2.6 盒形图
boxplot(fill(1, nrow(dclass)), dclass[:, :height])
boxplot(levelcode.(dclass[:, :sex]), dclass[:, :height])
数据框中sex
是分类变量,
boxplot
的横轴仅支持数值型,
所以用了CategoricalArrays.levelcode()
将因子值转换成相应的编码序号整数值。
boxplot(fill(1, nrow(dht)), dht[:, :trestbps])
boxplot(dht[:, :sex], dht[:, :trestbps])
12.2.7 正态QQ图
qqnorm(dclass[:, :height], qqline=:fit)
正态QQ图用来检查输入的变量是否来自正态分布总体, 如果散点与直线比较接近, 则可以认为符合。 如果散点的走向与直线明显偏离, 则认为非正态分布。
qqnorm(dht[:, :trestbps], qqline=:fit)
12.2.9 时间序列图
时间为序号的时间序列折线图:
= DataFrame(time=1:10, y = (1:10) .^2)
df lines(df[:,:time], df[:,:y])
横轴使用日期可以定制, 没有自动完成的例子。
12.2.10 曲面的等高线图
表现二元函数z = f(x, y)
的图像的一种方式是等高线图。
用等间隔的x
轴和y
轴的若干个点组成矩阵形式的网格,
在每个网格点上计算z
坐标,
输入x, y, z
后可以将z
值相等的点连线。
比如,如下的函数: \[\begin{aligned} f(x, y) =& \ \exp\{ -0.5 ((x + 1)^2 + 0.5 y^2) \} \cos(4x) \\ +& \ \exp\{-0.8 (2 x^2 + (y-1)^2) \} \cos(2y) . \end{aligned}\]
function surfd()
= 100
n = range(-pi, pi, n)
xs = range(-pi, pi, n)
ys = [exp(-0.5*((x + 1)^2 + 0.5*y^2))*cos(4*x) +
z exp(-0.8*(2*x^2 + (y-1)^2))*cos(2*y)
in xs, y in ys]
for x return (xs, ys, z)
end
= surfd() x, y, z
contour(x, y, z, levels=20)
12.2.11 曲面的染色等高线图
= surfd()
x, y, z = contourf(x, y, z, levels=20)
fig, ax, plt Colorbar(fig[1,2], plt)
fig
12.2.13 频数条形图
写一个计算变量离散取值频数的函数:
using StatsBase
function freq(x)
= StatsBase.countmap(x)
y = [k for (k, v) in y]
kk = [v for (k, v) in y]
vv return kk, vv
end
将类别变量转换为其显示字符串的函数:
= x -> levels(x)[levelcode.(x)] cat2string
dclass中性别的频数条形图:
= freq(cat2string(dclass[:, :sex]))
sex, n barplot(1:2, n,
=(; width=100, height=200,
axis=(1:2, sex) )) xticks
Makie不支持对分类变量自动统计频数再作条形图, 所以上述程序比较复杂。 第5节讲的AlgebraOfGarphics提供了自动统计频数作频数条形图的功能。
12.3 Makie重要概念和作图步骤
- 绘图板(figure),相当于绘图用的纸张。
- 坐标系统(axis),可以定位图形内容,设置大小等。
- 绘图(plots)。具体的散点、连线、曲面、热力图等绘图内容。
- 坐标轴、标题等标注。
- 图例,颜色对应条。
- 小图拼凑。
最基本的步骤是用Figure()
函数新建绘图板,
用Axis()
函数在此绘图板上建立一到多个绘图区域并自动生成坐标系统,
用散点、连线等作图函数作图。
也可以省略Figure()
和Axis()
调用,
直接用作图函数做出简单图形。
12.3.1 画布
用Figure()
函数新建一个画布,
可以在其中放置坐标轴、散点、连线、标题、图例、颜色代码表等内容。
其中可以用backgroundcolor
设置背景色,
用resolution
设置大小,
如(800, 600)
,单位是像素。
例:
using CairoMakie
activate!()
CairoMakie.
= Figure(backgroundcolor=:gray,
fig = (800, 300)) resolution
12.3.2 添加坐标轴
用Axis()
命令在已建立的画布中建立坐标系。
最常用的输入是选择画布分格的左上角格子,
如fig[1,1]
,
没有分格子时fig[1,1]
就是画布的全部空间。
这种分格称为“布局”(layout)。
如:
= Axis(fig[1,1]) ax
在Axis()
中用title
指定标题,
xlabel
指定x轴标签,
ylabel
指定y轴标签,如:
= Axis(fig[1,1],
ax = "这是标题",
title = "x",
xlabel = "y") ylabel
12.3.3 添加图形内容及颜色设置
在用Figure()
新建画布,
用Axis()
添加并设置坐标系统以后,
可以用各种绘图函数在选定的坐标系统中添加图形内容。
如:
using CairoMakie
activate!()
CairoMakie.
= Figure(backgroundcolor=:gray,
fig = (800, 600))
resolution = Axis(fig[1,1],
ax = "简单的折线图示例",
title = "x",
xlabel = "y")
ylabel lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
设置折线颜色用color
选项,如:
using Colors
= Figure()
fig = Axis(fig[1,1],
ax = "设置颜色的折线图示例",
title = "x",
xlabel = "y")
ylabel lines!(ax, dline01[:,:x], dline01[:,:y],
= :green)
color display(fig)
其中颜色可以用符号,
常用的:red
, :blue
, :black
, :orange
,
:yellow
, :green
, :cyan
, :purple
, :pink
, :brown
, gray
, white
,
Colors包中许多颜色名称,
还可以用编码表示颜色。
参见:
散点图例子:
= Figure()
fig = Axis(fig[1,1],
ax = "简单的散点图示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
散点图可以用color
指定散点颜色,
用markersize
指定大小(单位为像素),
用marker
指定散点的符号,
用strokecolor
指定散点轮廓线颜色,
用strokewidth
指定散点轮廓线宽度。
如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图设置示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y],
= :purple, marker = :utriangle, markersize=20)
color display(fig)
lines!()
,scatter!()
这些绘图函数输出是绘制的图形对象。
可以用坐标系统(Axis()
的输出)为第一自变量,
如上面的例子;
也可以输入坐标系统,
这时选择当前坐标系统,当前坐标系统可以用current_axis()
访问,
一般是最新生成的坐标系统。
如:
= Figure()
fig = Axis(fig[1,1])
ax scatter!(dline01[:,:x], dline01[:,:y])
display(fig)
12.3.4 多个图层
只要多次调用lines!()
, scatter!()
等函数就可以将多个图形叠加地画在同一坐标系中。
可以使用相同的数据或者不同的数据。如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图和折线图叠加示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y])
lines!(ax, dline02[:,:x], dline02[:,:y], color=:red)
display(fig)
只要在每个图层中用label
参数指定一个图层的标签,
然后调用axislegend()
函数就可以在指定的坐标系统中自动给出图例,如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图和折线图叠加示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y], label="Data A")
lines!(ax, dline02[:,:x], dline02[:,:y], color=:red, label="Data B")
axislegend(ax)
display(fig)
axislegend()
可以用参数position
设置位置,
用l, c, r表示左右位置,
t, c, b表示上下位置,
如默认的:rt
表示右上角。
也可以输入二元组的坐标。
可以不输入所针对的坐标系统,
这时默认选择最后生成的坐标系统。
除了使用统一的属性, 还可以为每个散点分别设置属性, 这只需要将属性选项对应到与输入数据个数等长的属性向量。 如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图分别设置示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y],
=dline01[:,:x] .*2 .+ 10,
markersize= range(0, 1, length=nrow(dline01)),
color = :thermal)
colormap display(fig)
其中colormap
指定一种调色盘,
可以用0到1的数值从调色盘中选择颜色,
这时color
可以指定一个0到1中的范围就可以表示一系列渐变颜色。
也可以直接给color
指定一个颜色值向量。
12.3.5 一次性完成作图
先调用Figure()
,再调用Axis()
,
然后再添加实际图形内容,
这是比较一般的步骤,
可以进行更多定制。
实际上,
直接调用绘图函数也能做出图形,
见节2的例子。
如:
lines(dline01[:,:x], dline01[:,:y])
注意lines
是不带叹号的。
这样的一次性作图的函数本身就会建立画布和坐标系统,
其返回值类型为FigureAxisPlot
,
这是画布(Figure)、坐标系统(Axis)、绘图对象的三个成分的元组。
这样的返回值可以直接用display()
函数显示图形,
在交互运行时也可以不需要调用display()
就可以直接显示。
也可以用如
= lines(dline01[:,:x], dline01[:,:y]) fig, ax, plt
这样的调用格式调用,
再显示fig
。
这样的好处是可以调整fig
和axis
的属性,
还可以对fig
修改布局。
12.4 Makie作图定制
12.4.1 作图函数的一般语法规则
散点图、折线图等函数都使用类似的函数调用规则。
每种图都有不带叹号的版本和带叹号的版本,
如scatter
和scatter!
。
以scatter
为例,
不带叹号的版本可以直接生成画布、坐标系统、绘图对象:
= scatter(args...; kwargs) fig, ax, plt
在命令行、Jupyter或Pluto环境中,
scatter
的调用放在单独一个语句或者多个语句末尾可以自动显示图形。
也可以后续调用在fig
指定的画布或ax
指定的坐标系统中增添内容,
这时就需要将fig
作为最后一个语句或者用draw(fig)
显示最终的图形。
如果不需要fig
, ax
, plt
在后续的修改中使用,
也可以简单地用
scatter(args...; kwargs)
作为命令行或单元格唯一命令或最后一个命令, 直接显示该命令的结果图形。
在已有画布的情况下, 也可以调用
= scatter(gridposition, args...; kwargs) ax, plt
在指定的布局小块或子布局块中作图。
如果后续不需要ax
, plt
的信息也可以直接写
scatter(gridposition, args...; kwargs)
指定布局位置的这种调用方法不支持自动显示结果, 还是要将涉及的画布在命令行或单元格的最后进行显示。
带叹号的版本只能在已有的画布或已有的坐标系统中增加内容, 不能单独生成画布, 返回值总是绘图对象, 不返回画布和坐标系统。 各种格式如:
scatter!(args...; kwargs...) # 使用当前默认坐标系统
scatter!(figure, args...; kwargs...) # 使用指定画布的[1,1]位置
scatter!(gridposition, args...; kwargs...) # 使用指定布局小块或子布局块
scatter!(axis, args...; kwargs...) # 使用指定坐标系统
scatter!(scene, args...; kwargs...) # 使用指定场景
需要的话可以用如
= scatter!(args...; kwargs...) plt
保存对应的图形对象。
12.4.2 在作图函数中设置画布和坐标系统属性
可以用Figure()
设置与绘图板有关的属性,
包括背景色,像素大小等;
用Axis()
设置与坐标系统有关的属性,
包括标题、轴标题、刻度设置等。
如:
= Figure(backgroundcolor = :gray80,
fig = (400, 300))
resolution = Axis(fig[1,1], title = "测试标题")
ax = scatter!(dline01[:,:x], dline01[:,:y], label="散点图")
plt fig
返回画布、坐标系统和绘图对象三元组的绘图函数(如scatter(args...; kwargs)
)
支持一个figure
选项,
输入为一个命名元组,
其中可以使用与Figure()
函数类似的关键字参数。
如:
scatter(dline01[:,:x], dline01[:,:y],
= (; backgroundcolor=:gray80, resolution=(400,300))) figure
返回画布、坐标系统和绘图对象三元组的绘图函数(如scatter(args...; kwargs)
)和返回坐标系统和绘图对象二元组的绘图函数(如scatter(gridposition, args...; kwargs)
)
支持一个axis
选项,可以在其中指定与坐标系统有关的属性。
属性写成命名元组的格式,
其中的元素值与Axis()
命令的关键字参数相同。
许多元素本身也会写成元组或者命名元组形式。
如:
scatter(dline01[:,:x], dline01[:,:y],
= (; backgroundcolor=:gray80, resolution=(400,300)),
figure = (; title="标题", xticks = 1:5)) axis
另一种调整画布和坐标轴属性的方法是用“主题”的方法进行统一的调整, 见后面关于主题的说明。
12.4.3 绘图对象属性
scatter
等一次性作图函数返回画布、坐标系统、绘图对象的三元组,
在指定布局位置时返回坐标系统、绘图对象的二元组,
scatter!()
这样的可变作图函数也返回绘图对象。
设plt
为一个绘图对象,
则plt.attributes
包括了绘图对象的各种属性,
比如颜色,符号,线型,字体等。
如:
= scatter(dline01[:,:x], dline01[:,:y])
fig, ax, plt plt.attributes
Attributes with 30 entries:
color => RGBA{Float32}(0.0,0.447059,0.698039,1.0)
colormap => viridis
cycle => [:color]
depth_shift => 0.0
diffuse => Float32[0.4, 0.4, 0.4]
distancefield => nothing
fxaa => false
glowcolor => (:black, 0.0)
glowwidth => 0.0
inspectable => true
linewidth => 1
marker => Circle
marker_offset => Float32[-4.5, -4.5]
markersize => 9
markerspace => pixel
model => Float32[1.0 0.0 0.0 0.0; 0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0]
nan_color => RGBA{Float32}(0.0,0.0,0.0,0.0)
overdraw => false
rotations => Billboard{Float32}(0.0)
shininess => 32.0
space => data
specular => Float32[0.2, 0.2, 0.2]
ssao => false
strokecolor => black
strokewidth => 0
transform_marker => false
transformation => Automatic()
transparency => false
uv_offset_width => (0.0, 0.0, 0.0, 0.0)
visible => true
对某个绘图函数如scatter
,
可以在命令行用?scatter
查询帮助,
就会显示它支持的属性。
如:
?scatter
12.4.3.1 颜色
Colors包定义了许多颜色名称, 参见:
ColorThemes包提供了许多调色盘,
其中的符号如:viridis
, :heat
可以用作调色盘参数colormap
的值,
参见:
程序如:
let
= range(-pi, pi, 100)
xs = range(-pi, pi, 100)
ys = [exp(-0.1*(x^2 + y^2))*(cos(x) + sin(y))
z in xs, y in ys]
for x = Figure()
fig = Axis(fig[1,1])
ax = contourf!(ax, z, colormap = :heat)
p Colorbar(fig[1,2], p)
display(fig)
end
又如:
let
= -2:0.1:2
x = 10 .- x .^2
y lines(x, y, colormap = :Blues, color = y)
end
colormap的值可以增加一个透明度,
如colormap = (:heat, 0.5)
,
这里0.5是透明度,
越小越透明。
可以用cgrad
函数从给定的若干个颜色生成渐变色,
用作colormap
参数的值。
可以用categorical_colors(:Blues, 3)
从ColorThemes包给出的用符号表示的调色盘生成指定个数的颜色,如:
= categorical_colors(:Blues, 3)
colors = Figure()
fig = Axis(fig[1,1])
ax scatter!(rand(3), rand(3), label="a", color = colors[2])
scatter!(rand(3), rand(3), label="b", color = colors[3])
axislegend()
fig
颜色也可以增加透明度参数,如color = (:red, 0.2)
。
12.4.4 主题
12.4.4.1 自定义
可以用set_theme!(...)
函数设置一批公用的的属性,称为主题,
其中关于绘图板的属性直接作为关键字参数输入,
关于坐标系统的选项用命名元组输入到Axis
参数中。
关于图例的选项用命名元组输入到Legend
参数中。
最后用没有自变量的settheme!()
取消这个主题的作用。
如:
set_theme!(; backgroundcolor=:gray80, resolution=(400,300),
= (; xtickgridvisible=true, ytickgridvisible=true,
Axis =:dot, ygridstyle=:dot),
xgridstyle= (; bgcolor = (:green, 0.2), framecolor=:yellow))
Legend = scatter(dline01[:,:x], dline01[:,:y], label="数据A")
fig, ax, plt scatter!(dline02[:,:x], dline02[:,:y], label="数据B")
axislegend()
set_theme!()
fig
上面程序中bgcolor = (:green, 0.2)
中的0.2
是颜色透明度的设置,
数值越小越透明。
可以用pallette
中的color
设置主题所用的调色盘,
这在多个图层重复时自动循环使用。
如:
set_theme!(; palette = (; color = [:green, :cyan]) )
= scatter(dline01[:,:x], dline01[:,:y], label="数据A")
fig, ax, plt scatter!(dline02[:,:x], dline02[:,:y], label="数据B")
axislegend()
set_theme!()
fig
12.4.4.2 内置主题
有一些内置的主题可用,
如theme_ggplot2()
, theme_minimal()
,
theme_black()
, theme_light()
, theme_dark()
。
上图改用theme_black()
的效果:
set_theme!( theme_black() )
= scatter(dline01[:,:x], dline01[:,:y], label="数据A")
fig, ax, plt scatter!(dline02[:,:x], dline02[:,:y], label="数据B")
axislegend()
set_theme!()
fig
12.4.4.3 调用主题
用settheme!
函数自定义或调用内置主题是使用主题的一种方法,
使用内置主题时也可以用关键字参数修改某一具体设置参数。
使用不带参数的settheme!()
取消这样的主题选择。
还可以用with_theme(自定义绘图函数, 主题)
的格式调用主题。
另一种方法是
with_theme(主题函数()) do
...
end
也可以编写自己的主题函数,从略。
12.4.4.4 设置循环(cycle)
同一坐标系统有多个散点或者折线图层时,
将自动循环使用颜色。
颜色可以用主题的palette
中的color
参数设置一个颜色名的向量,
还可以用categorical_colors(调色盘, n)
从Colors包的某个调色盘生成n
个颜色。
如:
let
= 1:10; y = 2 .* x
x = Figure(); ax = Axis(fig[1,1])
fig for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
figend
这使用了默认的主题和默认的调色盘, 有7个不重复的颜色。
人为指定三个颜色组成的调色盘:
let
= 1:10; y = 2 .* x
x = Figure(palette = (; color = [:red, :blue, :green]))
fig = Axis(fig[1,1])
ax for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
figend
上面的程序在Figure()
中定义了palette
,
也可以在settheme!()
函数中定义。
使用categorical_colors
的例子:
let
= 1:10; y = 2 .* x
x set_theme!(palette = (; color = categorical_colors(:PRGn, 10)))
= Figure()
fig = Axis(fig[1,1])
ax for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
figend
前几个例子中,
只循环利用了颜色,
没有循环利用线型、粗细等因素。
可以在set_theme!
中中定义cycle
参数,
要求哪些因素参与这种循环利用。
比如,cycle = Cycle([:color, :linestyle])
规定先循环利用所有的颜色,
然后再使用下一种线型。
set_theme!
的palette
参数,
除了可以用color
指定一个调色盘,
还可以用marker
指定可选的散点形状,
用linestyle
指定可选的线型。
循环颜色与线型的程序如:
let
= 1:10; y = 2 .* x
x set_theme!(palette = (; color = categorical_colors(:Accent_3, 3)),
= (; cycle = Cycle([:color, :linestyle])))
Lines = Figure()
fig = Axis(fig[1,1])
ax for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
figend
对于散点图,
可重复利用的包括颜色、散点形状,
在set_theme!
中写法如Scatter = (; cycle = Cycle([:color, :marker]))
。
这样的写法是将颜色轮流用一遍后才修改第二种元素,
还可以让这些元素并行地循环使用,这时在Cycle()
中加covary=true
选项,
如:
let
= 1:10; y = 2 .* x
x set_theme!(palette = (; color = categorical_colors(:Accent_3, 3)),
= (; cycle = Cycle([:color, :linestyle], covary=true)))
Lines = Figure()
fig = Axis(fig[1,1])
ax for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
figend
12.4.5 坐标轴设置
12.4.5.1 标题
在Axis()
函数中可以用title
设置标题,
用subtitle
设置子标题(在标题下面一行),
用xlabel
设置x轴标签,
用ylabel
设置y轴标签。
可以用titlealign
指定标题和小标题的对齐方式,
如:left
, :right
。
可以用titlecolor
指定标题颜色,
用titlefont
指定标题字体,
用titlesize
指定标题字体大小(像素)。
小标题也可以使用用类似选项。
可以用titlegap
指定标题与小标题之间的间隙(像素)。
如:
= Figure()
fig = Axis(fig[1,1],
ax = "简单的折线图示例",
title = "使用Axis()设置",
subtitle = 30, subtitlesize = 15,
titlesize =:green, subtitlecolor=:pink)
titlecolorlines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, [0, 10])
display(fig)
12.4.5.2 坐标轴范围
可以用xlims!()
设置x轴范围,如:
= Figure()
fig = Axis(fig[1,1],
ax = "简单的折线图示例",
title = "x",
xlabel = "y")
ylabel lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, [0, 10])
display(fig)
也可以写成xlims!(ax, 0, 10)
。
如果写成xlims!(ax, 10, 0)
,则x轴会逆转过来:
xlims!(ax, 10, 0)
display(fig)
对y轴可以类似地使用ylims!()
调整范围。
对x、y可以同时设置limits!(ax, x1, x2, y1, y2)
。
如果范围下限或者上限设置为nothing
则自动确定。
如xlims!(ax, 0, nothing)
自动设置上限。
也可以用关键字low
指定下限,
关键字high
指定上限。
可以在Axis()
中指定x轴和y轴的上下限如Axis(fig[1,1], limits=(;x1,x2,y1,y2))
,
其中的x1, x2, y1, y2
都可以取nothing
表示自动获取。如:
= Figure()
fig = Axis(fig[1,1], limits = (nothing, 10, nothing, nothing))
ax lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
12.4.5.3 刻度
在Axis()
中用xticks
设置x轴刻度,
用yticks
设置y轴刻度。
直接设置刻度线所在数值组成的向量,如:
= Figure()
fig = Axis(fig[1,1], xticks = 0:2:10)
ax lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
并没有达到我们的目的。
这是因为坐标轴刻度有一些内部算法,
程序输入的要求仅作为目标。
配合xlims!
:
= Figure()
fig = Axis(fig[1,1], xticks = 0:2:10)
ax lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 10)
display(fig)
xticks
的值也可以写成[0, 2, 4, 6, 8, 10]
这样的向量值。
也可以指定标签:
= Figure()
fig = Axis(fig[1,1], xticks = (0:2:6, ["t0", "t2", "t4", "t6"]))
ax lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
可以用WilkinsonTicks(n)
表示有n
个刻度,
如:
= Figure()
fig = Axis(fig[1,1], xticks = WilkinsonTicks(4))
ax lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
因为常用各种等间隔刻度,
所以提供了MultiplesTicks()
函数用来指定特殊间隔的刻度,如:
let
= DataFrame(x = 0:0.1:(4*pi))
da transform!(da, :x => ByRow(sin) => :y)
= Figure()
fig = Axis(fig[1,1], xticks = MultiplesTicks(5, pi, "π"))
ax lines!(ax, da[:,:x], da[:,:y])
figend
可以在Axis()
中用xtickformat
参数指定一个函数,
该函数输入刻度值向量,
返回显示字符串向量。
ytickformat
类似。
如:
= Figure()
fig = Axis(fig[1,1], xtickformat = (s -> string.(s) .* "E3"))
ax lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
xtickformat
还可以指定一个格式字符串,其中可以用如{:.2f}
这样的方法指定一个刻度值的输出格式,
也可以有其他原样内容。如:
= Figure()
fig = Axis(fig[1,1], xtickformat = "x = {:.2f}")
ax lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
可以用xticklabelrotation
指定一个数值使得x轴刻度值逆时针旋转指定的弧度数。
如:
= Figure()
fig = Axis(fig[1,1], xtickformat = "x = {:.2f}",
ax = pi/6)
xticklabelrotation lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
12.4.5.4 网格线
二维图一般只在下方显示x轴,
左侧显示y轴。
可以在Axis()
中加xticksmirrored=true
使得上侧也显示刻度线,
用xgridvisible=true
使得x轴刻度包括网格线,
还可以加xminorticksvisible=true
显示细刻度,
加xminorgridvisible=true
显示细刻度网格线。
需要细刻度时,指定xminorticks=IntervalsBetween(n)
表示两个粗刻度之间用细刻度等分为n段。
y轴类似。
= Figure()
fig = Axis(fig[1,1],
ax = true,
xticksmirrored = true,
yticksmirrored =true,
xgridvisible=true)
ygridvisiblelines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
对某个坐标系统ax
,
可以用hidespines!(ax)
令其不显示坐标轴。
可以用hidexdecorations!(ax)
令其不显示刻度线、刻度值、网格线。
12.4.5.5 对数坐标轴
在Axis()
中设yscale = log10
可以对y轴使用对数轴。x轴类似。
可取的值包括identity
(缺省值), log10
, log2
, log
, sqrt
, Makie.logit
。
12.4.5.6 宽高比
坐标系统属性aspect
用来控制宽高比。
如果不控制宽高比,
正圆可能会变成椭圆:
let
= 0:0.1:(2*pi)
th = cos.(th)
x = [x; x[1]]
x = sin.(th)
y = [y; y[1]]
y = DataFrame(x=x, y=y)
da lines(da[:,:x], da[:,:y])
end
用坐标系统选项aspect=1
控制宽高比为\(1:1\):
let
= 0:0.1:(2*pi)
th = cos.(th)
x = [x; x[1]]
x = sin.(th)
y = [y; y[1]]
y = DataFrame(x=x, y=y)
da lines(da[:,:x], da[:,:y],
= (; aspect = 1))
axis end
12.4.6 内部图例
在具体制作图形的函数(如lines()
)中用label
选项为某一层图形指定标签,
然后用axislegend()
制作某一坐标系统的图例。
如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图和折线图叠加示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y],
= "数据集A")
label lines!(ax, dline02[:,:x], dline02[:,:y], color = :red,
= "数据集B")
label axislegend(ax)
display(fig)
注意axislegend()
的函数名没有叹号。
可以不输入所针对的坐标系统,
这时针对最新生成的一个坐标系统。
用marker
可以修改散点形状,
取值如:circle
, :rect
, :utriangle
,
:dtriangle
, :diamond
, :pentagon
, :cross
, :xcross
等。
用linestyle
可以修改线型,取值如nothing
(实线),
:dash
, :dot
, :dashdot
, :dashdotdot
等。
如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图和折线图叠加示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y],
= :utriangle, label = "数据集A")
marker lines!(ax, dline02[:,:x], dline02[:,:y],
= :red, linestyle = :dot, label = "数据集B")
color axislegend(ax)
display(fig)
axislegend()
可以加merge=true
,
使得关于同一组数据的点、线图例合并,
可以用nbanks
指定分栏数,
用labelsize
指定标签文字大小像素数。
12.4.7 简单布局
Makie支持将画布灵活地分割为多个小块, 在每一小块上分别设定坐标系统作图。 这些小块可以是等大小的,如\(1 \times 2\), \(2 \times 1\), \(2 \times 2\), 也可以利用相邻格子合并的方法使各个小块占据不同大小。 还可以用命令调整某列或某行的宽度。
在Axis()
中输入绘图位置时,
默认使用第[1,1]
块,
可以不分割小块。
这里位置可用如[1,2]
, [2,1]
,
[2, 1:2]
,
使用范围时表示对应该范围的小块合并使用。如:
= Figure()
fig = Axis(fig[1, 1:2],
ax1 ="height", ylabel="weight")
xlabel= Axis(fig[1, 3],
ax2 ="age", ylabel="weight")
xlabel= Axis(fig[2, 1:3],
ax3 ="height")
xlabel fig
上面的结果就是用了\(2 \times 2\)布局, 然后将第二行的左右两格合并为一个小图。 分别在三个小图中作图:
scatter!(ax1, dclass[:, :height], dclass[:, :weight])
scatter!(ax2, dclass[:,:age], dclass[:,:weight])
hist!(ax3, dclass[:, :height])
display(fig)
有多个小图时,
如果小图之间的x变量, y变量相同,
可能需要对应的坐标轴使用相同的坐标范围。
用linkxaxis!(ax1, ax2)
可以使两个小图的x坐标轴联系起来,
y轴类似。
12.4.8 外部图例
可以指定外部的图例,
位置设定与指定每个小图位置类似。
内容由不同图层的label
参数决定。
如:
= Figure()
fig = Axis(fig[1,1],
ax = "散点图和折线图叠加示例",
title = "x",
xlabel = "y")
ylabel scatter!(ax, dline01[:,:x], dline01[:,:y],
= :utriangle, label = "数据集A")
marker lines!(ax, dline02[:,:x], dline02[:,:y],
= :red, linestyle = :dot, label = "数据集B")
color Legend(fig[1,2], ax)
display(fig)
12.4.9 颜色代码条
热力图之类的图形用不同颜色代表z坐标值大小,
一般应该附上一个将颜色与数值参照对应的颜色条作为说明。
使用函数Colorbar()
,摆放位置也与指定小图位置类似。
如:
let
= range(-pi, pi, 100)
xs = range(-pi, pi, 100)
ys = [exp(-0.1*(x^2 + y^2))*(cos(x) + sin(y))
z in xs, y in ys]
for x = Figure()
fig = Axis(fig[1,1])
ax = contourf!(ax, z)
p Colorbar(fig[1,2], p)
display(fig)
end
12.5 GridLayout与嵌套布局
在前面的例子中,
如果绘图板为fig
,
可以直接用fig[1,1]
, fig[1,2]
, fig[2,1:2]
这样的方法规定小图的分割。
这样的方法比较受限,
除了可以左右合并、上下合并的灵活性以外,
基本都是对齐的,
但有些布局不希望完全对齐。
为此,
在绘图板(用Figure()
生成)和坐标系统(用Axis()
生成)之间,
再额外添加一个虚拟的布局容器(用GridLayout()
生成),
这就增加了自由度,
布局容器是可以嵌套的。
12.5.1 实例
生成绘图板和嵌套的摆放容器:
= Figure(resolution = (1000, 700))
fig = fig[1,1] = GridLayout()
ga = fig[2,1] = GridLayout()
gb = fig[1:2, 2] = GridLayout() # 其中再嵌入方格摆放层
gcd = gcd[1,1] = GridLayout()
gc = gcd[2,1] = GridLayout(); gd
在ga给出的摆放容器内, 用基本的\(2 \times 2\)分割方法做出三个小图:
= Axis(ga[1,1])
axa1 = Axis(ga[2,1])
axa2 = Axis(ga[2,2])
axa3
let
= freq(dht[:, :sex])
sex, n1 barplot!(axa1, sex, n1)
= freq(dht[:, :cp])
cp, n2 barplot!(axa2, cp, n2)
= freq(dht[:, :restecg])
restecg, n3 barplot!(axa3, restecg, n3)
end
fig
在gb给出的摆放容器中, 作盒形图:
= Axis(gb[1,1])
axb boxplot!(axb, dht[:,:sex], dht[:,:thalach])
fig
在gc中作散点图:
= Axis(gc[1,1])
axc scatter!(axc, dht[:,:age], dht[:,:thalach])
fig
在gd中作直方图:
= Axis(gd[1,1])
axd hist!(axd, dht[:,:thalach])
fig
这样的结果还是a, b, c, d四块左右上下对齐的\(2 \times 2\)形状。 但是, 按照初始的设计,右侧的两个图实际上是合并在一起后又嵌套地分开的, 所有右侧的两个图的相对大小是可以独立于左侧的两个图调整的; 左侧两图和右侧两图的宽度也是可以调整的。
调整左右两栏的比例,调整右侧上下两块的比例:
colsize!(fig.layout, 1, Auto(0.5))
rowsize!(gcd, 1, Auto(1.5))
fig
这个例子还需要添加图例,对坐标轴、小图间隙进行调整。 Makie的GridLayout有很强的对齐调整功能。 详见Makie手册。
12.5.2 大小调整
可以指定某列小图的宽度或者某行小图的高度, 单位为像素。如:
colsize!(fig.layout, 1, Fixed(200))
rowsize!
类似。
可以指定某列小图的宽度占总宽度的比例, 没有指定的列自动分配,如:
colsize!(fig.layout, 1, Relative(1/3))
如果指定colsize!(fig.layout, 1, Auto())
,
则第一列小图宽度按其内容能容纳为限自动调整。
用Auto(1)
, Auto(2)
等值指定一个建议比例,
在确定宽度时有一系列优先级的判断和计算。
还可以用colsize!(fig.layout, 1, Aspect(1, 1))
表示第一列的宽度等于第一行的高度,
如果是colsize!(fig.layout, 3, Aspect(1, 2))
,
则表示第三列的宽度等于第一行高度的二倍。
12.5.3 间隙
用
colgap!(fig.layout, 1, Relative(0.1))
可以在第一列小图与第二列小图之间增加间隙,
宽度为绘图板总宽度10%。
可以用其它的宽度单位。
rowgap!
函数类似。
如果不指定那一列或那一行, 就是所有列或者所有行之间, 如:
rowgap!(fig.layout, 10)
对所有小图行之间都增加10个像素间隙。
12.5.4 使用突出部分
在坐标轴组成的方框内部是实际图形内容,
外部可以有标题、刻度线、刻度值、轴标签等内容,
外部的这些区域称为突出部分(protrusion)。
可以在突出部分添加内容,
使用方法类似于Figure(1,2)
这样的布局位置设定,
但这时改为Figure(1, 2, 突出位置)
这样的写法,
突出位置如Left()
, Top()
, TopLeft()
等。
用Label()
在这样的突出位置添加文字内容。
如:
= Figure()
fig = Axis(fig[1,1])
ax1 = Axis(fig[1,2])
ax2 scatter!(ax1, dht[:, :thalach], dht[:,:trestbps])
Label(fig[1,1,TopLeft()], "(a)")
Label(fig[1,1,Right()], "血压对最大心律", rotation = pi/2)
boxplot!(ax2, fill(1, nrow(dht)), dht[:,:trestbps])
Label(fig[1,2,TopLeft()], "(b)")
fig
12.6 各种图形函数参考
12.6.1 scatter
scatter
主要使用color
,
marker
,markersize
进行调整。
输入的数据可以是scatter(xs, ys)
,
即输入两个向量分别表示x坐标和y坐标,
也可以输入一个向量的向量或元组的向量,
其中每个元素代表一对x, y坐标。
这种做法如:
let
= 0:0.1:2*pi
xs = sin.(xs)
ys = collect(zip(xs, ys))
points scatter(points)
end
也可以用函数Point2f.(xs, ys)
将两个向量转换为成对坐标的向量的形式,
此函数将一对x, y值转换成Point类型。如:
let
= 0:0.1:2*pi
xs = sin.(xs)
ys = Point2f.(xs,ys)
points scatter(points)
end
允许为每个散点指定符号、颜色、大小,
只要为marker
, color
, markersize
输入一个向量。
指定多个颜色时,
用color
输入一个颜色序号序列,
然后用colormap
指定一个调色盘进行颜色映射。如:
let
= 0:0.1:2*pi
xs = sin.(xs)
ys = Point2f.(xs,ys)
points scatter(points, color=1:length(points), colormap=:thermal)
end
同一坐标系统的不同图层将自动获得不同的颜色,
可以用label
参数指定图例文字,
用axislegend()
绘制图例:
let
= Figure()
fig = Axis(fig[1,1])
ax scatter!(ax, rand(10), rand(10), label="数据1")
scatter!(ax, rand(10), rand(10), label="数据2")
axislegend(ax)
figend
12.6.1.1 散点符号参考
Makie的散点符号来自TeX Gyre Heros Makie字体, 可以直接使用unicode字符, 也有一些符号可以用Symbol类型访问。
许多中文输入法有输入字符的功能。 下面的例子展示了许多特殊字符:
let
= "☆○◇□△▽◁▷♡★●◆■▲▼◀▶↖↑↗←→↙↓↘√×❤⊙⊕※▬〓§Ψ☢"
marker_str = collect(marker_str)
markers = Figure()
fig = Axis(fig[1, 1], yreversed = true,
ax = (0.15, 0.15),
xautolimitmargin = (0.15, 0.15)
yautolimitmargin
)hidedecorations!(ax)
for (i, marker) in enumerate(markers)
= Point2f(fldmod1(i, 6)...)
p
scatter!(p, marker = marker, markersize = 20, color = :black)
end
figend
使用如:
scatter(rand(10), rand(10), marker='■', markersize=20)
注意要使用字符'■'
,
而不是字符串"■"
。
下面的例子给出了多个符号名表示的散点符号:
using CairoMakie
= [
markers_labels :rect, ":rect"),
(:star5, ":star5"),
(:diamond, ":diamond"),
(:hexagon, ":hexagon"),
(:cross, ":cross"),
(:xcross, ":xcross"),
(:utriangle, ":utriangle"),
(:dtriangle, ":dtriangle"),
(:ltriangle, ":ltriangle"),
(:rtriangle, ":rtriangle"),
(:pentagon, ":pentagon"),
(:star4, ":star4"),
(:star8, ":star8"),
(:vline, ":vline"),
(:hline, ":hline"),
(:x, ":x"),
(:+, ":+"),
(:circle, ":circle"),
('a', "'a'"),
('B', "'B'"),
('↑', "'\\uparrow'"),
('😄', "'\\:smile:'"),
('✈', "'\\:airplane:'"),
(
]
= Figure()
f = Axis(f[1, 1], yreversed = true,
ax = (0.15, 0.15),
xautolimitmargin = (0.15, 0.15)
yautolimitmargin
)hidedecorations!(ax)
for (i, (marker, label)) in enumerate(markers_labels)
= Point2f(fldmod1(i, 6)...)
p
scatter!(p, marker = marker, markersize = 20, color = :black)
text!(p, text = label, color = :gray70, offset = (0, 20),
= (:center, :bottom))
align end
f
用法如:
scatter(rand(10), rand(10), marker=:rect, markersize=20)
也可以将marker
参数输入为一个向量,
给每个散点分别指定符号;
可以将markersize
参数输入为一个向量,
给每个散点分别指定大小(单位是像素)。
12.7 AlgebraOfGraphics包
为了获得定制的高质量图形, 可以使用Makie的各种定制方法。 这些方法功能强大, 但是要学习的内容比较繁琐。 AlgebraOfGraphics包是与R语言的ggplot2包类似理念的作图包, 以Makie为基础, 但使用更方便, 更容易理解。 AlgebraOfGraphics利用了“图形的代数运算”的思想, 将作图分为若干个可以组合步骤, 形成若干图层。 支持直接使用数据框。
AlgebraOfGraphics的主要元素有:
- data, 引入数据框;
- mapping,将数据框的变量与坐标、颜色、形状等绘图的数值维度建立映射关系;
- visual, 规定与数据无关的一些作图信息, 比如统一的颜色,透明度,大小等;
- 分析,可以用一些简单的表达方法对数据进行变换后再用来作图。
这些元素可以用*
、+
等代数运算组合在一起构成最后的图形结果。
参考:
- AlgebraOfGraphics: http://juliaplots.org/AlgebraOfGraphics.jl/stable/
- Wickham, Hadley (2016). Ggplot2: Elegant graphics for data analysis. New York: Springer.
using AlgebraOfGraphics;
using CairoMakie
12.7.1 简单演示
12.7.1.1 分组变量条形图
set_aog_theme!()
= data(dht) * mapping(:cp) * frequency()
p1 draw(p1, axis = (width = 200, height = 400))
变量cp为胸痛类型:
- 典型心绞痛
- 非典型心绞痛
- 非心绞痛类型
- 无症状
AlgebraOfGraphics包用*
号连接互相补充的设定。
如果希望将图形输出为PNG文件,可将上述程序最后一行改成:
= draw(p1; axis = (width = 200, height = 400))
fig save("barplot-ex.png", fig, px_per_unit=3)
其中关键字参数px_per_unit
用来指定一个放大倍数,
可以省略。
可以将条形按性别分段, 用不同颜色代表性别:
= p1 *
p1b mapping(color = :sex => nonnumeric, stack = :sex => nonnumeric)
draw(p1b, axis = (width = 200, height = 400))
颜色、分组需要使用类别变量。 0表示女性,1表示男性。
并列格式的条形图:
= p1 *
p1c mapping(color = :sex => nonnumeric,
= :sex => nonnumeric)
dodge draw(p1c, axis = (width = 200, height = 400))
12.7.1.2 散点图
最大心律对年龄的散点图:
= data(dht) * mapping(:age, :thalach)
p1 draw(p1)
AlgebraOfGraphics用用+
使两个图层,
两个图层仅共享同一坐标系。
在散点图上面再添加一个拟合线图层:
= p1 + p1 * linear()
p1b draw(p1b)
增加一个颜色(color
)维度,
可以用不同颜色区分性别:
= p1 * mapping(color = :sex => nonnumeric)
p1c draw(p1c)
在没有明确的分组(group
)维度时,
颜色维度还起到了分组的作用。
按颜色分组的散点图图层和按颜色分组的拟合线图层:
= p1 * mapping(color = :sex => nonnumeric) +
p1d * linear() * mapping(color = :sex => nonnumeric)
p1 draw(p1d, axis = (width = 400, height = 300))
男女分别在一个切片(小图)中作图:
= p1 * mapping(layout = :sex => nonnumeric)
p1e draw(p1e, axis = (width = 300, height = 300))
= (p1 + p1 * linear()) *
p1f mapping(layout = :sex => nonnumeric)
draw(p1f, axis = (width = 300, height = 300))
12.7.1.3 二元密度图
考虑年龄和最大心律的二元分布密度, 可以用热力图表示。
= data(dht) * mapping(:age, :thalach) *
p2 density()
AlgebraOfGraphics.draw(p2, axis = (width = 300, height = 300))
作成等高线图:
= p2 * visual(Contour)
p2b draw(p2b, axis = (width = 300, height = 300))
用加号增加散点图层和拟合线图层:
= p1 +
p2c * linear() +
p1 * visual(Contour)
p2 draw(p2c, axis = (width = 300, height = 300))
12.7.2 保存为图像文件
设某个draw()
的作图结果保存为变量fig
,
则与Makie保存图像文件一样,
用
save(fname, fig)
可以保存为PNG格式,
其中fname
是以.png
或.PNG
结尾的文件名,
如"testsave.png"
。
可以用关键字参数px_per_unit
指定一个放大倍数。
12.7.3 data
函数
作图用的原始数据用data(df)
指定,
其中df
是数据框,
也可以是Tables.jl
格式兼容的其它表格数据。
注意需要区分数值型变量和分类变量,
分类变量需要转换为分类变量(CategoricalArray)格式。
另一种常用的df
类型是有名元组,
它可以比数据框更一般,
比如,
一个元素为矩阵,
另一个元素为向量。
例如:
= collect(range(0, 2π, 100))
x = sin.(x)
y = data((; x=x, y=y)) * mapping(:x, :y) * visual(Lines)
p1 draw(p1)
12.7.4 mapping
函数
映射函数mapping()
用来指定数据框中的变量如何与图形中表示数值的坐标、颜色、散点形状、散点大小等联系起来。
此函数有x, y, z
位置参数,
映射到x轴坐标、y轴坐标和z轴坐标。
颜色等图形维度用mapping()
的关键字参数输入。
这些维度有些仅允许对应到离散取值变量,
有些允许对应离散取值变量也允许连续取值变量。
如果映射中存在分类变量(如为某分类变量不同类别指定不同颜色),
就会将其它变量也按此变量分组。
也可以直接映射一个group
维,
用来明确地分组处理。
在映射变量时,可以用:变量名 => "显示名"
设置图形显示时的变量名,如:
= data(dht) * mapping(:age => "年龄", :thalach => "血压")
p1 draw(p1, axis = (width = 300, height = 300))
还可以用:变量名 => 变换函数 => "新变量名"
的方式指定变量的变换,
映射变换后的新变量,如:
= data(dclass) * mapping(:height => (x -> x / 100) => "身高", :weight => "体重")
p1 draw(p1, axis = (width = 300, height = 300))
注意其中的t -> t / 100
没有写成广播形式,
这是AlgebraOfGraphics自动进行了逐行变换。
也可以用DataFramesMeta.transform!
函数预先定义新变量,
此函数在指定变换时需要用加点的广播方式表示向量运算:
= copy(dclass)
dc transform!(dc,
DataFramesMeta.:height => (x -> x ./ 100) => "身高(米)")
= data(dc) * mapping("身高(米)", :weight => "体重")
p2 draw(p2, axis = (width = 300, height = 300))
12.7.4.1 简化的变换
有一些比较常用的变换, AlgebraOfGraphics包提供了相应的函数。
对分类变量,
renamer()
变换可以在mapping()
中临时指定不同的标签值,
如:
= data(dclass) * frequency() * mapping(
p1 :sex => renamer("F" => "女", "M" => "男") => "性别")
draw(p1, axis = (width = 200, height = 400))
分类变量的类别次序可以用sorter([新次序])
在mapping
中临时修改次序,如:
= data(dclass) * frequency() * mapping(
p2 :sex => sorter(["M", "F"]))
draw(p2, axis = (width = 200, height = 400))
需要将数值型变量作为分类变量使用时,
可以用nonnumeric
函数在mapping
中临时修改其用途,
如:
= data(dclass) * frequency() *
p3 mapping(:age => nonnumeric)
draw(p3, axis = (width = 200, height = 400))
注意上面的nonnumeric
不能写成nonnumeric()
。
另外,
在mappings()
中可以用:变量名 => verbatim
指定该变量原样使用,
不进行任何映射(变换),
可以用来在坐标系中指定坐标位置添加文本内容,
也可以输入适当的颜色名直接指定颜色。
12.7.5 visual
函数
visual
函数用来指定与数据无关的一些图形设定,
如统一的颜色、字体大小、透明度。
可以用visual
指定要制作的图形类型,如:
visual(Scatter)
: 散点图;visual(BarPlot)
: 条形图。visual(Line)
: 折线图,等等。
图形类型可以使用Makie支持的图形类型,
类型名使用首字母大写的“骆驼式”命名,
比如Makie中作带有散点的折线图的函数是scatterline
,
相应的类型就写成ScatterLine
。
一些mapping()
有默认的图形类型,
可以省略用visual()
指定图形类型的步骤。
比如,mapping()
中仅指定一个分类变量的位置参数,
就会自动进行频数统计并作频数条形图。
因为同样的映射可以做不同的图形,
所以可以先用data()
输入数据框,
用mapping()
输入映射,
在实际绘图时才添加visual()
设定,如:
= data(dline01) * mapping(:x, :y)
pv1 draw(pv1 * visual(Scatter, color=:blue), axis=(width=300, height=300))
draw(pv1 * visual(Lines), axis=(width=300, height=300))
draw(pv1 * visual(ScatterLines), axis=(width=300, height=300))
12.7.6 小图
可以将变量映射到col
,
结果制作小图,
小图根据指定的变量的值按列摆放。
如:
= data(dclass) * mapping(:height, :weight)
pdm1 = mapping(col = :sex)
pf1 draw(pdm1 * pf1)
也可以将变量映射到row
,
使得该变量每个值映射到一个小图,
按行摆放,如:
= data(dclass) * mapping(:height, :weight)
pdm1 = mapping(row = :sex)
pf1 draw(pdm1 * pf1)
可以同时使用col
和row
映射,
根据两个变量来区分小图。
这样的小图摆放方式称为GridLayout(格子摆放)。
为此,制作一个简单的样例数据:
= DataFrame(f1 = categorical(rand(["a", "b"], 100)),
dlay01 = rand(["c", "d"], 100),
f2 = randn(100), y0 = randn(100))
x0 transform!(dlay01, [:y0, :f1] => ByRow((x, f) -> f == "a" ? x + 1 : x + 4) => :y,
:x0, :f2] => ByRow((x, f) -> f == "c" ? x + 10 : x + 20) => :x) [
100 rows × 6 columns
f1 | f2 | x0 | y0 | y | x | |
---|---|---|---|---|---|---|
Cat… | String | Float64 | Float64 | Float64 | Float64 | |
1 | b | d | -0.942355 | -0.192019 | 3.80798 | 19.0576 |
2 | b | c | 0.186319 | -0.34768 | 3.65232 | 10.1863 |
3 | b | d | -1.19326 | 0.338637 | 4.33864 | 18.8067 |
4 | a | c | -0.327787 | 0.519947 | 1.51995 | 9.67221 |
5 | b | d | -0.736707 | -0.106367 | 3.89363 | 19.2633 |
6 | b | d | -1.92288 | -0.582359 | 3.41764 | 18.0771 |
7 | a | d | 1.71238 | 0.0491358 | 1.04914 | 21.7124 |
8 | b | c | -1.08963 | -0.924149 | 3.07585 | 8.91037 |
9 | b | c | -1.63007 | -0.682275 | 3.31772 | 8.36993 |
10 | b | c | 0.289208 | -1.46859 | 2.53141 | 10.2892 |
11 | a | c | -0.659801 | -0.195474 | 0.804526 | 9.3402 |
12 | b | c | 0.471143 | -0.377275 | 3.62273 | 10.4711 |
13 | a | c | -0.635577 | -2.26178 | -1.26178 | 9.36442 |
14 | b | d | 0.204938 | 0.178108 | 4.17811 | 20.2049 |
15 | b | d | 0.247488 | -1.41551 | 2.58449 | 20.2475 |
16 | a | c | 0.22891 | 1.27275 | 2.27275 | 10.2289 |
17 | b | d | -0.0311451 | 0.0476805 | 4.04768 | 19.9689 |
18 | b | d | 0.224916 | 0.703304 | 4.7033 | 20.2249 |
19 | b | d | 0.264228 | -0.972238 | 3.02776 | 20.2642 |
20 | b | c | -1.17171 | 0.0829377 | 4.08294 | 8.82829 |
21 | a | c | -0.452948 | -0.17994 | 0.82006 | 9.54705 |
22 | b | d | 0.758738 | 0.391855 | 4.39185 | 20.7587 |
23 | b | d | -0.173525 | -0.529281 | 3.47072 | 19.8265 |
24 | b | d | 0.506405 | 0.654003 | 4.654 | 20.5064 |
25 | a | c | -1.8688 | -0.0429526 | 0.957047 | 8.1312 |
26 | b | d | 0.910461 | -1.64598 | 2.35402 | 20.9105 |
27 | b | c | 0.15299 | 1.56182 | 5.56182 | 10.153 |
28 | a | d | 0.0741696 | 0.424776 | 1.42478 | 20.0742 |
29 | a | d | 0.875093 | 0.664427 | 1.66443 | 20.8751 |
30 | b | c | 0.191676 | 1.45628 | 5.45628 | 10.1917 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
= data(dlay01) * mapping(:x, :y)
pdm1 = mapping(row=:f1, col=:f2)
dlay1 draw(pdm1 * dlay1)
使用格子摆放小图时,
可以在draw()
中加facet
(切片,或小图)参数,
参数值为有名向量,
其中用linkxaxes
选择各个小图的x轴是否采用一致的坐标范围。
默认取:maximal
,
不仅上下对齐的x轴采用相同的范围,
实际上所有小图中的x轴都采用相同的范围,
而且仅画最下面一层的x轴,
如上图。
如果取:minimal
,
这时上下对齐的x轴的范围是相同的,
仅画最下面一层的x轴,
但左右的x轴范围不同。如:
draw(pdm1 * dlay1, facet = (; linkxaxes = :minimal))
如果取linkxaxes = :none
,
则每个小图分别画x轴且不使用相同的范围。如:
draw(pdm1 * dlay1, facet = (; linkxaxes = :none))
用layout
映射也起到区分小图的作用,这样的摆放方式称为自动换行(wrap)方式。
如:
= data(dclass) * mapping(:height, :weight)
pdm1 = mapping(layout = :sex)
pf1 draw(pdm1 * pf1)
col
, row
和layout
仅能映射到字符串变量或者字符串变量转换的类别变量。
对数值型,
可以借助nonnumeric
转换,如:
= data(dht) * mapping(:age, :thalach, layout = :cp => nonnumeric)
p1 draw(p1)
12.7.7 分析
某些图形类型需要进行建模分析, 比如拟合直线, 作直方图需要分组统计频数,等等。 这些分析都有相应的函数。
12.7.7.1 用histogram
函数作直方图
为了对连续取值变量作直方图,
可以调用histogram()
函数。如:
= data(dht) * mapping(:thalach) * histogram()
p1 draw(p1)
按性别分段:
= data(dht) * mapping(:thalach,
p1b =:sex => nonnumeric, stack=:sex => nonnumeric) * histogram()
colordraw(p1b)
按性别切片:
= data(dht) * mapping(:thalach,
d1c =:sex => nonnumeric) * histogram()
layoutdraw(d1c)
可以在histogram()
中指定一些参数,
如bins
指定分组数或具体分点。
12.7.7.2 用histogram
函数作二元直方图
心脏病数据集中年龄与血压的二元直方图:
= data(dht) * mapping(:age, :thalach) * histogram(bins=20)
p1 draw(p1)
12.7.7.3 用density
函数作密度估计曲线
对dht中的血压作密度估计曲线:
= data(dht) * mapping(:thalach) * AlgebraOfGraphics.density()
p1 draw(p1)
12.7.7.4 用density
函数作二元密度估计图
心脏病人年龄与血压的二元密度估计热力图,同于二元直方图:
= data(dht) * mapping(:age, :thalach) * AlgebraOfGraphics.density(npoints = 20)
p1 draw(p1)
可见二元密度估计图默认使用了二元直方图。 制作三维曲面图形:
= p1 * visual(Surface)
p1b draw(p1b, axis=(type=Axis3, zticks=0:0.1:0.2,
=(nothing, nothing, (0, 0.001)))) limits
曲面图最好使用GLMakie后端, 以获得交互功能。
12.7.7.5 用frequency
函数作频数条形图
当mapping
中仅有一个分类变量作为位置参数时,
默认就是作频数条形图。
用frequency()
可以对任何变量要求作这种图。
比如,dht中age是一个数值型变量,下面作不同年龄的频数条形图:
= data(dht) * mapping(:age) * frequency()
p1 draw(p1)
12.7.7.7 用linear
画拟合回归直线
对体重和身高拟合回归直线, 作散点图并叠加回归直线。 回归直线自动伴随有置信区间。
= data(dclass) * mapping(:height, :weight) *(
p1 visual(Scatter) +
linear())
draw(p1)
这个例子用了乘法到加法的分配律。
12.7.8 代数运算
AlgebraOfGraphics是“图形的代数”,
可以对单个图层或多个图层进行乘法(*
)或加法(+
)运算。
加法运算满足结合律,
基本满足交换律,
但加号后面的图形会覆盖在加号前面的图形上面,
所以交换后结果不完全相同。
乘法运算满足结合律。
加法与乘法之间满足乘法的右分配律,
左分配律则有一些折扣。
乘法一般用来递进地完成一个图层, 加法一般用来叠加两种不同的图形。 如果多个图层与多个图层相乘, 结果也是所有两两组合后两两相乘的结果, 即两个图层和两个图层相乘可以得到四个图层。
12.7.9 用draw
绘制图形
制作好的图层或多个图层用draw
或者draw!
函数绘制。
draw
自动制作图例,
而draw!
允许后续修改,不自动制作图例,
可以用colorbar!
和legend!
后续添加颜色条图例和一般图例。
draw()
的axis
参数输入各种绘图设置,
如绘图板的宽度、长度,标题、轴标签、轴刻度等。
12.7.9.1 在draw中设置标题
= data(dclass) * mapping(:height, :weight)
p1 draw(p1, axis = (; width=400, height=300, title="体重对身高",
="身高", ylabel = "体重")) xlabel
注意(; width=400, height=300)
这样的写法是“有名元组”的比较规范的写法。
当只有一个元素时必须这样写,
多个元素时不是必须的, 但这样写的用意比较明确。
12.7.9.2 在draw中设置坐标轴
draw()
的axis
参数中还可以规定许多关于坐标系统和坐标轴的设置。
如axis = (; aspect = 1.0)
规定宽高比:
= data(dclass) * mapping(:sex) * frequency()
p1 draw(p1, axis=(; aspect = 1/3))
在axis
中用xticks
指定x轴刻度数字序列,
yticks
指定y轴刻度数字序列,
如:
= data(dht) * mapping(:age, :thalach)
p2 draw(p2, axis = (; xticks = 30:20:70))
= data(dht) * mapping(:age, :thalach)
p3 draw(p3, axis = (; xticks = (30:20:70, ["三十", "五十", "七十"])))
function cp_recode(x)
= string.(Int.(x))
x = categorical(x)
x recode!(x, "1" => "典型心绞痛", "2" => "非典型心绞痛",
"3" => "非心绞痛", "4" => "无症状")
xend
= transform(dht, :cp => cp_recode => :cpc)
dht2 = data(dht2) * mapping(:cpc, :thalach) *
p4 visual(BoxPlot)
draw(p4)
上面的程序对cp变量进行了比较复杂的处理。
这是因为目前CategoricalArrays对字符串转换为分类变量支持最完善,
虽然数值也可以直接转换为字符串,
但是与AlgebraOfGraphics接口不太顺畅。
所以,写了转换函数recode_cp
,
先将cp原来的浮点型转换为整型,
再转换为字符串,
再转换为因子,
并对四个水平适当命名。
因为mapping()
中的变量变换都是期望逐行进行的,
而转换为因子是整列进行的,
所以额外用了transform
函数生成转换后的cpc变量,
而不是在mapping()
中调用recode_cp
。
如果x轴本来是字符型的,
需要修改标签值,
可以在mapping()
中用辅助的renamer()
函数进行修改。
12.7.9.3 在draw中设置绘图板
可以用draw()
的figure
选项输入一个有名元组,
在有名元组中填入背景色、大小等设置。
如draw(fig, figure = (; resolution = (1000, 800)))
。
例:
= data(dht) * mapping(:thalach, layout = :sex => nonnumeric) *
p1 histogram()
draw(p1, figure = (; backgrouncolor=:gray80, figure_padding = 10,
=(600, 300))) resolution
其中figure_padding
是整个绘图版周围边空宽度。
12.7.9.4 在draw中设置图例
绘图函数的图例是自动生成的,
可以在draw()
函数中用legend
参数指定一个有名元组进行图例的设置。
如:
= data(dht) * mapping(:age, :thalach) *
p1 mapping(color = :sex => renamer(0 => "女", 1 => "男") => "性别")
draw(p1)
上面关于不同性别使用不同颜色的图例自动标在右侧。
在draw()
中用figure()
进行一些修改:
draw(p1, legend = (; position = :top, titleposition = :left,
= true, padding = 5)) framevisible
12.7.9.5 在draw中设置调色盘
在Makie中用colormap设置连续值映射到颜色的调色盘,
对于离散值如性别,
可以用color
映射自动匹配颜色,
也允许在draw()
中用palettes
参数指定一个包含元素color
的命名元组,
在color
中人为指定颜色对应关系。
如:
function recode_sex(x)
= string.(Int.(x))
x = categorical(x)
x recode!(x, "0" => "女", "1" => "男")
xend
= transform(dht, :sex => recode_sex => :sexc)
dht2 = data(dht2) * mapping(:age, :thalach) *
p1 mapping(color = :sexc => "性别" )
draw(p1, palettes = (; color = ["女" => :red, "男" => :blue]))
这里为了将原数据集dht中的性别(sex)转换为字符型来源的类别变量,
单独写了转换函数,
还用了transform
函数,
而不是直接在mapping()
函数中转换。
mapping()
函数的转换有一些限制,
而且对数值转换为类别变量兼容性不好。
当需要用的颜色比较多时,
可以人为选择某种渐变色生成指定个数的颜色。
如cgrad(:cividis, 8, categorical=true)
从:cividis
调色盘生成8个渐变色。
12.7.9.6 在draw中设置渐变颜色条
渐变颜色条(colorbar)是将连续取值的变量映射为颜色时的一个对照图例。
可以在draw()
中用colorbar
参数输入一个有名元组用于设置渐变颜色条,
如position = :top
可以放在顶部,
size = 25
规定宽度。
为了修改数值到颜色的映射调色盘,
可以在visual()
函数中用colormap
参数指定一个连续颜色调色盘,
如colormap = :thermal
。
例:
= data(dht) * mapping(:age, :thalach) *
p1 density(npoints = 20) *
AlgebraOfGraphics.visual(Heatmap, colormap = :heat)
draw(p1, colorbar = (; position = :top, size = 25))
其它常用调色盘如:thermal
, :viridis
。
12.7.10 使用LaTeX标签
标题、坐标轴标签等可以使用LaTeX公式,
格式为L"公式"
,如L"\int_0^1 f(x)dx"
。
例:
let
= range(-2, 2, 100)
x = x .^ 2
y = DataFrame(x = x, y = y)
d = data(d) * mapping(:x, :y) * visual(Lines)
p1 draw(p1, axis = (; title = L"Graph of $y = x^2$",
xlabel="x", ylabel = L"x^2"))
end
12.7.11 Algebra与Makie的配合使用
可以在用Makie的Figure()
生成绘图板后,
用draw!()
在此绘图板的指定小块内作图。
如:
= Figure(resolution=(200,400))
fig = data(dht) * mapping(:sex) * frequency()
p1 draw!(fig[1,1], p1)
display(fig)
又如:
= Figure()
fig = data(dht) * mapping(:sex) * frequency()
p1 draw!(fig[1,1], p1)
= data(dht) * mapping(:age) * histogram()
p2 draw!(fig[1,2], p2)
colsize!(fig.layout, 1, Auto(0.5))
fig
两种作图函数混用:
= Figure()
fig = data(dht) * mapping(:sex) * frequency()
p1 draw!(fig[1,1], p1)
= Axis(fig[1,2])
ax2 scatter!(ax2, dht[:,:age], dht[:,:thalach])
colsize!(fig.layout, 1, Auto(0.5))
fig
draw!()
的第一参数也可以取为Axis()
的输出。
如:
= Figure()
fig = Axis(fig[1,1])
ax1 = data(dht) * mapping(:sex) * frequency()
p1 draw!(ax1, p1)
= Axis(fig[1,2])
ax2 scatter!(ax2, dht[:,:age], dht[:,:thalach])
colsize!(fig.layout, 1, Auto(0.5))
fig
12.8 AlgebraOfGraphics更多范例
这一节给出AlgebraOfGraphics包的更多比较完整的应用范例。
12.8.1 散点图和折线图
输入数据、映射是统一的, 可以将这两部分合并, 而visual(图形类型)用乘法与这两部分连接。
仅散点图:
= data(dline01) * mapping(:x, :y)
dm1 = visual(Scatter)
lay1 draw(dm1 * lay1)
仅折线:
= data(dline01) * mapping(:x, :y)
dm1 = visual(Lines)
lay1 draw(dm1 * lay1)
散点和折线两个图层,两个visual
用加法连接:
= data(dline01) * mapping(:x, :y)
dm1 = visual(Scatter) + visual(Lines)
lay1 draw(dm1 * lay1)
也可以使用多个数据集。如两条折线的图形:
= data(dline01) * mapping(:x, :y) * visual(Lines)
dp1 = data(dline02) * mapping(:x, :y) * visual(Lines)
dp2 draw(dp1 + dp2)
12.8.2 盒形图等
盒形图:
= data(dht) * mapping(:cp, :thalach) * visual(BoxPlot)
dp1 draw(dp1; axis=(width=200, height=400))
= data(dht) * mapping(:cp, :thalach) *
dp1 visual(BoxPlot, show_notch=true)
draw(dp1; axis=(width=200, height=400))
Makie的盒形图需要用x轴为分组,y轴为作图的变量。 如果没有分组,就需要预先在数据集中制作一个仅有一类的分类变量或者数值变量。
小提琴图:
= data(dht) * mapping(:cp, :thalach) * visual(Violin)
dp1 draw(dp1; axis=(width=200, height=400))
增加一个二值分组维度的背对背小提琴图:
= data(dht) * mapping(:cp, :thalach,
dp1 = :sex => nonnumeric, side = :sex => nonnumeric) * visual(Violin)
color draw(dp1; axis=(width=200, height=400))
正态QQ图:
= data(dclass) * mapping(:height) * visual(QQNorm, qqline=:fit)
dp1 draw(dp1)
12.8.3 对数轴
在draw()
函数中可以指定对数坐标轴。
考虑如下数据:
= DataFrame(x = 1:10,
dlog01 = [1, 1.5, 2, 3, 6, 9, 14, 20, 28, 30])
y = data(dlog01) * mapping(:x, :y)
dm1 draw(dm1)
将y轴制作成对数轴:
draw(dm1, axis = (;yscale=log))
对数轴要慎用。 散点图、折线图可以放心地使用对数轴, 但如果在散点图上拟合直线或曲线, 对数轴就会造成扭曲, 因为不同于R的ggplot2, AlgebraOfGraphics的对数轴不是对变换后的数据拟合模型, 而是对原始数据拟合模型然后用对数轴画出来。 密度估计也有这样的问题。
12.8.4 多个变量同时作图
有时数据中有多个类似的变量。 如:
= DataFrame(x = rand(100), y = rand(100), z = rand(100)) dn2
100 rows × 3 columns
x | y | z | |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.709204 | 0.809688 | 0.461517 |
2 | 0.256039 | 0.30611 | 0.503989 |
3 | 0.67271 | 0.812424 | 0.256249 |
4 | 0.871437 | 0.803494 | 0.223384 |
5 | 0.710344 | 0.779265 | 0.50994 |
6 | 0.0993146 | 0.723984 | 0.00352218 |
7 | 0.872053 | 0.317638 | 0.213957 |
8 | 0.763101 | 0.992335 | 0.79795 |
9 | 0.889567 | 0.699942 | 0.647065 |
10 | 0.0536789 | 0.226581 | 0.611227 |
11 | 0.23935 | 0.233382 | 0.991148 |
12 | 0.909953 | 0.236486 | 0.0794017 |
13 | 0.220036 | 0.0957219 | 0.699915 |
14 | 0.651666 | 0.721985 | 0.439505 |
15 | 0.382512 | 0.442361 | 0.794247 |
16 | 0.815084 | 0.298066 | 0.206512 |
17 | 0.349096 | 0.859199 | 0.0492409 |
18 | 0.236388 | 0.680518 | 0.355507 |
19 | 0.475883 | 0.404262 | 0.954434 |
20 | 0.714123 | 0.432249 | 0.892232 |
21 | 0.0124185 | 0.206874 | 0.118358 |
22 | 0.913992 | 0.656319 | 0.344226 |
23 | 0.429854 | 0.349274 | 0.845334 |
24 | 0.540799 | 0.843093 | 0.928278 |
25 | 0.388292 | 0.309083 | 0.971027 |
26 | 0.174892 | 0.0513927 | 0.272157 |
27 | 0.766642 | 0.31153 | 0.258802 |
28 | 0.646467 | 0.857145 | 0.872278 |
29 | 0.185689 | 0.301101 | 0.533727 |
30 | 0.773111 | 0.281102 | 0.282939 |
⋮ | ⋮ | ⋮ | ⋮ |
可以将三个变量组合为一个向量的向量,
然后进行统一处理。
这时,
可以用统一的dims(1)
变量来指代这三个变量的区别。
如:
= data(dn2) * mapping([:x, :y, :z] .=> "三个变量") *
p1 density() *
AlgebraOfGraphics.mapping(color = dims(1) => renamer(["x", "y", "z"]))
draw(p1)
注意因为是三个变量,所以用了.=>
的写法而不是=>
。
= data(dn2) * mapping(:x, [:y, :z] .=> "yz") *
p2 visual(Scatter) *
mapping(color = dims(1) => renamer(["y", "z"]))
draw(p2)
这种dims()
的还适用于多个变量与多个变量之间的图形。
这时既可以用dims(1)
,还可以用dims(2)
。
如:
= 100
n = DataFrame(
dn3 = 1 .+ randn(n),
x1 = 5 .+ randn(n),
x2 = 10 .+ randn(n),
y1 = 20 .+ randn(n))
y2 = data(dn3) * visual(Scatter) *
p3 mapping([:x1, :x2], [:y1 :y2], col = dims(1), row = dims(2))
draw(p3)
注意散点图的横坐标写成了[:x1, :x2]
,
纵坐标写成了[:y1 :y2]
,
这样运算结果构成一个\(2 \times 2\)矩阵,
[:x1, :x2]
是一个列向量,
所以用来区分结果矩阵的两行,
用dims(1)
标识;
[:y1 :y2]
是一个行向量,
所以用来区分结果矩阵的两列,
用dims(2)
标识。
在如上作多格图形时,
可以在draw()
中用facet
参数中的linxaxes
指定左右的x坐标轴是否采用统一范围,
linkyaxes
指定上下的y坐标轴是否采用统一范围。
缺省为:none
,用:all
表示要求对齐。
用:x
表示仅x轴。
如:
draw(p3, facet = (;linkxaxes = :all, linkyaxes = :all))
12.9 GLMakie
GLMakie是Makie的后端绘图引擎之一, 与CairoMakie相比, GLMakie长于交互和三维图形能力。 在Jupyter Notebook界面中, GLMakie的交互能力受限, 只能做出动态图形的一个静态版本。
12.9.1 交互能力
在命令行或MS VSCode这样的界面中, 会单独打开一个GLMakie图形窗口, 此窗口中的图形有较强的交互能力:
- 鼠标滚轮可以用来缩放图形;
- 拖选某一矩形区域可以聚焦显示此区域;
- 三维图形可以拖动从不同角度查看。
using GLMakie
activate!() GLMakie.
12.9.2 三维散点图、折线图
需要用Axis3
生成三维图需要的坐标系统。
可以用scatter!(ax, x, y, z)
作三维散点图,
这种图中散点形状并不受坐标轴伸缩的影响。
如:
= Figure()
fig = Axis3(fig[1, 1]; aspect=(1, 1, 1),
ax =0.5,
perspectiveness="年龄", ylabel="最大心率", zlabel="血压")
xlabelscatter!(ax, dht[:,:age], dht[:,:thalach], dht[:,:trestbps])
fig
还可以用meshscatter!
作三维散点图,
其中的符号是真正的几何形体,
会随坐标轴伸缩而变形。
如:
= Figure()
fig = Axis3(fig[1, 1]; aspect=(1, 1, 1),
ax =0.5,
perspectiveness="年龄", ylabel="最大心率", zlabel="血压")
xlabelmeshscatter!(ax, dht[:,:age], dht[:,:thalach], dht[:,:trestbps],
= 1)
markersize fig
可以在Axis3()
中用aspect=:data
使得散点符号不变形。
可以用lines!(ax, x, y, z)
制作三维折线图。
可以用scatterlines!(ax, x, y, z)
制作带有散点的三维折线图。
也可以将meshscatter!
和lines!
制作两个重叠图层。
12.9.3 三维曲面的各种图形
以前面定义的三维曲面为例。 作染色的三维曲面图, 在命令行或VSCode中可以拖动查看:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis3(fig[1,1], aspect=(1,1,1))
ax = surface!(ax, x, y, z)
plt
figend
制作网状线图:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis3(fig[1,1], aspect=(1,1,1))
ax = wireframe!(ax, x, y, z)
plt
figend
制作三维等高线图,在命令行和VSCode中可拖动查看:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis3(fig[1,1], aspect=(1,1,1))
ax = contour3d!(ax, x, y, z, levels=20)
plt
figend
使用二维的热力图表现:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis(fig[1,1], aspect=DataAspect())
ax = heatmap!(ax, x, y, z)
plt Colorbar(fig[1,2], plt)
figend
使用二维的等高线图:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis(fig[1,1], aspect=DataAspect())
ax = contour!(ax, x, y, z, levels=20)
plt
figend
使用有填充色的等高线图:
let
= surfd()
x, y, z = Figure(resolution=(800, 800))
fig = Axis(fig[1,1], aspect=DataAspect())
ax = contourf!(ax, x, y, z, levels=20)
plt Colorbar(fig[1,2], plt, height=Relative(0.7))
figend