20  CICLOS Y CONDICIONALES

Frecuentemente se hace necesario realizar tareas que exigen repetir conjuntos de instrucciones. La manera más eficiente de hacerlo es usando ciclos.

También es común que la ejecución de ciertas instrucciones dependa del cumplimiento de ciertos requisitos. Las diferentes rutas que puede tomar un algoritmo se manejan a través de condicionales.

Los ciclos y los condicionales son conceptos centrales en cualquier lenguaje de programación, y suelen aparecer de manera conspicua en la mayoría de las funciones de R. Su correcto manejo constituye el primer paso hacia la maestría en la escritura de funciones eficientes.

Los controladores de flujo permiten implementar estructuras repetitivas (ciclos) y condicionales en R. Los más usados para controlar el flujo de los ciclos son for, while y repeat. Por su parte, if, así como las variantes if ... else, if ... else if e ifelse son los más usados para controlar el flujo de los condicionales.

Ciclos. Cuando se requiere ejecutar un mismo conjunto de instrucciones más de una vez, resulta práctico establecer un ciclo. A cada una de las repeticiones del conjunto de instrucciones se le denomina iteración. El término ciclo —también denominado bucle en algunos lenguajes— hace referencia a la estructura de control que permite estas repeticiones. Para ello pueden utilizarse los controladores de flujo como for, while y repeat.

Condicionales. En otras situaciones se requiere que el conjunto de instrucciones se ejecute una sola vez si se satisface alguna condición, para lo cual se utilizan los controladores if, if ... else, if ... else if e ifelse.

¡Puede combinarlos!

Es posible usar simultáneamente diferentes controladores de flujo, anidando unos dentro de otros, ya sean de la misma categoría (p. e. varios ciclos for anidados unos dentro de otros) o de categorías diferentes (p. e. condicionales if anidados dentro de ciclos while).

El código 20.2 ilustra una de estas posibilidades.

Para todos los casos que se detallan en este capítulo, se usa el descriptor genérico instrucciones. Tales instrucciones pueden estar conformadas por una única instrucción o por un conjunto de instrucciones.

Si se trata de una única instrucción —y el controlador de flujo está dentro de una función—, no es obligatorio escribirla dentro de llaves, como sí lo es cuando se trata de varias instrucciones (cf. alerta 22.1).

Como si se tratara de funciones

Con el fin de favorecer la legibilidad del código, el uso de llaves y la ubicación del controlador else se aplicará como si se tratara de código dentro de una función.

Debe tenerse en cuenta, sin embargo, que este estilo —que es perfectamente válido dentro de funciones— puede generar errores cuando se usa en código aislado.

Estas directrices se detallan en el capítulo 22 y muy particularmente en la sección 22.4.

20.1 Número fijo de ciclos: controlador for

La siguiente es la sintaxis básica de los ciclos definidos mediante el controlador de flujo for:

for (i in valores) {
  instrucciones
}

Los ciclos basados en el controlador de flujo for se caracterizan por incluir un índice que forma parte de las instrucciones, cuyo valor cambia automáticamente en cada repetición del ciclo.

En la sintaxis ejemplificada anteriormente, i es el índice en cuestión; para su definición, puede usarse cualquier nombre sintácticamente válido en R (cf. capítulo 12). En cada ciclo, el índice irá tomando secuencialmente cada uno de los valores definidos en el vector valores. El conector in que va entre el índice y el vector de valores es invariable.

Es frecuente que el índice tome valores numéricos, definidos a través de una secuencia de enteros, tal y como se ilustra a continuación.

for (dígito in 0:5)
  cat(dígito, "\n")
0 
1 
2 
3 
4 
5 

En este caso el índice llamado dígito toma cada uno de los valores enteros entre 0 y 5, acorde con lo definido en la secuencia 0:5 (cf. sección 2.7). Para cada uno de tales valores se ejecuta la instrucción de imprimir dicho dígito y un salto de línea.

Inicialmente, dígito toma el primer valor de la serie (0). A continuación se ejecutan las instrucciones: se imprime el valor de dígito (0) y se adiciona un salto de línea. Ahí concluye la primera iteración.

Al inicio de la segunda iteración, dígito toma el segundo valor de la serie (1). A continuación se imprime el valor de dígito (1), con su correspondiente salto de línea.

Se continúa de esta manera hasta que dígito toma el último valor de la serie (5) y se ejecuta la última iteración, esto es, se imprime el valor de dígito (5) y un salto de línea.

