【r<-繪圖】傳統圖形繪製

blog link: https://shixiangwang.github.io/home/cn/post/2019-06-21-baseplot-addplot/

R 遵循畫家模式。

高級繪圖函數 + 低級繪圖函數才能讓圖形豐富多樣起來。

基本的低級繪圖函數

<caption style="box-sizing: inherit;">Table 1: 常見基本低級繪圖函數</caption>

函數 描述
points() 數據符號
lines() 線條
segments() 線段
arrows() 箭頭
xspline() 光滑曲線
rect() 矩形
polygon() 多邊形
polypath() 多邊形路徑
rasterImage() 位圖(在圖形上可以添加外部圖片)
text() 文本

添加圖形

還是要一頓操作猛如虎,才能學到本事。

x = 1:10
y = matrix(sort(rnorm(30)), ncol=3)
plot(x, y[, 1], ylim = range(y), ann=F, axes = F, type="l", col="gray")
box(col="gray")

開始加東西。

plot(x, y[, 1], ylim = range(y), ann=F, axes = F, type="l", col="gray")
box(col="gray")

points(x, y[,1])
lines(x, y[,2], col="gray")
points(x, y[,2], pch=2)
lines(x, y[,3], col="gray")
points(x, y[,3], pch=3)

[圖片上傳失敗...(image-5dfa54-1562491033952)]

在點旁邊添加文本有時候很有用,使用 pos 可以設置數據符號與文本之間的偏移量。

y = x = 1:5
plot(x, y, ann=F, axes=F, col="gray", pch=16)
box(col="gray")

text(x[-3], y[-3], c("right", "top", "bottom", "left"),
     pos=c(4, 3, 2, 1))
text(3, 3, "overlay")

text() 還可以接受 R 表達式。

y = x = 1:5
plot(x, y, ann=F, axes=F, col="gray", pch=16)
box(col="gray")

text(x[-3], y[-3], c("right", "top", "bottom", "left"),
     pos=c(4, 3, 2, 1))
text(3, 3, ~ x^2)

上述處理的都是向量數據,而matplot()matpoints()matlines()都是處理矩陣形式數據的。

繪圖工具

grid() 可以添加網格線; abline() 添加直線; box() 在圖形周圍繪製矩形;rug() 可以沿着座標軸繪製“地毯”圖。

x = runif(20, 1, 10)
y = x + rnorm(20)
plot(x, y, ann=F, axes=F, col="gray", pch=16)
box(col="gray")

lmfit = lm(y ~ x)
abline(lmfit)
arrows(5, 8, 7, predict(lmfit, data.frame(x=7)), 
       length = 0.1)
text(5, 8, "Line of best fit", pos = 2)
y = rnorm(50)
hist(y, main="", xlab="", ylab="", axes=F, border="gray", col="light gray")
box(col="gray")
rug(y, ticksize = 0.02)

在邊緣處添加圖形

mtext() 函數可以在邊緣區域的任何位置繪製文本,它的 outer 參數控制是在圖像區域還是外部區域的邊緣處輸出。 side 控制在哪個邊緣區域輸出,1 - 底部,2 - 左側,3 - 頂部,4 - 右側。

我們也可以在圖像區域或外部區域使用一般在繪圖區域使用的函數,不過有點麻煩。我們需要先設定 xpd 的狀態。 下面展示了一個例子:將繪製出的一個在兩個圖像之間穿越的矩形。

y1 = rnorm(100)
y2 = rnorm(100)

par(mfrow=c(2,1), xpd =NA)

# 繪製第一個圖形
plot(y1, type="l", axes=FALSE,
     xlab="", ylab="", main="")
box(col="gray")
mtext("Left end of margin", adj=0, side=3)
lines(x=c(20,20,40,40), y=c(-7, max(y1), max(y1), -7), lwd=3, col="gray")

# 繪製第二個圖形
plot(y2, type="l", axes=FALSE, 
     xlab="", ylab="", main="")
box(col="gray")
mtext("Right end of margin", adj=1, side=3)
lines(x=c(20, 20, 40, 40), y=c(7, min(y2), min(y2), 7), 
      lwd=3, col="gray")

圖例

legend() 函數用於在圖像中添加圖例或關鍵字。

