cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

【小ネタ】リストにNULLがある場合のpurrr::map系関数の挙動

タイトルが適切かわからないが、小ネタ。次のようなリストオブジェクトがあるとする。リストに含まれる要素はname, age, genderの値をもっているが、2番目の要素はgenderがない、というようなもの。

library(purrr)
x <- list(list(name = "A", age = 23L, gender = "Male"), 
    list(name = "B", age = 18L), list(name = "C", 
        age = 24L, gender = "Male"))

これをpurrrを使って処理する。全ての要素に含まれるname, agemap系の関数で参照できる。一方でgenderについては、要素の数が合わないためにエラーになる。

x %>% map("name")
## [[1]]
## [1] "A"
## 
## [[2]]
## [1] "B"
## 
## [[3]]
## [1] "C"
x %>% map_int("age")
## [1] 23 18 24
x %>% map_chr("gender")
# Error: Result 2 is not a length 1
# atomic vector

このような場合の処置として、map関数では.nullという引数を用意していて、NA_integer_を指定すると足りない値を欠損値として扱ってくれる。

x %>% map_chr("gender", .null = NA_integer_)
## [1] "Male" NA     "Male"

ニュースを見ると、どうやら0.2.1からの仕様らしい。それに関する議論もある。

また返り値をデータフレーム化するmap_df()では、要素数が一致しなくても、.nullを指定しなくても、欠損値として処理される。AIEEEEEE!!??

x %>% map_df(~.)
## # A tibble: 3 x 3
##    name   age gender
##   <chr> <int>  <chr>
## 1     A    23   Male
## 2     B    18   <NA>
## 3     C    24   Male

mapを利用する別の関数でも.nullが指定可能である。

x %>% at_depth(1, "gender", .null = NA_integer_) %>% 
    flatten_chr()
## [1] "Male" NA     "Male"

estatapiパッケージで読み込めないデータの取得を諦めない

先日、estatapiパッケージを使って政府統計の総合窓口 e-statが提供する統計データの取得を行おうとしました。

library(tidyverse)
library(estatapi)
df.list <- estat_getDataCatalog(appId = Sys.getenv("ESTAT_TOKEN"), searchWord = "自然公園")
df.tgt <- df.list %>% filter(NAME ==  "1_自然公園の利用者の推移")
df.tgt %>% use_series(`@id`)
##  [1] "000003207752" "000003207753" "000003598436" "000003598437"
##  [5] "000004030647" "000004030648" "000006926442" "000006926443"
##  [9] "000006926486" "000006926487" "000006926510" "000006926511"
## [13] "000007477438" "000007477439" "000007821915" "000007821916"
estat_getStatsData(Sys.getenv("ESTAT_TOKEN"), statsDataId = "000006926486")

検索キーワードに「自然公園」を指定し、統計データの一覧を取得します。その後、データの参照に必要なidをestat_getStatsData()に指定して実行するとエラーになります。idはあっているはずなのにナンデ!?

困っているとパッケージの開発者 id:yutannihilation さんが返信くださいました。

e-stat APIで取得できるデータはestat_getStatsList()! APIで取れないデータはestat_getDataCatalog()!!

というわけでRを使ったデータ取得を諦め… ませんでした。

estat_getDataCatalog()を実行して返ってくるデータフレームにはURLという変数があります。これはデータダウンロードに利用されるURLで、このURLにアクセスすることで対象のデータファイルがダウンロードされます。つまりestatapiの関数にはないですが、このURLを利用すればファイルのダウンロードが可能になるわけです。実際に試してみましょう。ファイルの拡張子についての情報もまたFORMATという変数に保存されています。

download.file(df.tgt$URL[1], paste0("dl_file", ".", tolower(df.tgt$FORMAT[1])))

RからAPIでダウンロードできないe-Statのデータをダウンロードすることができました。めでたしめでたし。

というか、httrでバイナリデータを取得してそれを読み込ませればファイルをダウンロードしなくて済むのでは? と思いきや、それはできなかった。

