ill-identified diary

所属組織の見解などとは一切関係なく小難しい話しかしません

ggplot2 で積み上げ折れ線グラフ(エリアプロット)を作成する方法

概要

時系列データセットを加工し, ggplot2 で作図する場合を例に, 以下のことに言及する.

  • ggplot2で複数の系列の折れ線を1つのグラフに表示する方法
  • さらにそれを積み上げグラフ(エリアプロット)に変形する方法
  • デザインを洗練する方法 -- 色系統の変更 -- 凡例の変更 -- 軸の目盛り数値のフォーマット変更

ただし, Rやggplot2 のごく基本的な使用方法を知っていることが前提. ggplot2 の基本は ggplot2 の自分用メモ集を作ろう あたりで.

下準備

まずはパッケージとデータを読み込む.

library(ggplot2)
library(reshape2) # melt 関数は ggplot で複数系列のデータをプロットできるように変換するのに必要

データの用意

積み上げグラフをプロットするには, まず複数系列のデータを1つのグラフ内に収めなければならない. よって, 先に複数系列のデータをグラフ化する方法を書いておく. まずはデータを用意.

data(Seatbelts) # イギリスの交通事故犠牲者
df <- data.frame(Seatbelts, year=rep(1969:1984,each=12) ) # データフレームへ変換

#年ごとの合計に簡略化 (要 plyr パッケージ)
library(plyr) 
df.yearly <- ddply(df, summarise, .variables="year", drivers=sum(drivers), front=sum(front), rear=sum(rear), .progress="text")
detach("package:plyr")

他のデータセット持って来いと言われそうだが, 積み上げ折れ線グラフの作例にはイギリス国内の交通事故犠牲者の時系列データ Seatbelt を使用. 1969-84年までの月ごとのデータ. driversKilled に運転手の死亡・重傷者, front に前部座席に座っていた乗員の事故死亡者・重傷者 後部座席に座っていた乗員の死亡・重傷者が rear に格納されている. ただ, そのままでは見づらいので, plyr パッケージで年単位の合計に加工しなおした. データフレーム df.yearly に変換したものをもとに, まずはこの3つの時系列データの推移を折れ線グラフで表す. ggplot() 関数にデータフレームを与え, geom_line() を3回用いてでそれぞれの系列に分けて表す, という方法でも複数系列のプロットはできる. しかし ggplot2 には複数系列のプロット用に自動で色分けしたり凡例を設定したりしてくれる機能があるため, これに則った構文を用いたほうが便利である. ただし, そのためにはデータフレームの構造を変えることが必要.

df.yearly オブジェクトは,

  year drivers front rear
1 1969   19951 11373 4912
2 1970   21939 12527 5307
3 1971   22309 12047 5244
.
.
.

というふうなデータフレームになっている. ggplot2 で複数の時系列データを扱う時, y軸の値を同一の列に収め, それぞれの値が元の系列のどれであったかを記録する列を別に作る. まずは実際にやってみた方が分かりやすい. reshape2 パッケージに含まれる melt 関数でこれを実行する.

library(reshape2)
df.melt <- melt(data=df.yearly, id.vars="year", measure.vars=c("drivers", "front", "rear")) 
head(df.melt)
  year variable value
1 1969  drivers 19951
2 1970  drivers 21939
3 1971  drivers 22309
.
.
.

このように, variable 列に元の系列の名前が, value 列にy軸の値が格納される. melt 関数では, id.vars 引数に x 軸に対応する列を指定し, measure.vars に元のデータフレームの列のうち, value 列に格納したいものを与える. また, 出力後の variabel, value という列名はそれぞれ変更可能だが, 変更にあまり意味はないと思うので省略.

図の作成

melt 関数で形成した複数系列のデータをプロットするには, 本質的には以下のコードだけでいい.

ggplot(df.melt) + geom_line(mapping=aes(x=year, y=value, group=variable))

f:id:ill-identified:20140222183348p:plain

