# 5  graphics 入门

## 5.1 绘图基础

### 5.1.1plot()

summary(iris)
#>   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width
#>  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100
#>  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300
#>  Median :5.800   Median :3.000   Median :4.350   Median :1.300
#>  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199
#>  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800
#>  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500
#>        Species
#>  setosa    :50
#>  versicolor:50
#>  virginica :50
#>
#>
#>

plot(Sepal.Length ~ Sepal.Width, data = iris)
plot(iris$Sepal.Width, iris$Sepal.Length, panel.first = grid())

### 5.1.2 标签

plot(
Sepal.Length ~ Sepal.Width, data = iris,
xlab = "Sepal Width", ylab = "Sepal Length",
main = "Edgar Anderson's Iris Data"
)

### 5.1.3 字体

plot(Sepal.Length ~ Sepal.Width, data = iris, ann = FALSE, family = "sans")
title(
xlab = "萼片宽度", ylab = "萼片长度",
main = "埃德加·安德森的鸢尾花数据", family = "Noto Serif CJK SC"
)

### 5.1.4 分组

plot(Sepal.Length ~ Sepal.Width, data = iris, col = Species, pch = 16)

plot(Sepal.Length ~ Sepal.Width, data = iris)
points(Sepal.Length ~ Sepal.Width, data = iris,
col = "#EA4335", pch = 16,
subset = Species == "setosa" & Sepal.Length > 5
)

### 5.1.5 配色

palette()
#> [1] "black"   "#DF536B" "#61D04F" "#2297E6" "#28E2E5" "#CD0BBC"
#> [7] "#F5C710" "gray62"

palette(value = c("#EA4335", "#4285f4", "#34A853", "#FBBC05"))

plot(Sepal.Length ~ Sepal.Width, data = iris, col = Species, pch = 16)

### 5.1.6 注释

plot(Sepal.Length ~ Sepal.Width, data = iris)
text(x = 4, y = 6.5, labels = "flower", col = "#EA4335")

### 5.1.7 图例

plot(Sepal.Length ~ Sepal.Width, data = iris, col = Species, pch = 16)
legend(x = "topright", title = "Species",
legend = unique(iris$Species), box.col = NA, bg = NA, pch = 16, col = c("#EA4335", "#4285f4", "#34A853") ) 图例放置在绘图区域以外，比如右边空区域。此时，通过点和文本构造图例。 op <- par(mar = c(4, 4, 3, 6)) plot( Sepal.Length ~ Sepal.Width, data = iris, col = Species, pch = 16, main = "Edgar Anderson's Iris Data" ) text(x = 4.7, y = 6.75, labels = "Species", pos = 4, offset = .5, xpd = T) points(x = 4.7, y = 6.5, pch = 16, cex = 1, col = "#EA4335", xpd = T) text(x = 4.7, y = 6.5, labels = "setosa", pos = 4, col = "#EA4335", xpd = T) points(x = 4.7, y = 6.3, pch = 16, cex = 1, col = "#4285f4", xpd = T) text(x = 4.7, y = 6.3, labels = "versicolor", pos = 4, col = "#4285f4", xpd = T) points(x = 4.7, y = 6.1, pch = 16, cex = 1, col = "#34A853", xpd = T) text(x = 4.7, y = 6.1, labels = "virginica", pos = 4, col = "#34A853", xpd = T) on.exit(par(op), add = TRUE) 在函数 plot() 内设置较宽的坐标轴范围，获得一个较宽的绘图区域，再用函数 points() 添加数据点，最后，使用函数 legend() 添加图例。 plot( x = c(2, 6), y = range(iris$Sepal.Length), type = "n",
xlab = "Sepal Width", ylab = "Sepal Length",
main = "Edgar Anderson's Iris Data"
)
points(Sepal.Length ~ Sepal.Width,
col = Species, pch = 16, data = iris
)
legend(x = "right", title = "Species",
legend = unique(iris$Species), box.col = NA, bg = NA, pch = 16, col = c("#EA4335", "#4285f4", "#34A853") ) ### 5.1.8 统计 添加分组线性回归线。按鸢尾花种类分组，线性回归模型拟合数据，抽取回归系数。首先，使用函数 split() 将数据集 iris 按变量 Species 分组拆分，得到一个列表，每个元素都是数据框。接着，调用函数 lapply() 将函数 lm() 作用到列表的每个元素上，得到一个列表，每个元素都是线性拟合对象。最后，再调函数 lapply() 将函数 coef() 应用到列表的每个元素上，得到回归模型的系数向量。 lapply( lapply( split(iris, ~Species), lm, formula = Sepal.Length ~ Sepal.Width ), coef ) #>$setosa
#> (Intercept) Sepal.Width
#>   2.6390012   0.6904897
#>
#> $versicolor #> (Intercept) Sepal.Width #> 3.5397347 0.8650777 #> #>$virginica
#> (Intercept) Sepal.Width
#>   3.9068365   0.9015345

