R上でロバストな標準化をしたい
データ分析をする際には各データを標準化するのが一般的だが、大抵の場合は特に何も考えずに平均0、分散1になるよう処理するのが基本だと思う。
これはRならscale()で実行できる。
ただし、この方法では外れ値によって大きく影響を受けることがある1。
ここ最近になってその辺りを解決できる標準化が必要になったので調べていたら、ロバストzスコアというそのものずばりな方法があった。
統計ガチ勢の方々には当たり前の話だと思うけど、一般的な標準化はデータXにおける平均値と標準偏差を用いて計算している。
この計算式における平均値を中央値へ、標準偏差を四分位範囲(IQR)2を標準正規分布に対応させた正規四分位範囲(NIQR)3へそれぞれ置き換えることによって、ロバストzスコアが算出できる。
式として書くと以下の通りである。
という訳で、とりあえずRで実装してみる。
なお、いつも通り結果の貼り付け方がよく分かっていないので、基本的には手元の環境で試してください。
Rのbase関数にはIQR()が入っているが、NIQRはIQRを定数で割ればいい4だけなので関数としては存在しない。
今回は使い勝手を重視して自作関数にしておく。
#IQR()と同様の使い方ができるように変数を入れておく NIQR <- function(x, na.rm = FALSE, type = 7){ IQR(x, na.rm = na.rm, type = type) / (qnorm(0.75)-qnorm(0.25)) }
中央値とNIQRを使った標準化をやってみる。
対象データはRユーザーお馴染みのiris。分かりやすいように1行目だけで実施してみる。
dat <- iris[[1]] (dat - median(dat)) / NIQP(dat)
もっと綺麗にコードを書けないか試していたら、scale()で簡単に実現できた。
scale()のヘルプ内にあるExampleには載っていない5が、centerとscaleにはロジカル(TRUE、FALSE)じゃなく整数も渡せる6ので、中央値とNIQRを渡せば処理してくれる。
scale(dat, median(dat), NIQP(dat))
こうすると出力される結果にcenterとscaleの情報も保存されるので、後で確認したい場合などには便利かもしれない。
上記では偏差としてNIQRを使用したが、他にも中央絶対偏差(MAD)7を用いて標準化する手法も存在する。
また、「MADは非対称性のあるデータにおいては性能が低下する」として、SnやQnというものも提案されている8。
調べた感じだとMADはそれなりに見かける印象だが、SnやQnは論文などで少し出てくる程度だった。
最近のコンピューターの性能的に「算出方法が複雑=計算量が多いから使いにくい」ってことではないと思うのだが、詳細は不明9。
計算式はかなりややこしいので、注釈につけた論文を直接見てください。
これも細かい計算云々は抜きにして、試しに計算してみる。
MADの計算はRに関数として元々入っているのでそれを活用。
また、SnとQnについてはrobustbaseパッケージを入れることで簡単に扱えるようになる。
mad(dat) library(robustbase) Sn(dat) Qn(dat)
robustbase::s_*(x, mu.too=T)
を使うと、中央値と偏差の両方が同時に取れる。
共分散などの計算式へ値を渡す時に活躍するらしい。
s_mad(dat, mu.too=T) s_Sn(dat, mu.too=T) s_Qn(dat, mu.too=T)
MAD、Sn、Qnを用いた標準化は、上でやったのと同様の処理によって実現できる10。
scale(dat, median(dat), mad(dat)) scale(dat, median(dat), Sn(dat)) scale(dat, median(dat), Qn(dat))
今回調べた結果として標準化や偏差の計算手法は色々と確認できたのだが、それぞれの使い分けとかについてはほとんど情報が得られなかった。
とりあえずIQRかMADを用いておけば問題はなさそうだけど、またどこかのタイミングで改めて確認したい。
Enjoy!!
-
例えばc(2, 3, 5, 6, 9)のデータに1000とかを混ぜて標準化すると悲惨なことになる(参考元:https://en.wikipedia.org/wiki/Robust_statistics#Examples)。↩
-
昇順で並び替えたデータの25%~75%の範囲のこと。Rではsummary(x)やquantile(x)で確認することができる。↩
-
IQRを『対応する範囲の標準正規分布の確率変数』(qnorm(0.75) - qnorm(0.25) ≒ 1.3489)で割ったもの。↩
-
計算的には$IQR \times 0.7413$($IQR_{N(0,1)}$の逆数)として実施することが多いみたいですが、今回は定義に沿って実装しています。↩
-
一応Argumentsにはそれっぽいことが書いてある。ヘルプはちゃんと読もう(戒め)。↩
-
整数どころかベクトルとかも大丈夫なので、行列xのそれぞれの行で別々の値を元に標準化したい場合はcenterとscaleにベクトル化して渡すとその通りに計算してくれる。たぶんtibbleデータに対してscale()を使った時と似たような動作じゃないかと思われる。あくまでも予想だけど。↩
-
Median Absolute Deviationの略なのだが、平均値を使って同様の処理をする平均絶対偏差もMean Absolute DeviationでMADと、かなりややこしい状況だったりする。なお、媒体によっては混同を避けるためにAAD(Average Absolute Deviation)としているが、Wikipediaの英語版には「AADは両方のMADを含みます」と書いてあったりして本当にややこしい。この辺りの話は後日書きます。↩
-
Rousseeuw,P.J.,andCroux,C.(1993).Alternativestothemedianabsolutedeviation,J.Amer.Statist.Assoc.88,1273-1283.↩
-
計算式が複雑だから言語やソフトによっては実装が難しいとか……?いやでもやれないことないでしょ……?みたいな感じで絶賛迷宮入り。誰か詳しい人いないですかね……。↩
-
MADを用いた標準化がこの方法でやっていて、かつ論文読んだ感じだとMADの代替(というか発展形)としてSnやQnが使えるとのことだったので、たぶん問題はない(はず)。間違ってたらすみません。↩
ガスハンドガンのマガジンメンテナンス
という訳で買ってみた。
仕事が笑えるほど忙しくてブログの更新どころではなかったんだけど、落ち着いたタイミングで約1年ぶりにサバゲーの予約を入れたら何となくマガジンのメンテナンスをしたくなったのでAmazonで買った。
なお今回はちょっと大きめの封筒でポストに投函されてた。過剰包装でネタにできなくなったけど助かる。
まずはベレッタのマガジンから放出バルブと注入バルブを外すために、レンチの凸部分をバルブの凹部分に上手く噛ませてゆっくり回す。
無理に力をかけると「ガリっ」とバルブの凹部分が削れて舐めたネジみたいになるので慌ててやらない方が無難。
それと、バルブを取り出す時に結構な割合でパッキンがマガジン側で置いてけぼりになるので注意。
その場合はゆっくりピンセットで取り出せば大丈夫。中に入り込んだらどうなるかは分からないし試したくない。
今回初めて注入バルブ見たんだけど、これの中にスプリング入ってるの知らなかった。
それと、これも初めて知ったけど、ベレッタのマガジンは新型と旧型で注入バルブの長さが全然違った。
最初へし折れたかと思ってビビりながらマガジン内を覗く羽目になった←
ちなみにHK45の注入バルブは長かった。
たぶん長い方が性能も良いんでしょう(適当)。
そんなところを比較しながら長く使ってるベレッタの放出バルブを外す。
覚悟はしてたけど、めっっっちゃ汚ぇ。
とりあえずシリコンスプレーに頼る。
その上で磨いたら綺麗になりました。
(※しかし綺麗になった写真を撮り忘れる痛恨のミス)
その後、放出バルブとマガジン上部のパッキンに軽くシリコンスプレーを吹いて終了。
後日サバゲーに持ち込んだところ、どれもきっちり初速73~74m/sくらい出てたので、たぶんメンテナンスの効果が出てる(はず)。
何かあった時に対応できるので、ハンドガンが好きな人は買っとくと便利だと思う。
DTの*_rows_selectedでデータテーブルからプロットを直接制御する(Shiny)
こちらはR Advent Calendar 2020 21日目の記事です。
8日目の記事でRアドカレに初参加した分際で2回目もやるってどうなのかとも思ったんですが、カレンダーが空いてるとひとまず予定突っ込みたくなる病気のせいで気が付いたら空いてる枠をクリックしてた()
という訳で、今回はShiny上で表示したデータテーブルの列をクリックで選択して、その列のデータでグラフを描く方法について書きます。
それと、Shinyを動作させられる状態でブログに貼る方法が分からないので、お手数ですがコードについては自身の環境で走らせてください(資料として画像は適宜貼ります)。
ちなみに今回のデータテーブルにはDTパッケージを利用する。
そのままデータを渡すだけでも非常に分かりやすい&動的なデータテーブルを作ってくれる代物で、RMarkdownやShinyにおけるインタラクティブな表を記載する際に重宝する。
見た目も弄れるので、とりあえずデータテーブル=DTパッケージでもいいと思う。
で、このパッケージの強力な点は拡張機能が豊富なところ。
一例を挙げると、
この辺りの機能が、関数内で少し記述を加えるだけで簡単に実装できてしまう。
複数機能を追加する場合はやや混乱しやすい3が、下記を参考にしていただければ何となく概要は掴めると思う。
日本語で解説しているページも多いので、調べてみると面白い。
iris %>% DT::datatable( rownames=FALSE, filter="top", #行のフィルターの実装 extensions=c("Buttons", "ColReorder"), #ボタンと行の並び替えの実装 options=list(autoWidth=TRUE, #表の幅の自動調整 dom="RBlfrtip", #表の要素の順番(B=ボタン、t=表など) lengthMenu = c(10, 20, 50), #表示できる列数 scrollX = TRUE, scrollY = "400px", scrollCollapse = TRUE, #スクロール機能 buttons=c("colvis", "csv") #実装するボタンの種類 ) )
拡張機能が豊富すぎて逆に困るレベルのDT::datatableなのだが、あまり使っている事例を見かけない機能もある。
そのうちの1つが、Shinyで利用する場合に使える「データテーブル上で選択した行の順番が取得できる」機能。
どういうことかと言うと、これを使うことでデータテーブルから選択したデータをプロットしたり、そのデータを解析したり、動的な処理に活用できる。
※上記ページの「2.1.1 Row Selection」参照
個人的には結構便利な機能なのだが、関数内で特に設定もせずに使える機能4だからなのか、Shiny限定の機能だからなのか、日本語で検索してもあまり引っ掛からない5。
という訳で、機能を布教する目的で紹介させていただきたい。
ちなみに公式ではirisデータを使って「選択した行のプロットの色を変える」図を作っているので、少し違う角度で試してみる。
まずは適当にShinyでデータテーブルを用意。
library(shiny) library(tidyverse) library(DT) ui <- fluidPage( titlePanel("Test"), mainPanel( DTOutput("dataTable") ) ) server <- function(input, output) { output$dataTable <- renderDT( diamonds ) } shinyApp(ui = ui, server = server)
ちなみに、最初の表みたいに機能を拡張したい場合もほぼ同様に行える。
library(shiny) library(tidyverse) library(DT) ui <- fluidPage( titlePanel("Test"), mainPanel( DTOutput("dataTable") ) ) server <- function(input, output) { output$dataTable <- renderDT( diamonds, rownames=FALSE, filter="top", extensions=c("Buttons"), options=list( autoWidth=TRUE, dom="RBlfrtip", buttons=c("colvis") ) ) } shinyApp(ui = ui, server = server)
本題の行選択についてやってみる。
データテーブルの行を選択すると、「何行目を選択したか」を数値ベクトルで返してくれるので、画面上に出してみる。
library(shiny) library(tidyverse) library(DT) ui <- fluidPage( titlePanel("Test"), mainPanel( verbatimTextOutput("Rows"), DTOutput("dataTable") ) ) server <- function(input, output) { output$dataTable <- renderDT( diamonds, filter="top" ) output$Rows <- renderPrint({ input$dataTable_rows_selected }) } shinyApp(ui = ui, server = server)
この行番号についてはデータテーブルに対するソートやフィルターには影響を受けない。
行を選択してからソートしてみても番号は変化しないし、適当にソートしてからデータテーブルの一番上の行を選択しても1行目とはならない。
この機能のおかげで、データテーブルをガンガン触りながら選択していくことが可能。
今回はdiamondsデータセットを例に、「ダイアモンドをカットと色で分けた場合のカラット数と値段の関係性」的なものを見れるようにしてみる。
- データセットは必要な列のみ選択してグループ化、nest()した上でデータテーブルに表示
- 各グループのデータ数も計算して追加
- nest()で纏まったデータはlist()形式になるが、これをデータテーブル上に表示しようとすると表示がめちゃくちゃになるためrenderDT()には渡さない
- 選択した行の番号を取得してグラフを描画する機能も追加
この辺を満たすようにコードを書いてみる。
library(shiny) library(tidyverse) library(DT) dat <- diamonds %>% select(cut, color, carat, price) %>% group_by(cut, color) %>% mutate( cur_data() %>% tally() ) %>% nest(data=c(carat, price)) ui <- fluidPage( titlePanel("Test"), mainPanel( fluidRow( column(width=6, DTOutput("dataTable") ), column(width=6, verbatimTextOutput("Rows"), plotOutput("Fig", width = 400) ) ) ) ) server <- function(input, output) { output$dataTable <- renderDT( dat %>% select(-data), filter="top" ) output$Fig <- renderPlot({ dat[input$dataTable_rows_selected,] %>% unnest(data) %>% ggplot( aes(carat, price) ) + geom_point() }) output$Rows <- renderPrint({ input$dataTable_rows_selected }) } shinyApp(ui = ui, server = server)
なお、このままだと複数の行をクリックした時に全てのデータが1つのグラフにプロットされてしまう。
そこでrenderDT()にselection="single"を加えると、1行ずつしか選択できなくなるため綺麗に動く。
library(shiny) library(tidyverse) library(DT) dat <- diamonds %>% select(cut, color, carat, price) %>% group_by(cut, color) %>% mutate( cur_data() %>% tally() ) %>% nest(data=c(carat, price)) ui <- fluidPage( titlePanel("Test"), mainPanel( fluidRow( column(width=6, DTOutput("dataTable") ), column(width=6, verbatimTextOutput("Rows") , plotOutput("Fig", width = 400) ) ) ) ) server <- function(input, output) { output$dataTable <- renderDT( dat %>% select(-data), filter="top", selection="single" ) output$Fig <- renderPlot({ dat[input$dataTable_rows_selected,] %>% unnest(data) %>% ggplot( aes(carat, price) ) + geom_point() }) output$Rows <- renderPrint({ input$dataTable_rows_selected }) } shinyApp(ui = ui, server = server)
また、facet_grid()を使ってプロットを自動的に分割する方法でも対応できる。
こちらは複数行を比較できるので、例えば実験ごとのデータを見比べたりする際に便利。
library(shiny) library(tidyverse) library(DT) dat <- diamonds %>% select(cut, color, carat, price) %>% group_by(cut, color) %>% mutate( cur_data() %>% tally() ) %>% nest(data=c(carat, price)) ui <- fluidPage( titlePanel("Test"), mainPanel( fluidRow( column(width=6, DTOutput("dataTable") ), column(width=6, verbatimTextOutput("Rows"), plotOutput("Fig", width = 400) ) ) ) ) server <- function(input, output) { output$dataTable <- renderDT( dat %>% select(-data), filter="top" ) output$Fig <- renderPlot({ dat[input$dataTable_rows_selected,] %>% unnest(data) %>% ggplot( aes(carat, price) ) + geom_point() + facet_grid(rows = vars(cut), cols = vars(color)) }) output$Rows <- renderPrint({ input$dataTable_rows_selected }) } shinyApp(ui = ui, server = server)
今回のようなグラフなら、例えばui.RにselectizeInput()を追加して、選択された行についてプロットする方法でも機能的には十分。
しかし、この方法ならデータテーブルとプロットをリンクすることで直感的な操作ができるため、データを感覚的に捉えやすくなる(と思う)。
「これが見たい!」と思った時に、対応する因子を確認して……選択肢から選んで……とやるより、見たい対象をデータテーブルでクリックするだけの方が思考にノイズが入らないので楽だと思う。
また、もし自分以外の人が使う可能性があるなら、直感的に操作できるUIにしておくことで説明がしやすくなる。
「データテーブルをクリックすればデータがプロットされます!」って言えば終わるとか強いと思う(小並感)。
あと作業量がそんなに変わらない割に何となく凄いことしているような雰囲気を出せるので、効果的に見せるという点でもオススメ。
ちなみに、列やセルに対する選択機能もあるため、応用すれば更に色々とできる。
library(shiny) library(tidyverse) library(DT) nameList <- colnames(diamonds) ui <- fluidPage( titlePanel("Test"), mainPanel( radioButtons("xAxis", label="Factor", choices = c("cut", "color", "clarity")), DTOutput("dataTable"), verbatimTextOutput("Rows"), plotOutput("Fig", width = 800) ) ) server <- function(input, output) { colNum <- reactive( input$dataTable_columns_selected ) output$dataTable <- renderDT( diamonds, filter="top", selection=list(target="column") ) output$Fig <- renderPlot({ diamonds %>% ggplot(aes( !!sym(input$xAxis), !!sym(nameList[colNum()]) )) + geom_boxplot() }) output$Rows <- renderPrint({ nameList[colNum()] }) } shinyApp(ui = ui, server = server)
これも正直radioButtons()とかで実現できるけど、データテーブルで選んだ列が表示されるのはなかなか気持ちいい。
とりあえずShinyに触れ始めた人6は下のページを見ているだけでも夢が広がると思う。
rstudio.github.io
DTパッケージのデータテーブルは他にもできることは多いので、ぜひ一度公式ページを眺めてみてください。
Enjoy!!
-
Shinyでは使えないので注意(解説:https://rstudio.github.io/DT/extensions.html)。↩
-
データテーブルに日本語が混ざっているとファイル内で文字化けするので、全て英語にしておく方が無難。↩
-
個人的にはoptionsのdomがやや厄介かも。行の並び替え("ColReorder")を実装する場合は大文字の"R"を追加する必要があったり、ボタンを実装した上で表の長さを変えるためのプルダウンメニューを表示するには"l"を追加したりと、単機能ごとの解説を読んでいるとハマる可能性はある(参考:https://datatables.net/reference/option/dom)。↩
-
正確には「行選択の場合は」特に設定もせずに使える。列やセルを選択するには設定が必要。↩
-
当社比。↩
-
筆者含む。↩
【R-bloggers翻訳】Python DashとR Shinyのどっちを選ぶべきか
R-bloggersを眺めていたら面白い記事が入っていた。
「ShinyとDashのどっちが今後もWebアプリを作っていくのに良いの?」って内容。
Rを使っている人からすると「Shinyから乗り換えるべきか否か」で読む感じか。
記事の流れとしては「それぞれの紹介」→「各ポイントにおける評価」→「全体のまとめ」となっている。
自分の場合、周囲にPythonを使う人が多いので、人とWebアプリを共有するならShinyよりDashがいいのかなとか思うことも結構ある。
そういう人の判断材料にもなりそうな記事なので、自分用のメモも兼ねて簡単に要点だけ纏めておく。
※Pythonは初心者に毛が生えた程度の人間なので、変な記述等あればご指摘ください。
R Shiny
Rのヘビーユーザーにはお馴染みのアレ。
個人的な最大のメリットは「Rの知識だけでも書ける」こと。
Python Dash
PythonでWebアプリケーションを作るためのフレームワーク。
Pythonだけでなく、もう少し広い範囲の知識が必要。
ShinyとDashの比較
6項目で比較しているので、順番に見ていく。
1. ツールとしての有用性
判定:引き分け
「どっちも十分にWebアプリを作ることができる」
性質は違うが、Webアプリの作成自体には差はないとのこと。
これは今回の議論の前提だと思うので、「ですよね」としか。
2. ボイラープレートコード(boilerplate code)
判定:R Shinyの勝利
「最小構成ならShinyがほんの少しだけ優位だけど、アプリが複雑になるほどDashは記述が複雑になるから差が開く」
ボイラープレートコードとは、いわゆる「絶対に書かないといけないコード」。Shinyなら「library(shiny)」とか「server <- function(input, output){}」などが該当する。
Shinyだとパッケージの読込コードが増える程度なので、確かにシンプルな構成かも。
UIのデザイン
判定:R Shinyの勝利
「ShinyのselectInput()やradioButtons()などは特に設定しなくても最初からデザインがいい感じになっているのに、Dashだと個別にやらないといけないから面倒」
サバゲーマー風に言うと「Shinyの方が箱出し性能(straight out of the box)はいい」ので、初期設定だけでそれっぽい見た目になるしコードも短く済む。
そのまま行ける東京マルイと、調整した方が使いやすい海外製みたいなものか(適当)。
でも初期設定のまま使ったりはしないでしょ?ということで、次の項目に進む。
CSSを利用したUIの変更
判定:Python Dashの勝利
「Shinyは初期設定でデザインがしっかりしている分、CSSで追加しようとすると少々手間がかかる」
上の項目で初期設定が貧弱と言われたDashだけど、それは裏を返せばCSSでの設定をやりやすいということになる。
そのためCSSを利用してデザインを変更する場合はDashに軍配が上がる。
Bootstrapを利用したUIの変更
判定:Python Dashの勝利
「ShinyだとBootstrapをまともに使えない、よって明らかにDashの勝ち」
Bootstrapとはフロントエンドのフレームワークの一種。Twitter社が作ったらしい。
あとトップページのアイコンがどう見ても座布団3枚。
詳しくないのでwikiで調べたら、GitHubで4位とかなり人気とのこと。
Dashだと「dash_bootstrap_components」ライブラリをインストールするだけで使えるが、Rだと対応していない(無いこともないのだが、最新版には対応できていない)。使うとしてもHTMLをまるっと記述するレベルで手間がかかる。
そのため、Bootstrapを利用したUIデザインを作りたい場合はほぼDash一択。
"reactive"性
判定:R Shinyの勝利
「Shinyの方が少ないコードで記述できる」
未だに「reactive」の綺麗な日本語訳が思いつけないShinyユーザーが大半を占めていると思うの自分だけだろうか。
「interactive」に近いんだけど、「双方向」とか「対話型」とかはまた少し違う感じだしなぁという。
Dashと比較すると、Shinyの方が少ないコードで書ける。実際、同じようなアプリを組むために必要なコードの行数はだいぶ異なる。
でもこの項目、reactivityについてはあんまり言及していないような……。
コード量について言及してるくらいだし、ここだけ展開に何となく違和感がある。
「インタラクティブなグラフを描くのが楽なのはどっち?」ってことなら明らかにShinyだけど、この項目はそういう解釈でいいのか……?
まとめ
結果:
- R Shiny……3点
- Python Dash……2点
- 引き分け……1点
Q.じゃあR Shinyを使えばいいの?
A.いや、状況とかによって違うんじゃない?
じゃあここまでの話はなんだったのか。
所属する組織がRメインならShinyを採用とか、Bootstrapをどうしても使いたいならDash一択とか、文脈的にはたぶんそういう話だと思うけど。
その上で、Shinyが優れているポイントを挙げていたので、抜粋。
- コード量が少ない
- React.jsの知識が不要(これってreactivityの項目で言及すべきでは?)
- 「intermediate reactive variables」がDashにはない
3つめの「intermediate reactive variables」について、Pythonに慣れ親しんでいる訳ではないのでちょっとよく分かっていない。
直訳すると「中間反応変数」なので、Shinyのreactive()のことか?
上の認識で合ってるなら、UIでの動きに対応して変化する変数を設定できないのがネックって意味合いになるので、reactivityの項目に書かれていた「1つのcallback内に必要なものをなるべく全部詰め込まないといけない」って部分の説明になりそう。
確かにこれならreactivityが低いってことになるので、この推測で合っててほしい(?)。
結論:
「パワフルで、カスタマイズできて、インタラクティブで、見た目がよくて、ユーザーの入力にちゃんと応答するダッシュボードを作りたければ、Shinyは最適」
コーディングの知識は必要だけど、R自体が複雑な言語じゃないから、2日~2週間もあればできちゃうよ!とのこと。
R独特の表現とか考えるとむしろ知識ない方が混乱しないレベルなので、むべなるかな。
個人的なまとめ
全体の印象は、「既にRまたはPythonを使っている人は慣れている方でやればいい」っていうのと、「これからWebアプリを作るためにどちらかを採用したいって人にはShinyがオススメ」ってところか。
初心者に勧めるならもっと他にあるんじゃないの?って疑問はさておき。
少なくとも、(デザイン面の自由度に多少の妥協ができるなら)RユーザーがわざわざPython Dashに切り替える必要はなさそう。
Enjoy!!
forcatsパッケージでggplot2の因子軸を反転させる
こちらはR Advent Calendar 2020 8日目の記事です。
初参加ですが宜しくお願い致します。
なお、内容的にはggplot2の初心者向けです。
因子型データの軸を反転させたかった時に「ggplot2 軸 factor 反転」とかで検索をかけてもfactorのlevelsを直接変更する以外の情報が上手く検索に引っ掛からなくて苦労した
(「ggplot2 forcats」だとガッツリ引っ掛かるので調べ方が悪いだけかもですが……)
ので、同じような疑問を抱えている人の一助になれれば幸いです。
では本題。
ggplot2でこんなグラフを作っているとする。
library(tidyverse) iris %>% tibble() %>% ggplot(aes(Species, Sepal.Length, fill=Species)) + geom_boxplot()
この時、x軸とy軸を入れ替えたい場合はcoord_flip()を利用する人が大半だと思う。
iris %>% tibble() %>% ggplot(aes(Species, Sepal.Length, fill=Species)) + geom_boxplot() + coord_flip()
個人的には1枚目のグラフの軸の入れ替えなのでsetosaが上に来てほしいのだが、ggplot2の仕様ではこうなってしまう。
なので、軸を反転させて修正したい。
これが連続値を扱ってる場合はscale_x_reverse()を仕込めば完了なのだが、因子(factor)が絡んでくるとggplot2側で処理ができないので一気に面倒になる。
それなのに反転させたい場合は大体が因子だったりする。
とりあえずグーグル先生に「ggplot2 軸 factor 反転」とかで質問すると「 factor() 内に levels = c() を仕込めば大丈夫だよ」と言われた。
iris %>% tibble() %>% mutate(Species = factor(Species, levels = c("virginica", "versicolor", "setosa"))) %>% ggplot(aes(Species, Sepal.Length, fill=Species)) + geom_boxplot() + coord_flip()
上の書き方だと因子型データの要素が増えるほど手間も増えてしまうので、たぶん因子型データのlevelsを文字列化して反転させたものを投げるのが一番現実的だと思う。
iris %>% tibble() %>% mutate(across(Species, ~factor(.x, levels = rev(levels(.x) %>% as.vector())))) %>% ggplot(aes(Species, Sepal.Length, fill=Species)) + geom_boxplot() + coord_flip()
しかし、この方法だと各因子に対するグラフの色が最初のグラフと異なってしまうため、複数のグラフのうちの1つだけに軸の入れ替え+反転をするような場面では使いにくい。
それを回避するにはmutate()を使わず、aes()内でx軸のデータのみ反転させればいい。
iris %>% tibble() %>% ggplot(aes( (factor(Species, levels = rev(levels(Species) %>% as.vector()) )), Sepal.Length, fill=Species)) + geom_boxplot() + coord_flip()
こちらのやり方の方が直感的に操作できると思うが、コードが非常に見辛い。
流石に毎度こういう書き方をするのはどうかと思ってしまう。
という訳で解決策を探して色々見ていたら、forcatsパッケージでやればいいとの情報を見かけた。
forcatsパッケージ自体はtidyverseの一部で、因子型データを操作するためのもの。
この中のfct_rev()で因子型データのlevelsを反転すればいいらしい。
早速だが試してみる。
library(forcats) iris %>% tibble() %>% ggplot(aes(fct_rev(Species), Sepal.Length, fill=Species)) + geom_boxplot() + coord_flip()
上と同じグラフで、しかもmutate()内でごちゃごちゃ書かずに済むので見通しも良くなった。 しかもこのやり方は、forcatsパッケージの関数なら大体対応できるみたいなので、因子型データの軸を上手く操作したい人にとっては応用の幅も広くていいと思う。
因子の軸を反転させたい人が、少しでも簡単に反転させられますように。
Enjoy!!