-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspeed.qmd
518 lines (379 loc) · 27.8 KB
/
speed.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
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
515
516
517
518
# Velocidad {#speed}
Como científico de datos, necesita velocidad. Puede trabajar con datos más grandes y realizar tareas más ambiciosas cuando su código se ejecuta rápidamente. Este capítulo le mostrará una forma específica de escribir código rápido en R. Luego usará el método para simular 10 millones de juegos de su máquina tragamonedas.
## Código Vectorizado
Puede escribir una pieza de código de muchas maneras diferentes, pero el código R más rápido generalmente aprovechará tres cosas: pruebas lógicas, creación de subconjuntos y ejecución de elementos. Estas son las cosas que R hace mejor. El código que usa estas cosas suele tener una cierta cualidad: está *vectorizado*; el código puede tomar un vector de valores como entrada y manipular cada valor en el vector al mismo tiempo.
Para ver cómo se ve el código vectorizado, compare estos dos ejemplos de una función de valor absoluto. Cada uno toma un vector de números y lo transforma en un vector de valores absolutos (por ejemplo, números positivos). El primer ejemplo no está vectorizado; `abs_bucle` usa un bucle `for` para manipular cada elemento del vector uno a la vez:
``` r
abs_bucle <- function(vec){
for (i in 1:length(vec)) {
if (vec[i] < 0) {
vec[i] <- -vec[i]
}
}
vec
}
```
El segundo ejemplo, `abs_set`, es una versión vectorizada de `abs_bucle`. Utiliza subconjuntos lógicos para manipular todos los números negativos en el vector al mismo tiempo:
``` r
abs_sets <- function(vec){
negs <- vec < 0
vec[negs] <- vec[negs] * -1
vec
}
```
`abs_set` es mucho más rápido que `abs_bucle` porque se basa en operaciones que R realiza rápidamente: pruebas lógicas, creación de subconjuntos y ejecución de elementos.
Puedes usar la función `system.time` para ver qué tan rápido es `abs_set`. `system.time` toma una expresión R, la ejecuta y luego muestra cuánto tiempo transcurrió mientras se ejecutaba la expresión.
Para comparar `abs_bucle` y `abs_set`, primero haga un vector largo de números positivos y negativos. `largo` contendrá 10 millones de valores:
``` r
largo <- rep(c(-1, 1), 5000000)
```
::: callout-note
`rep` repite un valor, o vector de valores, muchas veces. Para usar `rep`, dale un vector de valores y luego el número de veces para repetir el vector. R devolverá los resultados como un nuevo vector más largo.
:::
Luego puede usar `system.time` para medir cuánto tiempo le toma a cada función evaluar `largo`:
``` r
system.time(abs_bucle(largo))
## user system elapsed
## 15.982 0.032 16.018
system.time(abs_sets(largo))
## user system elapsed
## 0.529 0.063 0.592
```
::: callout-important
No confunda `system.time` con `Sys.time`, que devuelve la hora actual.
:::
Las primeras dos columnas de la salida de `system.time` informan cuántos segundos pasó su computadora ejecutando la llamada en el lado del usuario y en el lado del sistema de su proceso, una dicotomía que variará de un sistema operativo a otro.
La última columna muestra cuántos segundos transcurrieron mientras R ejecutaba la expresión. Los resultados muestran que `abs_set` calculó el valor absoluto 30 veces más rápido que `abs_bucle` cuando se aplicó a un vector de 10 millones de números. Puede esperar aceleraciones similares siempre que escriba código vectorizado.
**Ejercicio 13.1 (¿Qué tan rápido es abs?)** Muchas funciones de R preexistentes ya están vectorizadas y se han optimizado para funcionar rápidamente. Puede hacer que su código sea más rápido confiando en estas funciones siempre que sea posible. Por ejemplo, R viene con una función de valor absoluto incorporada, `abs`.
Comprueba cuánto más rápido `abs` calcula el valor absoluto de `long` que `abs_bucle` y `abs_set`.
*Solución.* Puedes medir la velocidad de `abs` con `system.time`. A `abs` le toma 0,05 segundos, a la velocidad del rayo, calcular el valor absoluto de 10 millones de números. Esto es 0,592 / 0,054 = 10,96 veces más rápido que `abs_set` y casi 300 veces más rápido que `abs_bucle`:
``` r
system.time(abs(largo))
## user system elapsed
## 0.037 0.018 0.054
```
## Cómo Escribir Código Vectorizado
El código vectorizado es fácil de escribir en R porque la mayoría de las funciones de R ya están vectorizadas. El código basado en estas funciones se puede vectorizar fácilmente y, por lo tanto, es rápido. Para crear código vectorizado:
1. Use funciones vectorizadas para completar los pasos secuenciales en su programa.
2. Use subconjuntos lógicos para manejar casos paralelos. Trate de manipular todos los elementos en un caso a la vez.
`abs_bucle` y `abs_set` ilustran estas reglas. Las funciones manejan dos casos y realizan un paso secuencial, Figure @fig-abs. Si un número es positivo, las funciones lo dejan solo. Si un número es negativo, las funciones lo multiplican por uno negativo.
```{r}
#| label: fig-abs
#| echo: FALSE
#| fig.cap: >
#| abs_loop usa un bucle for para clasificar los datos en uno de dos casos:
#| números negativos y números no negativos.
knitr::include_graphics("images/hopr_1001.png")
```
Puede identificar todos los elementos de un vector que caen en un caso con una prueba lógica. R ejecutará la prueba en forma de elementos y devolverá un `TRUE` para cada elemento que pertenezca al caso. Por ejemplo, `vec < 0` identifica cada valor de `vec` que pertenece al caso negativo. Puede usar la misma prueba lógica para extraer el conjunto de valores negativos con subconjunto lógico:
``` r
vec <- c(1, -2, 3, -4, 5, -6, 7, -8, 9, -10)
vec < 0
## FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
vec[vec < 0]
## -2 -4 -6 -8 -10
```
El plan de la Figura @fig-abs ahora requiere un paso secuencial: debe multiplicar cada uno de los valores negativos por uno negativo. Todos los operadores aritméticos de R están vectorizados, por lo que puede usar `*` para completar este paso de forma vectorizada. `*` multiplicará cada número en `vec[vec < 0]` por uno negativo al mismo tiempo:
``` r
vec[vec < 0] * -1
## 2 4 6 8 10
```
Finalmente, puede usar el operador de asignación de R, que también está vectorizado, para guardar el nuevo conjunto sobre el antiguo en el objeto `vec` original. Dado que `<-` está vectorizado, los elementos del nuevo conjunto se emparejarán con los elementos del antiguo conjunto, en orden, y luego se realizará la asignación por elementos. Como resultado, cada valor negativo será reemplazado por su pareja positiva, como en la figura @fig-assignment.
```{r}
#| label: fig-assignment
#| echo: FALSE
#| fig.cap: >
#| Use subconjuntos lógicos para modificar grupos de valores en su lugar.
#| Los operadores aritméticos y de asignación de R están vectorizados, lo que
#| le permite manipular y actualizar varios valores a la vez.
knitr::include_graphics("images/hopr_1002.png")
```
**Ejercicio 13.2 (Vectorizar una función)** La siguiente función convierte un vector de símbolos de tragamonedas en un vector de nuevos símbolos de tragamonedas. ¿Puedes vectorizarlo? ¿Cuánto más rápido funciona la versión vectorizada?
``` r
cambiar_simbolos <- function(vec){
for (i in 1:length(vec)){
if (vec[i] == "DD") {
vec[i] <- "comodín"
} else if (vec[i] == "C") {
vec[i] <- "as"
} else if (vec[i] == "7") {
vec[i] <- "rey"
}else if (vec[i] == "B") {
vec[i] <- "reina"
} else if (vec[i] == "BB") {
vec[i] <- "jota"
} else if (vec[i] == "BBB") {
vec[i] <- "diez"
} else {
vec[i] <- "nueve"
}
}
vec
}
vec <- c("DD", "C", "7", "B", "BB", "BBB", "0")
cambiar_simbolos(vec)
## "comodín" "as" "rey" "reina" "jota" "diez" "nueve"
muchas <- rep(vec, 1000000)
system.time(cambiar_simbolos(muchas))
## user system elapsed
## 30.057 0.031 30.079
```
*Solución.* `cambiar_simbolos` usa un bucle `for` para clasificar los valores en siete casos diferentes, como se muestra en la Figura @fig-change.
Para vectorizar `cambiar_simbolos`, cree una prueba lógica que pueda identificar cada caso:
``` r
vec[vec == "DD"]
## "DD"
vec[vec == "C"]
## "C"
vec[vec == "7"]
## "7"
vec[vec == "B"]
## "B"
vec[vec == "BB"]
## "BB"
vec[vec == "BBB"]
## "BBB"
vec[vec == "0"]
## "0"
```
```{r}
#| label: fig-change
#| echo: FALSE
#| fig.cap: >
#| cambiar_simbolos hace algo diferente para cada uno de los siete casos.
knitr::include_graphics("images/hopr_1003.png")
```
Luego escribe código que pueda cambiar los símbolos para cada caso:
``` r
vec[vec == "DD"] <- "comodín"
vec[vec == "C"] <- "as"
vec[vec == "7"] <- "rey"
vec[vec == "B"] <- "reina"
vec[vec == "BB"] <- "jota"
vec[vec == "BBB"] <- "diez"
vec[vec == "0"] <- "nueve"
```
Cuando combinas esto en una función, tienes una versión vectorizada de `cambiar_simbolos` que se ejecuta unas 14 veces más rápido:
``` r
cambiar_vec <- function (vec) {
vec[vec == "DD"] <- "comodín"
vec[vec == "C"] <- "as"
vec[vec == "7"] <- "rey"
vec[vec == "B"] <- "reina"
vec[vec == "BB"] <- "jota"
vec[vec == "BBB"] <- "diez"
vec[vec == "0"] <- "nueve"
vec
}
system.time(cambiar_vec(muchas))
## user system elapsed
## 1.994 0.059 2.051
```
O, mejor aún, use una tabla de búsqueda. Las tablas de búsqueda son un método vectorizado porque se basan en las operaciones de selección vectorizadas de R:
``` r
cambiar_vec2 <- function(vec){
tb <- c("DD" = "comodín", "C" = "as", "7" = "rey", "B" = "reina",
"BB" = "jota", "BBB" = "diez", "0" = "nueve")
unname(tb[vec])
}
system.time(cambiar_vec2(muchas))
## user system elapsed
## 0.687 0.059 0.746
```
Aquí, una tabla de búsqueda es 40 veces más rápida que la función original.
`abs_bucle` y `cambiar_simbolos` ilustran una característica del código vectorizado: los programadores a menudo escriben código no vectorizado más lento basándose en bucles `for` innecesarios, como el de `change_simbolos`. Creo que esto es el resultado de un malentendido general sobre R. Los bucles `for` no se comportan de la misma manera en R que en otros lenguajes, lo que significa que debe escribir código en R de manera diferente a como lo haría en otros lenguajes.
Cuando escribe en lenguajes como C y Fortran, debe compilar su código antes de que su computadora pueda ejecutarlo. Este paso de compilación optimiza cómo los bucles `for` en el código usan la memoria de su computadora, lo que hace que los bucles `for` sean muy rápidos. Como resultado, muchos programadores usan bucles `for` con frecuencia cuando escriben en C y Fortran.
Sin embargo, cuando escribe en R, no compila su código. Omite este paso, lo que hace que la programación en R sea una experiencia más fácil de usar. Desafortunadamente, esto también significa que no le da a sus bucles el aumento de velocidad que recibirían en C o Fortran. Como resultado, sus bucles se ejecutarán más lentamente que las otras operaciones que hemos estudiado: pruebas lógicas, creación de subconjuntos y ejecución por elementos. Si puede escribir su código con las operaciones más rápidas en lugar de un bucle `for`, debe hacerlo. Independientemente del idioma en el que escriba, debe intentar utilizar las funciones del idioma que se ejecute más rápido.
::: callout-tip
**if y for**
Una buena forma de detectar bucles `for` que podrían vectorizarse es buscar combinaciones de `if` y `for`. `if` solo se puede aplicar a un valor a la vez, lo que significa que a menudo se usa junto con un bucle `for`. El ciclo `for` ayuda a aplicar `if` a un vector completo de valores. Esta combinación generalmente se puede reemplazar con un subconjunto lógico, que hará lo mismo pero se ejecutará mucho más rápido.
:::
Esto no significa que nunca debas usar bucles `for` en R. Todavía hay muchos lugares en R donde los bucles `for` tienen sentido. Los bucles `for` realizan una tarea básica que no siempre se puede recrear con código vectorizado. Los bucles `for` también son fáciles de entender y se ejecutan razonablemente rápido en R, siempre que tome algunas precauciones.
## Cómo Escribir Bucles for Rápidos en R
Puede aumentar drásticamente la velocidad de sus bucles `for` haciendo dos cosas para optimizar cada bucle. Primero, haz todo lo que puedas fuera del bucle `for`. Cada línea de código que coloques dentro del bucle `for` se ejecutará muchas, muchas veces. Si una línea de código solo necesita ejecutarse una vez, colóquela fuera del ciclo para evitar la repetición.
En segundo lugar, asegúrese de que cualquier objeto de almacenamiento que utilice con el ciclo sea lo suficientemente grande como para contener *todos* los resultados del ciclo. Por ejemplo, ambos bucles a continuación necesitarán almacenar un millón de valores. El primer ciclo almacena sus valores en un objeto llamado `salida` que comienza con una longitud de *un millón*:
``` r
system.time({
salida <- rep(NA, 1000000)
for (i in 1:1000000) {
salida[i] <- i + 1
}
})
## user system elapsed
## 1.709 0.015 1.724
```
El segundo ciclo almacena sus valores en un objeto llamado `salida` que comienza con una longitud de *uno*. R expandirá el objeto a una longitud de un millón mientras ejecuta el bucle. El código de este ciclo es muy similar al código del primer ciclo, pero el ciclo tarda *37 minutos* más en ejecutarse que el primer ciclo:
``` r
system.time({
salida <- NA
for (i in 1:1000000) {
salida[i] <- i + 1
}
})
## user system elapsed
## 1689.537 560.951 2249.927
```
Los dos bucles hacen lo mismo, entonces, ¿qué explica la diferencia? En el segundo bucle, R tiene que aumentar la longitud de `salida` en uno para cada ejecución del bucle. Para hacer esto, R necesita encontrar un nuevo lugar en la memoria de su computadora que pueda contener el objeto más grande. Luego, R debe copiar el vector `salida` y borrar la versión anterior de `salida` antes de pasar a la siguiente ejecución del bucle. Al final del ciclo, R ha reescrito `salida` en la memoria de su computadora un millón de veces.
En el primer caso, el tamaño de `salida` nunca cambia; R puede definir un objeto `salida` en la memoria y usarlo para cada ejecución del bucle `for`.
::: callout-tip
Los autores de R usan lenguajes de bajo nivel como C y Fortran para escribir funciones básicas de R, muchas de las cuales usan bucles `for`. Estas funciones se compilan y optimizan antes de que se conviertan en parte de R, lo que las hace bastante rápidas.
Cada vez que vea `.Primitive`, `.Internal` o `.Call` escrito en la definición de una función, puede estar seguro de que la función está llamando al código de otro idioma. Obtendrá todas las ventajas de velocidad de ese idioma al usar la función.
:::
## Código Vectorizado en la Práctica
Para ver cómo el código vectorizado puede ayudarlo como científico de datos, considere nuestro proyecto de máquina tragamonedas. En [Bucles](#loops), calculó la tasa de pago exacta para su máquina tragamonedas, pero podría haber estimado esta tasa de pago con una simulación. Si jugaste a la máquina tragamonedas muchas, muchas veces, el premio promedio de todas las jugadas sería una buena estimación de la verdadera tasa de pago.
Este método de estimación se basa en la ley de los grandes números y es similar a muchas simulaciones estadísticas. Para ejecutar esta simulación, podría usar un bucle `for`:
``` r
ganadas <- vector(length = 1000000)
for (i in 1:1000000) {
ganadas[i] <- play()
}
mean(ganadas)
## 0.9366984
```
La tasa de pago estimada después de 10 millones de ejecuciones es 0,937, que está muy cerca de la tasa de pago real de 0,934. Tenga en cuenta que estoy usando la función `puntuacion` modificada que trata a los diamantes como comodines.
Si ejecuta esta simulación, notará que tarda un tiempo en ejecutarse. De hecho, la simulación tarda 342.308 segundos en ejecutarse, lo que equivale a unos 5,7 minutos. Esto no es particularmente impresionante, y puede hacerlo mejor usando código vectorizado:
``` r
system.time(for (i in 1:1000000) {
ganadas[i] <- play()
})
## user system elapsed
## 342.041 0.355 342.308
```
La función `puntuacion` actual no está vectorizada. Toma una sola combinación de tragamonedas y usa un árbol `if` para asignarle un premio. Esta combinación de un árbol `if` con un bucle `for` sugiere que podrías escribir una pieza de código vectorizado que tome *muchas* combinaciones de ranuras y luego use subconjuntos lógicos para operar en todas ellas a la vez.
Por ejemplo, podría reescribir `obt_simbolos` para generar *n* combinaciones de ranuras y devolverlas como una matriz *n* x 3, como la siguiente. Cada fila de la matriz contendrá una combinación de espacios para ser puntuada:
``` r
obt_muchos_simbolos <- function(n) {
rueda <- c("DD", "7", "BBB", "BB", "B", "C", "0")
vec <- sample(rueda, size = 3 * n, replace = TRUE,
prob = c(0.03, 0.03, 0.06, 0.1, 0.25, 0.01, 0.52))
matrix(vec, ncol = 3)
}
obt_muchos_simbolos(5)
## [,1] [,2] [,3]
## [1,] "B" "0" "B"
## [2,] "0" "BB" "7"
## [3,] "0" "0" "BBB"
## [4,] "0" "0" "B"
## [5,] "BBB" "0" "0"
```
También podría reescribir `play` para tomar un parámetro, `n`, y devolver `n` premios, en un data frame:
``` r
play_muchas <- function(n) {
simb_mat <- obt_muchos_simbolos(n = n)
data.frame(w1 = simb_mat[,1], w2 = simb_mat[,2],
w3 = simb_mat[,3], prize = puntuacion_muchas(simb_mat))
}
```
Esta nueva función facilitaría la simulación de un millón, o incluso 10 millones de jugadas de la máquina tragamonedas, que será nuestro objetivo. Cuando hayamos terminado, podrá estimar la tasa de pago con:
``` r
# jugadas <- play_muchas(10000000))
# mean(jugadas$premio)
```
Ahora solo necesitas escribir `puntuacion_muchas`, una versión vectorizada (¿matrixada?) de `puntuacion` que toma una matriz *n* x 3 y devuelve *n* premios. Será difícil escribir esta función porque `puntuacion` ya es bastante complicado. No espero que se sienta seguro haciendo esto por su cuenta hasta que tenga más práctica y experiencia de la que hemos podido desarrollar aquí.
Si desea probar sus habilidades y escribir una versión de `puntuacio_muchas`, le recomiendo que use la función `rowSums` dentro de su código. Calcula la suma de cada fila de números (o lógicos) en una matriz.
Si desea probarse a sí mismo de una manera más modesta, le recomiendo que estudie el siguiente modelo de función `puntuacion_muchas` hasta que comprenda cómo funciona cada parte y cómo las partes trabajan juntas para crear una función vectorizada. Para hacer esto, será útil crear un ejemplo concreto, como este:
``` r
simbolos <- matrix(
c("DD", "DD", "DD",
"C", "DD", "0",
"B", "B", "B",
"B", "BB", "BBB",
"C", "C", "0",
"7", "DD", "DD"), nrow = 6, byrow = TRUE)
simbolos
## [,1] [,2] [,3]
## [1,] "DD" "DD" "DD"
## [2,] "C" "DD" "0"
## [3,] "B" "B" "B"
## [4,] "B" "BB" "BBB"
## [5,] "C" "C" "0"
## [6,] "7" "DD" "DD"
```
Luego puede ejecutar cada línea de `puntuacion_muchas` contra el ejemplo y examinar los resultados a medida que avanza.
**Ejercicio 13.3 (Pon a Prueba tu Comprensión)** Estudie la función del modelo `puntuaciio_muchas` hasta que esté satisfecho de que entiende cómo funciona y podría escribir una función similar usted mismo.
**Ejercicio 13.4 (Desafío Avanzado)** En lugar de examinar la respuesta modelo, escriba su propia versión vectorizada de `puntuacion`. Suponga que los datos se almacenan en un *n* × 3 matriz donde cada fila de la matriz contiene una combinación de ranuras para puntuar.
Puede usar la versión de `puntuacion` que trata a los diamantes como comodines o la versión de `puntuacion` que no lo hace. Sin embargo, la respuesta de ejemplo usará la versión que trata a los diamantes como comodines.
*Solución.* `puntuacion_muchas` es una versión vectorizada de `puntuacion`. Puede usarlo para ejecutar la simulación al comienzo de esta sección en poco más de 20 segundos. Esto es 17 veces más rápido que usar un bucle `for`:
``` r
# los símbolos deben ser una matriz con una columna para cada ventana de la máquina tragamonedas
puntuacion_muchas <- function(simbolos) {
# Paso 1: Asigne el premio base basado en cerezas y diamantes ---------
## Cuente el número de cerezas y diamantes en cada combinación.
cerezas <- rowSums(simbolos == "C")
diamantes <- rowSums(simbolos == "DD")
## Los diamantes salvajes cuentan como cerezas
premio <- c(0, 2, 5)[cerezas + diamantes + 1]
## ...pero no si hay cero cerezas reales
### (cerezas es forzado a FALSE donde cerezas == 0)
premio[!cerezas] <- 0
# Paso 2: Cambie el premio por combinaciones que contengan tríos
iguales <- simbolos[, 1] == simbolos[, 2] &
simbolos[, 2] == simbolos[, 3]
pagos <- c("DD" = 100, "7" = 80, "BBB" = 40,
"BB" = 25, "B" = 10, "C" = 10, "0" = 0)
premio[iguales] <- pagos[simbolos[iguales, 1]]
# Paso 3: Cambia el premio por combinaciones que contengan todas barras ------
barras <- simbolos == "B" | simbolos == "BB" | simbolos == "BBB"
todas_barras <- barras[, 1] & barras[, 2] & barras[, 3] & !iguales
premio[todas_barras] <- 5
# Paso 4: Manejar comodines ---------------------------------------------
## combos con dos diamantes
dos_salvajes <- diamantes == 2
### Identificar el simbolo no salvaje
uno <- dos_salvajes & simbolos[, 1] != simbolos[, 2] &
simbolos[, 2] == simbolos[, 3]
dos <- dos_salvajes & simbolos[, 1] != simbolos[, 2] &
simbolos[, 1] == simbolos[, 3]
tres <- dos_salvajes & simbolos[, 1] == simbolos[, 2] &
simbolos[, 2] != simbolos[, 3]
### Tratar como tres de una clase
premio[uno] <- pagos[simbolos[uno, 1]]
premio[dos] <- pagos[simbolos[dos, 2]]
premio[tres] <- pagos[simbolos[tres, 3]]
## combos con un salvaje
un_salvaje <- diamantes == 1
### Tratar como todas las barras (si corresponde)
barras_salvajes <- un_salvaje & (rowSums(barras) == 2)
premio[barras_salvajes] <- 5
### Tratar como tres de una clase (si corresponde)
uno <- un_salvaje & simbolos[, 1] == simbolos[, 2]
dos <- un_salvaje & simbolos[, 2] == simbolos[, 3]
tres <- un_salvaje & simbolos[, 3] == simbolos[, 1]
premio[uno] <- pagos[simbolos[uno, 1]]
premio[dos] <- pagos[simbolos[dos, 2]]
premio[tres] <- pagos[simbolos[tres, 3]]
# Paso 5: Premio doble por cada diamante en combo ------------------
unname(premio * 2^diamantes)
}
system.time(play_muchas(10000000))
## user system elapsed
## 20.942 1.433 22.367
```
### Bucles vs Código Vectorizado
En muchos idiomas, los bucles `for` se ejecutan muy rápido. Como resultado, los programadores aprenden a usar bucles `for` siempre que sea posible cuando codifican. A menudo, estos programadores continúan confiando en los bucles `for` cuando comienzan a programar en R, normalmente sin tomar los pasos necesarios para optimizar los bucles `for` de R. Estos programadores pueden desilusionarse con R cuando su código no funciona tan rápido como les gustaría. Si cree que esto le puede estar pasando a usted, examine con qué frecuencia está usando bucles `for` y para qué los está usando. Si te encuentras usando bucles `for` para cada tarea, es muy probable que estés "hablando R con acento C". La cura es aprender a escribir y usar código vectorizado.
Esto no significa que los bucles `for` no tengan cabida en R. Los bucles `for` son una característica muy útil; pueden hacer muchas cosas que el código vectorizado no puede hacer. Tampoco debe convertirse en esclavo del código vectorizado. A veces llevaría más tiempo reescribir el código en formato vectorizado que dejar que se ejecute un bucle `for`. Por ejemplo, ¿sería más rápido dejar que la simulación de tragamonedas se ejecute durante 5,7 minutos o volver a escribir `puntuacion`?
## Resumen
El código rápido es un componente importante de la ciencia de datos porque puede hacer más con el código rápido que con el código lento. Puede trabajar con conjuntos de datos más grandes antes de que intervengan las restricciones computacionales y puede hacer más cálculos antes de que intervengan las restricciones de tiempo. El código más rápido en R se basará en las cosas que R hace mejor: pruebas lógicas, creación de subconjuntos y ejecución por elementos. He llamado a este tipo de código código vectorizado porque el código escrito con estas operaciones tomará un vector de valores como entrada y operará en cada elemento del vector al mismo tiempo. La mayoría del código escrito en R ya está vectorizado.
Si usa estas operaciones, pero su código no aparece vectorizado, analice los pasos secuenciales y los casos paralelos en su programa. Asegúrese de haber utilizado funciones vectorizadas para manejar los pasos y subconjuntos lógicos para manejar los casos. Tenga en cuenta, sin embargo, que algunas tareas no se pueden vectorizar.
## Resumen del Proyecto 3
Ahora ha escrito su primer programa en R, y es un programa del que debería estar orgulloso. `play` no es un simple ejercicio de `hola mundo`, sino un programa real que realiza una tarea real de una manera complicada.
Escribir nuevos programas en R siempre será un desafío porque la programación depende mucho de su propia creatividad, capacidad de resolución de problemas y experiencia escribiendo tipos de programas similares. Sin embargo, puede usar las sugerencias de este capítulo para hacer que incluso el programa más complicado sea manejable: divida las tareas en pasos y casos simples, trabaje con ejemplos concretos y describa posibles soluciones en español.
Este proyecto completa la educación que comenzaste en [Lo Más Básico](#Básico). Ahora puede usar R para manejar datos, lo que ha aumentado su capacidad para analizar datos. Usted puede:
- Cargue y almacene datos en su computadora, no en papel o en su mente
- Recuerda y cambia con precisión valores individuales sin depender de tu memoria
- Indique a su computadora que realice tareas tediosas o complejas en su nombre
Estas habilidades resuelven un problema logístico importante al que se enfrenta todo científico de datos: *¿cómo puede almacenar y manipular datos sin cometer errores?* Sin embargo, este no es el único problema al que se enfrentará como científico de datos. El siguiente problema aparecerá cuando intente comprender la información contenida en sus datos. Es casi imposible detectar ideas o descubrir patrones en datos sin procesar. Aparecerá un tercer problema cuando intente usar su conjunto de datos para razonar sobre la realidad, que incluye cosas que no están contenidas en su conjunto de datos. ¿Qué implican exactamente sus datos sobre cosas fuera del conjunto de datos? ¿Qué tan seguro puedes estar?
Me refiero a estos problemas como problemas logísticos, tácticos y estratégicos de la ciencia de datos, como se muestra en la Figura @fig-venn. Los enfrentará cada vez que intente aprender de los datos:
- **Un problema logístico:** - ¿Cómo puedes almacenar y manipular datos sin cometer errores?
- **Un problema táctico** - ¿Cómo puedes descubrir la información contenida en tus datos?
- **Un problema estratégico** - ¿Cómo puedes usar los datos para sacar conclusiones sobre el mundo en general?
```{r}
#| label: fig-venn
#| echo: FALSE
#| fig.cap: >
#| Los tres conjuntos de habilidades básicas de la ciencia de datos: programación
#| informática, comprensión de datos y razonamiento científico.
knitr::include_graphics("images/hopr_1004.png")
```
Un científico de datos completo deberá poder resolver cada uno de estos problemas en muchas situaciones diferentes. Al aprender a programar en R, domina el problema logístico, que es un requisito previo para resolver los problemas tácticos y estratégicos.
Si desea aprender a razonar con datos, o cómo transformar, visualizar y explorar sus conjuntos de datos con herramientas R, le recomiendo el libro [*R for Data Science*](http://r4ds.had.co.nz/), el volumen complementario de este libro. *R for Data Science* enseña un flujo de trabajo simple para transformar, visualizar y modelar datos en R, así como también cómo informar los resultados con el paquete R Markdown.