library(tidyverse)
## ─ Attaching packages ───────────────────────────────── tidyverse 1.2.1 ─
## ✔ ggplot2 3.1.0     ✔ purrr   0.2.5
## ✔ tibble  2.0.1     ✔ dplyr   0.7.8
## ✔ tidyr   0.8.2     ✔ stringr 1.3.1
## ✔ readr   1.1.1     ✔ forcats 0.3.0
## Warning: package 'tibble' was built under R version 3.5.2
## ─ Conflicts ─────────────────────────────────── tidyverse_conflicts() ─
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()

1 目的

2 plot{base}散布図に慣れる

2.1 散布図とは

2.2 描画に必要なデータ

\(xy\)平面上に1対のデータを点として表現するので,\(x\)軸,\(y\)軸に対応するデータが必要になる.plot関数では,x,yという引数それぞれに,ベクトルとして値を渡すことで,散布図を描画してくれる.

2.2.1 基本

二つの長さの同じベクトルを用意して,plotを使ってみよう.

v1 <- c(1,5,3,6,4)
v2 <- c(2,4,7,3,2)

ちなみに,plot(v1,v2)と引数を省略することもできる.その場合は,順序はx,yの順番で値を与える必要がある.

plot(x=v1, y=v2)

このベクトルとしてデータを渡すというイメージを定着させることが重要で,DataFrameMatrixを引数に与えてしまって,意図した結果が出ない場合がある. エラーになる場合をできるだけ列挙していこう(基本的にエラー文を読めば分かるはず).

2.2.2 'x' and 'x' lengths differ

このエラーは,xyに与えられたベクトルの長さが異なる場合に返される(そのまま...)

plot(x=v1, y=1)
xy.coords(x, y, xlabel, ylabel, log) でエラー: 'x' and 'y' lengths differ

なぜだ?と思ったら実際にlength()を使って調べてみると良い.

length(v1)
## [1] 5
length(1)
## [1] 1

2.2.3 データフレーム/行列の値を用いた散布図

実際のデータを可視化したい時,ほとんどの場合はデータフレーム型でデータを扱う.その場合であっても,plotには2つの同じ長さのベクトルを与えるということは変わらない.データフレームは,ある1列だけを取り出すとベクトルとして扱われる.以下はその確認.

is.vector(iris[,1])
## [1] TRUE
is.vector(iris$Sepal.Length)
## [1] TRUE

丁寧にやるならば,以下のようにすると理解しやすいかもしれない.

v1 <- iris[,1]
v2 <- iris$Sepal.Width
plot(v1, v2)

また,2列のデータフレームを1つ与えるとよしなに読み取ってくれる(1列目をx,2列目をy).

plot(iris[,c(1,2)])

2.2.4 xしか指定しない場合.(yを与えない場合)

引数xしか与えない場合は,yに要素番号(index)が与えられる.

plot(x=iris$Sepal.Length)

2.2.5 行に対するplot

ある1行を取り出した時,もし列数が3以上の場合は散布図行列が生成される.以下のコードの結果を見ると分かるように,データフレームの列については各列はベクトルであるが,行について,各行はベクトルではない.列ベクトルの集合のようなものだと考えると良いかもしれない.

is.vector(iris[1,])
## [1] FALSE

例えば,次のようにxirisの1行目の1,2,3列目を,yirisの2行目の1,2,3列目を与えると,各列のペアごとの散布図が生成されることがわかる.

iris[1,1:3]
##   Sepal.Length Sepal.Width Petal.Length
## 1          5.1         3.5          1.4
iris[2,1:3]
##   Sepal.Length Sepal.Width Petal.Length
## 2          4.9           3          1.4
plot(iris[1,1:3], iris[2,1:3])

基本的に扱いたいデータの単位は列になるはずなので,このようなこと気にする事は少ないと思うが,挙動としては知っておいて損はないかと思う.

2.3 点の見た目をいじる

2.3.1 type

