Upgrade to Pro — share decks privately, control downloads, hide ads and more …

object of type closure is not subsettable

object of type closure is not subsettable

Talk developed for rstudio::conf 2020 , January 27 - 30, 2020 in San Francisco by Jenny Bryan

Jennifer (Jenny) Bryan

January 30, 2020
Tweet

More Decks by Jennifer (Jenny) Bryan

Other Decks in Programming

Transcript

  1. @jennybc
    @JennyBryan
    Jennifer Bryan
    RStudio
    rstd.io/debugging
    object of type closure is not subsettable

    View Slide

  2. dat !<- data.frame(x = 1, y = 2)
    df$x
    #> Error in df$x: object of type 'closure' is not subsettable
    cat("Perhaps you wanted `dat$x`?\n")
    #> Perhaps you wanted `dat$x`?

    View Slide

  3. Fun!

    View Slide

  4. Less fun.
    Fun!

    View Slide

  5. WHAT'S YOUR MAIN DEBUGGING METHOD?
    A. I don’t have a method. Why do you think I’m here?!?
    B. I ask in an online forum or consult my local expert.
    C. Google is my BFF.
    D. Print debugging is my jam.
    E. I use R's debugging tools, like browser().
    Poll at slido.com (event code = HEXAGON)

    View Slide

  6. 1. RESET
    2. REPREX
    3. DEBUG
    4. DETER

    View Slide

  7. RESET
    1

    View Slide

  8. try exactly the same thing again.
    If at first you don’t succeed…

    View Slide

  9. Have you tried turning it OFF and ON again?

    View Slide

  10. If you love something, set it free.
    If it comes back, it is yours.
    If it does not, it never was.

    View Slide

  11. RESTART R
    Especially when things get weird

    View Slide

  12. View Slide

  13. R --no-save --no-restore-data

    View Slide

  14. R --no-save --no-restore-data
    !=
    rm(list = ls())

    View Slide

  15. WHICH PERSIST AFTER rm(list = ls())?
    A. library(dplyr)
    B. summary <- head
    C. options(stringsAsFactors = FALSE)
    D. Sys.setenv(LANGUAGE = "fr")
    E. x <- 1:5
    F. attach(iris)
    Poll at slido.com (event code = HEXAGON)

    View Slide

  16. WHICH PERSIST AFTER rm(list = ls())?
    A. library(dplyr) ✔
    B. summary <- head ✘
    C. options(stringsAsFactors = FALSE) ✔
    D. Sys.setenv(LANGUAGE = "fr") ✔
    E. x <- 1:5 ✘
    F. attach(iris) ✔
    Poll at slido.com (event code = HEXAGON)

    View Slide

  17. R --no-save --no-restore-data
    >>>
    rm(list = ls())

    View Slide

  18. A FRESH START
    Cleans workspace
    Resets options and env vars
    Clears search path

    View Slide

  19. not houseplants.
    R sessions are like crops,

    View Slide

  20. REPREX
    2

    View Slide

  21. brood, dither, and fret.
    If at first you don’t succeed…

    View Slide

  22. Don't wring hands and speculate.
    Work a small concrete example that
    reveals, confirms, or eliminates
    something.

    View Slide

  23. reprex
    minimum
    reproducible
    example

    View Slide

  24. View Slide

  25. template !<- "${EXCLAMATION} - your reprex is ${adjective}!"
    praise(template)
    #> Error in praise(template): could not find function "praise"

    View Slide

  26. library(praise)
    praise(template)
    #> Error in grepl(template_pattern, x): object 'template' not found

    View Slide

  27. library(praise)
    template !<- "${EXCLAMATION} - your reprex is ${adjective}!"
    praise(template)
    #> [1] "WOWIE - your reprex is glorious!"

    View Slide

  28. REPRODUCIBLE
    No reliance on hidden state

    View Slide

  29. View Slide

  30. View Slide

  31. Brooke Watson Madubuonwu
    fs!::dir_info(path = raw_data_path, # list xls[s] files
    regexp = "[.]xls[x]?$") %>%
    dplyr!::mutate(sheets = purrr!::map( # create list-col of
    path, ~ readxl!::excel_sheets)) %>% # worksheets
    tidyr!::unnest(sheets) %>% # get one row per worksheet
    dplyr!::mutate(data = purrr!::map2( # read data into a list-col
    path, sheets, # of data frames
    ~ readxl!::read_excel(.x, .y) %>% # call `as.character()`
    dplyr!::mutate_all(as.character) # on each column
    ))
    #> New names:
    #> * `` !-> `!..2`
    #> * `` !-> `!..3`
    #> * `` !-> `!..4`
    #> * `` !-> `!..5`
    #> * `` !-> `!..6`
    #> Error: the !!... list does not contain 3 elements

    View Slide

  32. Brooke Watson Madubuonwu
    fs!::dir_info(path = raw_data_path, # list xls[s] files
    regexp = "[.]xls[x]?$") %>%
    dplyr!::mutate(sheets = purrr!::map( # create list-col of
    path, ~ readxl!::excel_sheets)) %>% # worksheets
    tidyr!::unnest(sheets) %>% # get one row per worksheet
    dplyr!::mutate(data = purrr!::map2( # read data into a list-col
    path, sheets, # of data frames
    ~ readxl!::read_excel(.x, .y) %>% # call `as.character()`
    dplyr!::mutate_all(as.character) # on each column
    ))
    #> New names:
    #> * `` !-> `!..2`
    #> * `` !-> `!..3`
    #> * `` !-> `!..4`
    #> * `` !-> `!..5`
    #> * `` !-> `!..6`
    #> Error: the !!... list does not contain 3 elements

    View Slide

  33. Brooke Watson Madubuonwu
    fs!::dir_info(path = raw_data_path,
    regexp = "[.]xls[x]?$") %>%
    dplyr!::mutate(sheets = purrr!::map(
    path, ~ readxl!::excel_sheets)) %>%
    tidyr!::unnest(sheets) %>%
    dplyr!::mutate(data = purrr!::map2(
    path, sheets,
    ~ readxl!::read_excel(.x, .y) %>%
    dplyr!::mutate_all(as.character)
    ))
    #> New names:
    #> * `` !-> `!..2`
    #> * `` !-> `!..3`
    #> * `` !-> `!..4`
    #> * `` !-> `!..5`
    #> * `` !-> `!..6`
    #> Error: the !!... list does not contain 3 elements
    private xlsx files
    ~10 lines of code
    5 packages
    8 functions

    View Slide

  34. tidyverse/dplyr#4094
    inlined data
    2 lines of code
    1 package
    1 function
    dat !<- data.frame(`!..1` = 1)
    dplyr!::mutate_all(dat, as.character)
    #> Error: Column 1 must not have names
    #> of the form !!... or !..j.
    #> Use .name_repair to specify repair.

    View Slide

  35. MINIMAL
    Inputs are small and simple
    Extraneous packages
    Unnecessary function calls

    View Slide

  36. View Slide

  37. View Slide

  38. DEBUG
    3

    View Slide

  39. HAVE YOU GOTTEN STUCK IN THE DEBUGGER?
    A. R has a debugger? What is a debugger?
    B. No.
    C. Yes.
    D. Yes and I'm not even sure how I got there.
    Poll at slido.com (event code = HEXAGON)

    View Slide

  40. # Error : .onLoad failed in loadNamespace() for 'rJava', details:
    # call: dyn.load(file, DLLpath = DLLpath, ...)
    # error: unable to load shared object '/Users/janedoe/Library/R/3.6/library/rJava/libs/rJava.so':
    # libjvm.so: cannot open shared object file: No such file or directory
    # Error: loading failed
    # Execution halted
    # ERROR: loading failed
    # * removing '/Users/janedoe/Library/R/3.6/library/rJava/'
    # Warning in install.packages :
    # installation of package 'rJava' had non-zero exit status

    View Slide

  41. # Error : blah bl failed blah blah blah blah blah blah blah blah:
    # blah: bla.blah(blah, blahbla = blahbla, ...)
    # error: unable to blah blah blah bla '/blahb/blahbla/blahbla/b/b.b/blahbla/blahb/blah/blahb.so':
    # blahbl.so: cannot blah blah blah blah bla: No blah blah blah blah bl
    # Error: blah bl failed
    # Blah blah blah b
    # ERROR: blah bl failed
    # * removing '/blahb/blahbla/blahbla/b/b.b/blahbla/blahb/'
    # Warning in blah blah blah b :
    # blahblahblah bl blahbla 'blahb' bla bla-blah blah blahbl

    View Slide

  42. dat
    #> blackberry blueberry peach plum
    #> calories 4 1 59 30
    #> weight 9 2 150 78
    #> yumminess 6 8 10 5
    fruit_avg(dat, pattern = "berry")
    #> Found 2 fruits!
    #> calories weight yumminess
    #> 2.5 5.5 7.0

    View Slide

  43. dat
    #> blackberry blueberry peach plum
    #> calories 4 1 59 30
    #> weight 9 2 150 78
    #> yumminess 6 8 10 5
    fruit_avg(dat, pattern = "melon")
    #> Found 0 fruits!
    #> calories weight yumminess
    #> NaN NaN NaN

    View Slide

  44. dat
    #> blackberry blueberry peach plum
    #> calories 4 1 59 30
    #> weight 9 2 150 78
    #> yumminess 6 8 10 5
    fruit_avg(dat, pattern = "black")
    #> Found fruits!
    #> Error in rowMeans(mini_dat): 'x' must be an array
    #> of at least two dimensions

    View Slide

  45. dat
    #> blackberry blueberry peach plum
    #> calories 4 1 59 30
    #> weight 9 2 150 78
    #> yumminess 6 8 10 5
    fruit_avg(dat, pattern = "black")
    #> Found fruits!
    #> Error in rowMeans(mini_dat): 'x' must be an array
    #> of at least two dimensions

    View Slide

  46. View Slide

  47. traceback()
    options(error = recover)
    browser()

    View Slide

  48. base
    fruit_avg(dat, pattern = "black")
    #> Found fruits!
    #> Error in rowMeans(mini_dat): 'x' must be an array
    #> of at least two dimensions
    traceback()
    #> 3: stop("'x' must be an array of at least two dimensions")
    #> 2: rowMeans(mini_dat) at fruit_avg.R#5
    #> 1: fruit_avg(dat, pattern = "black")

    View Slide

  49. call
    stack
    trace
    back
    call stack
    trace back
    stack trace
    back trace
    call back

    View Slide

  50. call
    stack
    trace
    back
    call stack
    trace back
    stack trace
    back trace
    call back

    View Slide

  51. rlang
    fruit_avg(dat, pattern = "black")
    #> Found fruits!
    #> Error in rowMeans(mini_dat): 'x' must be an array
    #> of at least two dimensions
    rlang!::last_trace()
    #>
    #> 'x' must be an array of at least two dimensions
    #> Backtrace:
    #> █
    #> 1. └─global!::fruit_avg(dat, pattern = "black")
    #> 2. └─base!::rowMeans(mini_dat) R/fruit_avg.R:5:2

    View Slide

  52. RStudio

    View Slide

  53. View Slide

  54. Video of a tiny room hidden behind an electrical outlet.
    By Mozu Studios
    https:/
    /www.mozustudios.com
    https:/
    /www.instagram.com/p/B6NvxK7JnpU

    View Slide

  55. options(error = recover)
    fruit_avg(dat, "black")
    #> Found fruits!
    #> Error in rowMeans(mini_dat): 'x' must be an array
    #> of at least two dimensions
    Enter a frame number, or 0 to exit
    1: fruit_avg(dat, "black")
    2: fruit_avg.R#5: rowMeans(mini_dat)
    Selection: 1

    View Slide

  56. base
    Enter a frame number, or 0 to exit
    1: fruit_avg(dat, "black")
    2: fruit_avg.R#5: rowMeans(mini_dat)
    Selection: 1
    Browse[1]> ls.str()
    cols : int 1
    dat : 'data.frame': 3 obs. of 4 variables:
    $ blackberry: int 4 9 6
    $ blueberry : int 1 2 8
    $ peach : int 59 150 10
    $ plum : int 30 78 5
    mini_dat : int [1:3] 4 9 6
    pattern : chr "black"

    View Slide

  57. base
    Enter a frame number, or 0 to exit
    1: fruit_avg(dat, "black")
    2: fruit_avg.R#5: rowMeans(mini_dat)
    Selection: 1
    Browse[1]> ls.str()
    cols : int 1
    dat : 'data.frame': 3 obs. of 4 variables:
    $ blackberry: int 4 9 6
    $ blueberry : int 1 2 8
    $ peach : int 59 150 10
    $ plum : int 30 78 5
    mini_dat : int [1:3] 4 9 6
    pattern : chr "black"

    View Slide

  58. base
    Enter a frame number, or 0 to exit
    1: fruit_avg(dat, "black")
    2: fruit_avg.R#5: rowMeans(mini_dat)
    Selection: 1
    Browse[1]> ls.str()
    cols : int 1
    dat : 'data.frame': 3 obs. of 4 variables:
    $ blackberry: int 4 9 6
    $ blueberry : int 1 2 8
    $ peach : int 59 150 10
    $ plum : int 30 78 5
    mini_dat : int [1:3] 4 9 6
    pattern : chr "black"

    View Slide

  59. RStudio
    Enter a frame number, or 0 to exit
    1: fruit_avg(dat, "black")
    2: fruit_avg.R#5: rowMeans(mini_dat)
    Selection: 1

    View Slide

  60. fruit_avg !<- function(dat, pattern) {
    cols !<- grep(pattern, names(dat))
    mini_dat !<- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }

    View Slide

  61. fruit_avg !<- function(dat, pattern) {
    browser()
    cols !<- grep(pattern, names(dat))
    mini_dat !<- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }

    View Slide

  62. fruit_avg !<- function(dat, pattern) {
    browser()
    cols !<- grep(pattern, names(dat))
    mini_dat !<- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }

    View Slide

  63. fruit_avg !<- function(dat, pattern) {
    browser()
    cols !<- grep(pattern, names(dat))
    mini_dat !<- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }
    debug(fruit_avg)

    View Slide

  64. fruit_avg <- function(dat, pattern) {
    browser()
    cols <- grep(pattern, names(dat))
    mini_dat <- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }
    debug(fruit_avg)
    debug(fruit_avg)
    fruit_avg <- function(dat, pattern) {
    browser()
    cols <- grep(pattern, names(dat))
    mini_dat <- dat[ , cols]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }

    View Slide

  65. next slide is a GIF of debugging with browser() inside RStudio

    View Slide

  66. View Slide

  67. fruit_avg !<- function(dat, pattern) {
    cols !<- grep(pattern, names(dat))
    mini_dat !<- dat[ , cols, drop = FALSE]
    message("Found ", ncol(mini_dat), " fruits!")
    rowMeans(mini_dat)
    }

    View Slide

  68. HELP!
    ‣ Q
    ‣ RStudio ! stop button
    ‣ debug() + undebug()
    ‣ debugonce()
    I’m stuck in
    browser() and I can’t
    get out!
    Poll at slido.com (event code = HEXAGON)

    View Slide

  69. DETER
    4

    View Slide

  70. FIX IT ONCE? KEEP IT FIXED

    View Slide

  71. ADD A TEST
    # https:!//github.com/OWNER/REPO/issues/666
    test_that("fruit_avg() works for 0, 1, !>=2 matches", {
    dat !<- data.frame(a = 1:2, ab = 3:4, row.names = c("one", "two"))
    expect_equal(fruit_avg(dat, "abc"), c(one = NaN, two = NaN))
    expect_equal(fruit_avg(dat, "ab"), c(one = 3, two = 4))
    expect_equal(fruit_avg(dat, "a"), c(one = 2, two = 3))
    })

    View Slide

  72. ADD AN ASSERTION
    dat !<- read.csv("fruit.csv")
    if (!all(vapply(dat, is.numeric, logical(1)))) {
    stop("All columns of `dat` must be numeric")
    }
    fruit_avg(dat, pattern = "berry")

    View Slide

  73. AUTOMATE YOUR CHECKS
    R CMD check
    testthat::test_check()

    View Slide

  74. RUN YOUR CHECKS ON THEIR MACHINE
    Continuous integration

    View Slide

  75. USE MIND BENDY STUFF IN MODERATION

    View Slide

  76. The major difference between a
    thing that might go wrong and a
    thing that cannot possibly go wrong
    is that when a thing that cannot
    possibly go wrong goes wrong it
    usually turns out to be impossible to
    get at and repair.
    Douglas Adams

    View Slide

  77. LEAVE ACCESS PANELS
    readxl + xls2csv, excelgesis
    httr::with_verbose()
    rlang::qq_show()
    options(future.debug = TRUE)
    options(gargle_quiet = FALSE)
    curl::handle_setopt(h, verbose = TRUE)
    options(internet.info = 0)

    View Slide

  78. WRITE ERROR MESSAGES FOR HUMANS
    dat !<- data.frame(x = 1, y = 2)
    df$x
    #> Error: object of type 'closure' is not subsettable

    View Slide

  79. WRITE ERROR MESSAGES FOR HUMANS
    dat !<- data.frame(x = 1, y = 2)
    df$x
    #> Error: object of type 'function' is not subsettable

    View Slide

  80. WRITE ERROR MESSAGES FOR HUMANS
    dat !<- data.frame(x = 1, y = 2)
    df$x
    #> Error: Can't subset a function.
    #> Have you forgotten to define a variable named `df`?

    View Slide

  81. library(dplyr)
    filter(iris, Species = "setosa")
    #> Error: `Species` (`Species = "setosa"`) must not be
    #> named, do you need `!==`?
    WRITE ERROR MESSAGES FOR HUMANS

    View Slide

  82. @jennybc
    @JennyBryan
    Jennifer Bryan
    RStudio
    rstd.io/debugging
    Turn it off and on again
    Make a reprex
    Dig into the error
    Plan for the unexpected
    Thanks:
    Tidyverse team
    Christine Kuper

    View Slide

  83. IMAGE SOURCES
    ‣ Fret: https:/
    /unsplash.com/photos/OsC8HauR0e0
    ‣ Do same thing again: https:/
    /unsplash.com/photos/uxUUENpp01I
    ‣ Diver: https:/
    /unsplash.com/photos/wVvxjiLJr-g
    ‣ Ocean horizon background: https:/
    /unsplash.com/photos/sYzFIusQp3Q
    ‣ Calm sea background: https:/
    /unsplash.com/photos/IZ01rjX0XQA
    ‣ Coral reef background: https:/
    /unsplash.com/photos/T1Wru10gKhg
    ‣ Seaweed background: https:/
    /unsplash.com/photos/nAkC-KS444M
    ‣ Orchid: https:/
    /unsplash.com/photos/Ug6z9PCwr58
    ‣ Corn field: https:/
    /unsplash.com/photos/nCQXxsSg3oo
    ‣ On/off key: https:/
    /unsplash.com/photos/cw_uvISXkCI

    View Slide

  84. IMAGE SOURCES CONTINUED
    ‣ Sunlight under water background: https:/
    /unsplash.com/photos/K785Da4A_JA
    ‣ Garnishing with sauce: https:/
    /unsplash.com/photos/YaiY50wzWzI
    ‣ death certificate (modified): Public Domain, https:/
    /commons.wikimedia.org/w/index.php?
    curid=214170
    ‣ washing pot: https:/
    /unsplash.com/photos/-VhH4S1Lur8
    ‣ The Night King: https:/
    /cnet4.cbsistatic.com/img/vugy5MvUVBvwcJf0JvKIBd1RwJE=/
    1200x675/2019/04/22/2b2fee8d-111a-4d19-ae83-4e61899cfd47/1nightking.jpg (probably copyright HBO)
    ‣ Autopsy painting by Rembrandt: https:/
    /www.mauritshuis.nl/en/explore/the-collection/artworks/
    the-anatomy-lesson-of-dr-nicolaes-tulp-146/detailgegevens/ Public Domain, https:/
    /
    commons.wikimedia.org/w/index.php?curid=64281722
    ‣ Gray cube abstract wallpaper vector art https:/
    /unsplash.com/photos/1CVy8JStf3A

    View Slide