これを読んでわかること
haven
はSASやSTATA, SPSSなど変数ラベルのあるフォーマットのデータも読み込めるlabelled::var_label()
でデータフレームの各変数に一括してラベルを付加できるexpss::use_labels()
を使えばグラフや要約統計量に表示される変数名を簡単にラベルに切り替えられる
問題提起
Rは日本語をはじめマルチバイト文字のオブジェクト名を使うことができるが, 多くのパッケージ開発者はこの仕様に注意していないことが多く*1, マルチバイト文字のオブジェクトはよく不具合の原因になるため, なるべくASCII文字だけでオブジェクトを宣言することが推奨される.
すると例えばggplot2
では, どうしても日本語で表記したい場合はaes()
で変数選択するのとは別にlabs()
で日本語の軸ラベルを手動で書くことになる.
そんな中, r-wakalangに以下のような質問が投稿された.
データフレームの変数名にエイリアスをつけるような方法はありますか。 例えば、「身長、体重、年齢」の入ったデータをもらったときに、DFの変数名を“height”, “weight”, “age”のように変更せずに、コードの中ではアルファベットで扱うといった方法です。加えて、変数名の説明も一緒に記録できればなおうれしいのですが。例えば、"height", "身長", "生徒の背の高さ"、のように、コードで扱う変数、変数定義、変数の説明、ができると、グラフの座標名などでも使えるので助かります。
言われてみれば毎回手動で日本語のラベル付けをするのは結構無駄な作業かもしれない.
expss の概要
ちょっと調べてみると, expss
パッケージなるものを見つけた. これは最近できたものかと思っていたが, 初リリースは2016年と以外と古い.
このパッケージは日本語はおろか英語でもほとんど取り上げられていないようなのでこの場で紹介する. このパッケージが提供する機能は4つに大別できる.
- データフレームの各変数に, 変数名とは別に「ラベル」を設定できる
- 図表にラベルを反映できる
- ラベル情報を保持したまま, データフレームの結合や集計といった編集ができる
- htmlやLaTeX形式へのエクスポート(正確には
huxtable
との連携機能で,expss
単独の機能ではない)
たとえばSTATAやSASといったソフトウェアもデータセットの変数に, 変数名とは別にラベルを指定することができる. 特にSTATAは, ラベルとは別に変数の定義や説明文を表すnoteという属性も用意している. expss
はこれらのソフトウェアと似た機能を提供していると見ればいいだろう. そもそもこのパッケージはR上でSPSSっぽい関数名でデータフレームを操作する, というコンセプトで作られているらしい. (3) でいう関数はSPSSやEXCELの関数名と似せている.
しかし, 公式のチュートリアルは操作のほとんどがexpss
で完結することを前提としている. Rには他にも便利なパッケージがあり, それらと併用する場合はいろいろと不具合がある. 特に今回は, 「変数に日本語ラベルを付けたまま作業する方法はないか?」というのが問いなので, よく使われるパッケージとの競合をなるべく減らす使い方を紹介する.
データの読み込み
まずはパッケージを読み込む. expss
だけでなく, 併用することの多いだろうtidyverse
, haven
を読み込んでおく(この2つはもう有名だと思うので解説略). さらに, labelled
というパッケージもインストールして読み込む.
さらに, skimr
, summarytools
, Hmisc
も解説に使うので, 全ての結果を再現したい場合はこれらもインストールしてほしい. いずれもCRANに登録されているのでinstall.packages()
でインストールできる.
require(conflicted) require(tidyverse) require(expss) require(labelled) require(haven) conflict_prefer("is.labelled", "haven")
haven
は他の統計解析ソフトウェアのデータを読み込むパッケージであり, 上記のSAS, STATA, SPSSにも対応している. これらのソフトは変数にラベルを付けることができ, haven
はラベルの情報も読み込むことができる. 私は3つのソフトウェアどれも持っていないため, 適当なところからそれぞれのソフト独自のフォーマットのファイルを落としてきて検証する.
STATAのdtaファイルは日本販売代理店であるライトストーン社のページのagis4.zipに含まれるcensus.dta
を利用する. SASのデータセットは[Principle of Economics]という教科書のサポートページにあるairline.sas7bdat
を使う. SPSSは村瀬他『SPSSによる多変量解析』のサポートページにある「仙台調査 分析実習用データ(SPSS形式データファイル)」を使う.
download.file("https://www.lightstone.co.jp/stata/files/agis4.zip", "agis4.zip") unzip("agis4.zip") download.file("http://www.principlesofeconometrics.com/sas/airline.sas7bdat", "airline.sas7bdat") download.file("http://www.asahi-net.or.jp/~BV7Y-MRS/datasp/SENPO98FULL.sav", "SENPO98FULL.sav")
haven
のインポート用関数はどれも列ごとにattrs()
でlabel
属性を付加しているだけなので, 他のファイル形式でも同様の構文でラベルを付加できるはずである. なお, SPSSはhaven::read_sav()
とhaven::read_spss()
があり, さらにexpss::read_spss()
まであり, それぞれ挙動が違う. 今回使用するデータは日本語が含まれているため, 正しく読み込めるのは文字コードを指定できるread_sav()
だけである*2.
df_dta <- haven::read_dta("agis4/census.dta") df_sas7bdat <- read_sas("airline.sas7bdat") df_sav <- read_sav("SENPO98FULL.sav", encoding = "cp932") %>% mutate_if(is.labelled, ~as_factor(.x, levels = "values"))
ラベル名はじめデータフレームの概要を確認するには,expss::info()
関数を使う
info(df_dta)[, 1:7]
Name | Class | Length | NotNA | NA | Distincts | Label |
---|---|---|---|---|---|---|
state | character | 51 | 51 | 0 | 51 | NA |
tworace | numeric | 51 | 51 | 0 | 17 | Percent two or more racial background |
hhincome | numeric | 51 | 51 | 0 | 51 | Median Houshold Income |
white | numeric | 51 | 51 | 0 | 49 | Percent white |
ba | numeric | 51 | 51 | 0 | 47 | Percent with college degree or more education |
hhinc | numeric | 51 | 51 | 0 | 51 | NA |
info(df_sas7bdat)[, 1:7]
Name | Class | Length | NotNA | NA | Distincts | Label |
---|---|---|---|---|---|---|
YEAR | numeric | 32 | 32 | 0 | 32 | year |
Y | numeric | 32 | 32 | 0 | 32 | level of output |
W | numeric | 32 | 32 | 0 | 32 | wage rate |
R | numeric | 32 | 32 | 0 | 32 | interest rate |
L | numeric | 32 | 32 | 0 | 32 | labor input |
K | numeric | 32 | 32 | 0 | 32 | capital input |
info(df_sav)[, 1:7]
Name | Class | Length | NotNA | NA | Distincts | Label |
---|---|---|---|---|---|---|
Q1SEX | factor | 962 | 962 | 0 | 2 | 性 |
Q1NEN | numeric | 962 | 962 | 0 | 50 | 出生年(昭和) |
Q1GETU | numeric | 962 | 962 | 0 | 12 | 出生月 |
Q2 | factor | 962 | 926 | 36 | 8 | 生活価値意識 |
Q3A | factor | 962 | 951 | 11 | 5 | 経済生活満足度 |
(長いので後略)
どうやら各変数にはlabel
という属性が付加されている. また, クラスの一部がhaven_labelled
というものになっている. これは変数名のラベルだけでなく, 変数の値と, 対応するラベルの組をもつクラスで, 要はfactor
と同様のものである. そこで, haven::as_factor(., levels = "value")
を使ってfactor
に変換している. 実際にはfactor
と言っても順序型かたどうかという違いがあるので, そのあたりは自動判別不可能なので手動で設定する必要がある.
なお, STATAのdtaファイルには, ラベルに加えてnoteという説明文の属性も持つ. これもread_dta()
で読み込んでくれるが, 今回紹介するパッケージでは扱えない. 適宜attributes()
でアクセスして取り出す必要がある.
ラベルの追加
次に, R上で作成したデータフレームにラベルを追加・編集する方法を説明する. labelled::var_label()
で変数にラベルを追加できる. 公式の例ではmtcars
を使っていたが変数が多いのでいつものiris
を例にする. 長さが1のベクトルならばラベルとして認識され, 以下のSpecies
のように長さ2以上のベクトルを与えると, 変数ではなく値に対応するラベルを付けることもできる. しかし, この値ラベルは既存のfactor
でも代用できるし, 後で紹介するように使いづらいので元からあるfactor
でもいいように思う.
今回はtidyverse
との併用を考えているので, 最後にtibble
形式に変換している.
data(iris) var_label(iris) <- list( Species = "学名", Sepal.Length = "萼長", Sepal.Width = "萼幅", Petal.Length = "花弁長", Petal.Width = "花弁幅" ) iris <- as_tibble(iris)
ラベルは変数名とは違うので, $
や()
や空白など変数名では使えない文字が含まれても良い.
また, utils::View()
およびtibble::view()
でも列名に合わせて列ラベルが表示される.
逆にラベル情報を除去するなら haven::zap_label()
を使う.
分割表の作成
チュートリアルでは分割表や要約統計量の出力例をいくつも紹介している. まずは分割表を作ってみる. expss::use_labels()
を使うと組み込みの関数table()
の出力にラベルを適用してくれる. use_labels()
はwith()
関数と同じ要領で使える.
use_labels(iris, table(Species, Sepal.Width))
## 萼幅 ## 学名 2 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 ## setosa 0 0 1 0 0 0 0 0 1 6 4 5 2 9 6 3 3 ## versicolor 1 2 3 3 4 3 5 6 7 8 3 3 1 1 0 0 0 ## virginica 0 1 0 0 4 2 4 8 2 12 4 5 3 2 0 1 0 ## 萼幅 ## 学名 3.8 3.9 4 4.1 4.2 4.4 ## setosa 4 2 1 1 1 1 ## versicolor 0 0 0 0 0 0 ## virginica 2 0 0 0 0 0
たしかにラベルが日本語になった. 学名は当たり前だが日本語にできない. これも日本語にしたいならfactorのラベルを変更すべきだろう. Species
の値を和名にして, さらに変数ラベルも「和名」にしてしまう.
iris$Species <- factor(iris$Species, labels = c("ヒオウギアヤメ", "ブルーフラッグ", "バージニカ")) var_label(iris$Species) <- "和名"
要約統計量の出力
table()
と同様にsummary()
でも同じことができる. データフレームを与えたい場合は, ..data
を使う.
use_labels(iris, summary(..data))
## 萼長 萼幅 花弁長 花弁幅 ## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 ## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 ## Median :5.800 Median :3.000 Median :4.350 Median :1.300 ## Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199 ## 3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800 ## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500 ## 和名 ## ヒオウギアヤメ:50 ## ブルーフラッグ:50 ## バージニカ :50 ## ## ##
Hmisc::describe()
やsummarytools
の各種関数はexpss::use_labels()
すら不要で, そのまま与えるだけで表示できる.
Hmisc::describe(iris)
## iris ## ## 5 Variables 150 Observations ## -------------------------------------------------------------------------------- ## Sepal.Length : 萼長 ## n missing distinct Info Mean Gmd .05 .10 ## 150 0 35 0.998 5.843 0.9462 4.600 4.800 ## .25 .50 .75 .90 .95 ## 5.100 5.800 6.400 6.900 7.255 ## ## lowest : 4.3 4.4 4.5 4.6 4.7, highest: 7.3 7.4 7.6 7.7 7.9 ## -------------------------------------------------------------------------------- ## Sepal.Width : 萼幅 ## n missing distinct Info Mean Gmd .05 .10 ## 150 0 23 0.992 3.057 0.4872 2.345 2.500 ## .25 .50 .75 .90 .95 ## 2.800 3.000 3.300 3.610 3.800 ## ## lowest : 2.0 2.2 2.3 2.4 2.5, highest: 3.9 4.0 4.1 4.2 4.4 ## -------------------------------------------------------------------------------- ## Petal.Length : 花弁長 ## n missing distinct Info Mean Gmd .05 .10 ## 150 0 43 0.998 3.758 1.979 1.30 1.40 ## .25 .50 .75 .90 .95 ## 1.60 4.35 5.10 5.80 6.10 ## ## lowest : 1.0 1.1 1.2 1.3 1.4, highest: 6.3 6.4 6.6 6.7 6.9 ## -------------------------------------------------------------------------------- ## Petal.Width : 花弁幅 ## n missing distinct Info Mean Gmd .05 .10 ## 150 0 22 0.99 1.199 0.8676 0.2 0.2 ## .25 .50 .75 .90 .95 ## 0.3 1.3 1.8 2.2 2.3 ## ## lowest : 0.1 0.2 0.3 0.4 0.5, highest: 2.1 2.2 2.3 2.4 2.5 ## -------------------------------------------------------------------------------- ## Species : 和名 ## n missing distinct ## 150 0 3 ## ## Value ヒオウギアヤメ ブルーフラッグ バージニカ ## Frequency 50 50 50 ## Proportion 0.333 0.333 0.333 ## --------------------------------------------------------------------------------
summarytools::dfSummary(iris)
------------------------------------------------------------------------------------------------------------------------------- No Variable Label Stats / Values Freqs (% of Valid) Graph Valid Missing ---- --------------- -------- ------------------------ -------------------- -------------------------------- -------- --------- 1 Sepal.Length 萼長 Mean (sd) : 5.8 (0.8) 35 distinct values . . : : 150 0 [numeric] min < med < max: : : : : (100%) (0%) 4.3 < 5.8 < 7.9 : : : : : IQR (CV) : 1.3 (0.1) : : : : : : : : : : : : : 2 Sepal.Width 萼幅 Mean (sd) : 3.1 (0.4) 23 distinct values : 150 0 [numeric] min < med < max: : (100%) (0%) 2 < 3 < 4.4 . : IQR (CV) : 0.5 (0.1) : : : : . . : : : : : : 3 Petal.Length 花弁長 Mean (sd) : 3.8 (1.8) 43 distinct values : 150 0 [numeric] min < med < max: : . : (100%) (0%) 1 < 4.3 < 6.9 : : : . IQR (CV) : 3.5 (0.5) : : : : : . : : . : : : : : . 4 Petal.Width 花弁幅 Mean (sd) : 1.2 (0.8) 22 distinct values : 150 0 [numeric] min < med < max: : (100%) (0%) 0.1 < 1.3 < 2.5 : . . : IQR (CV) : 1.5 (0.6) : : : : . : : : : : . : : : 5 Species 学名 1. setosa 50 (33.3%) IIIIII 150 0 [factor] 2. versicolor 50 (33.3%) IIIIII (100%) (0%) 3. virginica 50 (33.3%) IIIIII -------------------------------------------------------------------------------------------------------------------------------
私が普段使っているskimr
は残念ながらまだ対応していないようだ.
表のエクスポート
表を出力してくれるパッケージはいろいろあるが, とりあえず情報の少ないexpss
パッケージのことを書いておく. これは各種関数は集計表をレポート向けに整形してくれる. 詳しくは公式のチュートリアルを見てもらう.
use_labels(iris, expss::cro(Species, Sepal.Width))
| | | 萼幅 | | | | | | | | | | | | | | | | | | | | | | | | | | 2 | 2.2 | 2.3 | 2.4 | 2.5 | 2.6 | 2.7 | 2.8 | 2.9 | 3 | 3.1 | 3.2 | 3.3 | 3.4 | 3.5 | 3.6 | 3.7 | 3.8 | 3.9 | 4 | 4.1 | 4.2 | 4.4 | | -- | ------------ | -- | --- | --- | --- | --- | --- | --- | --- | --- | -- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -- | --- | --- | --- | | 学名 | setosa | | | 1 | | | | | | 1 | 6 | 4 | 5 | 2 | 9 | 6 | 3 | 3 | 4 | 2 | 1 | 1 | 1 | 1 | | | versicolor | 1 | 2 | 3 | 3 | 4 | 3 | 5 | 6 | 7 | 8 | 3 | 3 | 1 | 1 | | | | | | | | | | | | virginica | | 1 | | | 4 | 2 | 4 | 8 | 2 | 12 | 4 | 5 | 3 | 2 | | 1 | | 2 | | | | | | | | #Total cases | 1 | 3 | 4 | 3 | 8 | 5 | 9 | 14 | 10 | 26 | 11 | 13 | 6 | 12 | 6 | 4 | 3 | 6 | 2 | 1 | 1 | 1 | 1 |
出力はmarkdown風だが, split_table_to_df()
を使えばデータフレームに変換してくれるので, さらに細かい体裁を調整してknitr::kable()
で出力, ということもできる.
また, expss_output_*
の各種関数を事前に実行すると様々なフォーマットになる.
グラフの作成
ggplot2
で散布図を作図してみる. まずはラベルを使わずいつもどおりlabs()を使った場合.
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + geom_point() + labs(x = "萼長", y = "萼幅", color = "和名")
ラベルを反映するにはやはりexpss::use_labels()
を使う. ちなみに軸の単位をパーセントとかにしたい場合はscales
パッケージを使えば良い.
use_labels( iris, ..data %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + geom_point() )
また, esquisse
で日本語の変数名が使えない問題も, 日本語ラベルのついただけのデータにしてしまえば問題ないが, esquisse
のエディタ内ではラベルを表示できないので, コードをコピペしてexpss::use_labels()
を使う形に修正しなければならない.
こういうラベルが簡潔なものだけだとlabs()
を書くのとあまり違いがない気もするが...
なお, 組み込みのグラフィックデバイスでも使える:
use_labels(iris, plot(Sepal.Width ~ Sepal.Length))
技術的に細かい話
なんでこんな変なことができるのか. 実はhaven
, labelled
, expss
のいずれも単に変数ごとにattr()
でオブジェクト属性としてラベルを付けているだけである. attr
は組み込みの機能のため, 多くのパッケージ開発者はこれを考慮して設計している可能性が高い(( haven
はhaven_labelled
という独自のクラスを定義しているが, tidyverse
に属しているので他パッケージとの連携が考慮されている.
)). 他にも似たような機能を持つパッケージはある. 例えば昔からあるものではHmisc::label()
. こちらはオブジェクト単体にしかラベルを与えられないし, user_labels()
に対応する機能もないからlabelled
とexpss
によってかなり使いやすくなっている. それ以外には, sjlabelled
というのもある.
よって問題は, それぞれのパッケージがどれだけ効率的に書ける構文として設計されているかの比較になる. 他のパッケージはexpss
のように表や図を出力するパッケージへの橋渡しはあまり考慮されていないため使いづらい. しかし, expss
は自己完結性を意識しすぎてlabelled
という独自クラスを定義しているため, ggplot2
と組み込み関数いがいでは競合しやすいようだ.
当初の質問通り, (SPSS, STATA, SASなど他のフォーマットからの)入力字や出力時のラベル情報を保存したいという意図ならば, ラベル追加(apply_labels()
)とラベル反映(use_labels()
)くらいで十分だが, 途中の工程もexpss
でやるのは少し不便だろう. そもそもこのパッケージは, R上でSPSSっぽい関数で操作するという目的で作られたものなので, 作業の多くをSPSSでやっている人いがいにはメリットがない. 私の普段の使い方からすると, ggplot用にラベルを保持するのに役に立つとは思うが, それ以外の場面では使いづらい.
一方で, expss::use_labels()
もかなり単純なしくみで, 単に内部で変数名と日本語ラベルを切り替えているだけである.
また, これらのパッケージでは変数名だけでなく, (既に紹介したhaven::labelled
のように)変数の値に対してもラベルを与える機能がある. しかしこれは他のパッケージが対応していないし, factor
で代用できることが多いので今回は使わないことにした.