plotは散布図だけではなく,線グラフなどもサポートしている.点と点を線で繋いでくれるが,その際はベクトルにおける各要素の前後の要素との間を直線で繋いでくれる.

2.3.1.1 type = l:グラフの種類を選ぶ

v1 <- c(1,5,3,6,4)
plot(v1, type = 'l')

線グラフの場合は,以下の引数を利用できる.

  • lty:line type つまり線の種類(整数または特定の文字列で指定)
  • blank: 0 or blank
  • solid(default): 1 or solid
  • dashed: 2 or dashed
  • dotted: 3 or dotted
  • dot dash: 4 or dotdash
  • long dash: 5 or longdash
  • two dash: 6 or twodash
  • lwd:line width つまり線の太さ.数値で指定.
df <- data.frame(x = seq(0.1, 10, 0.1))
df$y1 <- df$x
df$y2 <- df$x^2
df$y3 <- sin(df$x)*15
df$y4 <- log(df$x)
df$y5 <- {exp(df$x)}/{1+exp(df$x)}
df$y6 <- cos(df$x)*10

for(i in 1:6){
  if(i == 1){
    plot(x = df$x, y = df$y1, type='l',ylim = c(min(df), max(df)),
         lwd = i)
  }else{
    # lines()については後述
    lines(x = df$x, y = df[,i+1],
          lty = i, lwd = i)
  }
}

legend("topleft",
       legend = c("1:solid, lwd = 1",
                  "2:dashed, lwd = 2",
                  "3:dotted, lwd = 3",
                  "4:dot dash, lwd = 4",
                  "5:long dash, lwd = 5",
                  "6:twodash, lwd = 6"),
       lty = c(1,2,3,4,5,6),
       lwd = c(1,2,3,4,5,6))

2.3.1.2 type = 'b'

実際のデータ点がどこかわからない,という場合にはtype='b'を使うと良い.

plot(v1, type='b')

2.3.2 pch:点の形を変える

点の種類は○だけではない.いっぱいある.色々なウェブサイトでも紹介はされているが,ここでも再掲しよう.

v1 <- rep(1:10, 3)
v2 <- rep(1:3, each=10)

pchに数値を指定すると,対応する種類の点で描画してくれる.

plot(v1, v2, pch = 2)

pchの値も整数ベクトルで指定することができる.例えば,4つの整数を与えると,要素順に点の形が対応していき,足りない分は繰り返される形で点の種類が変わっていく.

plot(v1, v2, pch=c(1,2,3,4))

何種類あるか見てみる.

plot(v1, v2, pch=1:30)
## Warning in plot.xy(xy, type, ...): pch の値 '26' は未実装です
## Warning in plot.xy(xy, type, ...): pch の値 '27' は未実装です
## Warning in plot.xy(xy, type, ...): pch の値 '28' は未実装です
## Warning in plot.xy(xy, type, ...): pch の値 '29' は未実装です
## Warning in plot.xy(xy, type, ...): pch の値 '30' は未実装です

警告が出てきたが,25の数値まで実装されていることがわかる.

ちなみに,点を文字にすることもできる.

plot(v1, v2, pch = c('a','b','c','d','e','f','g',
                     'h','i','j','k','m','n','o',
                     'p','q','r','s','t','u','v'
                     ,'w','x','y','z'))

1文字を越えると,2文字目以降はよしなに消してくれる.

plot(v1, v2, pch = 'abd')

興味のあるイベントがどのタイミングで発生したか,という場合に縦棒「|」を使ったことがあります.

set.seed(100)
v1 <- sample(1:100, 20)
v2 <- rep(1, 20)
plot(v1, v2, pch = '|')

2.3.3 cex:サイズを変える

次は大きさを変えて見る.やはりベクトルで指定する.

v1 <- rep(1:10, 3)
v2 <- rep(1:3, each=10)
plot(v1, v2, pch=19, cex=1:30)

10でも相当でかい.ちなみにこの引数は小数点もカバーしている.