Producto de este ciclo, se tiene una impresión secuencial de los enteros entre 0 y 5, con un dígito por línea.


Aunque el uso de índices que varían a través de alguna secuencia de números enteros es bastante común, no es la única alternativa posible. El vector valores puede estar conformado por cualquier conjunto de elementos, tal y como se ilustra a continuación.

for (ind in seq(1, 1.5, 0.1))
  print(2 * ind)
[1] 2
[1] 2.2
[1] 2.4
[1] 2.6
[1] 2.8
[1] 3

En este caso el índice, al que se le ha denominado ind, toma los valores definidos en la secuencia, esto es, 1, 1.1, 1.2, 1.3, 1.4 y 1.5 (cf. sección 2.7). La instrucción en este caso consiste en imprimir el resultado de multiplicar cada uno de tales valores por 2.

¿¡Y el salto de línea!?

Se habrá notado que al usar print —a diferencia de cuando se usa cat— no es necesario incluir el salto de línea, pues este ya viene incorporado en la función.

Como contraparte, aparece el marcador de posición en cada una de las líneas (cf. sección 15.2.1).

¿Cómo queda mejor?

Depende de los gustos y necesidades.

También es posible asignar al índice una serie arbitraria de valores, sin que exista requerimiento alguno de intervalo u orden, tal y como se ilustra a continuación.

for (x in c(4, 3.14, -2))
  print(exp(x))
[1] 54.59815
[1] 23.10387
[1] 0.1353353

En este caso, el índice x toma secuencialmente cada uno de los tres valores definidos en el vector. Cada uno de tales valores es usado como argumento de la función exponencial. El resultado se envía a la consola.


Los diferentes valores que tome el índice ni siquiera tienen que ser numéricos. Bien podría usarse un vector tipo character. En tal caso, las instrucciones deben estar definidas de manera que operen sobre cadenas de texto, como en el siguiente ejemplo:

flores <- c('Amaranthus', 'Clavel', 'Hortensia', 'Gerbera')
for (f in flores)
  cat(f, "tiene", nchar(f), "caracteres\n")
Amaranthus tiene 10 caracteres
Clavel tiene 6 caracteres
Hortensia tiene 9 caracteres
Gerbera tiene 7 caracteres

En este caso, el índice f toma cada uno de los valores del vector flores. Para cada uno de tales elementos, se imprime un aviso indicando su número de caracteres.

20.2 Número condicionado de ciclos: controlador while

A continuación se presenta la sintaxis básica del controlador de flujo while:

while(condición) {
  instrucciones
}

El bloque de instrucciones que conforma el cuerpo del controlador de flujo while se ejecuta cíclicamente mientras la evaluación de condición sea verdadera (TRUE).

Con el fin de mantener la secuencia de esta presentación, a continuación se ilustra el uso del controlador de flujo while usando condiciones simples. En la sección 20.8 se detalla lo relativo a la formulación de condiciones.

Cuando se ejecutan ciclos de instrucciones controlados por while, es necesario que la variable de control sobre la cual se verifica la satisfacción de la condición forme parte de las instrucciones, con la posibilidad de que estas alteren su valor, constituyendo esta la ruta normal para terminar el ciclo.

Código 20.1
epsilon <- 1
while(epsilon > 0.01) {
  epsilon <- epsilon/2
  print(epsilon)
}
[1] 0.5
[1] 0.25
[1] 0.125
[1] 0.0625
[1] 0.03125
[1] 0.015625
[1] 0.0078125

La línea 1 —que no forma parte del ciclo— inicializa el valor de la variable de control epsilon. Aunque no es necesario que la variable de control aparezca en la línea previa al inicio del ciclo, sí es necesario que se haya definido previamente.

¿Definir o no definir?

Puesto que el argumento del controlador while consiste en la evaluación de una condición, sí es necesario haber definido la variable de control previamente.

Esto contrasta con el uso de los índices en los ciclos for, donde, además de no estarse evaluando ninguna condición, se definen explícitamente los diferentes valores del índice. El proceso termina cuando el índice haya tomado todos los valores.

En resumen, los ciclos while requieren una condición lógica basada en una variable de control definida previamente, mientras que los ciclos for iteran automáticamente a través de los valores del índice.

La línea 2 del código 20.1 define la condición que se evalúa. El resultado de esta evaluación es un valor lógico (TRUE/FALSE).

Todas las líneas comprendidas entre las llaves (en este caso, las líneas 3 y 4) conforman el bloque de instrucciones que se ejecuta si el resultado de evaluar la condición establecida en la línea 2 es TRUE.