group 引数に variable を与えることで, ggplot が系列を識別してくれる. ただし, これだけではどれがどの系列に対応するかがわかりづらい. そこで, 線種を指定する linetype もしくは色を指定する color 引数にも variable を与える.

ggplot(df.melt) + geom_line(mapping=aes(x=year, y=value, group=variable, linetype=variable, color=variable))

f:id:ill-identified:20140222183400p:plain

ラベル名を日本語にしたいとか, 色が毒々しいとかデザインに関するものは後回しにして, 先に積み上げグラフのプロット例も記す. 複数系列の折れ線グラフを積み上げ折れ線グラフにするのも非常に単純で, geom_line() に position="stack" を与えるだけで良い (aes の引数でないことに注意). さらに, 線だけでは積み上げグラフであると認識しづらいため, 各領域を塗りつぶすために, geom_ribbon 関数を使う. こちらにも, group=variable, position="stack" の指定が必要なのに加え, 各領域を塗りつぶす色も折れ線と統一するため, fill=variable とする. また, 輪郭をはっきりさせるため, linetype の指定はせず, また geom_ribbon() の alpha 引数を指定して塗りつぶしを半透明にしている.

(g.stack <- ggplot(df.melt) + geom_line(mapping=aes(x=year, y=value, group=variable, color=variable), position="stack")  + geom_ribbon(mapping=aes(x=year,  y=value, ymin=0, ymax=value, group=variable, fill=variable), position="stack", alpha=.7) )

f:id:ill-identified:20140222183424p:plain

position="stack" とした場合, aes に新たに ymax 引数を与えることになる. geom_line 関数ならば ymax を与えなくとも自動で y で代用してくれるが, geom_ribbon は挙動がおかしくなるので入れておく. geom_* 関数は, 基本的どれも position 引数の指定だけで積み上げグラフとして扱ってくれる. 例えば, 上のグラフに geom_text 数値を表記したい場合も, position="stack" としない場合, 積み上げグラフとテキストラベルの位置がずれることになる.

デザインの洗練

軸ラベル・凡例の設定

軸のラベルを変更する場合, xlab, ylab 関数があるが, labs 関数でラベルをまとめて変更できる. また, title で表題を指定できる. 凡例のタイトル (ここでは variable) は color, fill, linetype それぞれに設定できる. 一方なくすには theme() 関数に legend.title=element_blank() を与える. また, 凡例のラベルを変更する場合, scale_* 系の関数を使うことになる. 凡例のラベルも color, fill, ... ごとに設定されているので, 今回は塗りつぶし色の fill のみに設定し, 残りの linetype, color に対しては guide=F を与えることで非表示にしている. また, 凡例の並びとグラフの積み上がっている順序が逆なのも気になる. そこで, guide=guide_legend(reverse=T) を使って凡例の順序を逆転させた

legend.labels <- c("運転手", "前部座席乗員", "後部座席乗員")
( graph2 <- g.stack + labs(x="年", y="死傷者数(人)", title="イギリスにおける交通事故死傷者数" )  + scale_fill_discrete(name="", labels=legend.labels, guide = guide_legend(reverse=T) ) + scale_linetype_discrete(guide=F) + scale_color_discrete(guide=F) + theme_bw()  )

軸の目盛りの変更

また, scales パッケージ併用で目盛り数値を変更することも可能. 例えばあまりに大きな数字が含まれていると, 数値が自動的に指数表記になるため, それが困るという場合に利用できる.

library(scales)
graph2 + scale_y_continuous(labels=comma) # y軸に3桁区切りコンマを付ける
graph2 + scale_y_continuous(labels=dollar) # y軸に3桁区切りのコンマを付け, ドル表記する
graph2 + scale_y_continuous(labels=percent) # y軸をパーセントとして扱う

# 応用
graph2 + scale_y_continuous(labels=function(x) paste("¥", format(x, big.mark=","), sep="")) # scales を使わず, 円記号と3桁区切りコンマを付ける

scale_x_*() もしくは scale_y_*() の labels 引数には, 任意の文字列を与えても良い.

