这一部分介绍用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
Makie包
Makie是Julia语言的一个作图扩展包,
特点是高性能富,可扩展性强。
另外与另外常用的Plots扩展包相比,
Plots包依赖于不同的后端,
不同的后端支持的功能有多有少,
所以Plots只能选择其中的最小部分。
Makie也有不同后端,
但更强调不同后端的增强功能。
参考:
- Makie: https://makie.juliaplots.org/stable/index.html
Makie后端
Makie有如下后端:
- CairoMakie:主要是各种二维图,非动态。
能制作出版质量图形。 - GLMakie:利用OpenGL图形引擎功能,
性能强大,可以制作三维图形,
可以制作动态交互图形。
但不擅长制作矢量图。 - WGLMakie:支持在网页中的交互图形。
- RPRMakie:使用RadeonProRender引擎,
可以制作光线追踪图像。
安装和运行
安装任一后端的同时可以安装Makie。如:
using Pkg; Pkg.add("CairoMakie")
用某个后端作图前,
用using
调用包并调用该后端的activate!()
函数,如:
using CairoMakie
CairoMakie.activate!()
在最基本的REPL环境,
CairoMakie后端无法提供图形窗口。
但是VSCode、Jupyter、Pluto等集成编辑环境或笔记本软件都可以支持显示其结果。
GLMakie在REPL也可以提供自己的图形窗口显示。
样例数据
学生身高体重年龄
设当前工作目录有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
dclass = CSV.read("class19.csv", DataFrame)
transform!(dclass,
:sex => (s -> categorical(s)),
renamecols = false)
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 |
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
urlf = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
dht = CSV.read(Downloads.download(urlf), DataFrame,
header=0)
rename!(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
简单一次性完成的图形
折线图
考虑如下的简单数据:
dline01 = DataFrame(
x = 1:5,
y = [11, 13, 18, 15, 14])
5 rows × 2 columns
x | y | |
---|---|---|
Int64 | Int64 | |
1 | 1 | 11 |
2 | 2 | 13 |
3 | 3 | 18 |
4 | 4 | 15 |
5 | 5 | 14 |
dline02 = DataFrame(x = [0,1,3,6,7,8], y=[15, 13, 14, 11, 10, 9])
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
函数。
散点图
scatter(dline01[:, :x], dline01[:, :y])
散点折线图
scatterlines(dline01[:, :x], dline01[:, :y])
直方图
hist(dclass[:, :height], bins=6)
hist(dht[:,:trestbps], bins=15)
密度估计曲线图
density(dclass[:, :height])
using CairoMakie: density
density(dht[:, :trestbps])
盒形图
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])
正态QQ图
qqnorm(dclass[:, :height], qqline=:fit)
正态QQ图用来检查输入的变量是否来自正态分布总体,
如果散点与直线比较接近,
则可以认为符合。
如果散点的走向与直线明显偏离,
则认为非正态分布。
qqnorm(dht[:, :trestbps], qqline=:fit)
经验分布函数图
StatsBase.ecdf
可以计算经验分布函数。ecdfplot(x)
可以作经验分布函数图。
如:
ecdfplot(dht[:, :trestbps])
时间序列图
时间为序号的时间序列折线图:
df = DataFrame(time=1:10, y = (1:10) .^2)
lines(df[:,:time], df[:,:y])
横轴使用日期可以定制,
没有自动完成的例子。
曲面的等高线图
表现二元函数z = f(x, y)
的图像的一种方式是等高线图。
用等间隔的x
轴和y
轴的若干个点组成矩阵形式的网格,
在每个网格点上计算z
坐标,
输入x, y, z
后可以将z
值相等的点连线。
比如,如下的函数:
f(x,y)= exp{−0.5((x+1)2+0.5y2)}cos(4x)+ exp{−0.8(2×2+(y−1)2)}cos(2y).
function surfd()
n = 100
xs = range(-pi, pi, n)
ys = range(-pi, pi, n)
z = [exp(-0.5*((x + 1)^2 + 0.5*y^2))*cos(4*x) +
exp(-0.8*(2*x^2 + (y-1)^2))*cos(2*y)
for x in xs, y in ys]
return (xs, ys, z)
end
x, y, z = surfd()
contour(x, y, z, levels=20)
曲面的染色等高线图
x, y, z = surfd()
fig, ax, plt = contourf(x, y, z, levels=20)
Colorbar(fig[1,2], plt)
fig
曲面的热力图
x, y, z = surfd()
heatmap(x, y, z)
这个函数目前有BUG存在。
频数条形图
写一个计算变量离散取值频数的函数:
using StatsBase
function freq(x)
y = StatsBase.countmap(x)
kk = [k for (k, v) in y]
vv = [v for (k, v) in y]
return kk, vv
end
将类别变量转换为其显示字符串的函数:
cat2string = x -> levels(x)[levelcode.(x)]
dclass中性别的频数条形图:
sex, n = freq(cat2string(dclass[:, :sex]))
barplot(1:2, n,
axis=(; width=100, height=200,
xticks=(1:2, sex) ))
Makie不支持对分类变量自动统计频数再作条形图,
所以上述程序比较复杂。
第5节讲的AlgebraOfGarphics提供了自动统计频数作频数条形图的功能。
小结
上面的这些图都可以用很简单的程序完成。
但是,
许多图都只适合探索性分析使用,
不适合制作高质量出版图形,
因为缺少适当的坐标轴设置、标题、轴标题、图例等元素。
要制作更符合要求的图形,
需要进一步学习。
Makie重要概念和作图步骤
- 绘图板(figure),相当于绘图用的纸张。
- 坐标系统(axis),可以定位图形内容,设置大小等。
- 绘图(plots)。具体的散点、连线、曲面、热力图等绘图内容。
- 坐标轴、标题等标注。
- 图例,颜色对应条。
- 小图拼凑。
最基本的步骤是用Figure()
函数新建绘图板,
用Axis()
函数在此绘图板上建立一到多个绘图区域并自动生成坐标系统,
用散点、连线等作图函数作图。
也可以省略Figure()
和Axis()
调用,
直接用作图函数做出简单图形。
画布
用Figure()
函数新建一个画布,
可以在其中放置坐标轴、散点、连线、标题、图例、颜色代码表等内容。
其中可以用backgroundcolor
设置背景色,
用resolution
设置大小,
如(800, 600)
,单位是像素。
例:
using CairoMakie
CairoMakie.activate!()
fig = Figure(backgroundcolor=:gray,
resolution = (800, 300))
添加坐标轴
用Axis()
命令在已建立的画布中建立坐标系。
最常用的输入是选择画布分格的左上角格子,
如fig[1,1]
,
没有分格子时fig[1,1]
就是画布的全部空间。
这种分格称为“布局”(layout)。
如:
在Axis()
中用title
指定标题,xlabel
指定x轴标签,ylabel
指定y轴标签,如:
ax = Axis(fig[1,1],
title = "这是标题",
xlabel = "x",
ylabel = "y")
添加图形内容及颜色设置
在用Figure()
新建画布,
用Axis()
添加并设置坐标系统以后,
可以用各种绘图函数在选定的坐标系统中添加图形内容。
如:
using CairoMakie
CairoMakie.activate!()
fig = Figure(backgroundcolor=:gray,
resolution = (800, 600))
ax = Axis(fig[1,1],
title = "简单的折线图示例",
xlabel = "x",
ylabel = "y")
lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
设置折线颜色用color
选项,如:
using Colors
fig = Figure()
ax = Axis(fig[1,1],
title = "设置颜色的折线图示例",
xlabel = "x",
ylabel = "y")
lines!(ax, dline01[:,:x], dline01[:,:y],
color = :green)
display(fig)
其中颜色可以用符号,
常用的:red
, :blue
, :black
, :orange
,:yellow
, :green
, :cyan
, :purple
, :pink
, :brown
, gray
, white
,
Colors包中许多颜色名称,
还可以用编码表示颜色。
参见:
- Colors包文档: https://juliagraphics.github.io/Colors.jl/stable/
散点图例子:
fig = Figure()
ax = Axis(fig[1,1],
title = "简单的散点图示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
散点图可以用color
指定散点颜色,
用markersize
指定大小(单位为像素),
用marker
指定散点的符号,
用strokecolor
指定散点轮廓线颜色,
用strokewidth
指定散点轮廓线宽度。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图设置示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y],
color = :purple, marker = :utriangle, markersize=20)
display(fig)
lines!()
,scatter!()
这些绘图函数输出是绘制的图形对象。
可以用坐标系统(Axis()
的输出)为第一自变量,
如上面的例子;
也可以输入坐标系统,
这时选择当前坐标系统,当前坐标系统可以用current_axis()
访问,
一般是最新生成的坐标系统。
如:
fig = Figure()
ax = Axis(fig[1,1])
scatter!(dline01[:,:x], dline01[:,:y])
display(fig)
多个图层
只要多次调用lines!()
, scatter!()
等函数就可以将多个图形叠加地画在同一坐标系中。
可以使用相同的数据或者不同的数据。如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图和折线图叠加示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y])
lines!(ax, dline02[:,:x], dline02[:,:y], color=:red)
display(fig)
只要在每个图层中用label
参数指定一个图层的标签,
然后调用axislegend()
函数就可以在指定的坐标系统中自动给出图例,如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图和折线图叠加示例",
xlabel = "x",
ylabel = "y")
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
表示右上角。
也可以输入二元组的坐标。
可以不输入所针对的坐标系统,
这时默认选择最后生成的坐标系统。
除了使用统一的属性,
还可以为每个散点分别设置属性,
这只需要将属性选项对应到与输入数据个数等长的属性向量。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图分别设置示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y],
markersize=dline01[:,:x] .*2 .+ 10,
color = range(0, 1, length=nrow(dline01)),
colormap = :thermal)
display(fig)
其中colormap
指定一种调色盘,
可以用0到1的数值从调色盘中选择颜色,
这时color
可以指定一个0到1中的范围就可以表示一系列渐变颜色。
也可以直接给color
指定一个颜色值向量。
一次性完成作图
先调用Figure()
,再调用Axis()
,
然后再添加实际图形内容,
这是比较一般的步骤,
可以进行更多定制。
实际上,
直接调用绘图函数也能做出图形,
见节2的例子。
如:
lines(dline01[:,:x], dline01[:,:y])
注意lines
是不带叹号的。
这样的一次性作图的函数本身就会建立画布和坐标系统,
其返回值类型为FigureAxisPlot
,
这是画布(Figure)、坐标系统(Axis)、绘图对象的三个成分的元组。
这样的返回值可以直接用display()
函数显示图形,
在交互运行时也可以不需要调用display()
就可以直接显示。
也可以用如
fig, ax, plt = lines(dline01[:,:x], dline01[:,:y])
这样的调用格式调用,
再显示fig
。
这样的好处是可以调整fig
和axis
的属性,
还可以对fig
修改布局。
显示和保存
在显示结果图形时,
在交互交互环境下如果最后一个表达式是Figure对象,
或者FigureAxisPlot对象,
可以自动调用display()
函数显示图形。
如果在非交互情况下,
应该使用display(fig)
来显示一个图形结果。
为了保存绘图板fig
中的图形为PNG,
用如
其中fname
是以.png
或.PNG
结尾的文件名,如"testsave.png"
。
可以用关键字参数resolution
指定一个大小的二元组,
单位可以认为是像素(某些后端有缩放功能)。
Makie作图定制
作图函数的一般语法规则
散点图、折线图等函数都使用类似的函数调用规则。
每种图都有不带叹号的版本和带叹号的版本,
如scatter
和scatter!
。
以scatter
为例,
不带叹号的版本可以直接生成画布、坐标系统、绘图对象:
fig, ax, plt = scatter(args...; kwargs)
在命令行、Jupyter或Pluto环境中,scatter
的调用放在单独一个语句或者多个语句末尾可以自动显示图形。
也可以后续调用在fig
指定的画布或ax
指定的坐标系统中增添内容,
这时就需要将fig
作为最后一个语句或者用draw(fig)
显示最终的图形。
如果不需要fig
, ax
, plt
在后续的修改中使用,
也可以简单地用
作为命令行或单元格唯一命令或最后一个命令,
直接显示该命令的结果图形。
在已有画布的情况下,
也可以调用
ax, plt = scatter(gridposition, args...; kwargs)
在指定的布局小块或子布局块中作图。
如果后续不需要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...) # 使用指定场景
需要的话可以用如
plt = scatter!(args...; kwargs...)
保存对应的图形对象。
在作图函数中设置画布和坐标系统属性
可以用Figure()
设置与绘图板有关的属性,
包括背景色,像素大小等;
用Axis()
设置与坐标系统有关的属性,
包括标题、轴标题、刻度设置等。
如:
fig = Figure(backgroundcolor = :gray80,
resolution = (400, 300))
ax = Axis(fig[1,1], title = "测试标题")
plt = scatter!(dline01[:,:x], dline01[:,:y], label="散点图")
fig
返回画布、坐标系统和绘图对象三元组的绘图函数(如scatter(args...; kwargs)
)
支持一个figure
选项,
输入为一个命名元组,
其中可以使用与Figure()
函数类似的关键字参数。
如:
scatter(dline01[:,:x], dline01[:,:y],
figure = (; backgroundcolor=:gray80, resolution=(400,300)))
返回画布、坐标系统和绘图对象三元组的绘图函数(如scatter(args...; kwargs)
)和返回坐标系统和绘图对象二元组的绘图函数(如scatter(gridposition, args...; kwargs)
)
支持一个axis
选项,可以在其中指定与坐标系统有关的属性。
属性写成命名元组的格式,
其中的元素值与Axis()
命令的关键字参数相同。
许多元素本身也会写成元组或者命名元组形式。
如:
scatter(dline01[:,:x], dline01[:,:y],
figure = (; backgroundcolor=:gray80, resolution=(400,300)),
axis = (; title="标题", xticks = 1:5))
另一种调整画布和坐标轴属性的方法是用“主题”的方法进行统一的调整,
见后面关于主题的说明。
绘图对象属性
scatter
等一次性作图函数返回画布、坐标系统、绘图对象的三元组,
在指定布局位置时返回坐标系统、绘图对象的二元组,scatter!()
这样的可变作图函数也返回绘图对象。
设plt
为一个绘图对象,
则plt.attributes
包括了绘图对象的各种属性,
比如颜色,符号,线型,字体等。
如:
fig, ax, plt = scatter(dline01[:,:x], dline01[:,:y])
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
查询帮助,
就会显示它支持的属性。
如:
颜色
Colors包定义了许多颜色名称,
参见:
- https://juliagraphics.github.io/Colors.jl/stable/namedcolors/
ColorThemes包提供了许多调色盘,
其中的符号如:viridis
, :heat
可以用作调色盘参数colormap
的值,
参见:
- https://makie.juliaplots.org/stable/documentation/colors/
程序如:
let
xs = range(-pi, pi, 100)
ys = range(-pi, pi, 100)
z = [exp(-0.1*(x^2 + y^2))*(cos(x) + sin(y))
for x in xs, y in ys]
fig = Figure()
ax = Axis(fig[1,1])
p = contourf!(ax, z, colormap = :heat)
Colorbar(fig[1,2], p)
display(fig)
end
又如:
let
x = -2:0.1:2
y = 10 .- x .^2
lines(x, y, colormap = :Blues, color = y)
end
colormap的值可以增加一个透明度,
如colormap = (:heat, 0.5)
,
这里0.5是透明度,
越小越透明。
可以用cgrad
函数从给定的若干个颜色生成渐变色,
用作colormap
参数的值。
可以用categorical_colors(:Blues, 3)
从ColorThemes包给出的用符号表示的调色盘生成指定个数的颜色,如:
colors = categorical_colors(:Blues, 3)
fig = Figure()
ax = Axis(fig[1,1])
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)
。
主题
自定义
可以用set_theme!(...)
函数设置一批公用的的属性,称为主题,
其中关于绘图板的属性直接作为关键字参数输入,
关于坐标系统的选项用命名元组输入到Axis
参数中。
关于图例的选项用命名元组输入到Legend
参数中。
最后用没有自变量的settheme!()
取消这个主题的作用。
如:
set_theme!(; backgroundcolor=:gray80, resolution=(400,300),
Axis = (; xtickgridvisible=true, ytickgridvisible=true,
xgridstyle=:dot, ygridstyle=:dot),
Legend = (; bgcolor = (:green, 0.2), framecolor=:yellow))
fig, ax, plt = scatter(dline01[:,:x], dline01[:,:y], label="数据A")
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]) )
fig, ax, plt = scatter(dline01[:,:x], dline01[:,:y], label="数据A")
scatter!(dline02[:,:x], dline02[:,:y], label="数据B")
axislegend()
set_theme!()
fig
内置主题
有一些内置的主题可用,
如theme_ggplot2()
, theme_minimal()
,theme_black()
, theme_light()
, theme_dark()
。
上图改用theme_black()
的效果:
set_theme!( theme_black() )
fig, ax, plt = scatter(dline01[:,:x], dline01[:,:y], label="数据A")
scatter!(dline02[:,:x], dline02[:,:y], label="数据B")
axislegend()
set_theme!()
fig
调用主题
用settheme!
函数自定义或调用内置主题是使用主题的一种方法,
使用内置主题时也可以用关键字参数修改某一具体设置参数。
使用不带参数的settheme!()
取消这样的主题选择。
还可以用with_theme(自定义绘图函数, 主题)
的格式调用主题。
另一种方法是
with_theme(主题函数()) do
...
end
也可以编写自己的主题函数,从略。
设置循环(cycle)
同一坐标系统有多个散点或者折线图层时,
将自动循环使用颜色。
颜色可以用主题的palette
中的color
参数设置一个颜色名的向量,
还可以用categorical_colors(调色盘, n)
从Colors包的某个调色盘生成n
个颜色。
如:
let
x = 1:10; y = 2 .* x
fig = Figure(); ax = Axis(fig[1,1])
for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
fig
end
这使用了默认的主题和默认的调色盘,
有7个不重复的颜色。
人为指定三个颜色组成的调色盘:
let
x = 1:10; y = 2 .* x
fig = Figure(palette = (; color = [:red, :blue, :green]))
ax = Axis(fig[1,1])
for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
fig
end
上面的程序在Figure()
中定义了palette
,
也可以在settheme!()
函数中定义。
使用categorical_colors
的例子:
let
x = 1:10; y = 2 .* x
set_theme!(palette = (; color = categorical_colors(:PRGn, 10)))
fig = Figure()
ax = Axis(fig[1,1])
for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
fig
end
前几个例子中,
只循环利用了颜色,
没有循环利用线型、粗细等因素。
可以在set_theme!
中中定义cycle
参数,
要求哪些因素参与这种循环利用。
比如,cycle = Cycle([:color, :linestyle])
规定先循环利用所有的颜色,
然后再使用下一种线型。
set_theme!
的palette
参数,
除了可以用color
指定一个调色盘,
还可以用marker
指定可选的散点形状,
用linestyle
指定可选的线型。
循环颜色与线型的程序如:
let
x = 1:10; y = 2 .* x
set_theme!(palette = (; color = categorical_colors(:Accent_3, 3)),
Lines = (; cycle = Cycle([:color, :linestyle])))
fig = Figure()
ax = Axis(fig[1,1])
for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
fig
end
对于散点图,
可重复利用的包括颜色、散点形状,
在set_theme!
中写法如Scatter = (; cycle = Cycle([:color, :marker]))
。
这样的写法是将颜色轮流用一遍后才修改第二种元素,
还可以让这些元素并行地循环使用,这时在Cycle()
中加covary=true
选项,
如:
let
x = 1:10; y = 2 .* x
set_theme!(palette = (; color = categorical_colors(:Accent_3, 3)),
Lines = (; cycle = Cycle([:color, :linestyle], covary=true)))
fig = Figure()
ax = Axis(fig[1,1])
for off in 1:10
lines!(ax, x, y .+ off, label = "Line $(off)")
end
set_theme!()
fig
end
坐标轴设置
标题
在Axis()
函数中可以用title
设置标题,
用subtitle
设置子标题(在标题下面一行),
用xlabel
设置x轴标签,
用ylabel
设置y轴标签。
可以用titlealign
指定标题和小标题的对齐方式,
如:left
, :right
。
可以用titlecolor
指定标题颜色,
用titlefont
指定标题字体,
用titlesize
指定标题字体大小(像素)。
小标题也可以使用用类似选项。
可以用titlegap
指定标题与小标题之间的间隙(像素)。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "简单的折线图示例",
subtitle = "使用Axis()设置",
titlesize = 30, subtitlesize = 15,
titlecolor=:green, subtitlecolor=:pink)
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, [0, 10])
display(fig)
坐标轴范围
可以用xlims!()
设置x轴范围,如:
fig = Figure()
ax = Axis(fig[1,1],
title = "简单的折线图示例",
xlabel = "x",
ylabel = "y")
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
表示自动获取。如:
fig = Figure()
ax = Axis(fig[1,1], limits = (nothing, 10, nothing, nothing))
lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
刻度
在Axis()
中用xticks
设置x轴刻度,
用yticks
设置y轴刻度。
直接设置刻度线所在数值组成的向量,如:
fig = Figure()
ax = Axis(fig[1,1], xticks = 0:2:10)
lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
并没有达到我们的目的。
这是因为坐标轴刻度有一些内部算法,
程序输入的要求仅作为目标。
配合xlims!
:
fig = Figure()
ax = Axis(fig[1,1], xticks = 0:2:10)
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 10)
display(fig)
xticks
的值也可以写成[0, 2, 4, 6, 8, 10]
这样的向量值。
也可以指定标签:
fig = Figure()
ax = Axis(fig[1,1], xticks = (0:2:6, ["t0", "t2", "t4", "t6"]))
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
可以用WilkinsonTicks(n)
表示有n
个刻度,
如:
fig = Figure()
ax = Axis(fig[1,1], xticks = WilkinsonTicks(4))
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
因为常用各种等间隔刻度,
所以提供了MultiplesTicks()
函数用来指定特殊间隔的刻度,如:
let
da = DataFrame(x = 0:0.1:(4*pi))
transform!(da, :x => ByRow(sin) => :y)
fig = Figure()
ax = Axis(fig[1,1], xticks = MultiplesTicks(5, pi, "π"))
lines!(ax, da[:,:x], da[:,:y])
fig
end
可以在Axis()
中用xtickformat
参数指定一个函数,
该函数输入刻度值向量,
返回显示字符串向量。ytickformat
类似。
如:
fig = Figure()
ax = Axis(fig[1,1], xtickformat = (s -> string.(s) .* "E3"))
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
xtickformat
还可以指定一个格式字符串,其中可以用如{:.2f}
这样的方法指定一个刻度值的输出格式,
也可以有其他原样内容。如:
fig = Figure()
ax = Axis(fig[1,1], xtickformat = "x = {:.2f}")
lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
可以用xticklabelrotation
指定一个数值使得x轴刻度值逆时针旋转指定的弧度数。
如:
fig = Figure()
ax = Axis(fig[1,1], xtickformat = "x = {:.2f}",
xticklabelrotation = pi/6)
lines!(ax, dline01[:,:x], dline01[:,:y])
display(fig)
网格线
二维图一般只在下方显示x轴,
左侧显示y轴。
可以在Axis()
中加xticksmirrored=true
使得上侧也显示刻度线,
用xgridvisible=true
使得x轴刻度包括网格线,
还可以加xminorticksvisible=true
显示细刻度,
加xminorgridvisible=true
显示细刻度网格线。
需要细刻度时,指定xminorticks=IntervalsBetween(n)
表示两个粗刻度之间用细刻度等分为n段。
y轴类似。
fig = Figure()
ax = Axis(fig[1,1],
xticksmirrored = true,
yticksmirrored = true,
xgridvisible=true,
ygridvisible=true)
lines!(ax, dline01[:,:x], dline01[:,:y])
xlims!(ax, 0, 6)
display(fig)
对某个坐标系统ax
,
可以用hidespines!(ax)
令其不显示坐标轴。
可以用hidexdecorations!(ax)
令其不显示刻度线、刻度值、网格线。
对数坐标轴
在Axis()
中设yscale = log10
可以对y轴使用对数轴。x轴类似。
可取的值包括identity
(缺省值), log10
, log2
, log
, sqrt
, Makie.logit
。
宽高比
坐标系统属性aspect
用来控制宽高比。
如果不控制宽高比,
正圆可能会变成椭圆:
let
th = 0:0.1:(2*pi)
x = cos.(th)
x = [x; x[1]]
y = sin.(th)
y = [y; y[1]]
da = DataFrame(x=x, y=y)
lines(da[:,:x], da[:,:y])
end
用坐标系统选项aspect=1
控制宽高比为
1:1:
let
th = 0:0.1:(2*pi)
x = cos.(th)
x = [x; x[1]]
y = sin.(th)
y = [y; y[1]]
da = DataFrame(x=x, y=y)
lines(da[:,:x], da[:,:y],
axis = (; aspect = 1))
end
第二纵轴
可以用xaxisposition=:top
将x轴画在上方,
用yaxisposition=:right
将y轴画在右侧。
可以将两个坐标系统指定在绘图板的同一个格子中,
然后隐藏第二个坐标系统的坐标轴和x轴刻度、标签,设置y轴在右侧,
在两个坐标系统中分别制作一个图层,
就可以实现左右双侧纵轴。
内部图例
在具体制作图形的函数(如lines()
)中用label
选项为某一层图形指定标签,
然后用axislegend()
制作某一坐标系统的图例。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图和折线图叠加示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y],
label = "数据集A")
lines!(ax, dline02[:,:x], dline02[:,:y], color = :red,
label = "数据集B")
axislegend(ax)
display(fig)
注意axislegend()
的函数名没有叹号。
可以不输入所针对的坐标系统,
这时针对最新生成的一个坐标系统。
用marker
可以修改散点形状,
取值如:circle
, :rect
, :utriangle
,:dtriangle
, :diamond
, :pentagon
, :cross
, :xcross
等。
用linestyle
可以修改线型,取值如nothing
(实线),:dash
, :dot
, :dashdot
, :dashdotdot
等。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图和折线图叠加示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y],
marker = :utriangle, label = "数据集A")
lines!(ax, dline02[:,:x], dline02[:,:y],
color = :red, linestyle = :dot, label = "数据集B")
axislegend(ax)
display(fig)
axislegend()
可以加merge=true
,
使得关于同一组数据的点、线图例合并,
可以用nbanks
指定分栏数,
用labelsize
指定标签文字大小像素数。
简单布局
Makie支持将画布灵活地分割为多个小块,
在每一小块上分别设定坐标系统作图。
这些小块可以是等大小的,如
1×2,
2×1,
2×2,
也可以利用相邻格子合并的方法使各个小块占据不同大小。
还可以用命令调整某列或某行的宽度。
在Axis()
中输入绘图位置时,
默认使用第[1,1]
块,
可以不分割小块。
这里位置可用如[1,2]
, [2,1]
,[2, 1:2]
,
使用范围时表示对应该范围的小块合并使用。如:
fig = Figure()
ax1 = Axis(fig[1, 1:2],
xlabel="height", ylabel="weight")
ax2 = Axis(fig[1, 3],
xlabel="age", ylabel="weight")
ax3 = Axis(fig[2, 1:3],
xlabel="height")
fig
上面的结果就是用了
2×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轴类似。
外部图例
可以指定外部的图例,
位置设定与指定每个小图位置类似。
内容由不同图层的label
参数决定。
如:
fig = Figure()
ax = Axis(fig[1,1],
title = "散点图和折线图叠加示例",
xlabel = "x",
ylabel = "y")
scatter!(ax, dline01[:,:x], dline01[:,:y],
marker = :utriangle, label = "数据集A")
lines!(ax, dline02[:,:x], dline02[:,:y],
color = :red, linestyle = :dot, label = "数据集B")
Legend(fig[1,2], ax)
display(fig)
颜色代码条
热力图之类的图形用不同颜色代表z坐标值大小,
一般应该附上一个将颜色与数值参照对应的颜色条作为说明。
使用函数Colorbar()
,摆放位置也与指定小图位置类似。
如:
let
xs = range(-pi, pi, 100)
ys = range(-pi, pi, 100)
z = [exp(-0.1*(x^2 + y^2))*(cos(x) + sin(y))
for x in xs, y in ys]
fig = Figure()
ax = Axis(fig[1,1])
p = contourf!(ax, z)
Colorbar(fig[1,2], p)
display(fig)
end
使用LaTeX公式
可以在标题、轴标签等允许使用字符串的地方使用LaTeX公式。
写法为L"公式"
。
如:
let
x = 0:0.01:4
f = x -> exp(-0.2*x) * cos(2*pi*x)
y = f.(x)
fig, ax, plt = lines(x, y,
label = L"e^{-0.2 x} \cos(2 \pi x)")
text!(L"f(x) = e^x; \quad g(x) = \cos x", position = (1.5, 0.9))
axislegend()
fig
end
GridLayout与嵌套布局
在前面的例子中,
如果绘图板为fig
,
可以直接用fig[1,1]
, fig[1,2]
, fig[2,1:2]
这样的方法规定小图的分割。
这样的方法比较受限,
除了可以左右合并、上下合并的灵活性以外,
基本都是对齐的,
但有些布局不希望完全对齐。
为此,
在绘图板(用Figure()
生成)和坐标系统(用Axis()
生成)之间,
再额外添加一个虚拟的布局容器(用GridLayout()
生成),
这就增加了自由度,
布局容器是可以嵌套的。
实例
生成绘图板和嵌套的摆放容器:
fig = Figure(resolution = (1000, 700))
ga = fig[1,1] = GridLayout()
gb = fig[2,1] = GridLayout()
gcd = fig[1:2, 2] = GridLayout() # 其中再嵌入方格摆放层
gc = gcd[1,1] = GridLayout()
gd = gcd[2,1] = GridLayout();
在ga给出的摆放容器内,
用基本的
2×2分割方法做出三个小图:
axa1 = Axis(ga[1,1])
axa2 = Axis(ga[2,1])
axa3 = Axis(ga[2,2])
let
sex, n1 = freq(dht[:, :sex])
barplot!(axa1, sex, n1)
cp, n2 = freq(dht[:, :cp])
barplot!(axa2, cp, n2)
restecg, n3 = freq(dht[:, :restecg])
barplot!(axa3, restecg, n3)
end
fig
在gb给出的摆放容器中,
作盒形图:
axb = Axis(gb[1,1])
boxplot!(axb, dht[:,:sex], dht[:,:thalach])
fig
在gc中作散点图:
axc = Axis(gc[1,1])
scatter!(axc, dht[:,:age], dht[:,:thalach])
fig
在gd中作直方图:
axd = Axis(gd[1,1])
hist!(axd, dht[:,:thalach])
fig
这样的结果还是a, b, c, d四块左右上下对齐的
2×2形状。
但是,
按照初始的设计,右侧的两个图实际上是合并在一起后又嵌套地分开的,
所有右侧的两个图的相对大小是可以独立于左侧的两个图调整的;
左侧两图和右侧两图的宽度也是可以调整的。
调整左右两栏的比例,调整右侧上下两块的比例:
colsize!(fig.layout, 1, Auto(0.5))
rowsize!(gcd, 1, Auto(1.5))
fig
这个例子还需要添加图例,对坐标轴、小图间隙进行调整。
Makie的GridLayout有很强的对齐调整功能。
详见Makie手册。
大小调整
可以指定某列小图的宽度或者某行小图的高度,
单位为像素。如:
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))
,
则表示第三列的宽度等于第一行高度的二倍。
间隙
用
colgap!(fig.layout, 1, Relative(0.1))
可以在第一列小图与第二列小图之间增加间隙,
宽度为绘图板总宽度10%。
可以用其它的宽度单位。rowgap!
函数类似。
如果不指定那一列或那一行,
就是所有列或者所有行之间,
如:
对所有小图行之间都增加10个像素间隙。
使用突出部分
在坐标轴组成的方框内部是实际图形内容,
外部可以有标题、刻度线、刻度值、轴标签等内容,
外部的这些区域称为突出部分(protrusion)。
可以在突出部分添加内容,
使用方法类似于Figure(1,2)
这样的布局位置设定,
但这时改为Figure(1, 2, 突出位置)
这样的写法,
突出位置如Left()
, Top()
, TopLeft()
等。
用Label()
在这样的突出位置添加文字内容。
如:
fig = Figure()
ax1 = Axis(fig[1,1])
ax2 = Axis(fig[1,2])
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
对齐
每一个小图对应一个坐标系统,
上下的小图、左右的小图如何对齐可以用选项设置。
默认是按坐标轴方框对齐的。
可以在Axis()
中用alignmode
指定对齐方式,
缺省为Inside()
。
如果用Outside()
,
就会用包括刻度、标题、轴标签的无形方框对齐。
还可以用Outside(10)
,
其中10
表示包括刻度、标题、轴标签的无形方框再向外扩充10个像素空白后才对齐。
一般情况下应该使用缺省设置。
各种图形函数参考
scatter
scatter
主要使用color
,marker
,markersize
进行调整。
输入的数据可以是scatter(xs, ys)
,
即输入两个向量分别表示x坐标和y坐标,
也可以输入一个向量的向量或元组的向量,
其中每个元素代表一对x, y坐标。
这种做法如:
let
xs = 0:0.1:2*pi
ys = sin.(xs)
points = collect(zip(xs, ys))
scatter(points)
end
也可以用函数Point2f.(xs, ys)
将两个向量转换为成对坐标的向量的形式,
此函数将一对x, y值转换成Point类型。如:
let
xs = 0:0.1:2*pi
ys = sin.(xs)
points = Point2f.(xs,ys)
scatter(points)
end
允许为每个散点指定符号、颜色、大小,
只要为marker
, color
, markersize
输入一个向量。
指定多个颜色时,
用color
输入一个颜色序号序列,
然后用colormap
指定一个调色盘进行颜色映射。如:
let
xs = 0:0.1:2*pi
ys = sin.(xs)
points = Point2f.(xs,ys)
scatter(points, color=1:length(points), colormap=:thermal)
end
同一坐标系统的不同图层将自动获得不同的颜色,
可以用label
参数指定图例文字,
用axislegend()
绘制图例:
let
fig = Figure()
ax = Axis(fig[1,1])
scatter!(ax, rand(10), rand(10), label="数据1")
scatter!(ax, rand(10), rand(10), label="数据2")
axislegend(ax)
fig
end
散点符号参考
Makie的散点符号来自TeX Gyre Heros Makie字体,
可以直接使用unicode字符,
也有一些符号可以用Symbol类型访问。
许多中文输入法有输入字符的功能。
下面的例子展示了许多特殊字符:
let
marker_str = "☆○◇□△▽◁▷♡★●◆■▲▼◀▶↖↑↗←→↙↓↘√×❤⊙⊕※▬〓§Ψ☢"
markers = collect(marker_str)
fig = Figure()
ax = Axis(fig[1, 1], yreversed = true,
xautolimitmargin = (0.15, 0.15),
yautolimitmargin = (0.15, 0.15)
)
hidedecorations!(ax)
for (i, marker) in enumerate(markers)
p = Point2f(fldmod1(i, 6)...)
scatter!(p, marker = marker, markersize = 20, color = :black)
end
fig
end
使用如:
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:'"),
]
f = Figure()
ax = Axis(f[1, 1], yreversed = true,
xautolimitmargin = (0.15, 0.15),
yautolimitmargin = (0.15, 0.15)
)
hidedecorations!(ax)
for (i, (marker, label)) in enumerate(markers_labels)
p = Point2f(fldmod1(i, 6)...)
scatter!(p, marker = marker, markersize = 20, color = :black)
text!(p, text = label, color = :gray70, offset = (0, 20),
align = (:center, :bottom))
end
f
用法如:
scatter(rand(10), rand(10), marker=:rect, markersize=20)
也可以将marker
参数输入为一个向量,
给每个散点分别指定符号;
可以将markersize
参数输入为一个向量,
给每个散点分别指定大小(单位是像素)。
lines
lines
输入一对x向量和y向量,
或输入点的向量,
每个点为Point类型或者取有两个元素的数组或元组。
可以用color
、linestyle
、linewidth
进行调整。linewidth
指定粗细,
用像素单位。linestyle
可取值为:
nothing
: 实线,这是缺省值;:dash
: 短划虚线;:dot
: 点虚线;:dashdot
: 点划线;:dashdotdot
:两点短划虚线;- 一个数值向量,表示循环的短划长度比例。
heatmap
输入形式是向量x
, 向量y
,
矩阵z
,
这时z[i,j] = f(x[i], y[j])
。
也可以输入三个向量x
, y
, z
,
这时z[i] = f(x[i], y[i])
。
函数目前有BUG。
let
x, y, z = surfd()
heatmap(x, y, z)
end
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
简单演示
分组变量条形图
set_aog_theme!()
p1 = data(dht) * mapping(:cp) * frequency()
draw(p1, axis = (width = 200, height = 400))
变量cp为胸痛类型:
- 典型心绞痛
- 非典型心绞痛
- 非心绞痛类型
- 无症状
AlgebraOfGraphics包用*
号连接互相补充的设定。
如果希望将图形输出为PNG文件,可将上述程序最后一行改成:
fig = draw(p1; axis = (width = 200, height = 400))
save("barplot-ex.png", fig, px_per_unit=3)
其中关键字参数px_per_unit
用来指定一个放大倍数,
可以省略。
可以将条形按性别分段,
用不同颜色代表性别:
p1b = p1 *
mapping(color = :sex => nonnumeric, stack = :sex => nonnumeric)
draw(p1b, axis = (width = 200, height = 400))
颜色、分组需要使用类别变量。
0表示女性,1表示男性。
并列格式的条形图:
p1c = p1 *
mapping(color = :sex => nonnumeric,
dodge = :sex => nonnumeric)
draw(p1c, axis = (width = 200, height = 400))
散点图
最大心律对年龄的散点图:
p1 = data(dht) * mapping(:age, :thalach)
draw(p1)
AlgebraOfGraphics用用+
使两个图层,
两个图层仅共享同一坐标系。
在散点图上面再添加一个拟合线图层:
p1b = p1 + p1 * linear()
draw(p1b)
增加一个颜色(color
)维度,
可以用不同颜色区分性别:
p1c = p1 * mapping(color = :sex => nonnumeric)
draw(p1c)
在没有明确的分组(group
)维度时,
颜色维度还起到了分组的作用。
按颜色分组的散点图图层和按颜色分组的拟合线图层:
p1d = p1 * mapping(color = :sex => nonnumeric) +
p1 * linear() * mapping(color = :sex => nonnumeric)
draw(p1d, axis = (width = 400, height = 300))
男女分别在一个切片(小图)中作图:
p1e = p1 * mapping(layout = :sex => nonnumeric)
draw(p1e, axis = (width = 300, height = 300))
p1f = (p1 + p1 * linear()) *
mapping(layout = :sex => nonnumeric)
draw(p1f, axis = (width = 300, height = 300))
二元密度图
考虑年龄和最大心律的二元分布密度,
可以用热力图表示。
p2 = data(dht) * mapping(:age, :thalach) *
AlgebraOfGraphics.density()
draw(p2, axis = (width = 300, height = 300))
作成等高线图:
p2b = p2 * visual(Contour)
draw(p2b, axis = (width = 300, height = 300))
用加号增加散点图层和拟合线图层:
p2c = p1 +
p1 * linear() +
p2 * visual(Contour)
draw(p2c, axis = (width = 300, height = 300))
其它维度
mapping()
可以用位置参数输入条形图的变量,
输入散点图的横纵坐标变量,
其它的维度用关键字参数如:
color
颜色marker
散点图案group
分组layout
切片分组row
,col
: 分组小图。
保存为图像文件
设某个draw()
的作图结果保存为变量fig
,
则与Makie保存图像文件一样,
用
可以保存为PNG格式,
其中fname
是以.png
或.PNG
结尾的文件名,
如"testsave.png"
。
可以用关键字参数px_per_unit
指定一个放大倍数。
data
函数
作图用的原始数据用data(df)
指定,
其中df
是数据框,
也可以是Tables.jl
格式兼容的其它表格数据。
注意需要区分数值型变量和分类变量,
分类变量需要转换为分类变量(CategoricalArray)格式。
另一种常用的df
类型是有名元组,
它可以比数据框更一般,
比如,
一个元素为矩阵,
另一个元素为向量。
例如:
x = collect(range(0, 2π, 100))
y = sin.(x)
p1 = data((; x=x, y=y)) * mapping(:x, :y) * visual(Lines)
draw(p1)
mapping
函数
映射函数mapping()
用来指定数据框中的变量如何与图形中表示数值的坐标、颜色、散点形状、散点大小等联系起来。
此函数有x, y, z
位置参数,
映射到x轴坐标、y轴坐标和z轴坐标。
颜色等图形维度用mapping()
的关键字参数输入。
这些维度有些仅允许对应到离散取值变量,
有些允许对应离散取值变量也允许连续取值变量。
如果映射中存在分类变量(如为某分类变量不同类别指定不同颜色),
就会将其它变量也按此变量分组。
也可以直接映射一个group
维,
用来明确地分组处理。
在映射变量时,可以用:变量名 => "显示名"
设置图形显示时的变量名,如:
p1 = data(dht) * mapping(:age => "年龄", :thalach => "血压")
draw(p1, axis = (width = 300, height = 300))
还可以用:变量名 => 变换函数 => "新变量名"
的方式指定变量的变换,
映射变换后的新变量,如:
p1 = data(dclass) * mapping(:height => (x -> x / 100) => "身高", :weight => "体重")
draw(p1, axis = (width = 300, height = 300))
注意其中的t -> t / 100
没有写成广播形式,
这是AlgebraOfGraphics自动进行了逐行变换。
也可以用DataFramesMeta.transform!
函数预先定义新变量,
此函数在指定变换时需要用加点的广播方式表示向量运算:
dc = copy(dclass)
DataFramesMeta.transform!(dc,
:height => (x -> x ./ 100) => "身高(米)")
p2 = data(dc) * mapping("身高(米)", :weight => "体重")
draw(p2, axis = (width = 300, height = 300))
简化的变换
有一些比较常用的变换,
AlgebraOfGraphics包提供了相应的函数。
对分类变量,renamer()
变换可以在mapping()
中临时指定不同的标签值,
如:
p1 = data(dclass) * frequency() * mapping(
:sex => renamer("F" => "女", "M" => "男") => "性别")
draw(p1, axis = (width = 200, height = 400))
分类变量的类别次序可以用sorter([新次序])
在mapping
中临时修改次序,如:
p2 = data(dclass) * frequency() * mapping(
:sex => sorter(["M", "F"]))
draw(p2, axis = (width = 200, height = 400))
需要将数值型变量作为分类变量使用时,
可以用nonnumeric
函数在mapping
中临时修改其用途,
如:
p3 = data(dclass) * frequency() *
mapping(:age => nonnumeric)
draw(p3, axis = (width = 200, height = 400))
注意上面的nonnumeric
不能写成nonnumeric()
。
另外,
在mappings()
中可以用:变量名 => verbatim
指定该变量原样使用,
不进行任何映射(变换),
可以用来在坐标系中指定坐标位置添加文本内容,
也可以输入适当的颜色名直接指定颜色。
visual
函数
visual
函数用来指定与数据无关的一些图形设定,
如统一的颜色、字体大小、透明度。
可以用visual
指定要制作的图形类型,如:
visual(Scatter)
: 散点图;visual(BarPlot)
: 条形图。visual(Line)
: 折线图,等等。
图形类型可以使用Makie支持的图形类型,
类型名使用首字母大写的“骆驼式”命名,
比如Makie中作带有散点的折线图的函数是scatterline
,
相应的类型就写成ScatterLine
。
一些mapping()
有默认的图形类型,
可以省略用visual()
指定图形类型的步骤。
比如,mapping()
中仅指定一个分类变量的位置参数,
就会自动进行频数统计并作频数条形图。
因为同样的映射可以做不同的图形,
所以可以先用data()
输入数据框,
用mapping()
输入映射,
在实际绘图时才添加visual()
设定,如:
pv1 = data(dline01) * mapping(:x, :y)
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))
小图
可以将变量映射到col
,
结果制作小图,
小图根据指定的变量的值按列摆放。
如:
pdm1 = data(dclass) * mapping(:height, :weight)
pf1 = mapping(col = :sex)
draw(pdm1 * pf1)
也可以将变量映射到row
,
使得该变量每个值映射到一个小图,
按行摆放,如:
pdm1 = data(dclass) * mapping(:height, :weight)
pf1 = mapping(row = :sex)
draw(pdm1 * pf1)
可以同时使用col
和row
映射,
根据两个变量来区分小图。
这样的小图摆放方式称为GridLayout(格子摆放)。
为此,制作一个简单的样例数据:
dlay01 = DataFrame(f1 = categorical(rand(["a", "b"], 100)),
f2 = rand(["c", "d"], 100),
x0 = randn(100), y0 = randn(100))
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 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
pdm1 = data(dlay01) * mapping(:x, :y)
dlay1 = mapping(row=:f1, col=:f2)
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)方式。
如:
pdm1 = data(dclass) * mapping(:height, :weight)
pf1 = mapping(layout = :sex)
draw(pdm1 * pf1)
col
, row
和layout
仅能映射到字符串变量或者字符串变量转换的类别变量。
对数值型,
可以借助nonnumeric
转换,如:
p1 = data(dht) * mapping(:age, :thalach, layout = :cp => nonnumeric)
draw(p1)
分析
某些图形类型需要进行建模分析,
比如拟合直线,
作直方图需要分组统计频数,等等。
这些分析都有相应的函数。
用histogram
函数作直方图
为了对连续取值变量作直方图,
可以调用histogram()
函数。如:
p1 = data(dht) * mapping(:thalach) * histogram()
draw(p1)
按性别分段:
p1b = data(dht) * mapping(:thalach,
color=:sex => nonnumeric, stack=:sex => nonnumeric) * histogram()
draw(p1b)
按性别切片:
d1c = data(dht) * mapping(:thalach,
layout=:sex => nonnumeric) * histogram()
draw(d1c)
可以在histogram()
中指定一些参数,
如bins
指定分组数或具体分点。
用histogram
函数作二元直方图
心脏病数据集中年龄与血压的二元直方图:
p1 = data(dht) * mapping(:age, :thalach) * histogram(bins=20)
draw(p1)
用density
函数作密度估计曲线
对dht中的血压作密度估计曲线:
p1 = data(dht) * mapping(:thalach) * AlgebraOfGraphics.density()
draw(p1)
用density
函数作二元密度估计图
心脏病人年龄与血压的二元密度估计热力图,同于二元直方图:
p1 = data(dht) * mapping(:age, :thalach) * AlgebraOfGraphics.density(npoints = 20)
draw(p1)
可见二元密度估计图默认使用了二元直方图。
制作三维曲面图形:
p1b = p1 * visual(Surface)
draw(p1b, axis=(type=Axis3, zticks=0:0.1:0.2,
limits=(nothing, nothing, (0, 0.001))))
曲面图最好使用GLMakie后端,
以获得交互功能。
用frequency
函数作频数条形图
当mapping
中仅有一个分类变量作为位置参数时,
默认就是作频数条形图。
用frequency()
可以对任何变量要求作这种图。
比如,dht中age是一个数值型变量,下面作不同年龄的频数条形图:
p1 = data(dht) * mapping(:age) * frequency()
draw(p1)
expectation
函数
输入了多个位置参数后,
固定最后一个位置参数的值对前面的位置参数作图。
用linear
画拟合回归直线
对体重和身高拟合回归直线,
作散点图并叠加回归直线。
回归直线自动伴随有置信区间。
p1 = data(dclass) * mapping(:height, :weight) *(
visual(Scatter) +
linear())
draw(p1)
这个例子用了乘法到加法的分配律。
用smooth
拟合局部多项式曲线
对dclass的体重对身高散点图,
拟合局部多项式曲线:
p1 = data(dclass) * mapping(:height, :weight) *(
visual(Scatter) +
smooth(span=0.75) * visual(color=:red))
draw(p1)
smooth
的参数span
一般取为0到1之间的小数,
取值越大,得到的曲线越光滑。
文本图
指定x, y坐标和文字变量后,
可以在指定坐标显示文字内容。
文字变量需要是字符串类型。
如:
p1 = data(dclass) *
mapping(:name => verbatim, (:height, :weight) => Point) *
visual(Annotations, textsize=8)
draw(p1)
注意verbatim
的使用。这个变换说明前面的变量需要保持原来的值使用,
不作任何变换,
这里用来提供要标注的文本内容。
代数运算
AlgebraOfGraphics是“图形的代数”,
可以对单个图层或多个图层进行乘法(*
)或加法(+
)运算。
加法运算满足结合律,
基本满足交换律,
但加号后面的图形会覆盖在加号前面的图形上面,
所以交换后结果不完全相同。
乘法运算满足结合律。
加法与乘法之间满足乘法的右分配律,
左分配律则有一些折扣。
乘法一般用来递进地完成一个图层,
加法一般用来叠加两种不同的图形。
如果多个图层与多个图层相乘,
结果也是所有两两组合后两两相乘的结果,
即两个图层和两个图层相乘可以得到四个图层。
用draw
绘制图形
制作好的图层或多个图层用draw
或者draw!
函数绘制。draw
自动制作图例,
而draw!
允许后续修改,不自动制作图例,
可以用colorbar!
和legend!
后续添加颜色条图例和一般图例。
draw()
的axis
参数输入各种绘图设置,
如绘图板的宽度、长度,标题、轴标签、轴刻度等。
在draw中设置标题
p1 = data(dclass) * mapping(:height, :weight)
draw(p1, axis = (; width=400, height=300, title="体重对身高",
xlabel="身高", ylabel = "体重"))
注意(; width=400, height=300)
这样的写法是“有名元组”的比较规范的写法。
当只有一个元素时必须这样写,
多个元素时不是必须的, 但这样写的用意比较明确。
在draw中设置坐标轴
draw()
的axis
参数中还可以规定许多关于坐标系统和坐标轴的设置。
如axis = (; aspect = 1.0)
规定宽高比:
p1 = data(dclass) * mapping(:sex) * frequency()
draw(p1, axis=(; aspect = 1/3))
在axis
中用xticks
指定x轴刻度数字序列,yticks
指定y轴刻度数字序列,
如:
p2 = data(dht) * mapping(:age, :thalach)
draw(p2, axis = (; xticks = 30:20:70))
p3 = data(dht) * mapping(:age, :thalach)
draw(p3, axis = (; xticks = (30:20:70, ["三十", "五十", "七十"])))
function cp_recode(x)
x = string.(Int.(x))
x = categorical(x)
recode!(x, "1" => "典型心绞痛", "2" => "非典型心绞痛",
"3" => "非心绞痛", "4" => "无症状")
x
end
dht2 = transform(dht, :cp => cp_recode => :cpc)
p4 = data(dht2) * mapping(:cpc, :thalach) *
visual(BoxPlot)
draw(p4)
上面的程序对cp变量进行了比较复杂的处理。
这是因为目前CategoricalArrays对字符串转换为分类变量支持最完善,
虽然数值也可以直接转换为字符串,
但是与AlgebraOfGraphics接口不太顺畅。
所以,写了转换函数recode_cp
,
先将cp原来的浮点型转换为整型,
再转换为字符串,
再转换为因子,
并对四个水平适当命名。
因为mapping()
中的变量变换都是期望逐行进行的,
而转换为因子是整列进行的,
所以额外用了transform
函数生成转换后的cpc变量,
而不是在mapping()
中调用recode_cp
。
如果x轴本来是字符型的,
需要修改标签值,
可以在mapping()
中用辅助的renamer()
函数进行修改。
在draw中设置绘图板
可以用draw()
的figure
选项输入一个有名元组,
在有名元组中填入背景色、大小等设置。
如draw(fig, figure = (; resolution = (1000, 800)))
。
例:
p1 = data(dht) * mapping(:thalach, layout = :sex => nonnumeric) *
histogram()
draw(p1, figure = (; backgrouncolor=:gray80, figure_padding = 10,
resolution=(600, 300)))
其中figure_padding
是整个绘图版周围边空宽度。
在draw中设置图例
绘图函数的图例是自动生成的,
可以在draw()
函数中用legend
参数指定一个有名元组进行图例的设置。
如:
p1 = data(dht) * mapping(:age, :thalach) *
mapping(color = :sex => renamer(0 => "女", 1 => "男") => "性别")
draw(p1)
上面关于不同性别使用不同颜色的图例自动标在右侧。
在draw()
中用figure()
进行一些修改:
draw(p1, legend = (; position = :top, titleposition = :left,
framevisible = true, padding = 5))
在draw中设置调色盘
在Makie中用colormap设置连续值映射到颜色的调色盘,
对于离散值如性别,
可以用color
映射自动匹配颜色,
也允许在draw()
中用palettes
参数指定一个包含元素color
的命名元组,
在color
中人为指定颜色对应关系。
如:
function recode_sex(x)
x = string.(Int.(x))
x = categorical(x)
recode!(x, "0" => "女", "1" => "男")
x
end
dht2 = transform(dht, :sex => recode_sex => :sexc)
p1 = data(dht2) * mapping(:age, :thalach) *
mapping(color = :sexc => "性别" )
draw(p1, palettes = (; color = ["女" => :red, "男" => :blue]))
这里为了将原数据集dht中的性别(sex)转换为字符型来源的类别变量,
单独写了转换函数,
还用了transform
函数,
而不是直接在mapping()
函数中转换。mapping()
函数的转换有一些限制,
而且对数值转换为类别变量兼容性不好。
当需要用的颜色比较多时,
可以人为选择某种渐变色生成指定个数的颜色。
如cgrad(:cividis, 8, categorical=true)
从:cividis
调色盘生成8个渐变色。
在draw中设置渐变颜色条
渐变颜色条(colorbar)是将连续取值的变量映射为颜色时的一个对照图例。
可以在draw()
中用colorbar
参数输入一个有名元组用于设置渐变颜色条,
如position = :top
可以放在顶部,size = 25
规定宽度。
为了修改数值到颜色的映射调色盘,
可以在visual()
函数中用colormap
参数指定一个连续颜色调色盘,
如colormap = :thermal
。
例:
p1 = data(dht) * mapping(:age, :thalach) *
AlgebraOfGraphics.density(npoints = 20) *
visual(Heatmap, colormap = :heat)
draw(p1, colorbar = (; position = :top, size = 25))
其它常用调色盘如:thermal
, :viridis
。
使用LaTeX标签
标题、坐标轴标签等可以使用LaTeX公式,
格式为L"公式"
,如L"\int_0^1 f(x)dx"
。
例:
let
x = range(-2, 2, 100)
y = x .^ 2
d = DataFrame(x = x, y = y)
p1 = data(d) * mapping(:x, :y) * visual(Lines)
draw(p1, axis = (; title = L"Graph of $y = x^2$",
xlabel="x", ylabel = L"x^2"))
end
Algebra与Makie的配合使用
可以在用Makie的Figure()
生成绘图板后,
用draw!()
在此绘图板的指定小块内作图。
如:
fig = Figure(resolution=(200,400))
p1 = data(dht) * mapping(:sex) * frequency()
draw!(fig[1,1], p1)
display(fig)
又如:
fig = Figure()
p1 = data(dht) * mapping(:sex) * frequency()
draw!(fig[1,1], p1)
p2 = data(dht) * mapping(:age) * histogram()
draw!(fig[1,2], p2)
colsize!(fig.layout, 1, Auto(0.5))
fig
两种作图函数混用:
fig = Figure()
p1 = data(dht) * mapping(:sex) * frequency()
draw!(fig[1,1], p1)
ax2 = Axis(fig[1,2])
scatter!(ax2, dht[:,:age], dht[:,:thalach])
colsize!(fig.layout, 1, Auto(0.5))
fig
draw!()
的第一参数也可以取为Axis()
的输出。
如:
fig = Figure()
ax1 = Axis(fig[1,1])
p1 = data(dht) * mapping(:sex) * frequency()
draw!(ax1, p1)
ax2 = Axis(fig[1,2])
scatter!(ax2, dht[:,:age], dht[:,:thalach])
colsize!(fig.layout, 1, Auto(0.5))
fig
AlgebraOfGraphics更多范例
这一节给出AlgebraOfGraphics包的更多比较完整的应用范例。
散点图和折线图
输入数据、映射是统一的,
可以将这两部分合并,
而visual(图形类型)用乘法与这两部分连接。
仅散点图:
dm1 = data(dline01) * mapping(:x, :y)
lay1 = visual(Scatter)
draw(dm1 * lay1)
仅折线:
dm1 = data(dline01) * mapping(:x, :y)
lay1 = visual(Lines)
draw(dm1 * lay1)
散点和折线两个图层,两个visual
用加法连接:
dm1 = data(dline01) * mapping(:x, :y)
lay1 = visual(Scatter) + visual(Lines)
draw(dm1 * lay1)
也可以使用多个数据集。如两条折线的图形:
dp1 = data(dline01) * mapping(:x, :y) * visual(Lines)
dp2 = data(dline02) * mapping(:x, :y) * visual(Lines)
draw(dp1 + dp2)
盒形图等
盒形图:
dp1 = data(dht) * mapping(:cp, :thalach) * visual(BoxPlot)
draw(dp1; axis=(width=200, height=400))
dp1 = data(dht) * mapping(:cp, :thalach) *
visual(BoxPlot, show_notch=true)
draw(dp1; axis=(width=200, height=400))
Makie的盒形图需要用x轴为分组,y轴为作图的变量。
如果没有分组,就需要预先在数据集中制作一个仅有一类的分类变量或者数值变量。
小提琴图:
dp1 = data(dht) * mapping(:cp, :thalach) * visual(Violin)
draw(dp1; axis=(width=200, height=400))
增加一个二值分组维度的背对背小提琴图:
dp1 = data(dht) * mapping(:cp, :thalach,
color = :sex => nonnumeric, side = :sex => nonnumeric) * visual(Violin)
draw(dp1; axis=(width=200, height=400))
正态QQ图:
dp1 = data(dclass) * mapping(:height) * visual(QQNorm, qqline=:fit)
draw(dp1)
对数轴
在draw()
函数中可以指定对数坐标轴。
考虑如下数据:
dlog01 = DataFrame(x = 1:10,
y = [1, 1.5, 2, 3, 6, 9, 14, 20, 28, 30])
dm1 = data(dlog01) * mapping(:x, :y)
draw(dm1)
将y轴制作成对数轴:
draw(dm1, axis = (;yscale=log))
对数轴要慎用。
散点图、折线图可以放心地使用对数轴,
但如果在散点图上拟合直线或曲线,
对数轴就会造成扭曲,
因为不同于R的ggplot2,
AlgebraOfGraphics的对数轴不是对变换后的数据拟合模型,
而是对原始数据拟合模型然后用对数轴画出来。
密度估计也有这样的问题。
多个变量同时作图
有时数据中有多个类似的变量。
如:
dn2 = DataFrame(x = rand(100), y = rand(100), z = rand(100))
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)
变量来指代这三个变量的区别。
如:
p1 = data(dn2) * mapping([:x, :y, :z] .=> "三个变量") *
AlgebraOfGraphics.density() *
mapping(color = dims(1) => renamer(["x", "y", "z"]))
draw(p1)
注意因为是三个变量,所以用了.=>
的写法而不是=>
。
p2 = data(dn2) * mapping(:x, [:y, :z] .=> "yz") *
visual(Scatter) *
mapping(color = dims(1) => renamer(["y", "z"]))
draw(p2)
这种dims()
的还适用于多个变量与多个变量之间的图形。
这时既可以用dims(1)
,还可以用dims(2)
。
如:
n = 100
dn3 = DataFrame(
x1 = 1 .+ randn(n),
x2 = 5 .+ randn(n),
y1 = 10 .+ randn(n),
y2 = 20 .+ randn(n))
p3 = data(dn3) * visual(Scatter) *
mapping([:x1, :x2], [:y1 :y2], col = dims(1), row = dims(2))
draw(p3)
注意散点图的横坐标写成了[:x1, :x2]
,
纵坐标写成了[:y1 :y2]
,
这样运算结果构成一个
2×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))
GLMakie
GLMakie是Makie的后端绘图引擎之一,
与CairoMakie相比,
GLMakie长于交互和三维图形能力。
在Jupyter Notebook界面中,
GLMakie的交互能力受限,
只能做出动态图形的一个静态版本。
交互能力
在命令行或MS VSCode这样的界面中,
会单独打开一个GLMakie图形窗口,
此窗口中的图形有较强的交互能力:
- 鼠标滚轮可以用来缩放图形;
- 拖选某一矩形区域可以聚焦显示此区域;
- 三维图形可以拖动从不同角度查看。
using GLMakie
GLMakie.activate!()
三维散点图、折线图
需要用Axis3
生成三维图需要的坐标系统。
可以用scatter!(ax, x, y, z)
作三维散点图,
这种图中散点形状并不受坐标轴伸缩的影响。
如:
fig = Figure()
ax = Axis3(fig[1, 1]; aspect=(1, 1, 1),
perspectiveness=0.5,
xlabel="年龄", ylabel="最大心率", zlabel="血压")
scatter!(ax, dht[:,:age], dht[:,:thalach], dht[:,:trestbps])
fig
还可以用meshscatter!
作三维散点图,
其中的符号是真正的几何形体,
会随坐标轴伸缩而变形。
如:
fig = Figure()
ax = Axis3(fig[1, 1]; aspect=(1, 1, 1),
perspectiveness=0.5,
xlabel="年龄", ylabel="最大心率", zlabel="血压")
meshscatter!(ax, dht[:,:age], dht[:,:thalach], dht[:,:trestbps],
markersize = 1)
fig
可以在Axis3()
中用aspect=:data
使得散点符号不变形。
可以用lines!(ax, x, y, z)
制作三维折线图。
可以用scatterlines!(ax, x, y, z)
制作带有散点的三维折线图。
也可以将meshscatter!
和lines!
制作两个重叠图层。
三维曲面的各种图形
以前面定义的三维曲面为例。
作染色的三维曲面图,
在命令行或VSCode中可以拖动查看:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis3(fig[1,1], aspect=(1,1,1))
plt = surface!(ax, x, y, z)
fig
end
制作网状线图:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis3(fig[1,1], aspect=(1,1,1))
plt = wireframe!(ax, x, y, z)
fig
end
制作三维等高线图,在命令行和VSCode中可拖动查看:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis3(fig[1,1], aspect=(1,1,1))
plt = contour3d!(ax, x, y, z, levels=20)
fig
end
使用二维的热力图表现:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis(fig[1,1], aspect=DataAspect())
plt = heatmap!(ax, x, y, z)
Colorbar(fig[1,2], plt)
fig
end
使用二维的等高线图:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis(fig[1,1], aspect=DataAspect())
plt = contour!(ax, x, y, z, levels=20)
fig
end
使用有填充色的等高线图:
let
x, y, z = surfd()
fig = Figure(resolution=(800, 800))
ax = Axis(fig[1,1], aspect=DataAspect())
plt = contourf!(ax, x, y, z, levels=20)
Colorbar(fig[1,2], plt, height=Relative(0.7))
fig
end
韭菜热线原创版权所有,发布者:风生水起,转载请注明出处:https://www.9crx.com/75107.html