-
Notifications
You must be signed in to change notification settings - Fork 3
/
07-funciones.Rmd
516 lines (394 loc) · 11.6 KB
/
07-funciones.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# Funciones e iteracion
> “To understand computations in R, two slogans are helpful:
> Everything that exists is an object.
> Everything that happens is a function call."
> — John Chambers
```{r, message=FALSE, echo = FALSE, include=FALSE}
knitr::opts_chunk$set(error = TRUE)
```
## Funciones {-}
En R todas las operaciones son producto de la llamada a una función, esto
incluye operaciones como `+`, operadores que controlan flujo como `for`, `if` y
`while`, e incluso operadores para obtener subconjuntos como `[ ]` y `$`.
```{r}
a <- 3
b <- 4
a + b
`+`(a, b)
for (i in 1:2) print(i)
`for`(i, 1:2, print(i))
```
Para escribir código eficiente y fácil de leer es importante saber escribir
funciones, se dice que si hiciste *copy-paste* de una sección de tu código 3
o más veces es momento de escribir una función.
Escribimos una función para calcular un promedio ponderado:
```{r}
wtd_mean <- function(x, wt = rep(1, length(x))) {
sum(x * wt) / sum(wt)
}
```
Notemos que esta función recibe hasta dos argumentos:
1. `x`: el vector a partir del cual calcularemos el promedio y
2. `wt`: un vector de *ponderadores* para cada componente del vector `x`.
Notemos además que al segundo argumento le asignamos un valor predeterminado,
esto implica que si no especificamos los ponderadores la función usará el
valor predeterminado y promediara con mismo peso a todas las componentes.
```{r}
wtd_mean(c(1:10))
wtd_mean(1:10, 10:1)
```
Veamos como escribir una función que reciba un vector y devuelva
el mismo vector centrado en cero.
* Comenzamos escribiendo el código para un caso particular, por ejemplo,
reescalando el vector $(0, 5, 10)$.
```{r}
vec <- c(0, 5, 10)
vec - mean(vec)
```
Una vez que lo probamos lo convertimos en función:
```{r}
center_vector <- function(vec) {
vec - mean(vec)
}
center_vector(c(0, 5, 10))
```
#### Ejercicio {-}
Escribe una función que reciba un vector y devuelva el mismo vector reescalado
al rango 0 a 1.
* Comienza escribiendo el código para un caso particular, por ejemplo,
empieza reescalando el vector `r vec <- c(0, 5, 10)`. Tip: la función
`range()` devuelve el rango de un vector.
### Estructura de una función {-}
Las funciones de R tienen tres partes:
1. El cuerpo: el código dentro de la función
```{r}
body(wtd_mean)
```
2. Los formales: la lista de argumentos que controlan como puedes llamar a la
función,
```{r}
formals(wtd_mean)
```
3. El ambiente: el _mapeo_ de la ubicación de las variables de la función.
```{r}
library(ggplot2)
environment(wtd_mean)
environment(ggplot)
```
Veamos mas ejemplos, ¿qué regresan las siguientes funciones?
```{r, eval = FALSE}
# 1
x <- 5
f <- function(){
y <- 10
c(x = x, y = y)
}
rm(x, f)
# 2
x <- 5
g <- function(){
x <- 20
y <- 10
c(x = x, y = y)
}
rm(x, g)
# 3
x <- 5
h <- function(){
y <- 10
i <- function(){
z <- 20
c(x = x, y = y, z = z)
}
i()
}
# 4 ¿qué ocurre si la corremos por segunda vez?
j <- function(){
if (!exists("a")){
a <- 5
} else{
a <- a + 1
}
print(a)
}
x <- 0
y <- 10
# 5 ¿qué regresa k()? ¿y k()()?
k <- function(){
x <- 1
function(){
y <- 2
x + y
}
}
```
Las reglas de búsqueda determinan como se busca el valor de una variable libre
en una función. A nivel lenguaje R usa _lexical scoping_, esto implica que en R
los valores de los símbolos se basan en como se anidan las funciones cuando
fueron creadas y no en como son llamadas.
```{r}
f <- function(x) {
x + y
}
```
```{r, error=TRUE}
f(2)
```
Si creamos el objeto `y`.
```{r}
y <- 1
f(2)
```
Como consecuencia de las reglas de búsqueda de R, todos los objetos deben ser
guardados en memoria y, si uno no es cuidadoso se pueden cometer errores
fácilmente.
```{r}
y <- 100
f(2)
```
### Observaciones del uso de funciones {-}
1. Cuando llamamos a una función podemos especificar los argumentos en base a
posición, nombre completo o nombre parcial:
```{r, error=TRUE}
f <- function(abcdef, bcde1, bcde2) {
c(a = abcdef, b1 = bcde1, b2 = bcde2)
}
# Posición
f(1, 2, 3)
f(2, 3, abcdef = 1)
# Podemos abreviar el nombre de los argumentos
f(2, 3, a = 1)
# Siempre y cuando la abreviación no sea ambigua
f(1, 3, b = 1)
```
2. Los argumentos de las funciones en R se evalúan conforme se necesitan (*lazy
evaluation*),
```{r}
f <- function(a, b){
a ^ 2
}
f(2)
```
La función anterior nunca utiliza el argumento _b_, de tal manera que _f(2)_
no produce ningún error.
3. Funciones con el mismo nombre en distintos paquetes:
La función `filter()` (incluida en R base) aplica un filtro lineal a una serie
de tiempo de una variable.
```{r}
x <- 1:100
filter(x, rep(1, 3))
```
Ahora cargamos `dplyr`.
```{r, error=TRUE}
library(dplyr)
filter(x, rep(1, 3))
```
R tiene un conflicto en la función a llamar, nosotros requerimos usar
`filter` de stats y no la función `filter` de `dplyr`. R utiliza por default
la función que pertenece al último paquete que se cargó.
```{r}
search()
```
Una opción es especificar el paquete en la llamada de la función:
```{r}
stats::filter(x, rep(1, 3))
```
Como alternativa surge el paquete [conflicted](https://github.com/r-lib/conflicted)
que alerta cuando hay conflictos y tiene funciones para especificar a que
paquete se desea dar preferencia en una sesión de R.
## Iteración {-}
En análisis de datos es común implementar rutinas iteraivas, esto es, cuando
debemos aplicar los mismos pasos a distintas entradas. Veremos que hay dos
paradigmas de iteración:
1. Programación imperativa: ciclos `for` y ciclos `while`.
2. Programación funcional: los ciclos se implmentan mediante funciones,
La ventaja de la programación imperativa es que hacen la iteración de manera
clara, sin embargo, veremos que una vez que nos familiarizamos con el paradigma
de programación funcional, resulta en código más fácil de mantener y menos
propenso a errores.
### Ciclos for {-}
Supongamos que tenemos una base de datos y queremos calcular la media de sus
columnas numéricas.
```{r}
df <- data.frame(id = 1:10, a = rnorm(10), b = rnorm(10, 2), c = rnorm(10, 3),
d = rnorm(10, 4))
df
```
Podemos crear el código para cada columna pero esto involucra *copy-paste* y
no será muy práctico si aumenta el número de columnas:
```{r}
mean(df$a)
mean(df$b)
mean(df$c)
mean(df$d)
```
Con un ciclo `for` sería:
```{r}
salida <- vector("double", 4)
for (i in 1:4) {
salida[[i]] <- mean(df[[i+1]])
}
salida
```
Los ciclos `for` tienen 3 componentes:
1. La salida: `salida <- vector("double", 4)`. Es importante especificar el
tamaño de la salida antes de iniciar el ciclo `for`, de lo contrario el código
puede ser muy lento.
2. La secuencia: determina sobre que será la iteración, la función `seq_along`
puede resultar útil.
```{r}
salida <- vector("double", 5)
for (i in seq_along(df)) {
salida[[i]] <- mean(df[[i]])
}
seq_along(df)
```
3. El cuerpo: `salida[[i]] <- mean(df[[i]])`, el código que calcula lo que nos
interesa sobre cada objeto en la iteración.
#### Ejercicio {-}
* Calcula el número de valores únicos en cada columna de los datos `iris`.
```{r}
head(iris)
```
* Recordando la limpieza de datos de la sección anterior en uno de los
últimos ejercicios leíamos archivos de manera iteativa, el vector `paths`
contenía la ruta a distintos archivos csv. Crea la tabla de datos final usando
un ciclo `for`.
```{r}
paths <- list.files("data/specdata", pattern = ".csv", full.names = TRUE)
```
### Programación funcional {-}
Ahora veremos como abordar iteración usando programación funcional.
Regresando al ejemplo inicial de calcular la media de las columnas de una
tabla de datos:
```{r}
salida <- vector("double", 4)
for (i in 1:4) {
salida[[i]] <- mean(df[[i+1]])
}
salida
```
Podemos crear una función que calcula la media de las columnas de un
`data.frame`:
```{r}
col_media <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- mean(df[[i]])
}
salida
}
col_media(df)
col_media(select(iris, -Species))
```
Y podemos extender a crear más funciones que describan los datos:
```{r}
col_mediana <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- median(df[[i]])
}
salida
}
col_sd <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- sd(df[[i]])
}
salida
}
```
Podemos hacer nuestro código más general y compacto escribiendo una función
que reciba los datos sobre los que queremos iterar y la función que queremos
aplicar:
```{r}
col_describe <- function(df, fun) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- fun(df[[i]])
}
salida
}
col_describe(df, median)
col_describe(df, mean)
```
Ahora utilizaremos esta idea de pasar funciones a funciones para eliminar los
ciclos `for`.
La iteración a través de funciones es muy común en R, hay funciones para hacer
esto en R base (`lapply()`, `apply()`, `sapply()`). Nosotros utilizaremos las
funciones del paquete `purrr`,
La familia de funciones del paquete iteran siempre sobre un vector (vector
atómico o lista), aplican una
función a cada parte y regresan un nuevo vector de la misma longitud que el
vector entrada. Cada función especifica en su nombre el tipo de salida:
* `map()` devuelve una lista.
* `map_lgl()` devuelve un vector lógico.
* `map_int()` devuelve un vector entero.
* `map_dbl()` devuelve un vector double.
* `map_chr()` devuelve un vector caracter.
En el ejemplo de las medias, `map` puede recibir un `data.frame` (lista de
vectores) y aplicará las funciones a las columnas del mismo.
```{r}
library(purrr)
map_dbl(df, mean)
map_dbl(select(iris, -Species), median)
```
Podemos crear un vector de tipo caracter que nos almacene el tipo de dato
almacenado en cada columna de la tabla iris.
```{r}
map_chr(iris, class)
```
Si queremos calcular el número de faltantes en cada columna podemos hacer:
```{r}
sum(is.na(airquality$Ozone))
map_int(airquality, function(x) sum(is.na(x)))
```
O utilizar la notación `.`:
```{r}
map_int(airquality, ~ sum(is.na(.)))
```
Usaremos `map` para ajustar un modelo lineal a subconjuntos de los datos
`mtcars` determinados por el cilindraje del motor.
```{r}
models <- mtcars |>
group_split(cyl) |>
map(function(df) lm(mpg ~ wt, data = df))
```
Podemos usar la notación `.` para hacer código más corto:
```{r}
models <- mtcars |>
group_split(cyl) |>
map(~lm(mpg ~ wt, data = .))
models
```
Usemos `map_**` para unir tablas de datos que están almacenadas en múltiples
archivos csv.
```{r}
names(paths) <- basename(paths)
specdata_us_vec <- map(paths, ~readr::read_csv(., col_types = "Tddi"),
.id = "filename")
specdata_us_vec[[2]]
class(specdata_us_vec)
```
En este caso es más apropiado usar map_df
```{r}
specdata_us <- map_df(paths, ~readr::read_csv(., col_types = "Tddi"),
.id = "filename")
```
#### Ejercicio {-}
* Usa la función `map_**` para calcular el número de valores únicos en las
columnas de `iris`.
* Usa la función `map_**` para extraer el coeficiete de la variable `wt` para
cada modelo:
```{r}
models[[1]]$coefficients[2]
```
* Utiliza map_*** para crear un vector con el valor mínimo de cada columna
de los datos `airquality`. ¿Cómo eliminas los faltantes?
* Con el siguiente código agregamos una variable categórica de velocidad del
aire a los datos `airqualuty`, para cada categoría ajusta un modelo lineal
(con la función `lm`) para explorar la relación entre ozono y radiación solar.
```{r}
airquality$Wind.cat <- cut(airquality$Wind, breaks = c(1.5, 9, 12, 21))
```
Puedes descargar un acordeón de purrr [aquí](https://rstudio.github.io/cheatsheets/purrr.pdf).