Lo invitamos a hacer un seguimiento mental de lo que sucede en cada uno de los ciclos. Si gusta, puede verificar a continuación si su razonamiento coincide con la siguiente descripción:

  1. La ejecución de la línea 1 le asigna el valor 1 a la variable de control epsilon

  2. Se evalúa si epsilon > 0.01. El resultado es TRUE. Esto da lugar a que se ejecute el bloque de instrucciones (líneas 3 y 4)

  3. Se actualiza el valor de la variable de control epsilon; queda valiendo 0.5

  4. Se imprime el valor de epsilon (0.5)

  5. Se evalúa nuevamente la condición establecida en la línea 2. Puesto que 0.5 > 0.01, se ejecutan las instrucciones del bloque

  6. Se actualiza el valor del índice y se imprime

  7. Se repite este procesovarias veces, hasta que, tras ejecutar 7 iteraciones, epsilon toma el valor 0.0078125. Se imprime.

  8. Al verificar la condición epsilon > 0.01, se obtiene FALSE, con lo cual finaliza el ciclo

¡Búsquele salida!

Al definir ciclos mediante el controlador de flujo while, es necesario asegurarse de que la condición planteada dejé de cumplirse en algún momento, para proporcionar una vía de salida normal, evitando caer un ciclo de reprocesamiento infinito, que podría hacer que el programa se congele o que el sistema consuma recursos excesivos.

El siguiente fragmento de código ilustra un ciclo que se procesaría sin fin:

epsilon <- 1
while(epsilon > 0) {
  epsilon <- epsilon + 1
  print(epsilon)
}

¡Asegúrese de incluir siempre una ruta de escape lógica en los ciclos while!

20.3 Número indefinido de ciclos: controlador repeat

Aunque los controladores for y while son las formas más prácticas y seguras de manejar ciclos, conviene mencionar también el controlador repeat, que puede resultar útil en ciertos contextos.

A continuación se presenta la sintaxis básica del operador de flujo repeat:

repeat {
  instrucciones
}

Puesto que el operador de flujo repeat no incorpora ni un índice ni una condición que determinen la finalización del ciclo, es necesario que las instrucciones incluyan una condición de salida. Lo usual es hacerlo mediante un condicional if anidado (cf. sección 20.5).

A continuación se ilustra la definición de un ciclo cuya terminación está definida por el cumplimiento de una condición sencilla. Lo concerniente a la formulación de condiciones se detalla en la sección 20.8.

i <- 0
repeat {
  i <- i + 1
  print(i)
  if (i == 7)
    break
}

Este ciclo se rompe cuando se cumple la condición i == 7, dando lugar a que se ejecute el modificador de iteración break (cf. sección 20.4).

La instrucción break siempre debe colocarse dentro de un condicional. De no hacerlo así, el ciclo se interrumpiría en la primera iteración.

Un conjunto equivalente de instrucciones con el controlador while tiene la siguiente forma:

i <- 0
while(i != 7) {
  i <- i + 1
  print(i)
}

En este ejemplo, repeat y while son funcionalmente equivalentes, pero while resulta preferible porque expresa la condición de terminación de forma explícita desde el encabezado del ciclo, lo que facilita la lectura y reduce el riesgo de errores lógicos que pudieran generar ciclos infinitos. Por tal razón, while es preferido sobre repeat en programación estructurada.

No obstante, repeat podría resultar útil en situaciones en las que la variable de control se genere al interior del ciclo, tal y como se ilustra a continuación.

repeat {
  x <- runif(1)
  print(x)
  if (x > 0.99)
    break
}

En este tipo de escenarios, donde el criterio de parada depende de valores generados dinámicamente dentro del ciclo, repeat ofrece una alternativa natural y compacta. En la sección 20.4 se presenta nuevamente este fragmento de código, incluyendo sus salidas, y destacando el rol del modificador break.

20.4 Salto y fin forzado: modificadores de iteración break y next

Los ciclos for y while tienen mecanismos de terminación ordinarios: el primero finaliza cuando el índice ha recorrido todos los valores definidos; el segundo, cuando deja de cumplirse la condición lógica. Sin embargo, en ambos casos es posible forzar la salida del ciclo mediante modificadores de iteración como break y next.

Por su parte, los ciclos que se generan mediante el controlador de flujo repeat no incorporan un mecanismo de terminación ordinario o predecible, por lo que la única manera de interrumpirlos es modificando el flujo de los ciclos mediante break o next. Estos modificadores de iteración suelen ejecutarse tras la satisfacción de alguna condición establecida mediante un condicional if (cf. sección 20.5).

  • Cuando se ejecuta next, se omiten las instrucciones restantes de la iteración actual y se pasa a la siguiente

  • Al ejecutar break, se interrumpe de manera inmediata la iteración que se esté ejecutando y el ciclo completo, sin importar cuántas iteraciones falten

Considérese el siguiente ciclo for, que incorpora una condición interna, cuya satisfacción deriva en la ejecución del modificador de iteración next, que interrumpe una iteración específica:

for (dígito in 0:4) {
  if (dígito == 3)
    next
  print(dígito)
}
[1] 0
[1] 1
[1] 2
[1] 4

Nótese que en el ciclo anterior, cuando el dígito toma el valor 3, se salta su impresión; sin embargo, se continua con la siguiente iteración y se imprime 4. El ciclo finaliza cuando dígito hace el recorrido completo por todos los valores definidos mediante in.

Considérese ahora una situación análoga, en la que la satisfacción del condicional deriva en la ejecución de la instrucción break, que finaliza el ciclo anticipadamente, sin completar las iteraciones:

for (dígito in 0:4) {
  if (dígito == 3)
    break
  print(dígito)
}
[1] 0
[1] 1
[1] 2

En este caso no solo se termina la iteración que lanza el break, sin que alcance a imprimirse el valor que en ese momento tenía dígito (3), sino que se interrumpe el ciclo, sin importar que dígito no haya alcanzado a tomar todos los valores definidos mediante in. En este ciclo, dígito nunca toma el valor 4.


Consideremos a continuación un ciclo definido mediante repeat. Se instruye para generar un número aleatorio basado en la distribución uniforme continua y enviarlo a consola. Este proceso —cuyo resultado no puede predecirse— se realizará hasta que uno de los valores aleatorios obtenidos sea mayor que 0.99. En ese momento se dará por finalizado el ciclo.

repeat {
  x <- runif(1)
  print(x)
  if (x > 0.99)
    break
}
[1] 0.2216014
[1] 0.02423391
[1] 0.207119
[1] 0.2157335
[1] 0.4437236
[1] 0.1340762
[1] 0.3906578
[1] 0.3693158
[1] 0.668693
[1] 0.99211

En el presente ejemplo, la décima ejecución del ciclo genera el valor 0.99211, que se imprime en consola. Como este valor es mayor que 0.99, se cumple la condición del if y se ejecuta el break, finalizando el ciclo. Si se volviera a ejecutar el mismo ciclo, muy probablemente se ejecutaría un número de iteraciones diferente. ¡Pruébelo!

20.5 Condicionales simples: controlador if

El controlador de flujo if permite establecer una condición, bajo cuyo cumplimiento se ejecutarían las instrucciones, con base en el siguiente esquema:

if (condición) {
  instrucciones
}
¡Recuerde las llaves!

Cuando instrucciones consta de más de una instrucción, es obligatorio encerrarlas entre llaves (cf. sección 22.4).

En el siguiente ejemplo se ilustra el uso básico del condicional if, consistente en ejecutar las instrucciones si se satisface la condición y no hacer nada (saltar al siguiente bloque de instrucciones del script) en caso contrario.

if (length(x) != length(y)) {
  cat("¡Atención!")
  cat("\nLos vectores no tienen el mismo tamaño")
}

También puede usarse una secuencia de instrucciones similar a la anterior para detener la ejecución de un script, mediante la función stop, incluyendo un aviso personalizado, así:

if (length(x) != length(y))
  stop("Los vectores no tienen el mismo tamaño")
¡Tras de la anarquía hay un orden…!

Aunque el uso y la posición de las llaves parezca un tanto anárquico, existen convenciones de estilo ampliamente aceptadas, las cuales se presentan en el capítulo 22.

Tip 20.1: ¡Verifique la existencia de paquetes e instálelos!

Mediante el condicional if puede verificarse la existencia de un paquete que se requiera en un script, programando su instalación, con sus correspondientes dependencias, en caso de que no esté presente.

Para ello se usa la función require, que retorna TRUE si el paquete está disponible y queda cargado correctamente; de lo contrario, retorna FALSE, lo que la hace útil dentro de un condicional.

Para ilustración, considérese la evaluación de la existencia del paquete homals (cf. sección 4.2).

if (!require(homals)) {
  install.packages("homals", dependencies = T)
  library(homals)
}

