神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
fortable2
, andtable4a
+table4b
. You will need to perform four operations:
- Extract the number of TB cases per country per year.
- Extract the matching population per country per year.
- Divide cases by population, and multiply by 10000.
- 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 oftable1
. 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))
コードを見ての通り、まずはじめにfilter
でcases
だけのテーブルにしなければならない。
12.3 Spreading and gathering
12.3.3 Exercises
1. Why are
gather()
andspread()
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()
andgather()
have aconvert
argument. What does it do?
spread
とgather
した結果、元々は数値ベクトルだったyear
が文字列に変換されている。
また変数の順序が変わって元は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
はっきり言ってこのseparate
とunite
はそこまで重要かなと思う。
mutate
と文字列操作で完全に置き換えられる。
メリットは表現がシンプルになる、という点だけ。
12.4.3 Exercises
1. What do the
extra
andfill
do inseparate()
? 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()
andseparate()
have aremove
argument. What does it do? Why would you set it toFALSE
?
元のデータを残すかどうかを指定する。
デフォルトは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()
andextract()
. Why are there three variations of separation (by position, by separator, and with groups), byt only one unite?
tidyr::extract
は正規表現でマッチした部分を抜き出す関数。
separate
は分割なので、元々の文字列と分割後の文字列との文字数は一致する。
extract
は抜き出しているので一致するとは限らない。
分割する方法はいくつでも考えられるが、unite
でくっつける方法はセパレータくらいしかない。
12.5 Missing values
12.5.1 Exercises
1. Compare and contrast the
fill
arguments tospread()
andcomplete()
.
fill
はNA
を埋める対象の変数を指定する。本文中でいうところのexplicitly missing variable.
一方complete
は欠損している行を復元するためのキーとなる変数を指定する。本文中でいうところのimplicitly missing variable.
spread
は展開するkey
とvalue
を指定する。
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 anNA
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
andiso3
were redundant withcountry
. 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は危険だということがよく分かる例。