読者です 読者をやめる 読者になる 読者になる

私と驥悟く蜈オ

R dplyr

※この投稿は「私と里傭兵」大喜利Advent Calendar 2016 4日目の記事です*1

里傭兵は文字だ。文字通り。

みたいな書き出しではじまる小説があったがその話はここではしない*2。しかし、文字とは何なのか。

「里傭兵」という文字と「里傭兵」という文字は同じなのか。「里傭兵」という文字と「驥悟く蜈オ」という文字は違うのか。

あるいは文字として同じ「里傭兵」であっても中身は別物という可能性もある。あの著名な里傭兵氏でさえ、少し髪の色が変わっただけで私たちの認識回路はこうも揺さぶられる。

果たして、同じとはどういうことなのか。違うとはどういうことなのか。私とあなたは何が違うのか、何が同じなのか。なぜ他人を傷つけてはいけないのか。優しさとは。生きる意味とは。

こうした自己同一性に対する重い問いに、どこのご家庭にでもあるRでもって答えていきたい。

ここに、dplyrパッケージのselect()関数がある。これは、data.frameから指定した名前と同じカラムを抜き出す関数だ。使ってみよう。

library(dplyr)

d <- data.frame("里傭兵" = 1:3)

d2 <- select(d, 里傭兵)
d2
#>   里傭兵
#> 1      1
#> 2      2
#> 3      3

ここでは、select()に指定した「里傭兵」と同じであるとみなされた里傭兵という列が抜き出されている。しかし、もとの「里傭兵」と抜き出された「里傭兵」は本当に同じなのだろうか。

colnames(d)
#> [1] "里傭兵"

colnames(d2)
#> [1] "里傭兵"

identical(colnames(d), colnames(d2))
#> [1] TRUE

なるほどidentical()までもがこれは同じだと告げている。しかし実は、Windowsにおいてはこれらは別物である。Encoding()が違う。

Encoding(colnames(d))
#> [1] "unknown"

Encoding(colnames(d2))
#> [1] "UTF-8"

これは、dplyrが悪いのではなく、select()の内部で使われているc()の挙動のせいだ。c()に名前付きベクトルを渡すと、その名前が強制的にUTF-8に変換されてしまう。

x <- "φ"
names(x) <- "φ"

Encoding(names(x))
#> [1] "unknown"

Encoding(names(c(x)))
#> [1] "UTF-8"

この違いがどういうときに問題になるのかというと、dplyrにはgroup_by()とかdistinct()UTF-8の列名をうまく扱えないというバグがある。Why are you using UTF-8問題として世のWindowsユーザの心胆を寒からしめている。

例えばこの調子だ。元のdata.frameでは「里傭兵」をグループ化の変数として選んでくれるが、

d %>%
  group_by(里傭兵)
#> Source: local data frame [3 x 1]
#> Groups: 里傭兵 [3]
#> 
#>   里傭兵
#>    <int>
#> 1      1
#> 2      2
#> 3      3

UTF-8の列名だと、そのような列は存在しないという衝撃の事実が告げられる。

d2 %>%
  group_by(里傭兵)
#>  Error in grouped_df_impl(data, unname(vars), drop) : 
#>   unknown column '里傭兵'

# UTF-8の文字を渡してもだめ
d2 %>%
  group_by_(enc2utf8("里傭兵"))
#>  Error in grouped_df_impl(data, unname(vars), drop) : 
#>   unknown column '里傭兵'

私たちの目にはしっかりと見える「里傭兵」という列が、group_by()からは見えないのだという。いったいどこの虚空を彷徨っているのだ、「里傭兵」。

ちなみに、group_by()はその中で列を定義することもできるので、その小技を使えば列名が見つからないというエラーは回避できる。そうしてみると...

d2 %>%
  group_by(a = 里傭兵)
#> Source: local data frame [3 x 2]
#> Groups: a [3]
#> 
#>   驥悟く蜈オ     a
#>       <int> <int>
#> 1         1     1
#> 2         2     2
#> 3         3     3

ALAS!「里傭兵」は「驥悟く蜈オ」へと変貌してしまっている。

つまりどういうことかというと、

  • WindowsでRを使っている
  • 非ASCII文字の列名があるデータをdplyrで処理している
  • select()/transmute()group_by()/distinct()を使っている

という人はこのエラーで死ぬかもしれないので注意、ということだ。以下にissueは立てているのでそのうち修正されるかもしれない。

まとめ

同じものを見ていても、自分が見えているのと同じように他人にも見えているとは限らない。そのことを心に留めながら生きていきたい。

*1:一部ではDATUM STUDIO Advent Calendar 2016とも呼ばれているようです。

*2:円城塔の作品についてのポエムはそういえば別のブログに書いたことがあった:http://notchained.blogspot.jp/2012/04/blog-post_22.html