¿Cuál es la lógica de la condición?

La instrucción require(homals) intenta cargar el paquete homals. Si el paquete no está instalado o no logra cargarse correctamente, devuelve FALSE. El operador de negación ! convierte este resultado en TRUE (cf. sección 20.8), lo que activa la ejecución del bloque de instrucciones: se instala el paquete homals con sus dependencias y se carga.

Si al ejecutar require(homals) el paquete homals queda cargado correctamente, se rertorna un TRUE, que se convierte en FALSE mediante el operador de negación !. En tal caso, se salta el bloque de instrucciones.

¡Es una práctica controvertida!

La práctica de instruir la instalación de paquetes dentro de una función o script puede resultar cuestionable.

Una alternativa menos intrusiva consistiría en verificar la disponibilidad del recurso requerido y, en caso de no estar presente, mostrar un mensaje que invite al usuario a instalarlo manualmente.

Si bien esta opción es más respetuosa, podría generar retrasos o incluso interrupciones en el flujo de trabajo, especialmente entre usuarios poco experimentados, quienes, ante un “error”, podrían desistir del uso de la función.

Al escribir funciones personalizadas, es importante tener en cuenta este aspecto y considerar cuidadosamente el publico objetivo de la función.

En el caso de funciones destinadas a empaquetarse y subirse a la CRAN, este comportamiento es estrictamente inaceptable: la función no puede instalar paquetes por sí misma.

20.6 Condicionales compuestos: controladores if...else e if...else if

El controlador de flujo if puede complementarse con el controlador else para especificar un conjunto de instrucciones alternativas que se ejecutarían en caso de no satisfacerse la condición, así:

if (condición) {
  instrucciones
}
else {
  instrucciones_alternativas
} 

La posición del controlador else del código anterior es la recomendada cuando se trabaja dentro de funciones. No obstante, al trabajar código aislado, es obligatorio ubicar el controlador else en la misma línea de cierre del if (cf. sección 22.4), así:

if (condición) {
  instrucciones
} else {
  instrucciones_alternativas
} 

El siguiente fragmento de código ilustra un ejemplo particular que podría aparecer dentro de una función:

if (length(x) != length(y)) {
  cat("¡Error!") 
  cat("\nLos vectores no tienen el mismo tamaño")
}
else {
  print(z <- x * y)
}

Es posible encadenar una serie de estructuras if ... else if para incluir todas las alternativas que sean necesarias, derivando a un conjunto de instrucciones diferente para cada uno de los casos considerados.

Estas estructuras tienen la siguiente sintaxis general:

if (condición1) {
  instrucciones1
}
else if (condición2) {
  instrucciones2
}
.
.
.
}
else if (condiciónk) {
  instruccionesk
}
.
.
.
}
else {
  instruccionesz
}

Si al final de la secuencia se incluye else —que no admite la especificación de ninguna condición—, esta categoría recogería cualquier caso no incluido en las categorías que se hubieran definido anteriormente. Si no se incluye esta última categoría, solo se consideran aquellas definidas explícitamente.

Supóngase, por ejemplo, que se desea calcular la tarifa para un evento, dependiendo del tipo de asistente.

if (tipo == "estudiante")
  tarifa <- 80
else if (tipo == "docente")
  tarifa <- 100
else if (tipo == "egresado")
  tarifa <- 150
cat("El valor de su inscripción es:", tarifa)

Una estructura similiar, en la que se usa else para recoger cualquier otro caso no incluido en las categorías definidas previamente, tendría el siguiente aspecto:

if (tipo == "estudiante")
  tarifa <- 80
else if (tipo == "docente")
  tarifa <- 100
else if (tipo == "egresado")
  tarifa <- 150
else
  tarifa <- 200
cat("El valor de su inscripción es:", tarifa)

20.7 Condicionales compuestos vectorizados: condicional ifelse

Una estructura equivalente a if ... else, aplicable a los elementos de un vector o algún otro contenedor de elementos (v. gr. matriz, arreglo, data frame. cf. capítulo 8) es ifelse.

En este caso se evalúa la satisfacción de una condición sobre cada uno de los elementos del contenedor y, para cada uno de ellos, se genera una respuesta en caso de satisfacerse la condición y otra distinta si no se satisface.

En tal sentido, actúa de manera similar al par de controladores if...else, con else obligatorio, pero sobre cada uno de los elementos de un contenedor. El resultado es un objeto del mismo tamaño y atributos que el objeto sobre cuyos elementos se evalúa la condición.

