R for Data Scienceの例題を解く- Chapter 19 関数

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

過去の記事

19 Functions

19.2 When should you write a function?

19.2.1 Practice

1. Why is TRUE not a parameter to rescale01()? What would happen if x contained a single missing value, and na.rm was FALSE?

引数na.rmFALSEにセットする必要がないため。
na.rmFALSEにセットしてx = NAとするとNAを返す。
一方rescale01(NA)NAとWarningを返す。

2. In the second variant of rescale01(), infinite values are left unchanged. Rewrite rescale01() that -Inf is mapped to 0, and Inf is mapped to 1.

rescale02 <- function(x){
  rng <- range(x, na.rm=TRUE, finite = TRUE)
  out <- (x - rng[1])/(rng[2] - rng[1])
  ifelse(is.infinite(out), ifelse(out > 0, exp(1/out), 1/out), out)
}

もっとうまいやり方ないかなあ

3. Practice turning the following code snippets into functions. Think about what each function does. What would you call it? How many arguments does it need? Can you rewrite it to be more expressive or less duplicative?

density_na <- function(x){
  mean(is.na(x))
}

percent_ot_total <- function(x){
  x / sum(x, na.rm = TRUE)
}
# sum(x)がゼロのときエラーになる。

#3つめは良く分からん。xの合計がゼロだと分母ゼロでエラー、全部ゼロだとsdがエラーになる。

4. Follow http://nicercode.github.io/intro/writing-functions.html to write your own functions to compute the variance and skew of a numeric vector.

サイトにあるそのままのコードでいいのかしら

5. Write both_na(), a function that takes two vectors of the same length and returns the number of positions that have an NA in both vectors.

both_na <- function(x, y){
  if(length(x) == length(y)){
    which(is.na(x) & is.na(y))
  }
}

6. What do the following functions do? Why are they useful even though they are so short?

is_directory <- function(x) file.info(x)$is.dir
is_readable <- function(x) file.access(x, 4) == 0

それぞれ元々の処理と比較して関数として定義することでパイプで渡しやすくなる。
またfile.access(x, 4)==0よりもis_readable(x)の方が何の処理をしようとしているのかコードが理解しやすい。

7. Read the complete lyrics to “Little Bunny Foo Foo”. There’s a lot of duplication in this song. Extend the initial piping example to recreate the complete song, and use functions to reduce the duplication.

foo_foo <- little_bunny()
foo_foo %>%
  hop(through = forest) %>%
  scoop(up = field_mice) %>%
  bop(on = head)
good_fairly %>%
  like(not = TRUE,
    foo_foo %>%
      scoop(up = field_mice) %>%
      bop(on = head)
  ) %>%
  give(chance = to_change, number = three)
foo_foo %>%
  hop(through = forest) %>%
  scoop(up = field_mice) %>%
  bop(on = head)
good_fairly %>%
  like(not = TRUE,
    foo_foo %>%
      scoop(up = field_mice) %>%
      bop(on = head)
  ) %>%
  give(chance = to_change, number = two)
foo_foo %>%
  hop(through = forest) %>%
  scoop(up = field_mice) %>%
  bop(on = head)
good_fairly %>%
  like(not = TRUE,
    foo_foo %>%
      scoop(up = field_mice) %>%
      bop(on = head)
  ) %>%
  give(chance = to_change, number = one)
foo_foo %>%
  hop(through = forest) %>%
  scoop(up = field_mice) %>%
  bop(on = head)
good_fairly %>%
  like(not = TRUE,
    foo_foo %>%
      scoop(up = field_mice) %>%
      bop(on = head)
  ) %>%
  give(foo_foo, chance = no) %>%
  turn(foo_foo, into = goonie)

繰り返し部分を関数にしてみる。

foo_foo %>% little_bunny()
attitude <- function(x){
  x %>%
    hop(through = forest) %>%
    scoop(up = field_mice) %>%
    bop(on = head)
}

attitude(foo_foo)
good_fairly %>%
  like(not = TRUE, attitude(foo_foo)) %>%
  give(foo_foo, chance = to_change, number = 3L)
attitude(foo_foo)
good_fairly %>%
  like(not = TRUE, attitude(foo_foo)) %>%
  give(foo_foo, chance = to_change, number = 2L)
attitude(foo_foo)
good_fairly %>%
  like(not = TRUE, attitude(foo_foo)) %>%
  give(foo_foo, chance = to_change, number = 1L)
attitude(foo_foo)
good_fairly %>%
  like(not = TRUE, attitude(foo_foo)) %>%
  give(foo_foo, chance = to_change, number = 0L) %>%
  turn(foo_foo, into = goonie)

19.3 Functions are for humans and computers

19.3.1 Exercises

1. Read the source code for each of the following three functions, puzzle out what they do, and then brainstorm better names.

f1stringの先頭の文字列がprefixと一致しているかをチェックする。関数名はcheck_prefix
f2は入力したベクトルの最後の要素を除いたベクトルを返す。関数名はremove_last
f3はベクトルxの長さと同じ要素数のyを繰り返したベクトルを返す。関数名はrepete_length

2. Take a function that you’ve written recently and spend 5 minutes brainstorming a better name for it and its arguments.

ひみつ

3. Compare and contrast rnorm() and MASS::mvrnorm(). How could you make them more consistent?

まずbaseでは先頭がrで乱数発生の意味なのでmvrnormではなくrmvnormの方が一貫性がでる。
また分布のパラメータがrnormではmean, sdmvrnormではmuSigmaなので統一する。
引数のデフォルト値がrnormではmean=0, sd = 1に対して、mvrnormではn=0なので統一する。