# 分组线性拟合
iris_lm <- lapply(
split(iris, ~Species), lm, formula = Sepal.Length ~ Sepal.Width
)
# 将分组变量和颜色映射
cols <- c("setosa" = "#EA4335",  "versicolor" = "#4285f4", "virginica" = "#34A853")
# 设置图形边界以容纳标签和图例
op <- par(mar = c(4, 4, 3, 8))
# 绘制分组散点图
plot(
Sepal.Length ~ Sepal.Width,
data = iris, col = Species, pch = 16,
xlab = "Sepal Width", ylab = "Sepal Length",
main = "Edgar Anderson's Iris Data"
)
# 添加背景参考线
grid()
# 添加回归线
for (species in c("setosa", "versicolor", "virginica")) {
abline(iris_lm[[species]], col = cols[species], lwd = 2)
}
# 添加图例
legend(
x = "right", title = "Species", inset = -0.4, xpd = TRUE,
legend = unique(iris$Species), box.col = NA, bg = NA, lty = 1, lwd = 2, pch = 16, col = c("#EA4335", "#4285f4", "#34A853") ) # 恢复图形参数设置 on.exit(par(op), add = TRUE) ## 5.2 绘图进阶 ### 5.2.1 组合图形 点、线、多边形组合 x <- seq(-10, 10, length = 400) y1 <- dnorm(x) y2 <- dnorm(x, m = 3) op <- par(mar = c(5, 4, 2, 1)) plot(x, y2, xlim = c(-3, 8), type = "n", xlab = quote(Z == frac(mu[1] - mu[2], sigma / sqrt(n))), ylab = "Density" ) polygon(c(1.96, 1.96, x[240:400], 10), c(0, dnorm(1.96, m = 3), y2[240:400], 0), col = "grey80", lty = 0 ) lines(x, y2) lines(x, y1) polygon(c(-1.96, -1.96, x[161:1], -10), c(0, dnorm(-1.96, m = 0), y1[161:1], 0), col = "grey30", lty = 0 ) polygon(c(1.96, 1.96, x[240:400], 10), c(0, dnorm(1.96, m = 0), y1[240:400], 0), col = "grey30" ) legend(x = 4.2, y = .4, fill = c("grey80", "grey30"), legend = expression( P(abs(Z) > 1.96, H[1]) == 0.85, P(abs(Z) > 1.96, H[0]) == 0.05 ), bty = "n" ) text(0, .2, quote(H[0]:~ ~ mu[1] == mu[2])) text(3, .2, quote(H[1]:~ ~ mu[1] == mu[2] + delta)) on.exit(par(op), add = TRUE) ### 5.2.2 多图布局 布局函数 layout() 和图形参数设置函数 par() data(anscombe) form <- sprintf("y%d ~ x%d", 1:4, 1:4) fit <- lapply(form, lm, data = anscombe) op <- par(mfrow = c(2, 2), mgp = c(2, 0.7, 0), mar = c(3, 3, 1, 1) + 0.1, oma = c(0, 0, 2, 0)) for (i in 1:4) { plot(as.formula(form[i]), data = anscombe, col = "black", pch = 20, xlim = c(3, 19), ylim = c(3, 13), xlab = as.expression(substitute(x[i], list(i = i))), ylab = as.expression(substitute(y[i], list(i = i))), family = "sans" ) abline(fit[[i]], col = "black") text( x = 7, y = 12, family = "sans", labels = bquote(R^2 == .(round(summary(fit[[i]])$r.squared, 3)))
)
}
mtext("数据集的四重奏", outer = TRUE)

## 5.3 图形选择

### 5.3.1 颜色图

$f(x,y) = \begin{cases} \frac{\sin(\sqrt{x^2 + y^2})}{\sqrt{x^2 + y^2}}, & (x,y) \neq (0,0)\\ 1, & (x,y) = (0,0) \end{cases}$

y <- x <- seq(from = -8, to = 8, length.out = 51)
z <- outer(x, y, FUN = function(x, y) sin(sqrt(x^2 + y^2)) / sqrt(x^2 + y^2))
z[26, 26] <- 1

image(x = x, y = y, z = z, xlab = "$x$", ylab = "$y$")

### 5.3.2 透视图

op <- par(mar = c(0, 1, 2, 1))
persp(
x = x, y = y, z = z, main = "二维函数的透视图",
theta = 30, phi = 30, expand = 0.5, col = "lightblue",
xlab = "$x$", ylab = "$y$", zlab = "$f(x,y)$"
)