第一個例子展示在散點圖中添加圖例的方法,圖例將不同的組名和對應的符號關聯起來。前 2 個參數給定對於用戶座標系統, 圖例左上角的爲止。第 3 個參數提供圖例需要的標籤,此外,通過指定 pch 參數可以在標籤旁邊繪製符號。

with(iris,
     plot(Sepal.Length, Sepal.Width, pch=as.numeric(Species), cex=1.2))
legend(6.1, 4.4, c("setosa", "versicolor", "virginica"),
       cex=1.5, pch=1:3)

下一個例子展示條形圖添加圖例,圖例中組名對應不同的填充模式。

barplot(VADeaths[1:2, ], angle = c(45, 135), density = 20, 
        col="gray", names = c("RM", "RF", "UM", "UF"))
legend(0.4, 38, c("55-59", "50-54"), cex=1.5, angle = c(135, 45), density = 20, fill = "gray")

注意,怎麼將圖例符號對應於圖形完全是由用戶控制的。所以在繪製時一定要額外注意,相比於傳統圖形繪製, ggplot2lattice 包會自動映射,更爲方便。

座標軸

有時候我們想要修改座標軸,我們第一步需要禁止生成默認的座標軸,這一點可以通過設置大多數高級函數的 axes 參數實現。 通過 par() 指定 xaxt = "n"yaxt = "n" 也可以實現該目的。

下面舉一個定製座標軸的例子:

開始繪製一個初始圖形,並且繪製 y 軸的尺度是攝氏度。接下來再繪製一個華氏溫度的 y 軸。x 軸使用特殊標籤,而不是默認刻度線的數值位置。

# 先生成數據並繪製沒有數據符號和座標軸的空圖
x = 1:2
y = runif(2, 0, 100)
par(mar=c(4, 4, 2, 4))
plot(x, y, type = "n", xlim = c(0.5, 2.5), ylim = c(-10, 110),
     axes=FALSE, ann=FALSE)

# 指定主 y 軸攝氏度刻度位置。
axis(2, at=seq(0, 100, 20))  # 2 表示繪製在左邊,at指定刻度線位置
mtext("Temperature (Centigrade)", side = 2, line = 3)

# 繪製有特殊標籤的底部座標軸,以及表示華氏溫度的 y 軸
axis(1, at=1:2, labels = c("Treatment1", "Treatment2"))
axis(4, at=seq(0, 100, 20), labels = seq(0, 100, 20)*9/5 + 32)
mtext("Temperature (Fahrenheit)", side=4, line=3)
box()

# 最後畫一些溫度樣式的符號表示實際溫度
segments(x, 0, x, 100, lwd=20)
segments(x, 0, x, 100, lwd=16, col="white")
segments(x, 0, x, y, lwd = 16, col = "gray")

座標系統

在繪圖區域內的圖形輸出是根據座標軸的尺度自動定位的,而圖形邊緣處的文本則是根據距離繪圖區域邊界多少 文本行定位的。

par() 函數

一般情況下我們使用 par() 函數獲取或設定圖形的狀態。其中 dinfinpin 3個狀態反映了當前繪圖設備、圖像區域以及回去區域的尺寸(寬度和高度),以英寸爲單位。

par("din")
#> [1] 7 5
par("fin")
#> [1] 7 5
par("pin")
#> [1] 5.76 3.16

usr 反映座標軸範圍。

par("usr")
#> [1] 0 1 0 1

下面我們畫一把與實際物理尺寸對應的尺子。

# 繪製一個空白圖形並進行計算
plot(0:1, 0:1, type="n", axes=F, ann=F)
usr = par("usr")
pin = par("pin")
xcm = diff(usr[1:2])/(pin[1]*2.54)
ycm = diff(usr[3:4])/(pin[2]*2.54)

# 現在繪製的圖形是根據釐米表示的
par(xpd=NA)
rect(0 + 0.2*xcm, 0-0.2*ycm,
1 + 0.2*xcm, 0.3-0.2*ycm,
col="gray", border = NA)

# 繪製邊框、刻度和標籤
rect(0, 0, 1, 0.3, col="white")
segments(seq(1, 8, 0.1)*xcm, 0,
seq(1, 8, 0.1)*xcm, 
c(rep(c(0.5, 
rep(0.25, 4),
0.35, 
rep(0.25, 4)), 7), 
0.5)*ycm)
text(1:8*xcm, 0.6*ycm, 0:7, adj=c(0.5, 0))
text(8.2*xcm, 0.6*ycm, "cm", adj=c(0, 0))

