Plots 可以画出很多丰富的图。从画线、点、阴影填充都可以,但是在 Julia 上面,与 Python 上的 Matplotlib 的写法有很大的不同,这篇文章就是写一些基本的或者常用的用法,包括如何用 For 循环去画多个子图。
今天就来学习如何使用 Plots 灵活地画图,以不同的高斯分布图作为例子。
1/ 创建高斯分布概率密度
using Random, Distributions, Plots
Random.seed!(123);
x = collect(-8:0.5:8);
y_gaussian = zeros(length(x), 8)
mu = [0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5]
sig = [1., 1.2, 1.4, 1.6, 1.6, 1.4, 1.2, 1.]
for i in 1:8
y_gaussian[:, i] = pdf.(Normal(mu[i], sig[i]), x)
end
使用 Distributions
可以创建高斯分布概率密度图,先生成 8 个高斯分布的参数,分别为 mu, sig
数组,然后通过循环生成 8 个高斯概率密度分布,保存在 y_gaussian
上以待画图。
2/ 画出高斯分布图
我们的目标是,将八个高斯分布分成两行四列画出来,并且每一行,只有第一列显示 Y 轴刻度,即同一行的图共享 Y 坐标轴刻度,线的标签在小图里不画出来。
最后在图的下半部分,把所有的高斯分布一起画出来,并且显示标签。最终显示的效果如下:
思路就是从上到下是四个图,分别为标题、上面四个小图、中间四个小图和最下面的一个大图,所以这个图是子图套子图。
2.1/ 循环建立子图
为了方便,写一个函数,可以一次性完成一行四个小图的建立:
function plot_gaussians(x::Array, y::Array, colors)
figures = Any[];
nums = size(y)[2];
ymax = maximum(y);
yticks_attri = [true; fill(false, nums-1)];
for i in 1:nums
f = plot(x, y[:, i],
ylim=(0, ymax), label=false, grids=false,
yticks=yticks_attri[i], color=colors[i]);
push!(figures, f);
end
return plot(figures..., layout=(1, 4));
end
第一种画子图的方式,就是把子图一个一个放进一个数组,然后最后画出来。
函数 plot_gaussians(x::Array, y::Array, colors)
,接受参数 x, y
分别为 X 的坐标和高斯分布矩阵,colors
为每个高斯分布的画出来的颜色。
首先生成一个变量 figures=Any[]
待存放所有的子图。因为只有每一行的第一个图需要 Y 刻度,所以生成了一个数组 yticks_attri = [true; fill(false, nums-1)];
记录 yticks
的属性。
在画图的时候,将关键词 yticks
设为 false
就可以不显示坐标轴刻度,所以遍历每个高斯分布,然后让 yticks=yticks_attri[i]
。
因为要共享 Y 坐标轴,所以先 ymax = maximum(y);
得到 Y 轴的最大值,然后使用 ylim=(0, ymax)
Y 轴的显示范围。
label=false, grids=false
这两个关键词是不显示线条的标签,然后图的背景不显示交叉的虚线。
关键的两步是,使用 push!(figures, f);
把当前子图放进 figures
里面,然后用 plot(figures..., layout=(1, 4));
生成一个大的图。
...
:「splat」运算符,...
表示参数序列。该参数可以用于函数定义,以指示函数接受任意数量的参数;还可用于将函数应用于参数序列。
2.2/ 手动建立子图
另外用于建立子图的一种方法,就是自己一个一个手动写出来,与上面的区别就是,上一种是把图放进一个列表,然后利用 ...
运算符,这种情况适用于子图的数量是不定的时候,或者是可变,可循环的情况。
我们上面写了一个函数,接下来就是把各个小的子图组合成一个大图。
需要注意的是,在 Plots 里面图的标题只能是为小图写的,如果要为整个大图添加标题,那么就只能弄一个图,只包含了标题的,去掉其他元素的。
title = plot(title="Gaussian pdf Plot", framestyle=nothing, grid=false, showaxis=false, ticks=false, bottom_margin=-25Plots.px);
这一句就是创建大的标题,然后将坐标轴,边框啥的不要的元素都去掉,最后放在大图的上面就行了。
然后利用写好的函数 plot_gaussians
生成两个子图 f1, f2
。
colors = reshape(palette(:tab10)[:], 1, :);
title = plot(title="Gaussian pdf Plot", framestyle=nothing, grid=false,
showaxis=false, ticks=false, bottom_margin=-25Plots.px);
f1 = plot_gaussians(x, y_gaussian[:, 1:4], colors[1:4]);
f2 = plot_gaussians(x, y_gaussian[:, 5:end], colors[4:end]);
labels = reshape(["$(m), $(s)" for (m, s) in zip(mu, sig)], 1, :)
f3 = plot(x, y_gaussian, grids=false, label=labels, legend=:left, color=colors[:, 1:8]);
l = @layout([a{0.01h}; grid(3, 1, heights=[0.25 ,0.25, 0.5])])
plot(title, f1, f2, f3, layout=l, size(800, 600))
savefig("line.pdf")
我将整个大图分成两个部分进行布局,第一部分为标题(title),在最上面,给其一个比较窄的位置,然后下面是 3 行的区域,两个小子图为 0.25 的宽度,大图占一半,所以布局用宏表示为 l = @layout([a{0.01h}; grid(3, 1, heights=[0.25 ,0.25, 0.5])])
.
最后画出最终的图,前面的变量,可以一个一个添加进入,plot(title, f1, f2, f3, layout=l, size(800, 600))
保存图片 savefig("line.pdf")
,最终结果: