R

トップページ
Google
WWWを検索
サイト内を検索

前世紀、1990年代のやり方ですが、ggplot2パッケージが無くてもそこそこ見やすいグラフが描けます。

1 縦横サイズを指定したキャンバスを作る

plotbarplotcurvehistなどの高水準描画関数はグラフィックスデバイスのオープンもやってくれますが、高水準描画関数に上手くあてはまらない場合は、低水準描画関数を組み合わせて作図することになり、まずはキャンバスを作る必要があります。

以下の例では、(0, 0)から(2, 3)までの座標を取るプロット先のキャンバスを作ります。

plot.new()
xlim <- c(0, 2)
ylim <- c(0, 3)
plot.window(xlim=xlim, ylim=ylim)

なお、Rの高水準描画関数はdemo("graphics")で見られるものの他、assocplot, cdplot, dotchart, filled.contour, fourfoldplot, mosaicplot, persp, smoothScatter, spineplot, stars, stem, stripchart, sunflowerplotと多様にあるので、低水準描画関数を組み合わせる前に、既にあるもので間に合うかも知れません。ヘルプのサンプルコードをコピペしたら、見栄えを確認できます。またggplot2のようなパッケージをインストールすれば、手軽に見栄えのよいプロットが可能です。

2 描画関数のオプション

ヘルプに大きく書いてあれば助かるのですが、探すのが大変なパラメーターの値です。

