-
Notifications
You must be signed in to change notification settings - Fork 1
/
Control-flow.qmd
370 lines (267 loc) · 10.4 KB
/
Control-flow.qmd
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
# Flujo de control {#sec-control-flow}
```{r, include = FALSE}
source("common.R")
```
## Introducción
Hay dos herramientas principales de flujo de control: opciones y bucles. Las opciones, como declaraciones `if` y llamadas `switch()`, le permiten ejecutar código diferente dependiendo de la entrada. Los bucles, como `for` y `while`, le permiten ejecutar código repetidamente, normalmente con opciones cambiantes. Espero que ya esté familiarizado con los conceptos básicos de estas funciones, por lo que cubriré brevemente algunos detalles técnicos y luego presentaré algunas características útiles, pero menos conocidas.
El sistema de condiciones (mensajes, advertencias y errores), del que aprenderá en el @sec-conditions, también proporciona un flujo de control no local.
### Prueba {.unnumbered}
¿Quieres saltarte este capítulo? Anímate, si puedes responder las siguientes preguntas. Encuentre las respuestas al final del capítulo en la @sec-control-flow-answers.
- ¿Cuál es la diferencia entre `if` y `ifelse()`?
- En el siguiente código, ¿cuál será el valor de `y` si `x` es `TRUE`? ¿Qué pasa si `x` es `FALSE`? ¿Qué pasa si `x` es `NA`?
```{r, eval = FALSE}
y <- if (x) 3
```
- ¿Qué devuelve `switch("x", x = , y = 2, z = 3)`?
### Estructura {.unnumbered}
- La @sec-choices se sumerge en los detalles de `if`, luego analiza los parientes cercanos `ifelse()` y `switch()`.
- La @sec-loops comienza recordándole la estructura básica del bucle for en R, analiza algunos errores comunes y luego habla sobre las declaraciones `while` y `repeat` relacionadas.
## Opciones {#sec-choices}
\index{if}
La forma básica de una sentencia if en R es la siguiente:
```{r, eval = FALSE}
if (condition) true_action
if (condition) true_action else false_action
```
Si `conditionn` es `TRUE`, se evalúa `true_action`; si `condition` es `FALSE`, se evalúa la `false_action` opcional.
Por lo general, las acciones son declaraciones compuestas contenidas dentro de `{`:
```{r}
grade <- function(x) {
if (x > 90) {
"A"
} else if (x > 80) {
"B"
} else if (x > 50) {
"C"
} else {
"F"
}
}
```
`if` devuelve un valor para que pueda asignar los resultados:
```{r}
x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2
c(x1, x2)
```
(Recomiendo asignar los resultados de una declaración `if` solo cuando la expresión completa cabe en una línea; de lo contrario, tiende a ser difícil de leer.)
Cuando usa la forma de argumento único sin una declaración else, `if` invisiblemente (@sec-invisible) devuelve `NULL` si la condición es `FALSE`. Dado que funciones como `c()` y `paste()` descartan entradas `NULL`, esto permite una expresión compacta de ciertos modismos:
```{r}
greet <- function(name, birthday = FALSE) {
paste0(
"Hi ", name,
if (birthday) " and HAPPY BIRTHDAY"
)
}
greet("Maria", FALSE)
greet("Jaime", TRUE)
```
### Entradas inválidas
La condición debe evaluarse como un solo `TRUE` o `FALSE`. La mayoría de las otras entradas generarán un error:
```{r, error = TRUE}
if ("x") 1
if (logical()) 1
if (NA) 1
if (c(TRUE, FALSE)) 1
```
### if vectorizado
\index{ifelse()}
Dado que `if` solo funciona con un solo `TRUE` o `FALSE`, es posible que se pregunte qué hacer si tiene un vector de valores lógicos. Manejar vectores de valores es el trabajo de `ifelse()`: una función vectorizada con vectores `test`, `sí` y `no` (que se reciclarán a la misma longitud):
```{r}
x <- 1:10
ifelse(x %% 5 == 0, "XXX", as.character(x))
ifelse(x %% 2 == 0, "even", "odd")
```
Tenga en cuenta que los valores faltantes se propagarán a la salida.
Recomiendo usar `ifelse()` solo cuando los vectores `sí` y `no` son del mismo tipo, ya que de otro modo es difícil predecir el tipo de salida. Ver <https://vctrs.r-lib.org/articles/stability.html#ifelse> para una discusión adicional.
Otro equivalente vectorizado es el más general `dplyr::case_when()`. Utiliza una sintaxis especial para permitir cualquier número de pares de vectores de condición:
```{r}
dplyr::case_when(
x %% 35 == 0 ~ "fizz buzz",
x %% 5 == 0 ~ "fizz",
x %% 7 == 0 ~ "buzz",
is.na(x) ~ "???",
TRUE ~ as.character(x)
)
```
### declaración `switch()` {#sec-switch}
\index{switch()}
Estrechamente relacionada con `if` está la sentencia `switch()`. Es un equivalente compacto de propósito especial que le permite reemplazar código como:
```{r}
x_option <- function(x) {
if (x == "a") {
"option 1"
} else if (x == "b") {
"option 2"
} else if (x == "c") {
"option 3"
} else {
stop("Invalid `x` value")
}
}
```
con la más sucinta:
```{r}
x_option <- function(x) {
switch(x,
a = "option 1",
b = "option 2",
c = "option 3",
stop("Invalid `x` value")
)
}
```
El último componente de un `switch()` siempre debería arrojar un error; de lo contrario, las entradas no coincidentes devolverán invisiblemente `NULL`:
```{r}
(switch("c", a = 1, b = 2))
```
Si varias entradas tienen la misma salida, puede dejar el lado derecho de `=` vacío y la entrada "caerá" al siguiente valor. Esto imita el comportamiento de la sentencia `switch` de C:
```{r}
legs <- function(x) {
switch(x,
cow = ,
horse = ,
dog = 4,
human = ,
chicken = 2,
plant = 0,
stop("Unknown input")
)
}
legs("cow")
legs("dog")
```
También es posible usar `switch()` con una `x` numérica, pero es más difícil de leer y tiene modos de falla no deseados si `x` no es un número entero. Recomiendo usar `switch()` solo con entradas de caracteres.
### Ejercicios
1. ¿Qué tipo de vector devuelve cada una de las siguientes llamadas a `ifelse()`?
```{r, eval = FALSE}
ifelse(TRUE, 1, "no")
ifelse(FALSE, 1, "no")
ifelse(NA, 1, "no")
```
Lee la documentación y escribe las reglas con tus propias palabras.
2. ¿Por qué funciona el siguiente código?
```{r}
x <- 1:10
if (length(x)) "not empty" else "empty"
x <- numeric()
if (length(x)) "not empty" else "empty"
```
## Bucles {#sec-loops}
\index{loops} \index{loops!for@\texttt{for}} \index{for}
`for` los bucles se utilizan para iterar sobre los elementos de un vector. Tienen la siguiente forma básica:
```{r, eval = FALSE}
for (item in vector) perform_action
```
Para cada elemento en `vector`, `perform_action` se llama una vez; actualizando el valor de `item` cada vez.
```{r}
for (i in 1:3) {
print(i)
}
```
(Al iterar sobre un vector de índices, es convencional usar nombres de variables muy cortos como `i`, `j`, or `k`.)
N.B.: `for` asigna el `item` al entorno actual, sobrescribiendo cualquier variable existente con el mismo nombre:
```{r}
i <- 100
for (i in 1:3) {}
i
```
```{=tex}
\index{next}
\index{break}
```
Hay dos formas de terminar un bucle `for` antes de tiempo:
- `next` sale de la iteración actual.
- `break` sale de todo el bucle `for`.
```{r}
for (i in 1:10) {
if (i < 3)
next
print(i)
if (i >= 5)
break
}
```
### Errores comunes
\index{loops!common pitfalls}
Hay tres errores comunes a tener en cuenta al usar `for`. Primero, si está generando datos, asegúrese de asignar previamente el contenedor de salida. De lo contrario, el ciclo será muy lento; consulte las Secciones @sec-memory-profiling y @sec-avoid-copies para obtener más detalles. La función `vector()` es útil aquí.
```{r}
means <- c(1, 50, 20)
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
```
A continuación, tenga cuidado con la iteración sobre `1:length(x)`, que fallará de manera inútil si `x` tiene una longitud de 0:
```{r, error = TRUE}
means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
```
Esto ocurre porque `:` funciona tanto con secuencias crecientes como decrecientes:
```{r}
1:length(means)
```
Utilice `seq_along(x)` en su lugar. Siempre devuelve un valor de la misma longitud que `x`:
```{r}
seq_along(means)
out <- vector("list", length(means))
for (i in seq_along(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
```
Finalmente, es posible que encuentre problemas al iterar sobre los vectores de S3, ya que los bucles normalmente eliminan los atributos:
```{r}
xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
print(x)
}
```
Solucione esto llamando a `[[` usted mismo:
```{r}
for (i in seq_along(xs)) {
print(xs[[i]])
}
```
### Herramientas relacionadas {#sec-for-family}
```{=tex}
\index{while}
\index{repeat}
```
Los bucles `for` son útiles si conoce de antemano el conjunto de valores que desea iterar. Si no lo sabe, hay dos herramientas relacionadas con especificaciones más flexibles:
- `while(condition) action`: ejecuta `action` mientras `condition` sea `TRUE`.
- `repeat(action)`: repite `action` siempre (i.e. hasta que encuentre `break`).
R no tiene un equivalente a la sintaxis `do {acción} while (condition)` que se encuentra en otros idiomas.
Puede reescribir cualquier bucle `for` para usar `while` en su lugar, y puede reescribir cualquier bucle `while` para usar `repeat`, pero lo contrario no es cierto. Eso significa que `while` es más flexible que `for`, y `repeat` es más flexible que `while`. Sin embargo, es una buena práctica usar la solución menos flexible a un problema, por lo que debe usar `for` siempre que sea posible.
En términos generales, no debería necesitar usar bucles `for` para tareas de análisis de datos, ya que `map()` y `apply()` ya brindan soluciones menos flexibles para la mayoría de los problemas. Aprenderá más en el @sec-functionals.
### Ejercicios
1. ¿Por qué este código tiene éxito sin errores ni advertencias?
```{r, results = FALSE}
x <- numeric()
out <- vector("list", length(x))
for (i in 1:length(x)) {
out[i] <- x[i] ^ 2
}
out
```
2. Cuando se evalúa el siguiente código, ¿qué puede decir sobre el vector que se itera?
```{r}
xs <- c(1, 2, 3)
for (x in xs) {
xs <- c(xs, x * 2)
}
xs
```
3. ¿Qué le dice el siguiente código acerca de cuándo se actualiza el índice?
```{r}
for (i in 1:3) {
i <- i * 2
print(i)
}
```
## Respuestas de la prueba {#sec-control-flow-answers}
- `if` trabaja con escalares; `ifelse()` trabaja con vectores.
- Cuando `x` es `TRUE`, `y` será `3`; cuando `FALSE`, `y` será `NULL`; cuando `NA`, la declaración if arrojará un error.
- Esta instrucción `switch()` hace uso de fallas, por lo que devolverá 2. Consulte los detalles en la @sec-switch.