bookclub-advr

DSLC Advanced R Book Club
git clone https://git.eamoncaddigan.net/bookclub-advr.git
Log | Files | Refs | README | LICENSE

html.json (17862B)


      1 {
      2   "hash": "c432b7bf08c41a9f48e329de63c095e1",
      3   "result": {
      4     "markdown": "---\nengine: knitr\ntitle: Control flow\n---\n\n\n## Learning objectives:\n\n- Understand the two primary tools for control flow: **choices** and **loops**\n- Learn best practices to void common pitfalls\n- Distinguish when to use `if`, `ifelse()`, and `switch()` for choices\n- Distinguish when to use `for`, `while`, and `repeat` for loops\n\n::: {.callout-note}\nBasic familiarity with choices and loops is assumed.\n:::\n\n# Choices\n\n[Section 5.2 Choices]: #\n\n## `if` is the basic statement for a **choice** \n\nSingle line format\n\n\n::: {.cell}\n\n```{.r .cell-code}\nif (condition) true_action\nif (condition) true_action else false_action\n```\n:::\n\n\nCompound statement within `{`\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngrade <- function(x) {\n  if (x > 90) {\n    \"A\"\n  } else if (x > 80) {\n    \"B\"\n  } else if (x > 50) {\n    \"C\"\n  } else {\n    \"F\"\n  }\n}\n```\n:::\n\n\n## Results of `if` can be assigned\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx1 <- if (TRUE) 1 else 2\nx2 <- if (FALSE) 1 else 2\n\nc(x1, x2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1 2\n```\n:::\n:::\n\n\n:::{.callout-tip}\nOnly recommended with single line format; otherwise hard to read.\n:::\n\n## `if` without `else` can be combined with `c()` or `paste()` to create compact expressions\n\n - `if` without `else` invisibly returns `NULL` when `FALSE`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngreet <- function(name, birthday = FALSE) {\n  paste0(\n    \"Hi \", name,\n    if (birthday) \" and HAPPY BIRTHDAY\"\n  )\n}\ngreet(\"Maria\", FALSE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"Hi Maria\"\n```\n:::\n\n```{.r .cell-code}\ngreet(\"Jaime\", TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"Hi Jaime and HAPPY BIRTHDAY\"\n```\n:::\n:::\n\n\n[Section 5.2.1 Invalid Inputs]: #\n\n## `if` should have a single `TRUE` or `FALSE` condition, other inputs generate errors\n\n\n::: {.cell}\n\n```{.r .cell-code}\nif (\"x\") 1\n```\n\n::: {.cell-output .cell-output-error}\n```\n#> Error in if (\"x\") 1: argument is not interpretable as logical\n```\n:::\n\n```{.r .cell-code}\nif (logical()) 1\n```\n\n::: {.cell-output .cell-output-error}\n```\n#> Error in if (logical()) 1: argument is of length zero\n```\n:::\n\n```{.r .cell-code}\nif (NA) 1\n```\n\n::: {.cell-output .cell-output-error}\n```\n#> Error in if (NA) 1: missing value where TRUE/FALSE needed\n```\n:::\n\n```{.r .cell-code}\nif (c(TRUE, FALSE)) 1\n```\n\n::: {.cell-output .cell-output-error}\n```\n#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1\n```\n:::\n:::\n\n\n[Section 5.2.2 Vectorized If]: #\n\n## Use `ifelse()` for vectorized conditions\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nifelse(x %% 5 == 0, \"XXX\", as.character(x))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#>  [1] \"1\"   \"2\"   \"3\"   \"4\"   \"XXX\" \"6\"   \"7\"   \"8\"   \"9\"   \"XXX\"\n```\n:::\n\n```{.r .cell-code}\nifelse(x %% 2 == 0, \"even\", \"odd\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#>  [1] \"odd\"  \"even\" \"odd\"  \"even\" \"odd\"  \"even\" \"odd\"  \"even\" \"odd\"  \"even\"\n```\n:::\n:::\n\n\n::: {.callout-tip}\nOnly use `ifelse()` if both results are of the same type; otherwise output type is hard to predict.\n:::\n\n## Use `dplyr::case_when()` for multiple condition-vector pairs\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndplyr::case_when(\n  x %% 35 == 0 ~ \"fizz buzz\",\n  x %% 5 == 0 ~ \"fizz\",\n  x %% 7 == 0 ~ \"buzz\",\n  is.na(x) ~ \"???\",\n  TRUE ~ as.character(x)\n)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#>  [1] \"1\"    \"2\"    \"3\"    \"4\"    \"fizz\" \"6\"    \"buzz\" \"8\"    \"9\"    \"fizz\"\n```\n:::\n:::\n\n\n\n[Section 5.2.3 switch()]: #\n\n## `switch()` is a special purpose equivalent to `if` that can be used to compact code {transition=\"none-out\"}\n\n:::: {.columns}\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nx_option <- function(x) {\n  if (x == \"a\") {\n    \"option 1\"\n  } else if (x == \"b\") {\n    \"option 2\" \n  } else if (x == \"c\") {\n    \"option 3\"\n  } else {\n    stop(\"Invalid `x` value\")\n  }\n}\n```\n:::\n\n:::\n\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nx_option <- function(x) {\n  switch(x,\n    a = \"option 1\",\n    b = \"option 2\",\n    c = \"option 3\",\n    stop(\"Invalid `x` value\")\n  )\n}\n```\n:::\n\n:::\n::::\n\n## `switch()` is a special purpose equivalent to `if` that can be used to compact code {transition=\"none-in\"}\n\n::: {.callout-tip}\n - The last component of a `switch()` should always throw an error, otherwise unmatched inputs will invisibly return `NULL`.\n - Only use `switch()` with character inputs. Numeric inputs are hard to read and have undesirable failure modes.\n:::\n\n::: {.callout-caution}\nLike `if`, `switch()` can only take a single condition, not vector conditions\n:::\n\n\n## Avoid repeat outputs by leaving the right side of `=` empty\n\n- Inputs will \"fall through\" to the next value.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlegs <- function(x) {\n  switch(x,\n    cow = ,\n    horse = ,\n    dog = 4,\n    human = ,\n    chicken = 2,\n    plant = 0,\n    stop(\"Unknown input\")\n  )\n}\nlegs(\"cow\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 4\n```\n:::\n\n```{.r .cell-code}\nlegs(\"dog\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 4\n```\n:::\n:::\n\n\n[Section 5.3 Loops]: #\n\n# Loops\n\n## To iterate over items in a vector, use a `for` **loop**\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (item in vector) perform_action\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (i in 1:3) {\n  print(i)\n}\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1\n#> [1] 2\n#> [1] 3\n```\n:::\n:::\n\n\n::: {.callout-note}\nConvention uses short variables like `i`, `j`, or `k` for iterating vector indices\n:::\n\n## `for` will overwrite existing variables in the current environment\n\n\n::: {.cell}\n\n```{.r .cell-code}\ni <- 100\nfor (i in 1:3) {}\ni\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 3\n```\n:::\n:::\n\n\n## Use `next` or `break` to terminate loops early {transition=\"none-out\"}\n- `next` exits the current iteration, but continues the loop\n- `break` exits the entire loop\n\n## Use `next` or `break` to terminate loops early {transition=\"none-in\"}\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (i in 1:10) {\n  if (i < 3) \n    next\n\n  print(i)\n  \n  if (i >= 5)\n    break\n}\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 3\n#> [1] 4\n#> [1] 5\n```\n:::\n:::\n\n\n[Section 5.3.1 Common pitfalls]: #\n\n## Preallocate an output container to avoid slow loops\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmeans <- c(1, 50, 20)\nout <- vector(\"list\", length(means))\nfor (i in 1:length(means)) {\n  out[[i]] <- rnorm(10, means[[i]])\n}\n```\n:::\n\n\n:::{.callout-tip}\n`vector()` function is helpful for preallocation\n:::\n\n## Use `seq_along(x)` instead of `1:length(x)` {transition=\"none-out\"}\n- `1:length(x)` causes unexpected failure for 0 length vectors\n- `:` works with both increasing and decreasing sequences\n\n::: {.cell}\n\n```{.r .cell-code}\nmeans <- c()\n1:length(means)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1 0\n```\n:::\n\n```{.r .cell-code}\nseq_along(means)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> integer(0)\n```\n:::\n:::\n\n\n## Use `seq_along(x)` instead of `1:length(x)` {transition=\"none-in\"}\n:::: {.columns}\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nout <- vector(\"list\", length(means))\nfor (i in 1:length(means)) {\n  out[[i]] <- rnorm(10, means[[i]])\n}\n```\n\n::: {.cell-output .cell-output-error}\n```\n#> Error in rnorm(10, means[[i]]): invalid arguments\n```\n:::\n:::\n\n:::\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nout <- vector(\"list\", length(means))\nfor (i in seq_along(means)) {\n  out[[i]] <- rnorm(10, means[[i]])\n}\nout\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> list()\n```\n:::\n:::\n\n:::\n::::\n\n## Avoid problems when iterating over S3 vectors by using `seq_along(x)` and `x[[i]]`\n::: {}\n- loops typically strip attributes\n\n::: {.cell}\n\n```{.r .cell-code}\nxs <- as.Date(c(\"2020-01-01\", \"2010-01-01\"))\n```\n:::\n\n:::\n:::: {.columns}\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (x in xs) {\n  print(x)\n}\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 18262\n#> [1] 14610\n```\n:::\n:::\n\n:::\n::: {.column}\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (i in seq_along(xs)) {\n  print(xs[[i]])\n}\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"2020-01-01\"\n#> [1] \"2010-01-01\"\n```\n:::\n:::\n\n:::\n::::\n \n[Section 5.3.2 Related tools]: #\n \n## Use `while` or `repeat` when you don't know the number of iterations\n\n- `while(condition) action`: perfoms `action` while `condition` is `TRUE`\n- `repeat(action)`: repeats `action` forever (or until a `break`) \n \n## Always use the least-flexible loop option possible\n\n- Use `for` before `while` or `repeat`\n- In data analysis use `apply()` or `purrr::map()` before `for`\n\n# Quiz & Exercises {visibility=\"uncounted\"}\n\n[Section 5.1 Quiz]: #\n\n## What is the difference between if and ifelse()? {visibility=\"uncounted\"}\n\n::: {.fragment .fade-in}\n`if` works with scalars; `ifelse()` works with vectors.\n:::\n\n## In the following code, what will the value of `y` be if `x` is `TRUE`? What if `x` is `FALSE`? What if `x` is `NA`? {visibility=\"uncounted\"}\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ny <- if (x) 3\n```\n:::\n\n\n::: {.fragment .fade-up fragment-index=1}\nWhen `x` is `TRUE`, `y` will be `3`; when `FALSE`, `y` will be `NULL`; when `NA` the `if` statement will throw an error.\n:::\n\n## What does `switch(\"x\", x = , y = 2, z = 3)` return? {visibility=\"uncounted\"}\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nswitch(\n  \"x\",\n  x = ,\n  y = 2,\n  x = 3\n)\n```\n:::\n\n\n::: {.fragment .fade-in}\nThis `switch()` statement makes use of fall-through so it will return `2`.\n:::\n\n[Section 5.2.4 Exercises]: #\n\n## What type of vector does each of the following calls to ifelse() return? {visibility=\"uncounted\" transition=\"none-out\"}\n\nRead the documentation and write down the rules in your own words.\n\n::: {.cell}\n\n```{.r .cell-code}\nifelse(TRUE, 1, \"no\")\nifelse(FALSE, 1, \"no\")\nifelse(NA, 1, \"no\")\n```\n:::\n\n\n## What type of vector does each of the following calls to ifelse() return? {visibility=\"uncounted\" transition=\"none\"}\n\nThe arguments of `ifelse()` are named `test`, `yes` and `no`.\nIn general, `ifelse()` returns the entry for `yes` when `test` is `TRUE`,\nthe entry for `no` when `test` is `FALSE` \nand `NA` when `test` is `NA`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nifelse(TRUE, 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1\n```\n:::\n\n```{.r .cell-code}\nifelse(FALSE, 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"no\"\n```\n:::\n\n```{.r .cell-code}\nifelse(NA, 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n:::\n\n\n## What type of vector does each of the following calls to ifelse() return? {visibility=\"uncounted\" transition=\"none-in\"}\nIn practice, `test` is first converted to `logical` and if the result is neither `TRUE` nor `FALSE`, then `as.logical(test)` is returned.\n\n::: {.cell}\n\n```{.r .cell-code}\nifelse(logical(), 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> logical(0)\n```\n:::\n\n```{.r .cell-code}\nifelse(NaN, 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n\n```{.r .cell-code}\nifelse(NA_character_, 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n\n```{.r .cell-code}\nifelse(\"a\", 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n\n```{.r .cell-code}\nifelse(\"true\", 1, \"no\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1\n```\n:::\n:::\n\n\n## Why does the following code work? {visibility=\"uncounted\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nif (length(x)) \"not empty\" else \"empty\"\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"not empty\"\n```\n:::\n\n```{.r .cell-code}\nx <- numeric()\nif (length(x)) \"not empty\" else \"empty\"\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] \"empty\"\n```\n:::\n:::\n\n::: {.fragment .fade-up fragment-index=1}\n`if()` expects a logical condition, but also accepts a numeric vector where `0` is treated as `FALSE` and all other numbers are treated as `TRUE`.\nNumerical missing values (including `NaN`) lead to an error in the same way that a logical missing, `NA`, does.\n:::\n\n[Section 5.3.3 Exercises]: #\n\n## Why does this code succeed without errors or warnings? {visibility=\"uncounted\" transition=\"none-out\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- numeric()\nout <- vector(\"list\", length(x))\nfor (i in 1:length(x)) {\n  out[i] <- x[i] ^ 2\n}\nout\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [[1]]\n#> [1] NA\n```\n:::\n:::\n\n\n## Why does this code succeed without errors or warnings? {visibility=\"uncounted\" transition=\"none-in\"}\n- Subsetting behavior for out-of-bounds & `0` indices when using `[<-` and `[`\n- `x[1]` generates an `NA`. `NA` is assigned to the empty length-1 list `out[1]`\n- `x[0]` returns `numeric(0)`. `numeric(0)` is assigned to `out[0]`. Assigning a 0-length vector to a 0-length subset doesn't change the object.\n- Each step includes valid R operations (even though the result may not be what the user intended).\n\n## Walk-through {visibility=\"uncounted\" transition=\"none-out\"}\n\nSetup\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- numeric()\nout <- vector(\"list\", length(x))\n1:length(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1 0\n```\n:::\n:::\n\n\n## Walk-through {visibility=\"uncounted\" transition=\"none\"}\n\nFirst Iteration\n\n::: {.cell}\n\n```{.r .cell-code}\nx[1]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n\n```{.r .cell-code}\nx[1]^2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] NA\n```\n:::\n\n```{.r .cell-code}\nout[1]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [[1]]\n#> NULL\n```\n:::\n\n```{.r .cell-code}\nout[1] <- x[1]^2\nout[1]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [[1]]\n#> [1] NA\n```\n:::\n:::\n\n\n## Walk-through {visibility=\"uncounted\" transition=\"none\"}\n\nSecond Iteration\n\n::: {.cell}\n\n```{.r .cell-code}\nx[0]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> numeric(0)\n```\n:::\n\n```{.r .cell-code}\nx[0]^2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> numeric(0)\n```\n:::\n\n```{.r .cell-code}\nout[0]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> list()\n```\n:::\n\n```{.r .cell-code}\nout[0] <- x[0]^2\nout[0]\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> list()\n```\n:::\n:::\n\n\n## Walk-through {visibility=\"uncounted\" transition=\"none-in\"}\n\nFinal Result\n\n::: {.cell}\n\n```{.r .cell-code}\nout\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [[1]]\n#> [1] NA\n```\n:::\n:::\n\n\n## When the following code is evaluated, what can you say about the vector being iterated? {visibility=\"uncounted\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\nxs <- c(1, 2, 3)\nfor (x in xs) {\n  xs <- c(xs, x * 2)\n}\nxs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 1 2 3 2 4 6\n```\n:::\n:::\n\n\n::: {.fragment .fade-in}\nIn this loop `x` takes on the values of the initial `xs` (`1`, `2` and `3`), indicating that it is evaluated just once in the beginning of the loop, not after each iteration. (Otherwise, we would run into an infinite loop.)\n:::\n\n## What does the following code tell you about when the index is updated? {visibility=\"uncounted\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfor (i in 1:3) {\n  i <- i * 2\n  print(i) \n}\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n#> [1] 2\n#> [1] 4\n#> [1] 6\n```\n:::\n:::\n\n\n::: {.fragment .fade-in}\nIn a `for` loop the index is updated in the beginning of each iteration. Therefore, reassigning the index symbol during one iteration doesn't affect the following iterations. (Again, we would otherwise run into an infinite loop.)\n:::\n",
      5     "supporting": [
      6       "05_files/figure-revealjs"
      7     ],
      8     "filters": [
      9       "rmarkdown/pagebreak.lua"
     10     ],
     11     "includes": {
     12       "include-after-body": [
     13         "\n<script>\n  // htmlwidgets need to know to resize themselves when slides are shown/hidden.\n  // Fire the \"slideenter\" event (handled by htmlwidgets.js) when the current\n  // slide changes (different for each slide format).\n  (function () {\n    // dispatch for htmlwidgets\n    function fireSlideEnter() {\n      const event = window.document.createEvent(\"Event\");\n      event.initEvent(\"slideenter\", true, true);\n      window.document.dispatchEvent(event);\n    }\n\n    function fireSlideChanged(previousSlide, currentSlide) {\n      fireSlideEnter();\n\n      // dispatch for shiny\n      if (window.jQuery) {\n        if (previousSlide) {\n          window.jQuery(previousSlide).trigger(\"hidden\");\n        }\n        if (currentSlide) {\n          window.jQuery(currentSlide).trigger(\"shown\");\n        }\n      }\n    }\n\n    // hookup for slidy\n    if (window.w3c_slidy) {\n      window.w3c_slidy.add_observer(function (slide_num) {\n        // slide_num starts at position 1\n        fireSlideChanged(null, w3c_slidy.slides[slide_num - 1]);\n      });\n    }\n\n  })();\n</script>\n\n"
     14       ]
     15     },
     16     "engineDependencies": {},
     17     "preserve": {},
     18     "postProcess": true
     19   }
     20 }