2.1 点キャラクター(pch

pointslinesで指定できるpchパラメーターは以下の値を取れます。pch=21からpch=25では内側に色がつき、引数bgで指定できます。

上述の数値指定以外もでき、pch="."とすると一点で、その他の文字を指定(e.g. pch="p")すると指定した文字でプロットします。

2.2 線種(lty

linesarrowsで指定できるltyパラメーターは以下の値をとれます。

2.3 プロットのタイプ(type

点だけ、線だけ、点と線の組み合わせるなどのプロットの種類を指定するtypeパラメーターは、"p", "l", "o", "b", "c", "n", "h", "s", "S"の値を取れます。

以下はpch=21で描画した例です。

"h", "s", "S"はそんなに知られていないかも知れません。 type="h"は、実質的に棒グラフになります。

type="s"は、区間(x x+1)y(x)になるグラフで、例えばx=0y=0x=1y=0.64のとき、x=0.5y=0になります。 x+1のときはxの値とx+1の値が垂直な線で結ばれ、下図ではx=1のとき、(1, 0.00)(1, 0.64)が結ばれています。

type="S"は、区間(x x+1)y(x+1)になるグラフで、例えばx=0y=0x=1y=0.64のとき、x=0.5y=0.64になります。 x+1のときはx+1の値とx+2の値が垂直な線で結ばれ、下図ではx=1のとき、(1, 0.64)(1, 0.98)が結ばれています。

2.4 色(col,bg

グラフィックス関数の引数colbgには、colors関数でリストされる色の名前を指定するか、rgb関数の戻り値を指定することができます。

colors関数は戻り値の数が多いので、以下のようにgrepで絞って選びましょう。

grep("^red[0-9]*$", colors(), value=TRUE)
[1] "red"  "red1" "red2" "red3" "red4"

今までrgbを使う機会が無かったのですが、"brown"などと同様に入れられます。

par(mar=c(4, 4, 0, 0))
curve(dgamma(x, 2), 0, 10, lwd=10, col=rgb(0.5, 0.5, 0))

2.4.1 グラデーション

rainbow関数でグラデーションが得られるわけですが、実践的にはcolorRampPalette関数でグラデーション作成関数を作ることになると思います。

crp <- colorRampPalette(c("red", "darkred"))
crp(10)
 [1] "#FF0000" "#F20000" "#E50000" "#D80000" "#CB0000" "#BE0000" "#B10000"
 [8] "#A40000" "#970000" "#8B0000"

2.5 フォントサイズ

textmtextcexを指定するとフォントサイズが変わりますが、複数のテキストを扱うplottitleのパラメーターには以下が用意されています。

パラメーター 説明
cex.lab 縦横の軸のラベルの大きさ
cex.axis 目盛りの大きさ
cex.main タイトルの大きさ
cex.sub サブタイトルの大きさ

3 余白の調整

Rのプロットはデフォルトでは余白が多いです。しかし、TeXの方でタイトルや注釈を入れる場合は、プロット領域を大きく取りたくなるので、この余白を削りたくなります。 par関数のmarmgpオプションで余白を調整できるので覚えておきましょう。

3.1 marオプション

marオプションは、下・左・上・右の余白を指定します。

par(mar=c(5, 4, 4, 2) + 0.1)

3.2 mgpオプション

mgpオプションは軸ラベル・軸メモリ・軸線の位置を指定します。

par(mgp=c(3, 1, 0))

4 グラフ中文字列の上下左右の寄せ

text関数でプロット領域に文字を書き込むことができるわけですが、指定座標と文字位置の関係を示すadjパラメーターの意味を整理してみましょう。 キャンバスの中心に表示座標をとり、adjの値を色々と変えてみた結果が以下です。

  • c(1, 0)だと、文字列の右下に表示座標が来ます。
  • c(0, 0)だと、文字列の左下に表示座標が来ます。
  • c(0, 1)だと、文字列の左上に表示座標が来ます。
  • c(1, 1)だと、文字列の右上に表示座標が来ます。
  • 0.5を入れておくと中央にあわせになります。

つまり、adj=c(x, y)の値はtext関数の第1引数と第2引数が示す座標を左下においたマイナス方向への相対座標で、xは文字列の長さを単位にしており、yは文字列の高さを単位にしています。

mtext関数のadjはベクトルではなく、値一つのスカラー値になるので注意してください。上下寄せを指定できません。

5 調整に必要な情報の取得

par()$usrでプロット可能な座標の範囲を取得できます。par()$usr[1]が横軸の最小値、par()$usr[2]が横軸の最大値、par()$usr[3]が縦軸の最小値、par()$usr[4]が縦軸の最大値です。 strwidth関数で表示したときの文字列の長さ、strheight関数で高さを取得することができます。

6 数式の書き込み

表現式オブジェクトで表される数式をplotmain,xlab,ylablegendtextなどに代入し、プロットすることができます。

例えば、

text((par()$usr[1] + par()$usr[2])/2, 
    (par()$usr[3] + par()$usr[4])/2, 
    expression(f(x) == 
        frac(1, sqrt(2*pi*sigma^2))*
            exp(-frac((x - mu)^2, 2*sigma^2))), 
    cex=3)

とすると、以下のように中央に表示されます。

利用できる記号などは、?plotmathで出せるヘルプか、demo(plotmath)で見られる例を参照してください。

7 見栄えの良い軸のつけ方

以下のプロットの軸の見栄えをよくしてみましょう。

7.1 軸の目盛りを設定する

axes=FALSEオプションをつけてplotを行い、axis関数で軸を書きます。

at <- seq(-2.5, 2.5, 0.5)
labels <- sprintf("%.2f", at)
axis(1, at=at, labels=labels)
axis(2, at=seq(0, 0.6, 0.1))

7.2 軸の目盛りを回転する

縦軸の目盛りの数字が読みづらいのでlas=1を指定して回転させます。横軸のラベルが長くて、目盛りが表示できないときなどにも有効です。

at <- seq(-2.5, 2.5, 0.5)
labels <- sprintf("%.2f", at)
axis(1, at=at, labels=labels)
axis(2, at=seq(0, 0.6, 0.1), las=1)

7.2.1 目盛りの45°回転

axis関数のlabels引数をFALSEにして、text関数に引数xpd=TRUE, srt=-45をつけてラベルを書けば、位置あわせが煩雑ですが、実現できます。

axis(1, at=at, labels=FALSE)
text(x=at + 0.15, par("usr")[3] - 0.1, labels=labels, srt=-45, adj=c(1, 1), xpd=TRUE)
axis(2, at=seq(ylim[1], ylim[2], 0.1), las=1)

7.3 縦軸のラベル位置を、縦軸の上にする

スペースの都合などで縦軸のラベル位置を動かしたいときは、plotの引数にylab=""をつけたあと、text関数に引数xpd=TRUEをつけてラベルを描きます。

表示位置はpar()$usrでとれるキャバスのサイズの左上のさらに1行上にしますが、text関数の引数にxpd=TRUEが無いと、表示可能エリア外になって表示されないので注意してください。

text(par()$usr[1], par()$usr[4], "density", adj=c(0.5, -1), xpd=TRUE)

7.4 縦軸と縦軸の位置を詰める

例のヒストグラムで不要だと思いますが、axis関数の引数posで表示位置を詰めることもできます。

at <- seq(-2.5, 2.5, 0.5)
labels <- sprintf("%.2f", at)
axis(1, at=at, labels=labels, pos=0)
axis(2, at=seq(0, 0.6, 0.1), las=1, pos=at[1])
text(at[1], par()$usr[4], "density", adj=c(0.5, -1), xpd=TRUE)

8 凡例を書く

プロット後、legend("topright", "折れ線", lty=1)と言う風に凡例を書くことができますが、必然的にplinespointsにつけたパラメーターを統合した上に、見栄えを工夫すると凡例ボックスの枠線の色や太さといったパラメーターも入ってくるので、長くなります。試行錯誤する場合は、リストにまとめてdo.callするなりしましょう。

よく指定しそうなパラメーターをつけた例が以下です。

# 描画領域を作成する
par(mar=c(0, 0, 0, 0), bg="white")
plot.new()
xlim <- c(0, 1)
ylim <- c(0, 1)
plot.window(xlim=xlim, ylim=ylim)

params <- list(
    # 凡例の位置を指定
    "topleft", 
    # 凡例の描画域の隅からのマージン
    inset = 0.0,
    # 凡例のテキスト
    legend = c("point", "line", "type=\"b\"", "fill"), 
    # ポイントの形状,NAはポイントなし
    pch = c(21, NA, 22, NA),
    # 線の形状,NAは線なし
    lty = c(NA, 4, 1, NA),
    # 線の長さ
    seg.len = 3,
    # 線/ポイントの太さ
    lwd = c(3, 3, 3, NA),
    # 線/ポイントの色
    col = c("black", "red", "darkgreen", NA),
    # ポイントがpch=21~25のときの内側の色
    # "transparent"を入れておくと、type="o"に見える
    pt.bg = c("gray", NA, "green", NA),
    # 塗りつぶし色,NAは塗りつぶしなし
    fill = c(NA, NA, NA, "cyan3"),
    # 塗りつぶし領域の境界線の色
    border = c(NA, NA, NA, "blue"),
    # 塗りつぶし領域の斜線の密度(0~100),0は塗りつぶしなし
    density = c(0, 0, 0, 20),
    # 塗りつぶし領域の斜線の角度
    angle = c(NA, NA, NA, 60),
    # ポイント,線,塗りつぶし領域の大きさ
    cex = c(1.5, 1.5, 1.5, 1.5),
    # シンボル(i.e. 線/ポイント/塗りつぶし)とテキストの距離
    x.intersp = 1,
    # 凡例内の行間
    y.intersp = 1.25,
    # テキストの配置
    adj = c(0, 0.5), 
    # テキストの色
    text.col = c("black", "red", "darkgreen", "blue"),
    # 凡例の表題
    title = "凡例",
    # 凡例のシンボルとテキストを横並びにするか
    horiz = FALSE,
    # 縦並びのときは{ncol}段組みにできる
    ncol = 1, 
    # bty="n"のときは、bg, box.*は無視される
    # fillを使うときのbgはtransparentを推奨
    bty="o", bg = "transparent", box.col = "black", box.lwd=10)

do.call("legend", params)

params[[1]] <- "topright"
params[["ncol"]] <- 2
params[["inset"]] <- 0.04
params[["bty"]] <- "n"
do.call("legend", params)

params[[1]] <- "bottomright"
params[["horiz"]] <- TRUE
params[["ncol"]] = NULL 
params[["inset"]] <- 0.01
params[["bty"]] <- "o"
params[["cex"]] <- 1.125 # 全部まとめて指定もできる
do.call("legend", params)

凡例のグラフ内位置を"bottom", "bottomleft", "left", "top", "right", "center"insetで指定するのではなく、座標で指定することもできます。また、plot = FALSEで描画せずにサイズだけ戻してくれるので、やろうと思えば細かく調整も可能です。

ヘルプも充実しているのですが、ややこしいので試行錯誤して試した方がよいです。なお、シンボルのレイアウトを中央寄せにできず、density指定時の背景色を指定できないなど、fillの扱いが悪いです。プロットや折れ線が大事で、棒グラフはオマケ感が。

9 グラフを見やすくする

デフォルトだと視認性が低いことが多いので、目的にあわせて装飾しましょう。

9.1 参照線を描く

ablineで垂直もしくは水平線を描くことができ、grid関数でまとめて描画できます。

par(mar=c(5, 4, 2, 2))
curve(sin(x), -pi, pi)
abline(v=0.5, lty=4, lwd=2, col="pink") # 垂直線
abline(h=0.5, lty=5, lwd=2, col="pink") # 水平線
grid() # デフォルトでは目盛の位置

gridは引数nxnyで、横方向と縦方向の線の数を指定でき、NULLだと目盛りの位置、NAだと表示なしになります。また、対数スケールのときはequilogs = FALSEをつけておかないと、目盛の位置にあわせてくれません。

9.2 領域に色を塗る

polygon関数で色を塗ります。閉路を指定しないといけないので、やや煩雑です。

# 内側を塗った後に外側(plot)を描くので低水準描画関数のみを使う
par(mar=c(3, 3, 2, 2))
plot.new()
xlim <- c(0, 3)
ylim <- c(0, 1)
plot.window(xlim=xlim, ylim=ylim)

# 閉路をつくる
n <- 100
x <- c(seq(xlim[1], xlim[2], length.out=n), xlim[2], xlim[1])
y <- c(dexp(x[1:n]), 0, 0)

# 内側を塗る
polygon(x, y, density=50, col="gray", border=NA)

# 外側の線を描く
lines(x[1:n], y[1:n], lty=1, lwd=2, col="black")

# 縦軸と横軸を描く
at <- seq(xlim[1], xlim[2], length.out=10)
axis(1, at=at, labels=sprintf("%.2f", at))
at <- seq(ylim[1], ylim[2], length.out=11)
axis(2, at=at, labels=sprintf("%.2f", at), las=1)
text(par()$usr[1], par()$usr[4], "f(x)", 
    adj=c(0.5, 0), xpd=TRUE, cex=1.25)
text(par()$usr[2], par()$usr[3], "x", 
    adj=c(0, 0.5), xpd=TRUE, cex=1.25)

# 確率密度関数を書く
text((par()$usr[1] + par()$usr[2])/2, 
    (par()$usr[3] + par()$usr[4])/2, 
    expression(f(x) == lambda*exp(-lambda*x)), 
    cex=2, adj=c(0, 0.5))

9.3 参照線や塗りつぶしをプロットの背景に描く

参照線や塗りつぶしがプロットした線や点を見えづらくしてしまう場合は、plotpanel.first引数に表現式を与えることで、参照線や塗りつぶしを簡単に先に描画できます。

par(mar=c(5, 4, 1, 1), mfrow = c(1, 2))

# プロットした線が太すぎる垂直線に隠れる
curve(sin(x), -pi, pi)
abline(v=0, lty=1, lwd=100, col="pink")

# 太すぎる垂直線の上に線がプロットされる
curve(sin(x), -pi, pi, panel.first = {
    abline(v=0, lty=1, lwd=100, col="pink")
})

9.4 ビットマップを貼り付ける

プロットにビットマップを貼り付けることもできます。

plot.new()
xlim <- c(0, 1)
ylim <- c(0, 1)
plot.window(xlim=xlim, ylim=ylim)

# 行列をラスターにして描画
invader.m <- matrix(c(0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0,
1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0), 11, 9)
invader.r <- as.raster(ifelse(1==t(invader.m), "red", "transparent"))
# interpolate=TRUE(デフォルト)の場合はアンチエイリアスがかかる
rasterImage(invader.r, 0.0, 0.6, 0.0 + 0.4, 0.6 + 0.4, interpolate=FALSE)

# PNG画像を読み込むパッケージ
library(png)

# ローカルのPNG画像をラスターにして描画
img <- readPNG(system.file("img", "Rlogo.png", package="png"))
rasterImage(as.raster(img), 0.5, 0.2, 1.0, 0.7)

# ウェブのPNG画像をラスターにして描画(readPNGがhttp://を読まない)
is <- file("http://wh.anlyznews.com/img/R.png", "rb")
raw <- readBin(is, "raw", 1024*128)
close(is)
rasterImage(as.raster(readPNG(raw)), 0.0, 0.0, 0.4, 0.4)

jpegパッケージのreadJPEG関数を使えば、JPEG画像も扱えます。

10 日本語フォント

手っ取り早く日本語を扱う方法を記します。

10.1 ラスター(ビットマップ)/Windows Meta File

MS-Windowsでラスター(ビットマップ)かWindows Meta Fileを出力をする場合は、以下のようにフォントを登録してpar時にfamily引数を指定すると、とりあえず文字化けすることは回避できます。

windowsFonts(Meiryo = windowsFont("Meiryo"))
par(family="Meiryo")

10.2 Encapsulated PostScript

epsに出力する場合は、Ghostscriptをインストールした上で、以下のGhostscriptに標準で搭載されていて、names(postscriptFonts())で確認できる、既にRに登録されたFont family名の中から、日本語フォントを選んで指定しましょう。 標準ではJapan1, Japan1HeiMin, Japan1GothicBBB, Japan1Ryuminが選択肢になります。

例えば、プロットを画面に出力した後で、

dev.copy(postscript, file = "plot.eps", width=8, height=4, family="Japan1")
dev.off()
# フォントを埋め込む.標準ではフォントを埋め込まず,gsがインストールされていない環境では文字化けしうる.
embedFonts("plot.eps",
    outfile="plot-fonts-embedded.eps",
    options="-c \"<</NeverEmbed []>> setdistillerparams\" -f ")

と言う風に指定します。

10.3 EPSで扱えるフォントを増やす

例えばMS-WindowsでMeiryoを扱いたい場合は、Ghostscriptの設定ファイルのcidfmap1に、

/Meiryo            << /FileType /TrueType  /CSI [(Japan1) 6]  /Path (C:/Windows/Fonts/meiryo.ttc)    /SubfontID 0 >> ;
/Meiryo-Italic     << /FileType /TrueType  /CSI [(Japan1) 6]  /Path (C:/Windows/Fonts/meiryo.ttc)    /SubfontID 1 >> ;
/Meiryo-Bold       << /FileType /TrueType  /CSI [(Japan1) 6]  /Path (C:/Windows/Fonts/meiryob.ttc)   /SubfontID 0 >> ;
/Meiryo-BoldItalic << /FileType /TrueType  /CSI [(Japan1) 6]  /Path (C:/Windows/Fonts/meiryob.ttc)   /SubfontID 1 >> ;

と追記して保存してから、

if(!"Meiryo" %in% names(postscriptFonts())){
    # UCS-2BE: Universal Character Set coded in 2 octets, Big endian
    postscriptFonts(Meiryo = CIDFont("Meiryo", "UniJIS-UCS2-H", "UCS-2BE"))
}

とすると、EPSファイルを作るときにfamily="Meiryo"と指定できるようになります。 なお、標準的な設定でのインストールではなく、*.ttcファイルの位置が異なる場合は、ファイルパス(C:/Windows/Fonts/)を変更してください。

11 プロットを重ねる

一つ目をプロットした後、par(new=T)と命令し、二つ目をプロットするだけで、高水準グラフィックス関数のプロットを重ねることができます。 ただし、

  • 題名は片方だけで指定する
  • 2つのプロットのxlimをあわせておき、横軸の表示はaxis関数でまとめて行なう
  • 二つ目のプロットの縦軸はaxis関数の第1引数を4にして行なう

などの工夫をする必要があります。

obs <- 3000 # 乱数で生成する標本数は3000
s <- rnorm(obs, mean=0, sd=1) # 正規分布からベクトルsに乱数を生成
m <- 1.05 # mは上限調整用の係数
hm <- m*obs*(pnorm(0.5, 0, 1) - pnorm(0, 0, 1)) # ヒストグラムの高さの上限hmを設定
dm <- m*dnorm(0, 0, 1) # 分布関数の高さの上限dmを設定

par(mar=c(4, 4, 4, 4))

hist(s, breaks=12, main="標準正規分布", ylab="obs.", xlab="", xlim=c(-4, 4), ylim=c(0, hm), col="white", border="black", angle="45")

par(new=T) # 重ね書きを指定

curve(dnorm(x, 0, 1), -4, 4, main="", ylab="", xlab="", axes=FALSE, col="black", ylim=c(0, dm), lwd=1.5) # 軸目盛や軸ラベルは表示しない
axis(side=4) # 右側に分布関数の縦軸目盛を表示する

12 複数のグラフを並べる

par(mfrow = c(m, n))の後にm*n回プロットを行なえば、mn列で合計m*n個のグラフを同時にプロットできます。

12.1 変則的な分割を行なう

1行目は1つのプロットで2列を使い、2行目は2つのプロットを表示するような変則的な同時プロットも、split.screen関数で入れ籠構造をつくれば表示できます。

# omaはページ全体の余白の指定
# par(mar=c(4, 4, 0, 0), mgp=c(2.5, 1, 0), oma = c(0, 0, 0, 0))
par(mar=c(4, 4, 1, 1), mgp=c(3, 1, 0), oma = c(1, 1, 1, 1))
# 2行1列の親領域をまずつくり、スクリーン番号の戻り値を得る
ss_p <- split.screen(c(2, 1)) 
# スクリーン番号を指定して、1行2列の子領域をつくる
ss_c <- split.screen(c(1, 2), ss_p[2])
# 親領域のスクリーン番号を指定して描画
screen(ss_p[1])
curve(dlnorm, 0, 5, xlab="")
# 子領域のスクリーン番号を指定して描画
screen(ss_c[1])
curve(dnorm, -3, 3, xlab="")
# 子領域のスクリーン番号を指定して描画
screen(ss_c[2])
curve(dexp, 0, 3, xlab="")

13 グラフを保存する

pngpostscriptなどでグラフィックスデバイスを開いた後、plotをして、dev.offでグラフィックスデバイスを閉じれば保存できます。

png(filename = "sin.png", 
    width = 800, height = 600, bg="white", type="cairo")
curve(sin(x), -pi, pi)
dev.off()

13.1 グラフィックスデバイスの開閉とプロットを分離

グラフィックスデバイスのオープンとプロットを分離したい場合は、遅延実行を応用します。 プロット部分だけ関数化できるので、同じ描画のコードを、画面確認,Web用PNG,TeX用EPS,R Markdownなどに使いまわしたいときに便利です。

save_as_png <- function(fname, plot_expression){
    png(filename = fname,
        width = 800, height = 600, bg="white", type="cairo")
    plot_expression
    dev.off()
}

save_as_png("sin.png", { curve(sin(x), -pi, pi) })

13.2 ディスプレイに表示した後に保存

plotしてディスプレイに表示した後に保存したくなった場合は、dev.copydev.copy2epsを使います。

curve(sin(x), -pi, pi)
dev.copy(png,
    file = "sin.png", width=600, height=400, type="cairo")
dev.off()

14 終わり

思い出せたところを列挙しました。後で追記するかも知れません。


  1. C:\Program Files\gs\gs10.0.0\lib\cidfmapなど、Ghostscriptのインストール先のlib以下にあります。↩︎