plot(v1, v2, pch=19, cex=c(1:30)/10)

2.3.4 col:色を変える

形を変えたら次は色を変えたいと思うのは当然ですよね.そのためにはcolという引数にこれまたベクトルとして色を指定できる.色については

  • 整数
  • カラーコード
  • 色の名前(英語)

のどれかで指定することができる.ちなみに,混在していてもOK.

2.3.4.1 整数の場合

v1 <- rep(1:10, 3)
v2 <- rep(1:3, each=10)
plot(v1, v2, pch = 19, col=2)

colの場合,整数の1から8までが対応しているが,pchの場合と異なり8を超えた場合は繰り返しで対応されることに注意.

plot(v1, v2, pch=19, col=1:30)

2.3.4.2 単語指定

red, blueなどでも指定できる.かなりの種類の単語が実装されているので,是非一度ここのサイトを見てみてほしい.

plot(v1, v2, pch = 19, col = c('lightblue', 'red', 'blue', 'orange', 'darkred'))

2.3.4.3 カラーコードの場合

Webに少し詳しい人なら分かると思うが,色は#58FAD0のようなカラーコードで表される.この形式の文字列でも指定は可能だ.

plot(v1, v2, pch = 19, col = '#58FAD0')

2.3.4.4 透明度

カラーコードに透明度を含めても透明度を調整することができるが,tidyverseパッケージを使っているならalpha()関数を使うことでスッキリ記述できる. alpha(色, 透明度)と指定するだけで良い.

plot(c(1,2), c(1,1), cex = 10, pch = 19, col = c(alpha('orange', 0.9) ,alpha('orange', 0.3)))

2.4 表示する領域を調節する

点の見た目の次は,表示領域の調整について紹介する.一個前のプロットでは,点が大きすぎて少しはみ出してしまっていた.plotでは表示領域を調節する用の引数が用意されている.

2.4.1 xlim, ylim

\(x,y\)軸それぞれの範囲を指定するためには引数xlim, ylimを使う.この二つの引数にはc(0, 1)のように長さ2のベクトルを与える.1つ目の要素が範囲の最小値,2つ目の要素が範囲の最大値を表す.次に,\(x\)軸が-1から3,\(y\)軸が-1から10としたプロットのコードを紹介する.

plot(c(1,2), c(1,1), cex = 10, pch = 19, 
     col = c(alpha('orange', 0.9) ,alpha('orange', 0.3)),
     # x軸の範囲の設定
     xlim = c(-1, 3),
     # y軸の範囲の設定
     ylim = c(-1, 10))

さっきははみ出ていた点が領域内に収まるように調整できた.

2.5 タイトルや軸ラベルを変える

2.5.1 タイトルの変更

タイトルを表示したい場合は,mainという引数を利用する.なぜtitleという名前でないのかは知らない.

plot(v1, v2, main= 'Zelda')

もちろん日本語も表示できる.windowsの場合は何もなくても表示されるが,Macの場合はおまじないが必要となる.今は思考停止でそういうものだと思ってもらって良いかと思う.おまじない付きのコードは下記.

par(family = 'HiraKakuProN-W3')
plot(v1, v2, main = '日本語だコラ')

2.5.2 軸ラベルを変える

これまでのプロットでは,x,yに与えたベクトルの表示がそのまま軸ラベルに反映されていることに注意してほしい.v1と与えればそのままV1と表示されているし,c(1,2)と与えるとそのままc(1,2)と表示される.
これらは,xlab, ylabという引数に文字列を与えることで変更できる.

plot(v1, v2,
     main = 'Zelda',
     xlab = 'hello',
     ylab = 'world')

2.6 既に描画されているプロットに重ねて描画する

回帰直線を引いたりするとき,もちろん一気に描いてくれる関数は便利だしどんどん使って行った方が良いが,使わなくてもできるよということで紹介したい.

2.6.1 重ね書き用の関数

