R for Data Scienceの例題を解く- Chapter 12 Tidy data

神Hadley R for Data Science の例題たちとその解答を書き残します。
今回はChapter 12 Tidy dataです。

過去の記事

この章ではtidyrライブラリを使います。
これも含めて

library(tidyverse)

を本章で準備する環境にします。

この章でデモ用に使われるデータtable1, table2, etc. はパッケージtidyrに含まれているため、そのまま使うことができます。

Chapter 12 Tidy data

12.2 Tidy data

12.2.1 Exercises

1. Using prose, describe how the variables and observations are organised in each of the sample tables.

table1は4つの列から成立していて、それぞれの列が国、年、件数、人口を表している。
各行はある国、ある年での件数と人口という二つの数字の統計結果を意味している。

table2は4つの列から成立していて、4列目だけが数字を表しており、3列目はその数字が何を意味しているかを説明している。
4列目はvariableにならない。なぜなら件数と人口という二つの異なる数字が混ざっていて、数字の次元が一貫していないため。このため4列目の集計(合計や平均など)は意味をなさない。
各行はobservationになっていない。なぜなら観測は各地域各年で件数と人口との両方の統計がペアになっていることがデータに反映されていない。

table3は3列目が二つの情報(分母と分子)を持っている。
1つの列で1つのvariableを持つのがtidydata

table4は列がobservationになっている。(データ分析の経験が少ない人が作るエクセルでよく見る)
またvariableが二つのtableに分かれていて、tableがdatasetの単位になっていない。

2. Compute the rate for table2, and table4a + table4b. You will need to perform four operations:

  1. Extract the number of TB cases per country per year.
  2. Extract the matching population per country per year.
  3. Divide cases by population, and multiply by 10000.
  4. Store back in the appropriate place.

Which representation is easiest to work with? Which is hardest? Why?

まずtable2の処理。

#step1
table2a <- filter(table2, type == "cases")

#step2
table2b <- mutate(table2a, pop = filter(table2, type == "population")$count)

#step3
table2c <- mutate(table2b, rate = count / pop * 10000)

#step4
bind_rows(table2, transmute(table2c, country, year, type = "rate", count = rate))

次にtable4の処理。

#step1
case1999 <- table4a$`1999`
case2000 <- table4a$`2000`

#step2
pop1999 <- table4b$`1999`
pop2000 <- table4b$`2000`

#step3
rate1999 <- case1999/pop1999
rate2000 <- case2000/pop2000

#step4
table4c <- tibble(
 country = c("Afganistan", "Brazil", "China"),
 `1999` = rate1999,
 `2000` = rate2000
)

どちらも良くないがtable4の方が大量の中間変数を作らなければならなかったため、より嫌だ。
しかも調査する年が増えると中間変数もコードも増えていくので嫌だ。

3. Recreate the plot showing change in cases over time using table2 instead of table1. What do you need to do first?

table2 %>%
 filter(type == "cases") %>%
 ggplot(aes(year, type)) +
  geom_line(aes(group = country), color = "grey50") +
  geom_point(aes(color = country))

コードを見ての通り、まずはじめにfiltercasesだけのテーブルにしなければならない。

12.3 Spreading and gathering

12.3.3 Exercises

1. Why are gather() and spread() not perfectly symmetrical? Carefully consider the following example:

stocks <- tibble(
  year = c(2015, 2015, 2016, 2016),
  half = c(1, 2, 1, 2),
  return = c(1.88, 0.59, 0.92, 0.17)
)
stocks %>%
  spread(year, return) %>%
  gather("year", "return", `2015`:`2016`)

Both spread() and gather() have a convert argument. What does it do?

spreadgatherした結果は変数の順序が変わる。
元はyear, half, returnの順であったのが、half, year, returnの順になっている。

spread, gatherでデータを動かすとき元のデータ型がFactor型であった場合にうまくいかない。
この場合はconvert = TRUEとすることで自動的に数値や文字列に変換して処理してくれる。

2. Why does this code fail?

table4a %>%
  gather(1999, 2000, key = "year", value = "cases")

変数名にバッククオートが足りない。
すなわち下記のように修正すればよい。

table4a %>%
  gather(`1999`, `2000`, key = "year", value = "cases")

3. Why does spreading this tibble fail? How could you add a new column to fix the problem.