4. Make a case for why norm_r(), norm_d() etc would be better than rnorm(), dnorm(). Make a case for the opposite.

たとえば分布を固定して色々な処理を行う場合はnorm_rnorm_dといった関数たちを使うのでこちらの方が検索しやすい。
逆に分布を固定しない場合、たとえば数種類の分布から乱数を発生させたい場合はrnormrunifなどといった関数たちを使うのでこちらの方が検索しやすい。

19.4 Conditional excecution

19.4.4 Exercises

1. What’s the difference between if and ifelse()? Carefully read the help and construct three eamples that illustrate the key differences.

ifはプログラムの流れを制御する。制御する条件はスカラー。
ifelse()はベクトル値を返す関数。制御条件(ベクトル)によって返す要素の値を決定する。

#example 1
if (is.numeric(x)){
  x * 2
}
ifelse(is.na(x), 0, x)

ifは条件が合わなければNULLを返す。
ifelseは必ずベクトルを返す。

#example 2
if (sum(x) != 0){
  x / sum(x)
}
ifelse(x != 0, 1/x, 0)

ifは処理を制御しているので、何でもできる。
ifelseはベクトルを制御しているので、できることはベクトルを作るだけ。

#example 3
if (is.numeric(x)){
  cat("x is numeric.\n")
}
ifelse(is.na(x), "element of x is NA", "element of x isn't NA")

2. Write a greting function that says “good morning”, “good afternoon”, or “good evening”, depending on the time of day. (Hint: use a time argument that defaults to lubridate::now(). That will make it easier to test your function.)

greeting <- function(){
  t <- lubridate::now()
  h <- lubridate::hour(t)
  if (h %in% 6:12){
    cat("good morning!\n")
  } else if (h %in% 13:17){
    cat("good afternoon!\n")
  } else {
    cat("good evening!\n")
  }
}

3. Implement a fizzbuzz function. It takes a single number as input. If the number is divisible by three, it returns “fizz”. If it’s divisible by five it returns “buzz”. If it’s divisible by three and five, it returns “fizzbuzz”. Otherwise, it returns the number. Make sure you first write working code before you create the function.

fizzbuzz <- function(x){
  fb <- paste0(
    if (x %% 3 == 0) "fizz",
    if (x %% 5 == 0) "buzz"
  )
  if (length(fb) > 0) fb else x
}

4. How could you use cut() to simplify this set of nested if-else statements?

if (temp <= 0) {
  "freezing"
} else if (temp <= 10) {
  "cold"
} else if (temp <= 20) {
  "cool"
} else if (temp <= 30) {
  "warm"
} else {
  "hot"
}

How would you change the call to cut() if I’d used < instead of <=? What is the other chief advantage of cut() for this problem? (Hint: what happens if you have many values in temp?)

cut(temp, breaks = c(-Inf, 0, 10, 20, 30, Inf), labels = c("freezing", "cold", "cool", "warm", "hot"))
cut(temp, breaks = c(-Inf, 0, 10, 20, 30, Inf), labels = c("freezing", "cold", "cool", "warm", "hot"), right = FALSE)

cutを使えばtempがベクトルでも同時に処理できる。
一方ifの制御は長さ1のベクトルしか扱うことができない。
よってcutのやり方の方が圧倒的に優れている。

5. What happens if you use switch() with numeric values?

switchの2つめ以降の引数が選択されて返される。
小数点以下は切り捨てで、存在しなければNULL

switch(1, "one", "two", "three")
# [1] "one"
switch(2, "one", "two", "three")
# [1] "two"
switch(1.1, "one", "two", "three")
# [1] "one"
switch(4, "one", "two", "three")

6. What does this switch() call do? What happens if x is “e”?

switch(x,
  a = ,
  b = "ab",
  c = ,
  d = "cd"
)

Experiment, then carefully read the documentation.

xaの場合、なんと"ab"を返す。
helpによれば、見つけた値がmissing valueだった場合には次のnon-missing valueを返すとのこと。
よってxaの場合はa = ,でmissingなので、その次に指定されているbの場合の"ab"を返すようだ。
なぜこんなヘンテコな機能になっているのかさっぱり分からない。

xを”e”にすればNULLを返す。

19.5 Function arguments

19.5.5 Exercises

1. What does commas(letters, collapse = "-") do? Why?

エラーを返す。
関数commasの中で呼ばれているstr_cの引数としてcollapseが二度設定されてしまうため。

2. It’d be nice if you could supply multiple characters to the pad argument, e.g. rule("Title", pad = "-+"). Why doesn’t this currently work? How could you fix it?

元の関数定義のままpad="-+"とすると、行数をオーバーして二行にわたって-+が出力されてしまう。
繰り返し回数をpadの文字数で割り算してやらなければならない。

rule1 <- function(..., pad = "-"){
  title <- paste0(...)
  width <- getOption("width") - nchar(title) - 5
  cat(title, " ", stringr::str_dup(pad, width/nchar(pad)), "\n", sep = "")
}

3. What does the trim argument to mean() do? When might you use it?

極端な数値を上下対象に取り除いて平均を取る。
たとえばtrim=0.1であれば、ベクトルxのうち上位10%と下位10%とを除いて平均を取る。

4. The default value for the method argument to cor() is c("pearson", "kendall", "spearman"). What does that mean? What value is used by default?

相関係数の計算方法にもバリエーションがあって、それぞれの発明者の名前が付けられている。
デフォルトはpearson

カテゴリー: R4DS

コメントを残す