El bucle for básico
Un bucle for en R ejecuta un bloque de código una vez por cada elemento de un vector o lista. La variable del bucle toma cada valor por turnos:
Tres elementos, tres pasadas. No hay ningún contador que preparar ni ninguna condición que mantener - for (fruit in fruits) se lee como "para cada fruit en fruits", y eso es exactamente lo que hace. La misma forma itera sobre los elementos de una lista, sobre una secuencia de números (for (i in 1:10)) o sobre los nombres de cualquier cosa mediante names().
Imprimir dentro de un bucle requiere print() o cat()
En la consola, escribir x imprime su valor. Dentro del cuerpo de un bucle, no - la impresión automática solo ocurre en el nivel superior, y una expresión suelta dentro de un bucle se evalúa y se tira:
Así que cuando un bucle "no hace nada", esto es lo primero que hay que comprobar. Usa print() para echar un vistazo rápido a cualquier valor, o cat() cuando estés componiendo tú una línea de salida (recuerda el "\n" - cat() no añade ninguno).
Iterar sobre índices con seq_along()
A veces necesitas la posición además del valor - para escribir en otro vector en el mismo índice o para imprimir una lista numerada. Itera sobre índices con seq_along():
Verás for (i in 1:length(prices)) en código antiguo, y esconde un bug de verdad. Cuando el vector está vacío, length() es 0, y 1:0 no significa "ninguna iteración" - los dos puntos cuentan hacia atrás:
Un bucle sobre 1:length(empty) se ejecuta dos veces, indexando elementos que no existen (empty[1] es NA, y las asignaciones crean entradas fantasma). seq_along() devuelve una secuencia vacía para un vector vacío, así que el cuerpo del bucle simplemente no se ejecuta nunca. Que seq_along() sea la costumbre; su hermano seq_len(n) hace el mismo trabajo cuando tienes un conteo en lugar de un vector.
Recoger resultados: preasigna, no hagas crecer
El primer instinto natural para ir acumulando resultados es este:
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
Funciona, pero cada c(squares, ...) reserva un vector totalmente nuevo y copia dentro todos los elementos antiguos. Al final has copiado unos 50 millones de números para producir 10.000. Este antipatrón del vector que crece es la mayor razón por la que la gente dice que los bucles de R son lentos.
La solución: crea el resultado a tamaño completo primero y luego asigna por índice:
Para resultados que no son números, la misma idea aplica con character(n), logical(n) o vector("list", n) para una lista de cosas arbitrarias. Los bucles preasignados son perfectamente rápidos.
next y break
Dos palabras clave dirigen un bucle desde dentro de su cuerpo: next abandona la pasada actual y salta al siguiente elemento; break sale del bucle por completo.
Esto imprime 2 4 6 8: los valores impares se saltan con next, y cuando llega el 10, break termina el bucle antes de imprimir. Usa next para descartar casos pronto (mantiene el cuerpo principal sin sangrar) y break cuando un bucle ha encontrado lo que buscaba y no tiene motivo para continuar.
Bucles anidados
El cuerpo de un bucle puede contener otro bucle. El bucle interior se ejecuta hasta el final en cada una de las pasadas del exterior - la demostración clásica es una tabla de multiplicar:
Para cada fila i, el bucle interior recorre todas las columnas j, y el salto de línea de la fila se imprime después de que termine el bucle interior. Los bucles anidados están bien a dos niveles; a tres o más, el cuerpo suele estar pidiendo convertirse en una función, o el cálculo entero está pidiendo ser un outer() o una operación matricial.
El anidamiento también cubre recorrer una lista de vectores - bucle exterior sobre la lista, bucle interior (o mejor, una llamada vectorizada) sobre cada elemento:
Fíjate en que al final no hay bucle interior - mean() se encarga de todo el vector interno en una llamada. Esa observación se generaliza, lo que nos lleva a la parte honesta.
Cuándo no usar un bucle
R es un lenguaje vectorizado: sus operaciones básicas ya funcionan sobre vectores enteros de una vez. Un bucle que transforma cada elemento uno a uno suele ser una forma más larga de escribir una sola línea:
Prefiere la forma vectorizada siempre que exista una - es más corta, más difícil de equivocar y más rápida. Para aplicar una función a cada elemento de una lista, la familia apply (sapply, lapply, vapply) juega el mismo papel.
Pero no conviertas esto en el dogma de "los bucles son malos". Un bucle es la herramienta correcta cuando las iteraciones dependen de las anteriores (estado acumulado, simulaciones), cuando estás haciendo efectos secundarios como escribir archivos, o simplemente cuando el bucle es la versión que tú y tus lectores entendéis de un vistazo. Un bucle claro gana a una sola línea ingeniosa que nadie puede descifrar.
Lo que te llevas
for (x in v) { ... }ejecuta el cuerpo una vez por elemento - sin llevar la cuenta de ningún contador.- Dentro de un bucle, imprime explícitamente con
print()ocat(); las expresiones sueltas se descartan. - Itera sobre posiciones con
seq_along(v), nunca con1:length(v)- este último se ejecuta dos veces con un vector vacío. - Preasigna los resultados con
numeric(n)/vector("list", n)y asigna por índice; hacer crecer conc()lo copia todo en cada pasada. nextsalta una pasada,breaksale; prefiere las operaciones vectorizadas cuando existan, pero no temas a un bucle legible.
Lo siguiente: bucles que se ejecutan hasta que ocurre algo en lugar de una vez por elemento - while y repeat.
Preguntas frecuentes
¿Cómo se escribe un bucle for en R?
for (x in v) { ... } - la variable del bucle x toma cada elemento del vector (o lista) v por turnos, y el cuerpo se ejecuta una vez por elemento. Para iterar sobre posiciones en lugar de valores, usa for (i in seq_along(v)) e indexa con v[i].
¿Por qué usar seq_along en vez de 1:length(v) en R?
Cuando v está vacío, length(v) es 0, así que 1:length(v) se convierte en 1:0 - el vector de dos elementos c(1, 0) - y el cuerpo del bucle se ejecuta dos veces contra elementos que no existen. seq_along(v) devuelve una secuencia vacía para un vector vacío, así que el bucle se ejecuta correctamente cero veces.
¿Por qué mi bucle for en R no imprime nada?
La impresión automática de R solo ocurre con expresiones escritas en el nivel superior de la consola. Dentro del cuerpo de un bucle, una expresión suelta como x se evalúa y se descarta. Envuélvela en print(x), o usa cat(...) cuando quieras dar formato tú a la salida.
¿Son lentos los bucles for en R?
El bucle en sí está bien - lo lento es hacer crecer un vector dentro de él con result <- c(result, ...), que copia todo el vector en cada pasada. Preasigna con numeric(n) o vector("list", n) y asigna por índice, y un bucle rendirá perfectamente. Dicho esto, cuando una operación vectorizada o una llamada de la familia apply expresa la misma idea, suele ser a la vez más rápida y más clara.