ill-identified diary

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

[R] データフレームの変数に日本語ラベルを付けたいとき

この記事は最終更新日から3年以上が経過しています

これを読んでわかること

  • havenSASやSTATA, SPSSなど変数ラベルのあるフォーマットのデータも読み込める
  • labelled::var_label()でデータフレームの各変数に一括してラベルを付加できる
  • expss::use_labels()を使えばグラフや要約統計量に表示される変数名を簡単にラベルに切り替えられる

問題提起

Rは日本語をはじめマルチバイト文字のオブジェクト名を使うことができるが, 多くのパッケージ開発者はこの仕様に注意していないことが多く*1, マルチバイト文字のオブジェクトはよく不具合の原因になるため, なるべくASCII文字だけでオブジェクトを宣言することが推奨される. すると例えばggplot2では, どうしても日本語で表記したい場合はaes()で変数選択するのとは別にlabs()で日本語の軸ラベルを手動で書くことになる.

そんな中, r-wakalangに以下のような質問が投稿された.

データフレームの変数名にエイリアスをつけるような方法はありますか。 例えば、「身長、体重、年齢」の入ったデータをもらったときに、DFの変数名を“height”, “weight”, “age”のように変更せずに、コードの中ではアルファベットで扱うといった方法です。加えて、変数名の説明も一緒に記録できればなおうれしいのですが。例えば、"height", "身長", "生徒の背の高さ"、のように、コードで扱う変数、変数定義、変数の説明、ができると、グラフの座標名などでも使えるので助かります。

言われてみれば毎回手動で日本語のラベル付けをするのは結構無駄な作業かもしれない.

expss の概要

ちょっと調べてみると, expssパッケージなるものを見つけた. これは最近できたものかと思っていたが, 初リリースは2016年と以外と古い.

gdemin.github.io

このパッケージは日本語はおろか英語でもほとんど取り上げられていないようなのでこの場で紹介する. このパッケージが提供する機能は4つに大別できる.

  1. データフレームの各変数に, 変数名とは別に「ラベル」を設定できる
  2. 図表にラベルを反映できる
  3. ラベル情報を保持したまま, データフレームの結合や集計といった編集ができる
  4. htmlやLaTeX形式へのエクスポート(正確にはhuxtableとの連携機能で, expss単独の機能ではない)

たとえばSTATAやSASといったソフトウェアもデータセットの変数に, 変数名とは別にラベルを指定することができる. 特にSTATAは, ラベルとは別に変数の定義や説明文を表すnoteという属性も用意している. expssはこれらのソフトウェアと似た機能を提供していると見ればいいだろう. そもそもこのパッケージはR上でSPSSっぽい関数名でデータフレームを操作する, というコンセプトで作られているらしい. (3) でいう関数はSPSSEXCELの関数名と似せている.

しかし, 公式のチュートリアルは操作のほとんどが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属性を付加しているだけなので, 他のファイル形式でも同様の構文でラベルを付加できるはずである. なお, SPSShaven::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 = "和名")

f:id:ill-identified:20200330013329p:plain
labs()を使用

ラベルを反映するにはやはりexpss::use_labels()を使う. ちなみに軸の単位をパーセントとかにしたい場合はscalesパッケージを使えば良い.

use_labels(
  iris,
  ..data %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + geom_point()
  )

f:id:ill-identified:20200330013432p:plain
`use_labels()`を使う

また, esquisseで日本語の変数名が使えない問題も, 日本語ラベルのついただけのデータにしてしまえば問題ないが, esquisseのエディタ内ではラベルを表示できないので, コードをコピペしてexpss::use_labels()を使う形に修正しなければならない.

こういうラベルが簡潔なものだけだとlabs()を書くのとあまり違いがない気もするが...

なお, 組み込みのグラフィックデバイスでも使える:

use_labels(iris, plot(Sepal.Width ~ Sepal.Length))

f:id:ill-identified:20200330013458p:plain
組み込みのグラフィックデバイス

技術的に細かい話

なんでこんな変なことができるのか. 実はhaven, labelled, expssのいずれも単に変数ごとにattr()でオブジェクト属性としてラベルを付けているだけである. attrは組み込みの機能のため, 多くのパッケージ開発者はこれを考慮して設計している可能性が高い(( havenhaven_labelledという独自のクラスを定義しているが, tidyverseに属しているので他パッケージとの連携が考慮されている. )). 他にも似たような機能を持つパッケージはある. 例えば昔からあるものではHmisc::label(). こちらはオブジェクト単体にしかラベルを与えられないし, user_labels()に対応する機能もないからlabelledexpssによってかなり使いやすくなっている. それ以外には, sjlabelledというのもある.

よって問題は, それぞれのパッケージがどれだけ効率的に書ける構文として設計されているかの比較になる. 他のパッケージはexpssのように表や図を出力するパッケージへの橋渡しはあまり考慮されていないため使いづらい. しかし, expssは自己完結性を意識しすぎてlabelledという独自クラスを定義しているため, ggplot2と組み込み関数いがいでは競合しやすいようだ.

当初の質問通り, (SPSS, STATA, SASなど他のフォーマットからの)入力字や出力時のラベル情報を保存したいという意図ならば, ラベル追加(apply_labels())とラベル反映(use_labels())くらいで十分だが, 途中の工程もexpssでやるのは少し不便だろう. そもそもこのパッケージは, R上でSPSSっぽい関数で操作するという目的で作られたものなので, 作業の多くをSPSSでやっている人いがいにはメリットがない. 私の普段の使い方からすると, ggplot用にラベルを保持するのに役に立つとは思うが, それ以外の場面では使いづらい.

一方で, expss::use_labels()もかなり単純なしくみで, 単に内部で変数名と日本語ラベルを切り替えているだけである.

また, これらのパッケージでは変数名だけでなく, (既に紹介したhaven::labelledのように)変数の値に対してもラベルを与える機能がある. しかしこれは他のパッケージが対応していないし, factorで代用できることが多いので今回は使わないことにした.

*1:例えば, GUI上で要約統計量やグラフ作図を操作するesquisseパッケージでは変数名にマルチバイト文字が含まれていると正常に動作しないとか, Windows版RStudioではマルチバイト文字のオブジェクト名を書くとGUIでの操作が正常でなくなるといった情報が過去に寄せられている. ただし今回はこれらの具体例の解決策を提示するものではない.

*2:SPSSのデータファイルが10年以上前のものなので, 最新版で作成したものだと挙動がかわるかもしれない. SPSSは一度も使ったことがないのでよくわからない.