Esta es la sintaxis básica de este controlador:

ifelse (condición, valor_si_verdadero, valor_si_falso)

Considérese el siguiente código:

nota <- c(3.2, 4.3, 5.0, 1.5, 2.9)
ifelse (nota >= 3.0, "Aprobado", "Reprobado")
[1] "Aprobado"  "Aprobado"  "Aprobado"  "Reprobado" "Reprobado"
¡No es lo mismo ifelse que if...else!

Cuando se observa la instrucción:

ifelse (nota >= 3.0, "Aprobado", "Reprobado")

podría pensarse que se trata de una forma simplificada de escribir:

if (nota >= 3.0)
  resultado <- "Aprobado"
else
  resultado <- "Reprobado"

Sin embargo, las diferencias van más allá de la mera sintaxis:

  1. En ifelse, cada evaluación TRUE o FALSE da lugar a una única acción. En cambio, if...else permite ejecutar bloques de instrucciones para cada resultado.

  2. Al usar ifelse, la acción consiste en hacerle corresponder un valor a cada uno de los elementos del objeto de entrada. Por su parte, los controladores if...else permiten realizar acciones de diversa índole.

  3. if...else evalúa la condición una sola vez, es decir, de forma escalar. En contraste, ifelse la evalúa sobre cada uno de los elementos de un objeto, es decir, de manera vectorizada.

20.8 Formulación de condiciones

De los operadores de flujo cubiertos en este capítulo, if con sus variantes y while dependen de la satisfacción de una condición, ya sea simple o compuesta. Para su formulación, puede usarse cualquier instrucción cuya evaluación produzca un valor lógico TRUE/FALSE.

Así, por ejemplo, la evaluación de la clase o el tipo de un objeto, mediante funciones como is.numeric, is.character, is.factor o is.data.frame puede aparecer en la formulación de una condición. También son comunes otras funciones informativas como is.atomic, is.na, anyNA, is.nan, is.finite, isFALSE e isTRUE.

¿isTRUE?

La función isTRUE evalúa si una expresión es verdadera, devolviendo un resultado TRUE. En cualquier otro caso —incluyendo FALSE, NA, NaN, NULL o vectores lógicos— el resultado es FALSE.

Puesto que la forma más común de formular una condición es directamente con una expresión lógica, el uso de isTRUE puede parecer redundante. Sin embargo, constituye una forma robusta de evaluar condiciones, evitando errores ante entradas diferentes de TRUE o FALSE.

Considérese el siguiente código:

x <- NA
if (x > 0)
  cat("Mayor que cero")

Al evaluar x > 0, el resultado obtenido es NA. Y puesto que la evaluación de if exige un valor lógico (TRUE o FALSE), se genera un error y el proceso se detiene.

Considérese un código equivalente, usando isTRUE:

x <- NA
if (isTRUE(x > 0))
  cat("Mayor que cero")

Al evaluar x > 0, el resultado obtenido es NA, y al evaluar isTRUE(NA) se obtiene FALSE, que es una entrada válida para if, lo que permite que el proceso continúe su curso normal, sin generar errores ni advertencias1.

isTRUE resulta especialmente útil en funciones con entradas inciertas, como NA, NaN, NULL o vectores lógicos.

¿isFALSE?

La función isFALSE es el complemento lógico y funcional de isTRUE. Evalúa si una expresión es falsa (FALSE), devolviendo un resultado TRUE. En cualquier otro caso —incluyendo TRUE, NA, NaN, NULL o vectores lógicos— el resultado es FALSE.

Al igual que isTRUE, isFALSE permite escribir condicionales más generales en funciones con entradas inciertas.

¿Entonces, son más robustos y seguros?

Puesto que isTRUE e isFALSE permiten manejar situaciones en las que la alternativa de la condición planteada da lugar a valores diferentes de un escalar lógico TRUE/FALSE, suelen presentarse como opciones seguras y robustas para evaluar la satisfacción de condiciones.

En tal sentido, podría creerse que una buena práctica consistiría en usarlos siempre como filtros intermedios en los condicionales con if y while. Esto, sin embargo, tiene sus matices.

isTRUE e isFALSE son de gran utilidad al escribir condicionales cuyo proceso de evaluación normal pudiera producir valores diferentes de un escalar lógico TRUE/FALSE. Su uso es definitivamente recomendado en tales situaciones.