people <- tribble(
  ~name,             ~key,    ~value,
  #-----------------|--------|------
  "Phillip Woods",   "age",       45,
  "Phillip Woods",   "height",   186,
  "Phillip Woods",   "age",       50,
  "Jessica Cordero", "age",       37,
  "Jessica Cordero", "height",   156
)

1行目と3行目とがnameとkeyの両方が重複しているためうまくいかない。
new columnを追加して問題を回避せよとあるが、重複しているPhillip Woodsが同一の人間なのか、そのときのheightはどちらに対応しているのかがよくわからない。

4. Tidy the simple tibble below. Do you need to spread or gather it? What are the variables?

preg <- tribble(
  ~pregnant, ~male, ~female,
  "yes", NA, 10,
  "no", 20, 12
)
preg %>%
  gather(key = "sex", value = "number", -pregnant, na.rm = TRUE)
# A tibble: 3 x 3
##  pregnant sex    number
##* <chr>    <chr>   <dbl>
##1 no       male       20
##2 yes      female     10
##3 no       female     12

12.4 Separating and uniting

はっきり言ってこのseparateuniteはそこまで重要かなと思う。
mutateと文字列操作で完全に置き換えられる。
メリットは表現がシンプルになる、という点だけ。

12.4.3 Exercises

1. What do the extra and fill do in separate()? Experiment with the various options for the following two toy datasets.

tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>%
  separate(x, c("one", "two", "three"))

tibble(x = c("a,b,c", "d,e", "f,g,i")) %>%
  separate(x, c("one", "two", "three"))

extraは文字列で"warn", "merge", "drop"のどれかを取る。デフォルトは"warn"
separateで指定した変数の数に対して実際に分離で作られる変数候補の数が多いときの挙動を指定するもの。
"merge"は最後の列に残りのすべてをくっつけて詰め込む。
たとえば

tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>% 
  separate(x, c("one", "two", "three"), extra = "merge")
#> # A tibble: 3 x 3
#>   one   two   three
#>   <chr> <chr> <chr>
#> 1 a     b     c    
#> 2 d     e     f,g  
#> 3 h     i     j  

の2行3列の値のようになる。
一方、"drop"では

tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>% 
  separate(x, c("one", "two", "three"), extra = "drop")
#> # A tibble: 3 x 3
#>   one   two   three
#>   <chr> <chr> <chr>
#> 1 a     b     c    
#> 2 d     e     f  
#> 3 h     i     j  

と分離した後の切れ端は捨てられる。
"warn"は警告を出して処理は"drop"で行う。

一方fillは指定列数に対して生成データ数が多い時の挙動を指定する。
指定する候補は文字列で"warn", "right", "left"のどれか。
separateは空いた値をNAで埋めるが、"right", "left"はそれらを右にNAを置くか左に置くかを指定できる。
デフォルト値"warn"は警告を出して右詰にする。

tibble(x = c("a,b,c", "d,e", "f,g,i")) %>%
  separate(x, c("one", "two", "three"), fill = "right")
#> # A tibble: 3 x 3
#>   one   two   three
#>   <chr> <chr> <chr>
#> 1 a     b     c    
#> 2 d     e     <NA> 
#> 3 f     g     i   

2. Both unite() and separate() have a remove argument. What does it do? Why would you set it to FALSE?

元のデータを残すかどうかを指定する。
デフォルトはTRUEで残さない。

tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>%
  separate(x, c("one", "two", "three"), fill = "left", remove = FALSE)
#> # A tibble: 3 x 4
#>   x       one   two   three
#>   <chr>   <chr> <chr> <chr>
#> 1 a,b,c   a     b     c    
#> 2 d,e,f,g d     e     f    
#> 3 h,i,j   h     i     j   

3. Compare and contrast separate() and extract(). Why are there three variations of separation (by position, by separator, and with groups), byt only one unite?

tidyr::extractは正規表現でマッチした部分だけを抜き出す関数。
アウトプットが一列なのが違う点。separateは柔軟に列数が変わる。

12.5 Missing values

12.5.1 Exercises

1. Compare and contrast the fill arguments to spread() and complete().

fillNAを埋める対象の変数を指定する。本文中でいうところのexplicitly missing variable.
一方completeは欠損している行を復元するためのキーとなる変数を指定する。本文中でいうところのimplicitly missing variable.
spreadは展開するkeyvalueを指定する。

2. What does the direction argument to fill() do?