覆蓋輸出

有時在同一圖中繪製 2 個數據集非常有用,此時數據集共享一個 x 變量,但擁有不同的 y 尺度。

至少有 2 種方法:

  • 調用 par(new=TRUE) 實現 2 個不同圖形在彼此之上重疊繪製
  • 在繪製第二個數據之前顯式地重置 usr 狀態

方法一

drunkness = ts(c(3875, 4846, 5128, 5773, 7327,
                 6688, 5582, 3473, 3186, rep(NA, 51)),
               start = 1912, end = 1971)

par(mar = c(5, 6, 2, 4))
plot(drunkness, lwd=3, col="gray", ann=F, las=2)
mtext("Drunkness\nRelated Arrest", side=2, line=3.5)
par(new=TRUE)
plot(nhtemp, ann=F, axes=F)
mtext("Temperature (F)", side=4, line=3)
title("Using par(new=TRUE)")
axis(4)

方法二

該方法只繪製一個圖形。

par(mar=c(5, 6, 2, 4))
plot(drunkness, lwd=3, col="gray", ann=F, las=2)
mtext("Drunkness\nRelated Arrest", side=2, line=3.5)
usr = par("usr")
par(usr=c(usr[1:2], 47.6, 54.9))
lines(nhtemp)
mtext("Temperature (F)", side=4, line=3)
title("Using par(usr=...)")
axis(4)

方法三

一些高級函數提供了一個叫 add 的參數,如果設置爲 TRUE,將會在現有圖形上添加輸出。

with(trees,
     expr = {
     plot(Height, Volume, pch=3, xlab="Height (ft)", 
          ylab=expression(paste("Volume ", "ft^3")))
     symbols(Height, Volume, circles = Girth/12,
             fg="gray", inches = FALSE, add=TRUE)
       })

特殊情況

隱藏的座標軸尺度

因爲這個原因,在條形圖和箱線圖中添加圖形輸出會比較麻煩。爲何做到這點,我們需要獲取函數的返回值。這個值會給出函數繪製的每一個條形的中點 x 位置。

條形圖例子:添加水平參考線段

y = sample(1:10)
midpts = barplot(y, col="lightgray")
width = diff(midpts[1:2])/4
left = rep(midpts, y - 1) - width
right = rep(midpts, y - 1) + width
heights = unlist(apply(matrix(y, ncol=10),
                       2, seq))[-cumsum(y)]
segments(left, heights, right, heights,
         col="white")

箱線圖例子:添加抖動點

with(ToothGrowth,
     {
       boxplot(len ~ supp, border = "gray",
               col="lightgray", boxwex=0.5)
       points(jitter(rep(1:2, each=30), 0.5),
              unlist(split(len, supp)),
              cex=0.5, pch=16)
     })

繪製三維圖像

添加圖像步驟:

  1. 獲取 persp() 函數返回的變換矩陣 (本身該函數會繪製三維圖像)
  2. 使用 trans3d() 函數將三維位置轉換爲二位位置
  3. 將以上結果傳給標準函數,如 lines()text()

下面繪製火山並添加等高線。

z = 2*volcano
x = 10 * (1:nrow(z))
y = 10 * (1:ncol(z))

trans = persp(x, y, z, zlim=c(0, max(z)),
              theta=150, phi=12, lwd=.5,
              scale=F, axes=F)

# 計算等高線
clines = contourLines(x, y, z)
# 將等高線 x 與 y 頂點位置傳給 trans3d()
lapply(clines,
       function(contour) {
         lines(trans3d(contour$x, contour$y, 0, trans))
       })
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL
#> 
#> [[3]]
#> NULL
#> 
#> [[4]]
#> NULL
#> 
#> [[5]]
#> NULL
#> 
#> [[6]]
#> NULL
#> 
#> [[7]]
#> NULL
#> 
#> [[8]]
#> NULL
#> 
#> [[9]]
#> NULL
#> 
#> [[10]]
#> NULL
#> 
#> [[11]]
#> NULL
#> 
#> [[12]]
#> NULL
#> 
#> [[13]]
#> NULL
#> 
#> [[14]]
#> NULL
#> 
#> [[15]]
#> NULL
#> 
#> [[16]]
#> NULL
#> 
#> [[17]]
#> NULL
#> 
#> [[18]]
#> NULL
#> 
#> [[19]]
#> NULL
#> 
#> [[20]]
#> NULL