Si, en contraste, se plantea un condicional en el que cualquier valor diferente de un escalar lógico TRUE/FALSE solo surgiría por una especificación incorrecta, podría resultar preferible que la función se detenga y obligue al usuario a repensar su uso, en lugar de generar una salida limpia que quizá no corresponda con la intención del usuario.

La formulación de condiciones a menudo conlleva el uso de operadores lógicos, es decir, operadores cuya evaluación genera un valor lógico. La tabla 20.1 presenta algunos de los más comunes.

Tabla 20.1: Operadores lógicos más comunes en R
Operador Descripción
< Menor que
<= Menor o igual que
> Mayor que
>= Mayor o igual que
== Igual
!= Diferente
! Negación
%/% División entera
%% Módulo o residuo
%in% Incluido en
& Y lógico
| O lógico
&& Y lógico de cortocircuito
|| O lógico de cortocircuito

Los primeros seis operadores de la tabla 20.1 se usan para comparar dos expresiones: una a la izquierda del operador; la otra a la derecha. Cuando tales comparaciones están adecuadamente planteadas, su evaluación genera un valor lógico TRUE/FALSE.

¡No es igual!

Es importante destacar la diferencia existente entre el operador de igualdad == y el operador =.

El operador de igualdad == se utiliza para verificar la satisfacción de una condición (que la expresión a la izquierda del operador tenga igual valor que la de su derecha).

El operador = se utiliza para asignar un valor a un argumento dentro de una función o como operador de asignación dentro de la función transform (cf. sección 16.2)2.

El símbolo ! puede anteponerse a cualquiera de las funciones de información presentadas al comienzo de esta sección (!is.numeric, !is.atomic, !is.na…) para generar preguntas negativas: no es numérico, no es atómico, no es NA

Asimismo, pude usarse ! para cambiar el valor de un resultado lógico:

!TRUE
[1] FALSE
!FALSE
[1] TRUE

El operador %/% ubicado entre dos escalares realiza la división entera entre el escalar de la izquierda y el de la derecha.

27 %/% 4
[1] 6

El operador %% ubicado entre dos escalares devuelve el residuo de realizar la división entera entre el escalar de la izquierda y el de la derecha.

27 %% 4
[1] 3

Este operador podría utilizarse para definir si un número es par o impar:

a <- round(runif(1, 0, 100), 0)
if (a %% 2 == 0)
  cat(a, "es par")
else
  cat(a, "es impar")

La línea 1 calcula un valor aleatorio de la distribución uniforme continua entre 0 y 100. La función round lo redondea con 0 cifras decimales, es decir, que lo convierte en un entero entre 0 y 100.

En la línea 2 se usa el operador módulo %%, para evaluar si el residuo de dividir el valor aleatorio entre 2 es 0.

En caso de satisfacerse la condición establecida en la línea 2, se ejecuta la línea 3. En caso contrario, se ejecuta la línea 5.


El operador %in% se usa para evaluar si el valor de la izquierda está contenido en el vector de la derecha. Este operador devuelve TRUE si el valor de la izquierda se encuentra dentro del vector de la derecha; de lo contrario, devuelve FALSE.

Considérense los siguientes fragmentos de código:

"d" %in% c("a", "e", "i", "o", "u")
[1] FALSE
"d" %in% c("a", "b", "c", "d", "e")
[1] TRUE

20.8.1 Condiciones compuestas

Los operadores lógicos &, |, && y || permiten combinar varias expresiones condicionales.

Cuando se usa alguno de los operadores & o && —que se interpretan como Y— es necesario que todas las condiciones evaluadas resulten TRUE para que el resultado general sea TRUE.

if (a %% 2 == 1 & b %% 2 == 1)   # Usando el operador &
  cat("Ambos números son impares")

# Equivalentemente:  
if (a %% 2 == 1 && b %% 2 == 1)  # Usando el operador && 
  cat("Ambos números son impares")

Cuando se usa alguno de los operadores | o || —que se interpretan como O— basta con que al menos una de las condiciones evaluadas resulte TRUE para que el resultado general sea TRUE.

if (a == 0 | b == 0)   # Usando el operador |
  cat("El producto es cero")

# Equivalentemente:  
if (a == 0 || b == 0)  # Usando el operador || 
  cat("El producto es cero")

Los resultados producidos por los operadores lógicos de un símbolo pueden ser iguales a los producidos por los operadores lógicos de doble símbolo. Sin embargo, la manera en la que se evalúan condiciones compuestas varía.