element_text() 関数は文字の大きさや角度, 位置を調整できる. よって, これを使えば y軸ラベルを回転させ, 横書きにし, ラベルを上の方に持ってくることで日本で一般的な (?) 見た目にできる. 詳しくは element_text() および theme() 関数のヘルプ参照.

graph2 + theme(axis.title.y = element_text(angle = 90*0, vjust = 1) ) # vjust で [0,1] の範囲で垂直方向の位置調整

# この例ではグラフの左側が間延びするのでラベルを改行して調整. タイトルも改行可能.
graph2 + theme(axis.title.y = element_text(angle = 90*0, vjust = 1) ) + ylab("死傷者数\n(人)")

f:id:ill-identified:20140222184350p:plain

色の設定

塗りつぶし色を同系統にしたい場合, scale_fill_brewer() 関数でカラーパレットを指定できる. ただし, 今回は geom_line の線の色である color も scale_color_brewer() で変更したほうが良い.

graph2 + scale_fill_brewer(palette = "Greys") + scale_color_brewer(palette="greys")

カラーパレットについては, RColorBrewer パッケージからコントラストのはっきりしたものを利用できる.

参考

[R] 都知事選挙を題材に学ぶ ggplot2 の作例

概要

前回 (ggplot2 で積み上げ折れ線グラフ(エリアプロット)を作成する方法) に引き続き, ggpplot の作例を紹介する. 前回は時系列データから積み上げ折れ線グラフを作成したのに対し, 今回は2014年2月9日に行われた東京都知事選挙の結果という横断面データを用いてグラフを作成する. 具体的には, 以下のような作例を紹介する.

  • 棒グラフ (バープロット) を用いて候補者別・世代別得票数を可視化する
  • バープロットのデザインのバリエーションをいくつか紹介
    • 縦横を転置する方法
    • 円グラフに変換する関数

データフレームの作成

去る2014年2月9日に行われた東京都知事選のデータから棒グラフを作成する. ここでは, 【更新】東京都知事選挙(2014年2月9日実施)投票動向分析 で算出されている主要候補者別・世代別投票数の値を借りて作成する.

# source: http://www.jgnn.net/ls/2014/02/201429.html
vote <- data.frame(matrix(c(73954,230315,325399,291591,473307,718413,2112979,37339,132650,158198,145424,222066,286918,982595,22946,124288,148190,152014,259093,249532,956063,64141,126449,167988,95295,95295,61697,610865), nrow=4, ncol=7, byrow=T))
colnames(vote) <-c("twenties","thirties","forties","fifties","sixties","seventies","sum")
vote$candidates <- c("舛添氏", "宇都宮氏", "細川氏", "田母神氏")

この時点で, vote オブジェクトの中身は

  twenties thirties forties fifties sixties seventies     sum candidates
1    73954   230315  325399  291591  473307    718413 2112979     舛添氏
2    37339   132650  158198  145424  222066    286918  982595   宇都宮氏
3    22946   124288  148190  152014  259093    249532  956063     細川氏
4    64141   126449  167988   95295   95295     61697  610865   田母神氏

となっているはずである. この数値をもとに, 出処のサイトにあるような棒グラフを作成してみる. この場合, グラフの x 軸は各候補者のラベルに, y 軸は (世代別に色分けされた) 得票数の合計になる. ここでも前回同様, reshape2 パッケージの melt 関数を利用することになる.

library(ggplot2)
library(reshape2)
 
vote.melt <- melt(vote, id.vars="candidates", measure.vars=colnames(vote)[1:6])

今回も, 積み上げ棒グラフを作成するだけなら簡単に可能である.

(g.stack <- ggplot(vote.melt) + geom_bar(mapping=aes(x=candidates, y=value, group=variable, fill=variable), stat="identity") )

f:id:ill-identified:20140222231420p:plain