github.com

期待。

readxlパッケージ 1.0.0の主要な変更点

先日、エクセルファイルのデータをRに読み込ませるパッケージのreadxlパッケージの1.0.0がCRANに登録されました。

実はしばらく更新を追えていなくて、色々な新機能があったのでメモしておきます。まとめると以下の通りです。気になった点を挙げていますので、詳細はNEWSをご覧ください。

  • エクセルファイル読み込み関数に2つの引数の追加
    • 範囲を指定した読み込みのためのrange引数
    • 読み込み時の行制限としてn_maxの適用
  • 列指定の際の挙動に大きな変更
    • col_types引数にlist, logical, guessが指定することが可能に
    • blankの廃止。代わりにskipの指定
    • 欠損値に複数の値を指定可能に
  • その他
    • 変数名の初期値がX__1からX__0に変更

特に気になるのが読み込み時の範囲指定とcol_types引数のlist対応です。それぞれ詳しく見ていきましょう。

読み込み時の範囲指定

エクセルファイルだと、目的のデータ以外の備考や合計値を算出している行などが含まれていることがよくありますね。これまでの readxlパッケージでは、対象のシートに含まれるセルをよしなにデータフレームに変換して読み込み、そこから利用者が必要なデータを選択するという手間がありました。今回のバージョンで追加されたrange引数は、その手間を省略するのに大変役立ちます。

エクセルファイルを印刷する際、「範囲指定した部分のみを印刷」という処理をしたことがある人が多いと思います。range引数はそれと同じく、read_excel()の実行時に範囲を指定できるようになりました。これにより直接必要な箇所だけを読み込ませることができます。

range引数の指定方法は、エクセルで使われるA1:B2 (A1セルからB2セルまで)のような記述が可能です。また範囲指定の専用の関数としてcell_rows()anchored()なども実装されています。

col_types引数のlist対応

Rのデータフレームの性質として、変数内の値はベクトルとして扱われ、そのデータ型は統一される、というものがあります。 そのため、文字列と日付が含まれる列を読み込む場合、日付は文字列に変換されてしまいます。一方でリストは異なるデータ型の値を保持するのに適しています。

今回、引数col_typesがlistに対応したことで、複数のデータ型が含まれる列であっても元の値を保持しておくことが可能になっています。少しわかりにくいのでヘルプに掲載されているコードを実行してみましょう。

library(readxl)
(df <- read_excel(readxl_example("clippy.xlsx"), 
    col_types = c("text", "list")))
## # A tibble: 4 × 2
##                   name      value
##                  <chr>     <list>
## 1                 Name  <chr [1]>
## 2              Species  <chr [1]>
## 3 Approx date of death <dttm [1]>
## 4      Weight in grams  <dbl [1]>

2列目、value列はlistとして格納されているのがわかります。各行は、実際の値でなく、データ型を示しています。リストとして保存することで、文字列、日付・時間、数値という異なるデータ型が共存しています。比較のため、通常の方法で読み込む例も示します。

read_excel(readxl_example("clippy.xlsx"))
## # A tibble: 4 × 2
##                   name     value
##                  <chr>     <chr>
## 1                 Name    Clippy
## 2              Species paperclip
## 3 Approx date of death     39083
## 4      Weight in grams       0.9

今度は直接、値が出力されました。しかし3行目の値が39083となっている点に注意です。これは元のエクセルファイルでは日付となっていたものでした。value列(ベクトル)が文字列として処理された結果、値が変化してしまいました。今回のアップデートでは、list列として処理することで、この問題を改善しています。

readxlのメジャーリリースとなる1.0.0が出たことで、Clippy君は悲しんでいるようですが、アップデートが済んでいない方は、ぜひ新しいreadxlをお試しください。

おまけ… RStudioのGUIからエクセルファイルも読み込めます

f:id:u_ribo:20170504123036p:plain

Enjoy!