---
title: "Functional programming"
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, comment = NA)
```



```{r}
f <- function() {
  return(3)
}
f

x <- 4
x

f()

g <- function(y) {
  2*y
}
g
y
g()
g(y = 3)
g(3)


h <- function(y = 1, z = 2) {
  return(y^z)
}
h()
h(1, 2)
h(z = 2, y = 1)
h(2, 1)


calc_stat <- function(x, f) {
  f(x)
}
calc_stat
calc_stat(1:10, sqrt)

#calc_stat(abs, sqrt) # sqrt(abs)
calc_stat(calc_stat(-(1:10), abs), sqrt)
```

```{r}
g <- function(f) {
  f
}
g
g(sqrt)(1:10)

g <- function(y) {
  function() {
    4*y
  }
}
h <- g(1:10)
h
h()
```





# Functions are first class citizens

```{r}
sqrt(9)
sqrt(c(9, 16))
```

```{r}
sqrt
abs
function(x) x + 2
sqrt(9)
f <- function(x) x + 2
f(9)
(function(x) x + 2)(9)
```


```{r}
f <- function(x) {
  if (x == 1L) {
    return(NA)
  }
  
  return(5)
}
f(4)
f(1)

```



```{r}
do_calc <- function(f, x) {
  f(x)
}
do_calc(sqrt, 9)
do_calc(sqrt, c(9, 16))
do_calc(abs, -9)
do_calc(function(x) x + 2, 9)
```

# Map functions

```{r}
library(tidyverse)
theme_set(theme_bw())
```

Based on package `purrr` (R4DS, Chapter 21 - latter part).

Workhorses in `purrr`:

* `map()` makes a list.
* `map_lgl()` makes a logical vector.
* `map_int()` makes an integer vector.
* `map_dbl()` makes a double vector.
* `map_chr()` makes a character vector.

```{r}
c(9, 16, 25)
map(c(9, 16, 25), sqrt)
map_dbl(c(9, 16, 25), sqrt)
```

Each takes vector (or list) as input, applies a function to each element, 
and then returns a new vector (or list) with same length.

Type: determined by the suffix to the map function (`_int` etc.).

Usage not trivial!

**The important thing is that you solve the problem that you're working on, not write the most concise and elegant code (although that's definitely something you want to strive towards!).**

```{r,echo=FALSE,include=FALSE}
df <- tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)
```

To compute the column mean we may use the `map_dbl` as we expect a double in return. However there are 
many different ways to get the same results...

```{r, eval=1}
str(df)
is.list(df)

# base R:
apply(df, 2, mean)
colMeans(df)
sapply(df, mean)
lapply(df, mean)
unlist(lapply(df, mean))

# tidyverse + base R:
df %>% summarise_all(mean) %>% unlist()
df %>% summarise_if(is.numeric, mean) %>% unlist()

# purrr:
map_dbl(df, mean) 
```

`purrr::map*` functions: designed for `tidy` data and `tidyverse`:
Always take the data as first argument, they keep names, are fast, and are
consistent (type stable)
```{r}
# purrr:
df %>% map_dbl(median)
df %>% map(function(x) x + 2)
df %>% map(~ . + 2)
df %>% map(~ . + 2) %>% map_dbl(mean)
df %>% map_dbl(function(x) mean(x, trim = 0.3))
df %>% map_dbl(mean, trim = 0.3) # trim argument to mean(), gives trimmed mean
```

* `.` refers to the current list element

Generally for the formulas: `~ . + 2`, is converted to a function. There are three ways to refer to the arguments:

* For a single argument function, use `.`
* For a two argument function, use `.x` and `.y`
* For more arguments, use `..1`, `..2`, `..3` etc

Often `.x` is used:

```{r}
df %>% map(~ . + 2)
df %>% map(~ .x + 2)
all.equal(df %>% map(~ . + 2),
          df %>% map(~ .x + 2))