前回の geom_line() 等では, 積み上げグラフにするには position="stack" とすれば良かった. バープロットでも有効だが, 上の場合は省略できる. stat 引数は, デフォルトでは stat="bin" となっており, これはヒストグラムの用に x に対応する行の数をカウントしてプロットする. これは集計されてないデータをプロットする際には便利だが, 今回は既に集計されたものを用いるため, 不適当であり, stat="identity" を指定する((非集計データの場合は "stack" を使う)). さらに, 全ての棒の高さを1に揃え, 候補者ごとに投票した世代の比率を表したい場合は, position="fill" とする.

ggplot(vote.melt) + geom_bar(mapping=aes(x=candidates, y=value, group=variable, fill=variable), stat="identity", position="fill")

f:id:ill-identified:20140222231446p:plain

ただし, 既に参照元で示されているように, 分母 (=各候補者の総得票数, 各世代の有権者の総数) が大きくことなるため, このグラフは見る人に誤解を与える可能性が高い. 比率で表したグラフでは田母神氏に投票した40代が非常に多いように見えるが, 絶対数ではさほど多くない.

一方, 積み上げるのでなく, 横に並べたい (side-by-side chart) 場合, position="dodge" とする.

(g.dodge <- ggplot(vote.melt) + geom_bar(mapping=aes(x=candidates, y=value, group=variable, fill=variable), stat="identity", position="dodge") )

f:id:ill-identified:20140222231456p:plain

絶対数と比率を同時に比較したい場合は, この図が一番適しているのかもしれない.

デザインの洗練

さしあたって, 前回の記述を踏まえ,

  • 縦軸の目盛りを自然数表記に
  • ラベルを全て日本語に
  • 色を変更

といった変更をまとめて行う. これらは前回と同様の方法なので解説は省略.

label.gen <- c("20代", "30代", "40代", "50代", "60代", "70代以上")
(g.dodge2 <- g.dodge + scale_y_continuous(labels=function(x) x/10000) + labs(x="候補者", y="投票者数\n (万人)", title="2014年度東京都知事選挙\n主要候補者・世代別推定投票者数") + theme(axis.title.y = element_text(angle = 90*0, vjust = 1) ) + scale_fill_brewer(palette="Spectral", name="", labels=label.gen) )

さらに, 次のような変更を加える.

  • それぞれの棒にその候補者に占める比率を表示
  • 凡例をグラフ下部に移動
  • 候補者の並びを得票数順に.
vote.ratio <- function(x) paste(format(x/rep(vote$sum,6)*100, digits=2), "%", sep="") # 比率を計算し, 書式を整える関数を定義
( g.dodge2 <- g.dodge2 + geom_text(aes(x=candidates, y=value, ymax=value, label=vote.ratio(value), group=variable ), position=position_dodge(width=1, height=1), size=3 ) + theme(legend.position="bottom") + scale_x_discrete(limits=c("田母神氏", "細川氏", "宇都宮氏", "舛添氏")) )

geom_text() 関数を使い, パーセントを表示した. position=“dodge” は geom_text には適用できず, %表示のテキストが重なってしまうため, position_dodge() で代用した. 凡例の位置は, theme(legend.position="bottom") で下部に移動. もちろん, “left”, “top” 等も可能. 候補者の並びについては, グラフを作成する前に候補者名を factor型として扱い, levels を設定することでも変えられるが, すでにグラフを作ってしまったため, scale_x_discrete() 関数で並び替えている.

f:id:ill-identified:20140222231539p:plain

縦横を転置する方法

coord_flip() 関数で棒グラフを横に伸ばすことができる.

g.dodge2 + coord_flip()

theme() で与えた軸ラベルの設定は, coord_flip() で変わらないようなので注意.

f:id:ill-identified:20140222231635p:plain

円グラフに変換する方法

棒グラフに対し + coord_polar(theta = "y") とすると, 縦軸の大きさを角度で表した円グラフ (pie chart) として出力できる. 3D円グラフについては, ggplot2 は3D円グラフの作成に向いてないし, 作成に使わないでください.

(画像省略)

参考