Cuando se usan operadores lógicos & o |, se evalúa cada una de las condiciones antes de determinar el resultado de la evaluación conjunta. En contraste, && y || son operadores de cortocircuito, lo que implica que la evaluación se detiene tan pronto como el resultado pueda determinarse. Esto resulta útil cuando la evaluación de la segunda expresión es costosa o puede causar errores, por ejemplo, cuando se intenta acceder a objetos inexistentes.

  • La evaluación de FALSE && cualquier_expresión genera un resultado FALSE en cuanto se lee el valor FALSE que aparece a la izquierda del operador &&, sin necesidad de evaluar cualquier_expresión.

  • De forma análoga, la evaluación de TRUE || cualquier_expresión genera un resultado TRUE en cuanto se lee el valor TRUE que aparece a la izquierda del operador ||, sin necesidad de evaluar cualquier_expresión.

¿¡Cortocircuito!?

Los operadores lógicos && y || son opciones más eficientes que sus contrapartes & y | para formular condiciones dentro de estructuras de control como if y while, pues evitan evaluaciones innecesarias mediante cortocircuito.

¿¡Lógicos vectorizados o escalares!?

Existe otra diferencia entre el grupo de operadores lógicos de un solo símbolo (& y |), también llamados operadores lógicos vectorizados y el grupo de operadores lógicos de doble símbolo (&& y ||), también llamados operadores lógicos escalares.

Los operadores lógicos vectorizados permiten evaluar la satisfacción de condiciones compuestas sobre cada uno de los elementos de un vector, dando como resultado un vector de valores lógicos, con valores TRUE para cada uno de los elementos que satisfacen la condición compuesta, y FALSE en caso contrario.

En contraste, los operadores lógicos escalares únicamente permiten evaluar condiciones compuestas en las que participen escalares. Si se usan para evaluar condiciones sobre vectores de longitud mayor que 1, generan error.

El siguiente código ilustra cómo podría aprovecharse la característica de los operadores lógicos vectorizados para evaluar una condición compuesta sobre cada uno de los elementos de un vector:

Código 20.2
x <- runif(20)
ext <- x < 0.05 | x > 0.95
for (i in 1:20) {
  if (ext[i])
    cat("La observación", i, "es extrema\n")
}

La línea 1 genera 20 valores aleatorios basados en la distribución uniforme continua y los asigna al vector x.

En la línea 2 se usa el operador lógico vectorizado | para evaluar la satisfacción de una condición compuesta sobre cada uno de los elementos de x: que el valor sea menor que 0.05 o mayor que 0.95. El correspondiente resultado lógico para cada uno de los elementos de x se guarda en el vector de valores lógicos ext.

¡Solo operadores lógicos vectorizados!

Si en la línea 2 se hubiera usado el operador escalar || en lugar del operador vectorizado |, se habría generado un error y el proceso se habría detenido.

La línea 3 define el inicio de un ciclo for para recorrer cada uno de los elementos del vector de valores lógicos ext.

En la línea 4 se inicia un condicional if, que, al estar dentro de un ciclo for, se evalúa para el \(i-\)ésimo elemento del vector ext.

A primera vista puede parecer extraño que ext[i] funcione como argumento del if, ya que no formula explícitamente ninguna condición. No obstante, el uso de un valor lógico también constituye una opción válida. Un valor TRUE da lugar a la ejecución de instrucciones contenidas en el bloque if. La instrucción if (ext[i]) se lee: “Si ext[i] es TRUE”. Este mismo concepto es válido para el argumento de los ciclos while.

La línea 5 ejecuta la acción de exhibir un aviso para los valores extremos.

¡Únicamente por fuera!

La funcionalidad que tienen los operadores lógicos vectorizados de evaluar la satisfacción de una condición compuesta sobre cada uno de los elementos de un vector puede usarse como paso previo a un if o while, pero no directamente para definir las condiciones dentro de estas estructuras.

Los controladores de flujo if y while exigen que las condiciones estén formuladas de manera tal que, al evaluarse se produzca un valor lógico escalar TRUE/FALSE.

Asimismo, puede usarse un escalar lógico TRUE/FALSE proveniente de una evaluación previa, pero no se admiten vectores lógicos de longitud mayor que 1.

La manera de incluir las evaluaciones realizadas previamente sobre cada uno de los elementos de un vector mayor que 1 está mediada por el uso de un ciclo for.


  1. Cuando hay varios paréntentesis, las evaluaciones se realizan de los más internos hacia los más externos.↩︎

  2. Fuera de estos contextos, se desaconseja su uso como operador de asignación en lugar del operador <- (cf. capítulo 22).↩︎