### 5.3.3 等值线图

contour(x = x, y = y, z = z, xlab = "$x$", ylab = "$y$")

### 5.3.4 填充等值线图

filled.contour(
x = x, y = y, z = z, asp = 1,
color.palette = hcl.colors,
plot.title = {
title(
main = "二维函数的填充等值线图",
xlab = "$x$", ylab = "$y$"
)
},
plot.axes = {
grid(col = "gray")
axis(1, at = 2 * -4:4, labels = 2 * -4:4)
axis(2, at = 2 * -4:4, labels = 2 * -4:4)
points(0, 0, col = "blue", pch = 16)
},
key.axes = {
axis(4, seq(-0.2, 1, length.out = 9))
}
)

## 5.4 总结

### 5.4.1 tinyplot 包

tinyplot 包扩展 Base R 函数 plot() 的功能，在公式语法方面和 lattice 包很接近。另一个值得一提的 R 包是 basetheme ，用来设置 Base R 绘图主题。

library(tinyplot)
tinyplot(Sepal.Length ~ Sepal.Width | Species, data = iris,
palette = "Tableau 10", pch = 16)

with(iris, {
tinyplot(y = Sepal.Length, x = Sepal.Width, by = Species,
palette = "Tableau 10", pch = 16)
})

op <- par(mar = c(5, 4, .5, .5))
tinyplot(Sepal.Length ~ Sepal.Width | Species,
data = iris, palette = "Tableau 10", pch = 16,
legend = legend("bottom!", title = "Species of iris", bty = "o")
)

with(iris, tinyplot(
density(Sepal.Length), by = Species,
bg = "by",   # 分组填充
grid = TRUE, # 背景网格
palette = "Tableau 10",
legend = list("topright", bty = "o") # 右上角图例
))

### 5.4.2 plot3D 包

• plotrix 一个坐落于 R 的红灯区的 R 包。基于 Base R 各类绘图函数。
• scatterplot3d 基于 Base R 绘制三维散点图。
• misc3d 绘制三维图形的杂项，支持通过 Base R、 tcltk 包和 rgl 包渲染图形。
• plot3D 依赖 misc3d 包，加强 Base R 在制作三维图形方面的能力。

library(plot3D)
image2D(volcano,
shade = 0.2, rasterImage = TRUE, asp = 0.7,
xlab = "南北方向", ylab = "东西方向",
main = "奥克兰 Maunga Whau 地形图", clab = "高度",
contour = FALSE, col = hcl.colors(100),
colkey = list(
at = 90 + 20 * 0:5, labels = 90 + 20 * 0:5,
length = 1, width = 1
)
)
op <- par(mar = c(1, 1.5, 0, 0))
persp3D(
x = 1:87, y = 1:61, z = volcano, col = hcl.colors(100),
ticktype = "detailed", colkey = FALSE, expand = 1,
phi = 35, theta = 125, bty = "b2", shade = TRUE,
ltheta = 100, lphi = 45,
xlab = "\n南北方向", ylab = "\n东西方向", zlab = "\n高度"
)

from matplotlib import cm
from matplotlib.colors import LightSource
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 设置 PGF 后端渲染图形
import matplotlib as mpl
mpl.use("pgf")
# XeLaTeX 编译图形
plt.rcParams.update({
"text.usetex": True,
"pgf.texsystem": "xelatex",
"pgf.rcfonts": False,    # don't setup fonts from rc parameters
"pgf.preamble": "\n".join([
r"\usepackage[fontset=fandol,UTF8]{ctex}",
]),
})
# 读取数据
# DataFrame 转 Array
z = volcano.to_numpy()
# 数据行、列数
nrows, ncols = z.shape
# 线性序列
x = np.linspace(1, 87, ncols)
y = np.linspace(1, 61, nrows)
# 类似 R 语言函数 expand.grid()
xv, yv = np.meshgrid(x, y)
# 设置主题
fig, ax = plt.subplots(subplot_kw=dict(projection="3d"))
# 观察视角
ax.view_init(azim=30, elev=30)
# 设置坐标轴标签
ax.set_xlabel(r"南北方向", rotation=45)
ax.set_ylabel(r"东西方向", rotation=-15)
ax.set_zlabel(r"高度", rotation=90)
# 去掉多余的边空
fig.set_tight_layout(True)
# 光源照射的角度
ls = LightSource(270, 45)
# 自定义调色板
rgb = ls.shade(z, cmap=cm.viridis, vert_exag=0.1, blend_mode="soft")
# 三维透视图
surf = ax.plot_surface(
xv, yv, z, rstride=1, cstride=1, facecolors=rgb,