創建新圖形

plot.new() 函數開啓一個新的繪圖(與 frame() 等價),並將 x 與 y 尺度設置爲 (0, 1) 區間。

plot.window() 函數重置用戶座標系統的尺度。

plot.xy() 在繪圖區域繪製數據符號和線條。

從頭創建一個簡單圖形

plot.new()  # 開啓一個空白繪圖
plot.window(range(pressure$temperature),
            range(pressure$pressure))  # 設定座標軸尺度
plot.xy(pressure, type="p")  # 繪製圖形
box()  # 圍繞繪圖區域繪製矩形
axis(1) # 添加軸線
axis(2) # 添加軸線

這和 plot() 繪製的散點圖完全一致。

從頭創建一個複雜圖形

繪製泰坦尼克號成年男性和女性倖存者數目。

groups = dimnames(Titanic)[[1]]
males = Titanic[, 1, 2, 2]
females = Titanic[, 2, 2, 2]

males
#>  1st  2nd  3rd Crew 
#>   57   14   75  192
females
#>  1st  2nd  3rd Crew 
#>  140   80   76   20
# 6 行內容,4行用戶繪製條形,1行用於繪製x軸,1行繪製圖例(-1)
par(mar=c(0.5, 3, 0.5, 1))
plot.new()
plot.window(xlim = c(-200,200), ylim = c(-1.5, 4.5))

ticks = seq(-200, 200, 100)
y = 1:4
h = 0.2

# 開始畫圖
# x 軸繪製在繪圖區域內部(設置pos=0)
lines(rep(0,2), c(-1.5, 4.5), col="gray")
segments(-200, y, 200, y, lty = "dotted")
rect(-males, y-h, 0, y+h, col="darkgray")
rect(0, y-h,  females, y+h, col="lightgray")
mtext(groups, at=y, adj=1, side=2, las=2)
par(cex.axis=0.5, mex=0.5)
axis(1, at=ticks, labels=abs(ticks), pos=0)

# 在底部繪製圖例
# 確保矩形能包含文字
tw = 1.5*strwidth("females")
rect(-tw, -1-h, 0, -1+h, col="darkgray")
rect(0, -1-h, tw, -1+h, col="lightgray")
text(0, -1, "males", pos=2)
text(0, -1, "females", pos=4)

創建繪圖函數

xy.coords()允許在新建的函數中靈活指定 x 與 y 參數。該函數接收 x 參數與 y 參數並且創建一個標準的包含 x 值、y 值以及座標軸合理標籤的對象。

一個新的繪圖函數可能需要強制將 xpd 狀態設定爲 NA,從而在繪圖區域外繪製線條和文本。這種情況下可以在函數的末尾恢復初始的繪圖狀態。

可以採用以下技術:

# 放在函數的開始部分
opar = par(no.readonly = TRUE)
on.exit(par(opar))

下面是一個繪圖模板(可以看做 plot() 函數的精簡版本),提供了一個供他人使用的繪圖函數的出發點。

plot.newclass = 
  function(x, y=NULL, 
           main="", sub="",
           xlim=NULL, ylim=NULL,
           axes=TRUE, ann=par("ann"),
           col=par("col"), 
           ...) {
    xy = xy.coords(x, y)
    if (is.null(xlim)) {
      xlim = range(xy$x[is.finite(xy$x)])
    }
    if (is.null(ylim)) {
      ylim = range(xy$y[is.finite(xy$y)])
    }
    opar = par(no.readonly = TRUE)
    on.exit(par(opar))
    plot.new()
    plot.window(xlim, ylim, ...)
    points(xy$x, xy$y, col=col, ...)
    if (axes) {
      axis(1)
      axis(2)
      box()
    }
    if (ann) {
      title(main=main, sub=sub, xlab=xy$xlab, ylab=xy$ylab, ...)
    }
           }

資料:《R繪圖系統》(第二版)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章