基本的には以下の2つの関数で事足りるはずである(これ以外思いつかなかった).

  • points():点を追加
  • lines():線を追加

2.6.1.1 points()

# orangeで1を描画
plot(x = c(1,10), y = c(1,10), pch = '1', col = 'darkorange', cex = 3)

# skyblueで2を追記
points(x = 5, y = 5, pch = '2', col = 'skyblue', cex =5)

2.6.1.2 lines()

線上に追記したい場合はlines()を使う.

plot(c(1,10,4,6,2,3,6), type='l', col = 'darkorange', lwd = 2)

lines(c(10,4,6,2,3,5))

2.6.2 par()による重ね書き

par()関数は,プロットする前に設定を渡す関数である.フォントの指定だったり,描画する領域のサイズなど色々な指定ができる.詳しくは?parでヘルプを参照されたい.
ここではpar(new=TRUE)を噛ませることで重ね書きできることを紹介する.使い方は以下の通り.

plot(1,1, pch=19, col="darkorange", cex = 2)
par(new=TRUE) # 次に書くプロットは既にあるプロットの上から書くことにする
plot(1.4, 1, pch = 18, col = "skyblue",  cex = 5)

この方法は少し問題がある.それは,上記で紹介したpoints(), lines()とは異なり,プロットの軸やラベルも含めた全体を上から重ね書きしてしまうところだ.軸ラベルも重なってぐちゃぐちゃになっていることがわかる.この場合は次のようにすると良い.

  • xlim, ylimの引数で描画領域を合わせる
  • axis = FALSE としてどちらかのプロットの軸を表示させない
  • main, xlab, ylab には''を指定して表示させない

などである.上記のプロットを修正してみる.

plot(1,1, pch=19, col="darkorange", cex = 2,
     xlim = c(-1, 2), ylim = c(-1, 2))
par(new=TRUE) # 次に書くプロットは既にあるプロットの上から書くことにする
plot(1.4, 1, pch = 18, col = "skyblue",  cex = 5,
     xlim = c(-1, 2), ylim = c(-1, 2),
     axes = FALSE, 
     xlab = '', ylab = '')

par(new = TRUE)による重ね書きのメリットは,y軸が2軸のプロットを描けるところかと思う.この方法は少し応用的な部分になるので割愛する.気になる方は是非調べてみてほしい.

2.7 matplot, matpoints, matlinesの紹介

最後に,複数系列を一度に指定して描画できるmatplot系関数を紹介する.
ただ,matpoints, matlinesの使い方はmatplotとほぼ同じでpoints, linesと挙動は同じなのでmatplotのみ説明する.

2.7.1 matplot

この関数はx,yそれぞれデータフレームまたは行列を指定する.この時,列番号に対応して点が描画される.実際,点として描画するだけならあまり恩恵は内容に思えるが,列ごとにpch, colを変えてくれるのが便利である.もちろん指定もできるし,その場合は列に対応させたベクトルを与えれば良い. yのみ指定する場合は,xindexで補間してくれるが,ここでは解説のため指定する.

Y <- iris[, -5]
head(Y)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3.0          1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5.0         3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4
X <- matrix(rep(1:nrow(iris), 4), ncol=4, byrow=FALSE)
head(X)
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    2    2    2    2
## [3,]    3    3    3    3
## [4,]    4    4    4    4
## [5,]    5    5    5    5
## [6,]    6    6    6    6
matplot(x=X, y=Y, col = c('gray', 'darkorange', 'skyblue', 'purple'))

3 終わりに

せっかくRを使うなら可視化が手軽にできるということを知って欲しいという思いと,plotでよくつまづくポイントを解決する情報は目的によって散らばっているなぁと感じたため,自分なりに過去つまづいたことを思い出しながらまとめてみました.まだまだ足りないところや,こうやった方が良い!という点はご指摘いただけると嬉しいです. プログラミング経験のない人にとってはplot一つとっても慣れるまでが少し大変ですが,誰かの参考になれば嬉しいです!