.directionオプションには文字列で"up""down"かを指定する。
"up"ではNAがあったときNAでない値から見て上向きにコピーする。
"down"ではNAがあったときNAでない値から見て下向きにコピーする。デフォルトはこちら。

12.6 Case Study

12.6.1 Exercises

1. In this case study I set na.rm = TRUE just to make it easier to check that we had the correct values. Is this reasonable? Think about how missing values are represented in this dataset. Are there implicit missing values? Whats the difference between an NA and zero?

元々のwhoデータセットでは各行は『国×年』で、調査されていない、もしくはデータが存在しない対象はNAになっている。
na.rm=TRUEとすることでそのような『国×年×調査対象』の行が存在しなくなってしまうが、国も年も調査対象も事前に定義済みであるため、欠落=データなしと理解するように約束すれば問題は起こらない。
なお『国×年』の組み合わせは年々増えており、調査対象が広がっていることが分かる。

who %>% count(year) %>% spread(year, n) %>% print(width = Inf)
### A tibble: 1 x 34
##  `1980` `1981` `1982` `1983` `1984` `1985` `1986` `1987` `1988` `1989` `1990` `1991` `1992` `1993` `1994` `1995` `1996` `1997` `1998`
##   <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>
##1    212    212    212    212    212    212    212    212    212    212    212    212    212    212    212    212    212    212    212
##  `1999` `2000` `2001` `2002` `2003` `2004` `2005` `2006` `2007` `2008` `2009` `2010` `2011` `2012` `2013`
##   <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>  <int>
##1    212    212    212    213    213    213    214    214    214    214    214    216    217    217    217

調査していないのか、調査した結果ゼロだったのかは意味が全く異なる。
よってゼロとNAは絶対に区別しなければならない。

2. What happens if you neglect the mutate() step? ( mutate(key = stringr::str_replace(key, "newrel", "new_rel")) )

newrelの行では関数separateが値を3つに分割できない。
この値はNAで埋められ、またseparateがエラーを返す。

3. I claimed that iso2 and iso3 were redundant with country. Confirm this claim.

> who %>% count(country, iso2, iso3) %>% count(country) %>% count(nn > 1)
# A tibble: 1 x 2
  `nn > 1`     n
  <lgl>    <int>
1 FALSE      219
> who %>% count(country, iso2, iso3) %>% count(iso2) %>% count(nn > 1)
# A tibble: 1 x 2
  `nn > 1`     n
  <lgl>    <int>
1 FALSE      219
> who %>% count(country, iso2, iso3) %>% count(iso3) %>% count(nn > 1)
# A tibble: 1 x 2
  `nn > 1`     n
  <lgl>    <int>
1 FALSE      219

4. For each country, year, and sex compute the total number of cases of TB. Make an informative visualisation of the data.

who5 %>%
  group_by(country, year, sex) %>%
  summarise(cases = sum(cases, na.rm=TRUE)) %>%
  ggplot(aes(year, cases)) +
    geom_line(aes(group = country)) +
    facet_wrap(~sex)

結核の数は一部の国で飛び抜けて多く、またこれらの国々で現在も増加傾向であることが観察できる。
どの国か。

who5 %>%
  group_by(country) %>%
  filter(max(cases) > 20000) %>%
  group_by(country, year, sex) %>%
  summarise(cases = sum(cases)) %>%
  ggplot(aes(year, cases)) +
    geom_line(aes(group = country, color = country)) +
    facet_wrap(~sex)


中国とインドが飛び抜けて多く、インドネシアと南アフリカがそれを追いかける。
タンザニアだけは抑制に成功している。

と考えるのは危険で、単に調査対象が広がったことにより集計件数が増えている可能性がある。

who5 %>%
  group_by(country, year, type) %>%
  summarise(cases = sum(cases, na.rm=TRUE)) %>%
  ggplot(aes(year, cases)) +
    geom_line(aes(group = country)) +
    facet_wrap(~type)


やっぱり。
中国では最も件数の多い主要な結核であるtype == "sp"は減っている。
最近type epやtype snの調査も開始したため増えているように見える。
全体としては評価されるべき結核対策をしている。

一方もう一つの結核大国インドでは純粋にtype spだけが増加している。
また他の種類の結核についても情報公開していないため検討できない。
もう一歩頑張って欲しい。

やはりimplicit missingは危険だということがよく分かる例。

コメントを残す