class: center, middle, inverse, title-slide #
R II: Programming
##
Loops, Functions, & More
###
Michael Clark
###
m-clark.github.io
@statsdatasci
CSCAR, UM
###
2020-10-13
--- class: inverse background-image: url(https://github.com/m-clark/m-clark.github.io/raw/master/img/Rlogo.svg)
--- class: inverse middle center ### *Basics* ### *Iterative Programming* ### *Writing Functions* ### *More* --- class: inverse middle center animated rollIn rollOut # https://animate.style/ # Basics <br> <br> <i class="fas fa-laptop-code fa-5x"></i> --- class: inverse center # Basics: Objects -- <br> ## What is an <span class="" style = "text-shadow: 0 0 50px #EBA42BFF">*object*</span>? -- ## Anything! -- <div class="" style = "font-size:20pt"> data frames<br> lists<br> vectors<br> matrices<br> strings<br> plots </div> --- class: inverse # Basics: Objects Everything in R revolves around the objects we create. ```r my_value = 'M' my_vector = rnorm(100) my_df = data.frame(A = rnorm(100), B = runif(100)) my_plot = qplot(A, B, data = my_df) my_list = list( my_value, my_vector, my_df, my_plot ) ``` --- class: inverse # Basics: Objects How do we understand them? -- ```r library(tidyverse) glimpse(diamonds) ``` ``` Rows: 53,940 Columns: 10 $ carat <dbl> 0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23, 0.30, 0.23, 0.22, 0.31, 0.20, 0.32, 0.30, 0.30, 0.30, 0.30, 0.30, 0.23, 0.23, 0.31, 0.31, 0.23, 0.24, 0.30, 0.23, 0.23, 0.23, 0.23, 0.23, 0.2… $ cut <ord> Ideal, Premium, Good, Premium, Good, Very Good, Very Good, Very Good, Fair, Very Good, Good, Ideal, Premium, Ideal, Premium, Premium, Ideal, Good, Good, Very Good, Good, Very Good, Very Good, Very Good… $ color <ord> E, E, E, I, J, J, I, H, E, H, J, J, F, J, E, E, I, J, J, J, I, E, H, J, J, G, I, J, D, F, F, F, E, E, D, F, E, H, D, I, I, J, D, D, H, F, H, H, E, H, F, G, I, E, D, I, J, I, I, I, I, D, D, D, I, G, I, … $ clarity <ord> SI2, SI1, VS1, VS2, SI2, VVS2, VVS1, SI1, VS2, VS1, SI1, VS1, SI1, SI2, SI2, I1, SI2, SI1, SI1, SI1, SI2, VS2, VS1, SI1, SI1, VVS2, VS1, VS2, VS2, VS1, VS1, VS1, VS1, VS1, VS1, VS1, VS1, SI1, VS2, SI2,… $ depth <dbl> 61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4, 64.0, 62.8, 60.4, 62.2, 60.2, 60.9, 62.0, 63.4, 63.8, 62.7, 63.3, 63.8, 61.0, 59.4, 58.1, 60.4, 62.5, 62.2, 60.5, 60.9, 60.0, 59.8, 60.7, 59.… $ table <dbl> 55.0, 61.0, 65.0, 58.0, 58.0, 57.0, 57.0, 55.0, 61.0, 61.0, 55.0, 56.0, 61.0, 54.0, 62.0, 58.0, 54.0, 54.0, 56.0, 59.0, 56.0, 55.0, 57.0, 62.0, 62.0, 58.0, 57.0, 57.0, 61.0, 57.0, 57.0, 57.0, 59.0, 58.… $ price <int> 326, 326, 327, 334, 335, 336, 336, 337, 337, 338, 339, 340, 342, 344, 345, 345, 348, 351, 351, 351, 351, 352, 353, 353, 353, 354, 355, 357, 357, 357, 402, 402, 402, 402, 402, 402, 402, 402, 403, 403, 4… $ x <dbl> 3.95, 3.89, 4.05, 4.20, 4.34, 3.94, 3.95, 4.07, 3.87, 4.00, 4.25, 3.93, 3.88, 4.35, 3.79, 4.38, 4.31, 4.23, 4.23, 4.21, 4.26, 3.85, 3.94, 4.39, 4.44, 3.97, 3.97, 4.28, 3.96, 3.96, 4.00, 4.04, 3.97, 4.0… $ y <dbl> 3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05, 4.28, 3.90, 3.84, 4.37, 3.75, 4.42, 4.34, 4.29, 4.26, 4.27, 4.30, 3.92, 3.96, 4.43, 4.47, 4.01, 3.94, 4.30, 3.97, 3.99, 4.03, 4.06, 4.01, 4.0… $ z <dbl> 2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39, 2.73, 2.46, 2.33, 2.71, 2.27, 2.68, 2.68, 2.70, 2.71, 2.66, 2.71, 2.48, 2.41, 2.62, 2.59, 2.41, 2.47, 2.67, 2.40, 2.42, 2.41, 2.42, 2.42, 2.4… ``` --- class: inverse # Basics: Objects ```r lm_mod = lm(mpg ~ ., data=mtcars) str(lm_mod, 1) # works for functions too! try str(lm) ``` ``` List of 13 $ coefficients : Named num [1:12] 17.8198 -1.6603 1.6374 0.0139 -0.0461 ... ..- attr(*, "names")= chr [1:12] "(Intercept)" "cyl6" "cyl8" "disp" ... $ residuals : Named num [1:32] -0.802 -0.193 -3.473 1.755 0.995 ... ..- attr(*, "names")= chr [1:32] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710" "Hornet 4 Drive" ... $ effects : Named num [1:32] -113.65 1.04 -28.7 7.59 -4.3 ... ..- attr(*, "names")= chr [1:32] "(Intercept)" "cyl6" "cyl8" "disp" ... $ rank : int 12 $ fitted.values: Named num [1:32] 21.8 21.2 26.3 19.6 17.7 ... ..- attr(*, "names")= chr [1:32] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710" "Hornet 4 Drive" ... $ assign : int [1:12] 0 1 1 2 3 4 5 6 7 8 ... $ qr :List of 5 ..- attr(*, "class")= chr "qr" $ df.residual : int 20 $ contrasts :List of 1 $ xlevels :List of 1 $ call : language lm(formula = mpg ~ ., data = mtcars) $ terms :Classes 'terms', 'formula' language mpg ~ cyl + disp + hp + drat + wt + qsec + vs + am + gear + carb .. ..- attr(*, "variables")= language list(mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb) .. ..- attr(*, "factors")= int [1:11, 1:10] 0 1 0 0 0 0 0 0 0 0 ... .. .. ..- attr(*, "dimnames")=List of 2 .. ..- attr(*, "term.labels")= chr [1:10] "cyl" "disp" "hp" "drat" ... .. ..- attr(*, "order")= int [1:10] 1 1 1 1 1 1 1 1 1 1 .. ..- attr(*, "intercept")= int 1 .. ..- attr(*, "response")= int 1 .. ..- attr(*, ".Environment")=<environment: R_GlobalEnv> .. ..- attr(*, "predvars")= language list(mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb) .. ..- attr(*, "dataClasses")= Named chr [1:11] "numeric" "factor" "numeric" "numeric" ... .. .. ..- attr(*, "names")= chr [1:11] "mpg" "cyl" "disp" "hp" ... $ model :'data.frame': 32 obs. of 11 variables: ..- attr(*, "terms")=Classes 'terms', 'formula' language mpg ~ cyl + disp + hp + drat + wt + qsec + vs + am + gear + carb .. .. ..- attr(*, "variables")= language list(mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb) .. .. ..- attr(*, "factors")= int [1:11, 1:10] 0 1 0 0 0 0 0 0 0 0 ... .. .. .. ..- attr(*, "dimnames")=List of 2 .. .. ..- attr(*, "term.labels")= chr [1:10] "cyl" "disp" "hp" "drat" ... .. .. ..- attr(*, "order")= int [1:10] 1 1 1 1 1 1 1 1 1 1 .. .. ..- attr(*, "intercept")= int 1 .. .. ..- attr(*, "response")= int 1 .. .. ..- attr(*, ".Environment")=<environment: R_GlobalEnv> .. .. ..- attr(*, "predvars")= language list(mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb) .. .. ..- attr(*, "dataClasses")= Named chr [1:11] "numeric" "factor" "numeric" "numeric" ... .. .. .. ..- attr(*, "names")= chr [1:11] "mpg" "cyl" "disp" "hp" ... - attr(*, "class")= chr "lm" ``` --- class: inverse # Basics: Objects *Classes* tell us the kind of object we're dealing with. *Methods* work on different classes. ```r class(diamonds) # data.frame summary(diamonds) class(diamonds$clarity) # ordered factor summary(diamonds$clarity) class(lm_mod) # lm summary(lm_mod) ``` --- class: inverse # Basics: Objects ```r methods('summary') ``` ``` [1] summary,ANY-method summary,DBIObject-method summary,diagonalMatrix-method summary,sparseMatrix-method summary.aov summary.aovlist* [7] summary.aspell* summary.bench_mark* summary.check_packages_in_dir* summary.connection summary.corAR1* summary.corARMA* [13] summary.corCAR1* summary.corCompSymm* summary.corExp* summary.corGaus* summary.corIdent* summary.corLin* [19] summary.corNatural* summary.corRatio* summary.corSpher* summary.corStruct* summary.corSymm* summary.data.frame [25] summary.Date summary.default summary.Duration* summary.ecdf* summary.factor summary.gam [31] summary.ggplot* summary.glm summary.gls* summary.haven_labelled* summary.hcl_palettes* summary.infl* [37] summary.Interval* summary.lm summary.lme* summary.lmList* summary.loess* summary.manova [43] summary.matrix summary.mlm* summary.modelStruct* summary.nls* summary.nlsList* summary.packageStatus* [49] summary.pdBlocked* summary.pdCompSymm* summary.pdDiag* summary.pdIdent* summary.pdIdnot* summary.pdLogChol* [55] summary.pdMat* summary.pdNatural* summary.pdSymm* summary.pdTens* summary.Period* summary.POSIXct [61] summary.POSIXlt summary.ppr* summary.prcomp* summary.princomp* summary.proc_time summary.reStruct* [67] summary.rlang_error* summary.rlang_trace* summary.shingle* summary.srcfile summary.srcref summary.stepfun [73] summary.stl* summary.table summary.trellis* summary.tukeysmooth* summary.varComb* summary.varConstPower* [79] summary.varExp* summary.varFixed* summary.varFunc* summary.varIdent* summary.varPower* summary.vctrs_sclr* [85] summary.vctrs_vctr* summary.warnings see '?methods' for accessing help and source code ``` --- class: inverse # Basics: Documentation Use the <span class="func" style = "">sample</span> function to get a random sample of 10 values from the numbers 1 to 5. ```r sample(?) ``` -- Don't know what to put? -- Consult the help file! --- class: inverse # Basics: Documentation  --- class: inverse # Basics: Exercises With one function, find out what the class, number of rows, number of columns are of the following object, including what kind of object the last three columns are. Inspect the help file also. ```r library(dplyr) ?starwars ``` --- class: inverse middle center animated zoomIn rollOut # https://animate.style/ # Iterative Programming <br> <br> <i class="fas fa-sync-alt fa-5x"></i> --- class: inverse # Iterative Programming: For Loops Do you do something like this? ```r means1 = mean(df$x) means2 = mean(df$y) means3 = mean(df$z) means4 = mean(df$q) ``` -- <i class="far fa-times-circle fa-9x" style="position: relative; top: -200px; height: -500px; display: block"></i> --- class: inverse # Iterative Programming: For Loops Write a *loop* instead! -- ```r for (column in c('x','y','z','q')) { mean(df[[column]]) } ``` What's going on here? -- We've created an iterative process in which: -- *for* every *element* in `c('x','y','z','q')`... -- Do something! --- class: inverse # Iterative Programming: For Loops ```r weather = nycflights13::weather for (column in c('temp', 'humid', 'wind_speed', 'precip')) { print(mean(weather[[column]], na.rm = TRUE)) } ``` ``` [1] 55.26039 [1] 62.53006 [1] 10.51749 [1] 0.004469079 ``` --- class: inverse # Iterative Programming: For Loops We can set things up before the loop - make changes more easily - slight speed gain ```r weather = nycflights13::weather cols = c('temp', 'humid', 'wind_speed', 'precip') result = vector('list', length(cols)) names(result) = cols for (column in cols) { result[[column]] = mean(weather[[column]], na.rm = TRUE) } result ``` ``` $temp [1] 55.26039 $humid [1] 62.53006 $wind_speed [1] 10.51749 $precip [1] 0.004469079 ``` --- class: inverse # Iterative Programming: For Loops Using *while* is exactly the same. Useful when you have a specific check - a *condition* ```r columns = c('temp','humid','wind_speed', 'visib', 'pressure') nyc_means = c() i = 1 while (i <= length(columns)) { nyc_means[i] = mean(weather[[columns[i]]], na.rm = TRUE) i = i + 1 } nyc_means %>% round(2) ``` ``` [1] 55.26 62.53 10.52 9.26 1017.90 ``` --- class: inverse # Iterative Programming: Implicit Loops We can loop without `for` or `while`! - <span class="func" style = "">apply</span> family - <span class="func" style = "">purrr</span> family --- class: inverse # Iterative Programming: Implicit Loops Why? - Cleaner/simpler code - Environment clear of unnecessary objects - Potentially more reproducible - more likely to use generalizable functions - Parallelizable --- class: inverse # Iterative Programming: Implicit Loops *apply* - arrays, matrices, data.frames *lapply*, *sapply*, *vapply* - lists, data.frames, vectors *tapply* - grouped operations (table *apply*) *mapply* - multivariate version of *sapply* *replicate* - performs an operation N times --- class: inverse # Iterative Programming: Implicit Loops How do they work? ```r columns = c('temp','humid','wind_speed', 'visib', 'pressure') apply(weather[columns], 2, mean, na.rm=T) # 2 for columns, 1 for rows ``` ``` temp humid wind_speed visib pressure 55.260392 62.530059 10.517488 9.255372 1017.898751 ``` ```r my_list = list(1, 1:10, 1:20) lapply(my_list, sum) ``` ``` [[1]] [1] 1 [[2]] [1] 55 [[3]] [1] 210 ``` --- class: inverse # Iterative Programming: Implicit Loops How do they work? Find a function, **apply** it to an object! ```r my_list = list(1:3, 4:6, 7:9) sapply(my_list, sum) # lapply that simplifies if possible ``` ``` [1] 6 15 24 ``` ```r replicate(2, rnorm(3)) # also simplifies if possible ``` ``` [,1] [,2] [1,] -0.6381753 -0.1583511 [2,] 0.1686866 0.6102414 [3,] 0.4510163 1.8485468 ``` --- class: inverse # Iterative Programming: Implicit Loops purrr is best with *data.frame* or *list* objects - works the same way in spirit as **apply*. - more control over output - more strict about input - can be frustrating! --- class: inverse # Iterative Programming: Implicit Loops ```r map(my_list, sum) ``` ``` [[1]] [1] 6 [[2]] [1] 15 [[3]] [1] 24 ``` ```r map_int(my_list, sum) ``` ``` [1] 6 15 24 ``` ```r map_df(my_list, ~data.frame(x = sum(.))) ``` ``` x 1 6 2 15 3 24 ``` --- class: inverse # Iterative Programming: Implicit Loops ```r diamonds %>% map_at( vars(carat, depth, price), function(x) x > median(x) ) %>% as_tibble() ``` ``` # A tibble: 53,940 x 10 carat cut color clarity depth table price x y z <lgl> <ord> <ord> <ord> <lgl> <dbl> <lgl> <dbl> <dbl> <dbl> 1 FALSE Ideal E SI2 FALSE 55 FALSE 3.95 3.98 2.43 2 FALSE Premium E SI1 FALSE 61 FALSE 3.89 3.84 2.31 3 FALSE Good E VS1 FALSE 65 FALSE 4.05 4.07 2.31 4 FALSE Premium I VS2 TRUE 58 FALSE 4.2 4.23 2.63 5 FALSE Good J SI2 TRUE 58 FALSE 4.34 4.35 2.75 6 FALSE Very Good J VVS2 TRUE 57 FALSE 3.94 3.96 2.48 7 FALSE Very Good I VVS1 TRUE 57 FALSE 3.95 3.98 2.47 8 FALSE Very Good H SI1 TRUE 55 FALSE 4.07 4.11 2.53 9 FALSE Fair E VS2 TRUE 61 FALSE 3.87 3.78 2.49 10 FALSE Very Good H VS1 FALSE 61 FALSE 4 4.05 2.39 # … with 53,930 more rows ``` --- class: inverse # Iterative Programming: Implicit Loops Faster implicit loops: parallelization! - <span class="pack" style = "">parallel</span> package in base R - <span class="func" style = "">apply</span> family - <span class="func" style = "">parApply</span>, <span class="func" style = "">parLapply</span>, etc. - <span class="pack" style = "">furrr</span> package - <span class="pack" style = "">purrr</span> functions - requires <span class="pack" style = "">futures</span> package - <span class="func" style = "">future_map*</span> functions --- class: inverse slide-title-90 # Iterative Programming: Looping with Lists <div style="text-align: center;"> <i class="fa fa-list-ol fa-3x"></i> </div> <br> Lists are easy to loop over! And we can put anything in them! --- class: inverse slide-title-90 # Iterative Programming: Looping with Lists Here we create models of varying complexity. ```r library(mgcv) # for gam mtcars$cyl = factor(mtcars$cyl) mod_lm = lm(mpg ~ wt, data = mtcars) mod_poly = lm(mpg ~ poly(wt, 2), data = mtcars) mod_inter = lm(mpg ~ wt * cyl, data = mtcars) mod_gam = gam(mpg ~ s(wt), data = mtcars) mod_gam_inter = gam(mpg ~ cyl + s(wt, by = cyl), data = mtcars) ``` --- class: inverse slide-title-90 # Iterative Programming: Looping with Lists ```r model_list = list( lm = mod_lm, poly = mod_poly, inter = mod_inter, gam = mod_gam, gam_inter = mod_gam_inter ) # lowest wins model_list %>% map_df(AIC) %>% sort() ``` ``` # A tibble: 1 x 5 gam_inter inter poly gam lm <dbl> <dbl> <dbl> <dbl> <dbl> 1 151. 155. 158. 159. 166. ``` --- class: inverse # Iterative Programming: Exercises Exercise 1 With the following matrix, use apply and the sum function to get row or column sums of the matrix x. ```r x = matrix(1:9, 3, 3) ``` --- class: inverse # Iterative Programming: Exercises Exercise 2 Use a map function to create a data frame of the column means. See `?map` to see all your options. ```r d = tibble( x = rnorm(100), y = rnorm(100, 10, 2), z = rnorm(100, 50, 10), ) ``` --- class: inverse middle center animated bounceInDown rollOut # https://animate.style/ # Functions <br> <br> <i class="fa fa-terminal fa-5x"></i> --- class: inverse # Functions: Starting Out What's a function? <div style='background-color: #fff; text-align: center'> <img src="img/function.svg" style="display:block; margin: 0 auto; width: 33%"> </div> Scary?! --- class: inverse # Functions: Starting Out Not really! ```r square_it = function(x) x^2 add_one = function(x) x + 1 3 %>% square_it() %>% add_one() ``` ``` [1] 10 ``` --- class: inverse # Functions: Starting Out You use functions all the time. Why not make your own? Key idea: *reusable code* --- class: inverse # Functions: Starting Out ```r mean(myvar) sd(myvar) sum(is.na(myvar)) ``` Let's create a function that would do this. -- And spit out a usable data frame! -- ```r my_summary <- function(x) { data.frame( mean = mean(x), sd = sd(x), N_missing = sum(is.na(x)) ) } ``` --- class: inverse # Functions: Starting Out ```r my_summary <- function(x) { data.frame( mean = mean(x), sd = sd(x), N_missing = sum(is.na(x)) ) } ``` `x` is an arbitrary name for an input. - In R, these are called *arguments* - These inputs will determine the output --- class: inverse # Functions: Starting Out Typically data has missing values. ```r load('data/gapminder.RData') ``` ```r my_summary(gapminder_2019$lifeExp) ``` ``` mean sd N_missing 1 NA NA 516 ``` -- Now the function doesn't do what we want. 😢 -- Let's fix this! 😀 --- class: inverse # Functions: Starting Out We have a function, so we can easily modify it! Add an argument to deal with missing values. ```r my_summary_na <- function(x, remove_na = TRUE) { data.frame( mean = mean(x, na.rm = remove_na), sd = sd(x, na.rm = remove_na), N_missing = sum(is.na(x)) ) } my_summary_na(gapminder_2019$lifeExp) ``` ``` mean sd N_missing 1 43.13218 16.31355 516 ``` --- class: inverse # Functions: DRY *<div class="" style = "text-align: center">Don't Repeat Yourself!</div>* We would like more efficiency where possible. A good rule of thumb is: > If you are writing the same set of code more than twice, you should write a function to do it instead. --- class: inverse # Functions: DRY ```r good_mileage_displ_low_cyl_4 = if_else(cyl == 4 & displ < mean(displ) & hwy > 30, 'yes', 'no') good_mileage_displ_low_cyl_6 = if_else(cyl == 6 & displ < mean(displ) & hwy > 30, 'yes', 'no') good_mileage_displ_low_cyl_8 = if_else(cyl == 8 & displ < mean(displ) & hwy > 30, 'yes', 'no') good_mileage_displ_high_cyl_4 = if_else(cyl == 4 & displ > mean(displ) & hwy > 30, 'yes', 'no') good_mileage_displ_high_cyl_6 = if_else(cyl == 6 & displ > mean(displ) & hwy > 30, 'yes', 'no') good_mileage_displ_high_cyl_8 = if_else(cyl == 8 & displ > mean(displ) & hwy > 30, 'yes', 'no') ``` Problems? --- class: inverse code-only ```r good_mileage <- function( cylinder = 4, mpg_cutoff = 30, displ_fun = mean, displ_low = TRUE, cls = 'compact' ) { if (displ_low == TRUE) { # condition to check, if it holds, result <- mpg %>% # filter data given the arguments filter( cyl == cylinder, * displ <= displ_fun(displ), hwy >= mpg_cutoff, class == cls ) } else { # if the condition doesn't hold, filter result <- mpg %>% # the data this way instead filter( cyl == cylinder, * displ >= displ_fun(displ), hwy >= mpg_cutoff, class == cls ) } result # return the object } ``` --- class: inverse # Functions: DRY So what's going on here? Not a whole lot really. The function just filters the data to observations that match the input criteria, and returns that result at the end. We also put *default values* to the arguments, which can be done at your discretion. --- class: inverse # Functions: Conditionals The core of that function uses a *conditional statement* - using standard <span class="func" style = "">if...else</span> structure. The <span class="func" style = "">if</span>: determines whether a condition holds. -- If so... - proceed to the next step in the brackets. -- If not... - skip to the <span class="func" style = "">else</span> part. --- class: inverse # Functions: Conditionals .pull-left[ ```r function (x, y) { if (x == 'cat') { ... # do something to y } else { ... # do something else } } ``` ] .pull-right[ ```r function (x, y) { if (x == 'cat') { ... } else if (x == 'otter') { ... } else if ( x == 'puppy' & y > 5 ) { ... } . . . else { } } ``` ] --- class: inverse # Functions: Conditionals At this point we have a nice function that is: - *flexible* - *reusable* - *extensible* - *efficient* How would you add an argument to filter based on year? --- class: inverse code-only ```r good_mileage <- function( cylinder = 4, mpg_cutoff = 30, displ_fun = mean, displ_low = TRUE, cls = 'compact', year = 2008 ) { if (displ_low == TRUE) { result <- mpg %>% filter( cyl == cylinder, displ <= displ_fun(displ), hwy >= mpg_cutoff, class == cls, * year == year ) } . . . } ``` --- class: inverse # Functions: Anonymous Functions Sometimes we just need a quick and easy function - one-off application - especially when using <span class="func" style = "">apply</span>/<span class="func" style = "">map</span> ```r ctyhwy = select(mpg, cty, hwy) map_dbl(ctyhwy, sd) ``` ``` cty hwy 4.255946 5.954643 ``` ```r map_dbl(ctyhwy, function(x) mean(x) / 2) ``` ``` cty hwy 8.429487 11.720085 ``` --- class: inverse # Functions: Anonymous Functions The second function doesn't exist as an independent R object - *Anonymous functions* - Sometimes called *lambda functions* But it works just the same. Try this one! ```r # some variables have a mad = 0, and so return Inf (x/0) or NaN (0/0) mtcars %>% map_df(function(x) (x - median(x))/mad(x)) ``` --- class: inverse # Functions: Exercises ### Excercise 1 Write a function that takes the log of the sum of two values (i.e. just two single numbers) using the <span class="func">log</span> function. Just remember that within a function, you can write R code just like you normally would. ```r log_sum <- function(a, b) { ? } ``` --- class: inverse # Functions: Exercises ### Excercise 1b What happens if the sum of the two numbers is negative? You can't take a log of a negative value, so it's an error. How might we deal with this? Try using a conditional to provide an error message using the <span class="func">stop</span> function. The first part is basically identical to the function you just did. But given that result, you will need to check for whether it is negative or not. The message can be whatever you want. ```r log_sum <- function(a, b) { ? if (? < 0) { stop('Your message here.') } else { ? return(your_log_sum_results) # this is an arbitrary name, change accordingly } } ``` --- class: inverse # Functions: Exercises ### Exercise 2 Let's write a function that will take a numeric variable and convert it to a character string of 'positive' vs. 'negative'. We can use `if {}... else {}` structure. - Input: a single vector of numbers - Output: will recode any negative value to 'negative' and positive values to 'positive'. --- class: inverse # Functions: Exercises ### Exercise 2 Here is an example of how we would just do it as a one-off. ```r set.seed(123) # so you get the exact same 'random' result x <- rnorm(10) if_else(x < 0, "negative", "positive") ``` Now try your hand at writing a function for that. ```r pos_neg <- function(?) { ? } ``` --- class: inverse middle center animated bounceInUp fadeOutUpBig # https://animate.style/ # More <br> <div class="" style = "text-align: center"><i class="fab fa-r-project fa-5x"></i></div> --- class: inverse # More: Code Style .pull-left[ Think about... - Why does your code exist? - Consistency - Code length - Spacing - Naming ] .pull-right[ <br> <br> <img src="img/monkey_thinking.jpg" style="display:block; margin: 0 auto; width: 100%"> ] --- class: inverse # More: Code Style These are actually difficult problems No easy solutions Practice --- class: inverse # More: Vectorization Compare: ```r mymatrix = matrix(runif(100), 10, 10) mymatrix_log = log(mymatrix) identical(apply(mymatrix, 2, log), log(mymatrix)) ``` ``` [1] TRUE ``` ```r res = bench::mark(loop = apply(mymatrix, 2, log), vector = log(mymatrix), time_unit = 'us') select(res, expression, median) # microseconds ``` ``` # A tibble: 2 x 2 expression median <bch:expr> <dbl> 1 loop 41.9 2 vector 0.999 ``` --- class: inverse # More: Vectorization The whole vector is considered, rather than each element individually. Preprocessing is done once rather than the n iterations of the loop. --- class: inverse # More: Regular Expressions *Regular expression* (regex for short) > A sequence of characters that can be used as a search pattern for a string. Common operations are to merely detect, extract, or replace the matching string Different flavors for different languages ```r pattern = '^r.*shiny[0-9]$' string = c('r is the shiny', 'r is the shiny1', 'r shines brightly') str_detect(string, pattern) ``` ``` [1] FALSE TRUE FALSE ``` --- class: inverse # More: Regular Expressions What is that gibberish? `'^r.*shiny[0-9]$'` - **^** : starts with, so `^r` means starts with r - **.** : any character (wild card) - ***** : match the preceding zero or more times - **shiny** : match 'shiny' - *[0-9]* : any digit 0-9 (note that we are still talking about strings, not actual numbered values) - *$* : ends with preceding --- class: inverse # More: Regular Expressions - **[a-z]** : letters a-z - **[A-Z]** : capital letters - **\+** : match the preceding one or more times - **()** : groupings - **|** : logical or - [a-z]|[0-9] (a lower case letter or a number) - **?** : preceding item is optional, and will be matched at most once. - 'look ahead' and 'look behind' - **\** : escape a character, - want to search for a period instead of using it as a regex pattern, you'd use \. - R needs \\\, i.e. double slashes, for escape. --- class: inverse # More: Regular Expressions Regular expression patterns are difficult to learn at best. But... -- Every question has been asked on StackOverflow. 😄 --- class: inverse # More: Code Style Exercises ### Exercise 1 For the following model related output, come up with a name for each object. ```r lm(hwy ~ cyl, data = mpg) # hwy mileage predicted by number of cylinders summary(lm(hwy ~ cyl, data = mpg)) # the summary of that lm(hwy ~ cyl + displ + year, data = mpg) # an extension of that ``` --- class: inverse # More: Code Style Exercises ### Exercise 2 Fix this code. ```r x=rnorm(100, 10, 2) y=.2* x+ rnorm(100) data = data.frame(x,y) q = lm(y~x, data=data) summary(q) ``` --- class: inverse # More: Vectorization Exercises ### Exercise 1 Show a non-vectorized (e.g. a loop) and a vectorized way to add a two to the numbers 1 through 3. --- class: inverse # More: Vectorization Exercises ### Exercise 2 <span class="func" style = "">colSums</span> vs. <span class="func" style = "">apply</span>, which is faster? - Test it with the <span class="pack" style = "">bench</span> package. ```r x = matrix(rpois(100000, lambda = 5), ncol = 100) bench::mark( cs = colSums(x), app = apply(x, 2, sum), time_unit = 'ms' ) ``` --- class: inverse # More: Regex Exercises ### Exercise 1 Using <span class="pack" style = "">stringr</span> and <span class="func" style = "">str_replace</span>, replace all the states a's with nothing. ```r library(stringr) str_replace(state.name, pattern = ?, replacement = ?) ``` --- class: last-slide, inverse, center, middle 