すっからかん(欠損値しかない)の列を削除して完全データにしたい
【20170518 追記】
あーだこーだしているが、ゆた兄さんに教えてもらった方法を使った方が楽チン。
@u_ribo ちがった、_at()じゃなくて_if()でよかったのでした! 存在を忘れてた。
— Hiroaki Yutani (@yutannihilation) 2017年5月17日
df.list %>% select_if(function(x) VIM::countNA(x) < 1)
追記終わり。
時として、データの特定の列が全て欠損している、ということがある。分析を進めて行く上でこのような列は不要となるため、列選択によって除外するという方法をよくとる。
それを行うためにはまず、各列の欠損値を数えて、完全データとなっている列だけを選択する必要がある。それをRでやる。パイプ処理と合わせて効率的にやる。そんな方法。
次のデータを例にする。すべてを出力しないが、変数が多く、TABLE_CATEGORYやTABLE_SUB_CATEGORY1のように欠損値しかもたない列があるというのが特徴となっている。
df.list %>% ncol()
## [1] 35
df.list[, 1:6] %>% head()
## # A tibble: 6 x 5
## `@id` NAME TABLE_CATEGORY
## <chr> <chr> <chr>
## 1 000003207760 5_国立公園の利用者数(公園、都道府県別) <NA>
## 2 000003598444 5_国立公園の利用者数(公園、都道府県別) <NA>
## 3 000004030655 5_国立公園の利用者数(公園、都道府県別) <NA>
## 4 000006926450 5_国立公園の利用者数(公園、都道府県別) <NA>
## 5 000006926494 5_国立公園の利用者数(公園、都道府県別) <NA>
## 6 000006926518 5_国立公園の利用者数(公園、都道府県別) <NA>
## # ... with 2 more variables: TABLE_NO <chr>, TABLE_NAME <chr>
VIM::aggr()
でのデータの可視化確認。左側の棒グラフで縦軸が1.0に達しているように、特定の列は欠損値しかもたない。
df.list %>% VIM::aggr(plot = TRUE, prop = TRUE, col = "purple", cex.lab = 0.7, number = TRUE)
最初に各列の欠損値をカウントする。これにはVIMパッケージ(欠損データの可視化や補完を行う)のcountNA
関数を使うか、欠損値の判定を行う関数is.na()
の返り値(欠損であれば1となる)を合計する処理(sum(is.na(x))
)を適用すれば良い。
df.list$TABLE_CATEGORY %>% VIM::countNA()
## [1] 8
df.list$TABLE_CATEGORY %>% is.na() %>% sum()
## [1] 8
これをデータフレームの各列に適用させる。apply()
を用いても良いが、後の処理を考えてpurrrを使う。purrrのmap()
関数は返り値がリストであるが、map_*()
を使うことで返り値をベクトルにし、データ型についても指定したできるようになる。今回は欠損値の合計を実数として取得するのでmap_int()
を使う。
library(dplyr) library(purrr)
# df.list %>% apply(2, VIM::countNA) df.list %>% map_int(VIM::countNA)
## @id NAME
## 0 0
## TABLE_CATEGORY TABLE_NO
## 8 0
## TABLE_NAME TABLE_SUB_CATEGORY1
## 0 8
## TABLE_SUB_CATEGORY2 TABLE_SUB_CATEGORY3
## 8 8
## URL DESCRIPTION
## 0 8
## FORMAT RELEASE_DATE
## 0 5
## LAST_MODIFIED_DATE RESOURCE_LICENCE_ID
## 8 0
## LANGUAGE STAT_NAME
## 0 0
## ORGANIZATION DATASET_NAME
## 0 0
## TABULATION_CATEGORY TABULATION_SUB_CATEGORY1
## 0 8
## TABULATION_SUB_CATEGORY2 TABULATION_SUB_CATEGORY3
## 8 8
## TABULATION_SUB_CATEGORY4 TABULATION_SUB_CATEGORY5
## 8 8
## CYCLE SURVEY_DATE
## 0 0
## PUBLISHER CONTACT_POINT
## 0 0
## CREATOR FREQUENCY_OF_UPDATE
## 0 0
## LANDING_PAGE DATASET_@id
## 0 0
## DATASET_DESCRIPTION DATASET_LAST_MODIFIED_DATE
## 8 8
## DATASET_RELEASE_DATE
## 5
次に、ここから欠損を含む列と欠損していない列を区別する。これにはpurrr::keep
, purrr::discard
による要素の取捨選択を適用させることで実行する。条件式として、対象(今回はデータフレームの各列の欠損値合計)が0となる要素を選択するようにする。
df.list %>% map_int(VIM::countNA) %>% keep(~ .x == 0)
## @id NAME TABLE_NO
## 0 0 0
## TABLE_NAME URL FORMAT
## 0 0 0
## RESOURCE_LICENCE_ID LANGUAGE STAT_NAME
## 0 0 0
## ORGANIZATION DATASET_NAME TABULATION_CATEGORY
## 0 0 0
## CYCLE SURVEY_DATE PUBLISHER
## 0 0 0
## CONTACT_POINT CREATOR FREQUENCY_OF_UPDATE
## 0 0 0
## LANDING_PAGE DATASET_@id
## 0 0
これらの情報を用いて、列選択を行う。dplyr::one_of()
は文字列ベクトルで与えた変数を選択するselect_helpers
関数群の一種である。
df.list.mod <- df.list %>% select( one_of( df.list %>% map_int(VIM::countNA) %>% keep(~ .x == 0) %>% names() ))
改めて欠損数を確認しておこう。
df.list.mod %>% map_int(VIM::countNA)
## @id NAME TABLE_NO
## 0 0 0
## TABLE_NAME URL FORMAT
## 0 0 0
## RESOURCE_LICENCE_ID LANGUAGE STAT_NAME
## 0 0 0
## ORGANIZATION DATASET_NAME TABULATION_CATEGORY
## 0 0 0
## CYCLE SURVEY_DATE PUBLISHER
## 0 0 0
## CONTACT_POINT CREATOR FREQUENCY_OF_UPDATE
## 0 0 0
## LANDING_PAGE DATASET_@id
## 0 0
このような処理で完全データを得ることができる。
keep()
とone_of()
の組み合わせは、このほかにも色々な条件に応用できそうだ。
Enjoy!
【小ネタ】リストに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, ageはmap
系の関数で参照できる。一方で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 さんが返信くださいました。
@u_ribo あー、「XLS形式だからダメ」です! getDataCatalogは主にAPIから取れないデータを検索するためのAPIで、APIから取れるやつを検索するのはgetStatsListの方なんだよね...
— Hiroaki Yutani (@yutannihilation) 2017年5月10日
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でバイナリデータを取得してそれを読み込ませればファイルをダウンロードしなくて済むのでは? と思いきや、それはできなかった。
期待。