```


For consistency, see `sapply` that attempts to `simplify` the output to an array if dimensions allow.
```{r}
sapply(df, range) # base R
sapply(df, range, simplify = FALSE) # base R
lapply(df, range) # base R
df %>% map(range) # purrr
```

## Fitting models

Fit a model to each chunk of a dataset (chunks defined by specific levels of a variable). 

Here, we use the `split` function to construct a list of data where each chunk is defined by
`cyl`. We could do this as `split(mtcars, mtcars$cyl)`, however, the pipe allows us to do this by:
```{r}
models <- mtcars %>% 
  split(.$cyl) %>%  # `.` refers to the current list element
  map(~ lm(mpg ~ wt, data = .))
```
where `map` operates on each element in the list. 

```{r}
analysis_lm <- function(data) {
  fit <- lm(mpg ~ wt, data = data)
  fit
}
mtcars %>% filter(cyl == 6) %>% analysis_lm()

mtcars %>% 
  split(.$cyl) %>%  # `.` refers to the current list element
  map(analysis_lm)
```


Hence, we can operate on the `models` using `map` and `map_dbl()` to extract a numeric feature about the model.
```{r, eval = 2}
models %>% map(summary) %>% map_dbl(~ .$r.squared)
models %>% map(summary) %>% map_dbl("r.squared")
```
The naming refer to the `cyl` levels that was splitted on.

### Exercises

R4DS:
<https://r4ds.had.co.nz/iteration.html#the-map-functions>
21.5.3 Exercises

In the above, copy the chunk with `analysis_lm` and adapt it to return the $R^2$ values as a numeric vector (i.e. like the last chunk, but with an explicit function).


# Mapping over more than a single argument

## Mapping over two lists

Assume that we are to simulate from a normal distribution but with different combination of means and standard
deviations
$$
(\mu_1, \sigma_1) = (5, 1)\quad (\mu_2, \sigma_2) = (10, 5)\quad (\mu_3, \sigma_3) = (-3, 10)\quad
$$
Then we would have 
```{r}
mu <- c(5, 10, -3)
sigma <- c(1, 5, 10)
```

For this task, the `map2` is handy
```{r}
map2(mu, sigma, rnorm, n = 5)
```

![maps2 calls](lists-map2.png)

What happens if `mu` and `sigma` are of different lengths you ask? It fails!
```{r, error = TRUE}
map2(c(mu, mu), sigma, rnorm, n = 5)
```

### Mapping over multiple (more than two) lists

Then we use `pmap`, it is harder to get once head around and should be used with care as
```{r}
n <- c(1, 3, 5)
args1 <- list(n, mu, sigma)
args1
args1 %>% pmap(rnorm)
rnorm(args1[[1]][1], args1[[2]][1], args1[[3]][1])
rnorm(args1[[1]][2], args1[[2]][2], args1[[3]][2])
rnorm(args1[[1]][3], args1[[2]][3], args1[[3]][3])
```

In this case I would prefer to use a different construct
```{r}
tbl <- tibble(n = n, mu = mu, sigma = sigma)
tbl
tbl %>% 
  rowwise() %>% # needed to avoid unfornutate misunderstanding of vectorised functions
  mutate(norm = list(rnorm(n = n, mean = mu, sd = sigma))) %>% 
  pull(norm)
```

### Using different function for different parameters

If our problem suggests that different functions should be used for different parts of data or parameters,
we can use the `invoke_map` function. This allows looping over `fun` and `pars` at the same time
```{r}
f <- c("runif", "rnorm", "rpois") ## Uniform, Normal, Poisson distributions (sample from)

param <- list(
  list(min = -1, max = 1),        ## Parameters for uniform
  list(mean = -10, sd = 5),       ## Parameters for normal
  list(lambda = 10)               ## The single parameters for Poisson
)

str(param)

invoke_map(f, param, n = 5)
```

![invoke_map calls (as in book)](lists-invoke.png)


