Chapter 3 Programación intermedia en R

3.1 Condicionales y flujos de control

3.1.1 Operadores relacionales

Los operadores relacionales nos van a permitir comparar objetos o elementos de R. Existen varios tipos: igualdad, desigualdad, mayor y menor que, mayor o igual y menor o igual que. Veámoslos primeros con objetos que contienen un solo elemento y luego los veremos con vectores y matrices.

3.1.1.1 Comparar un único elemento

3.1.1.1.1 Igualdad (==)

Para ver si dos objetos son iguales, se utiliza el doble símbolo de igualdad ==.

TRUE == TRUE # vemos si TRUE es igual a TRUE.
## [1] TRUE
TRUE == FALSE # vemos si TRUE es igual a FALSE
## [1] FALSE
"hola" == "adiós" # vemos si "hola" es igual a "adiós"
## [1] FALSE
1 == 2 # vemos si 1 es igual a 2
## [1] FALSE
2*3 == 9-3
## [1] TRUE

Como vemos, el resultado nos devuelve un resultado lógico TRUE o FALSE.

3.1.1.1.2 Desigualdad (!=)

Para ver si dos objetos son diferentes, se utiliza el doble símbolo de desigualdad !=.

TRUE != TRUE # vemos si TRUE es diferente a TRUE.
## [1] FALSE
TRUE != FALSE # vemos si TRUE es diferente a FALSE
## [1] TRUE
"hola" != "adiós" # vemos si "hola" es diferente a "adiós"
## [1] TRUE
1 != 2 # vemos si 1 es diferente a 2
## [1] TRUE

Como vemos, el resultado nos devuelve un resultado lógico TRUE o FALSE.

3.1.1.1.3 Mayor y menor que (< y >)

Para ver si un objeto es mayor que otro, esto es fácil de ver con valores numéricos.

3 > 2 # 3 es mayor que 2
## [1] TRUE
2 > 3 # 2 es mayor que 3
## [1] FALSE

Pero, ¿cómo sabemos si una cadena de caracteres es “mayor” que otra? ¿Y un valor lógico respecto a otro?

# En una cadena de caracteres, se utiliza el orden alfabético para saber si mayor o menor que
"hola" > "adiós" # como la H va después que la A, R considera que es mayor
## [1] TRUE
"Hola" > "hola" # la comparación es sensible a mayúsculas. Pesa más minúsculas
## [1] TRUE
"hola" > "hola y adiós" # cuanto más caracteres haya, más valor tendrá
## [1] FALSE
# En una varaible lógico con TRUE y FALSE, R asigna el valor de 1 a TRUE y de 0 a FALSE
TRUE > FALSE
## [1] TRUE
FALSE > TRUE
## [1] FALSE
TRUE == 1 # comprobación del valor de TRUE
## [1] TRUE
FALSE == 0 # comprobación del valor de FALSE
## [1] TRUE
3.1.1.1.4 Mayor o igual y menor o igual que (<= y >=)

Funciona de forma similar al caso anterior.

3 <= 3
## [1] TRUE
3 >= 2
## [1] TRUE
"hola" >= "hierba"
## [1] TRUE
TRUE <= TRUE
## [1] TRUE

3.1.1.2 Comparar vectores

Vamos a verlo con un ejemplo.

# Visitas a mi web en la última semana
visitas_web <- c(251, 365, 158, 485, 325, 598, 478)

# ¿Han superado las 400 visitas diarias?
visitas_web > 400
## [1] FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE

Siguiendo con el ejemplo anterior, vamos a comparar las visitas de la web de competencia.

# Generamos el vector
web_competencia <- c(548, 254, 457, 375, 471, 698, 215) 

# Comparamos nuestros datos con los de la competencia
visitas_web > web_competencia # ¿Son mayores nuestras visitas que las de la competencia, comparando uno a uno los días?
## [1] FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE

3.1.2 Comparar matrices

Primero vamos a unir los dos vectores anteriores visitas_web y web_competencia en una matriz.

web <- matrix(c(visitas_web, web_competencia), nrow = 2, byrow = TRUE, dimnames = list(c("Web mía", "Web competencia"), c("L", "M", "M", "J", "V", "S", "D")))

¿Cuántos días las visitas fueron superiores o iguales a 500?

web >= 500
##                     L     M     M     J     V    S     D
## Web mía         FALSE FALSE FALSE FALSE FALSE TRUE FALSE
## Web competencia  TRUE FALSE FALSE FALSE FALSE TRUE FALSE

3.1.3 Operadores lógicos

Los operadores lógicos nos permiten combinar valores lógicos o booleanos.

Los hay de 3 tipos:

  • AND, representado por &
  • OR, representado por |
  • NOT, representado por !

3.1.3.1 Comparar un único elemento

3.1.3.1.1 Operador AND (&)

El operador AND se escribe con el símbolo &.

# Cuando tenemos valores lógicos, devuelve solo TRUE cuando los dos operadores lógicos son TRUE
TRUE & TRUE
## [1] TRUE
TRUE & FALSE
## [1] FALSE
FALSE & TRUE
## [1] FALSE
FALSE & FALSE
## [1] FALSE

Interpretemos lo anterior con el siguiente ejemplo numérico.

# Generamos un objeto "x" con un valor
x <- 12

# Veamos si cumple el requisito de ser mayor de 5 y (AND) menor de 15.
x > 5 & x < 15
## [1] TRUE
# Veámoslo en detalle
x > 5  # es TRUE
## [1] TRUE
x < 15 # es TRUE
## [1] TRUE
# Por eso TRUE y TRUE da TRUE.

# Si lo hacemos con otro valor
x <- 17
x > 5 & x < 15
## [1] FALSE
# Da FALSE porque la segunda parte es FALSE.
3.1.3.1.2 Operador OR (|)

El operador OR se escribe con el símbolo |.

# Cuando tenemos valores lógicos, devuelve TRUE cuando aprece al menos un TRUE
TRUE | TRUE
## [1] TRUE
TRUE | FALSE
## [1] TRUE
FALSE | TRUE
## [1] TRUE
FALSE | FALSE
## [1] FALSE

Interpretemos lo anterior con el siguiente ejemplo numérico.

# Generamos un objeto "y" con un valor
y <- 4

# Veamos si cumple el requisito de ser menor de 5 o (OR) mayor de 15.
y < 5 | y > 15
## [1] TRUE
# Veámoslo en detalle
y < 5  # es TRUE
## [1] TRUE
y > 15 # es FALSE
## [1] FALSE
# Como hay un TRUE, devuelve TRUE.

# Si lo hacemos con otro valor
y <- 14
y < 5 | y > 15
## [1] FALSE
# Da FALSE porque los dos son FALSE.
3.1.3.1.3 Operador NOT (!)

El operador NOT se escribe con el símbolo !. Este operador niega el valor lógico usado y devuelve el contrario.

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

Vamos a acumular NOT’s.

!!TRUE # El primer TRUE se convierte en FALSE con el primer NOT, y se convierte en TRUE nuevamente con el segundo NOT.
## [1] TRUE
!!!FALSE # El primer FALSE se convierte en TRUE con el primer NOT, se convierte en FALSE con el segundo NOT, y en TRUE con el tercero
## [1] TRUE

De hecho funciona como dando la solución opuesta. Veámoslo.

!(x < 5) # este valor de x con el operador NOT
## [1] TRUE
x >= 5   # es exactamente lo mismo que esta expresión
## [1] TRUE

Es muy útil cuando se construyen funciones.

is.numeric(5)
## [1] TRUE
!is.numeric(5)
## [1] FALSE
is.numeric("hola")
## [1] FALSE
!is.numeric("hola")
## [1] TRUE

3.1.3.2 Comparar vectores

De forma análoga a un único elemento, comparar vectores con operadores lógicos lo hace comparando uno a uno. Veámoslo.

# Coge el primer elemento del primer vector y lo compara con el primer elemento del segundo.
# Y así sucesivamente.
# Nos devuelve un vector con las comparaciones.
c(TRUE, TRUE, FALSE, FALSE) & c(TRUE, FALSE, TRUE, FALSE) # usando AND
## [1]  TRUE FALSE FALSE FALSE
c(TRUE, TRUE, FALSE, FALSE) | c(TRUE, FALSE, TRUE, FALSE) # usando OR
## [1]  TRUE  TRUE  TRUE FALSE
!c(TRUE, TRUE, FALSE, FALSE) # usando NOT
## [1] FALSE FALSE  TRUE  TRUE

En relación con nuestros datos web:

# En nuestra web, ¿qué días tuvimos más de 450 visitas y en la de la competencia menos de 400?
visitas_web > 450 & web_competencia < 400
## [1] FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE

3.1.3.3 Comparar matrices

Vamos a seguir usando la matriz creada anteriormente web.

web
##                   L   M   M   J   V   S   D
## Web mía         251 365 158 485 325 598 478
## Web competencia 548 254 457 375 471 698 215

¿Cuándo las visitas se situaron entre 450 (sin incluir) y 500 (incluyéndolo)?

web > 450 & web <= 500
##                     L     M     M     J     V     S     D
## Web mía         FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE
## Web competencia FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE

3.1.3.4 & vs. && y | vs. ||

En R pueden usarse ambos, pero el resultado lógico puede ser diferente, principalmente si estamos trabajando con vectores. En el caso de los vectores, solo nos devuelve un resultado ya que compara única y exclusivamente el primer elemento de cada vector.

# Usamos el doble && y || sobre el mismo ejemplo que antes
c(TRUE, TRUE, FALSE, FALSE) && c(TRUE, FALSE, TRUE, FALSE) # usando AND. Comparar primeros elementos de cada vector. TRUE & TRUE
## [1] TRUE
c(TRUE, TRUE, FALSE, FALSE) || c(TRUE, FALSE, TRUE, FALSE) # usando OR. Comparar primeros elementos de cada vector. TRUE |  TRUE
## [1] TRUE

Vemos que ambas comparaciones devuelven un único TRUE.

3.1.3.5 !NOT. Da la vuelta al resultado

¿Qué devuelve esta expresión?

x <- 5
y <- 7

!(!(x < 4) & !!!(y > 12))
## [1] FALSE

Vamos a verlo por partes:

  • x < 4 es FALSE, ya que x <- 5. Con su NOT delantes se convierte en TRUE.
  • y > 12 es FALSE, ya que y <- 7. Con sus 3 NOT delante, se convierte en TRUE -> FALSE -> TRUE.
  • Por lo tanto, la expresión AND de TRUE & TRUE es TRUE.
  • Si empleamos ahora el primer NOT de la expresión, el resultado final es FALSE.

3.1.3.6 sum() para sumar los TRUE

sum(visitas_web > 400) # suma de los TRUE (días) con más de 400 visitas
## [1] 3

3.1.4 Sentencias condicionales

Teniendo en cuenta los operadores relacionales y lógicos, R nos ofrece un modo de utilizar sus resultados para cambiar el comportamiento de nuestros scripts de R, concretamente de if y else

3.1.4.1 if(condition) {} else {}

Si la condición de if es TRUE, se ejecuta la expresión de su interior. La expresión básica es:

# Expresión básica de if
if(condición) {
  expresión
}
# Ejemplo básico. Como el valor es negativo, y la condición es que sea negativo, se ejecutará la expresión porque es TRUE.
x <- -2

if (x < 0) {
  print("x es un número negativo")
}
## [1] "x es un número negativo"
# Si ahora es un valor positivo y la condición es que sea negativo, el resultado es FALSE y no se ejecutará la expresión.
x <- 10

if (x < 0) {
  print("x es un número negativo")
}

Como hemos visto, si no se cumple la condición, no se ejecuta la expresión y no aparece ningún resultado. Esto se soluciona con else. else siempre va asociado a if, y ejecuta su expresión siempre y cuando no se cumpla la condición de if. Es importante destacar que else siempre va en la misma línea que la llave cerrada } del if. Su expresión es:

# Estructura básica de if y else
if(condición){
  expresión1
} else {
  expresión2
}
# Como -2 es negativo, la condición de ser negativo es TRUE y se ejecuta la expresión de if
x <- -2

if (x < 0) {
  print("x es un número negativo")
} else {
  print("x es un número positivo ó 0")
}
## [1] "x es un número negativo"
# Como 10 es negativo, la condición de ser negativo es FALSE y se ejecuta la expresión de else
x <- 10

if (x < 0) {
  print("x es un número negativo")
} else {
  print("x es un número positivo ó 0")
}
## [1] "x es un número positivo ó 0"

Podemos añadir tantos else como consideremos dentro de nuestro código, añadiendo else if, teniendo en cuenta que siempre va a ir leyendo de arriba hacia abajo, y en cuanto encuentre un TRUE ejecutará la expresión.

# Expresión básica
if (condición1) {
  expresión1
} else if (condición2) {
  expresión2
} else if (condición3) {
  expresión3
} else {
  expresión4
}
# Ejemplo
x <- 5

if (x < 0) {
  print("x es un número negativo")
} else if (x == 0) {
  print ("x es igual a 0")
} else {
  print("x es un número positivo")
}
## [1] "x es un número positivo"

En el ejemplo anterior, si x <- 5, la primera condición es FALSE (x < 0), la segunda condición es FALSE también (x == 0), por lo que se ejecuta la expresión de else. Si x <- 0, la primera condición sería FALSE, mientras que la segunda sería TRUE, ejecutando la expresión print("x es igual a 0").

Es importante recordar que R va leyendo el código hasta que encuentra un TRUE, momento en el que ignora el resto del código situado por debajo. Si las condiciones no son mutuamente excluyentes, R se queda con el primer TRUE que encuentre.

# En este ejemplo, las dos primeras condiciones son TRUE, pero solo se ejecuta la primera (x < 0) porque es la primera que lee R, ignorando el resto del código.
x <- -5

if (x < 0) {
  print("x es un número negativo")
} else if (x < -3 & x >= -10) {
  print ("x es menor que -3")
} else {
  print("x es un número positivo ó 0")
}
## [1] "x es un número negativo"

3.1.4.2 Encadenando if y else

Podemos encadenar if y else dentro de expresiones if y else anteriores.

numero <- 7

if (numero < 10) {
  if (numero < 5) {
    result <- "extra pequeño"
  } else {
    result <- "pequeño"
  }
} else if (numero < 100) {
  result <- "medio"
} else {
  result <- "grande"
}
print(result)
## [1] "pequeño"

3.2 Loops

3.2.1 while loop

El while loop es similar a if, ya que ejecuta el código de su interior si la condición es TRUE. A diferencia de if, while loop se ejecuta continuamente mientras que la condición sea TRUE. La estructura básica es:

while (condición) {
  expresión
}
control <- 1

while(control <= 5) {
  print(paste("Control es", control))
  control <- control + 1
}
## [1] "Control es 1"
## [1] "Control es 2"
## [1] "Control es 3"
## [1] "Control es 4"
## [1] "Control es 5"

En el while anterior, control <- 1, por lo que la condición control <= 5 es TRUE. Pero también es TRUE para control <- 2 ó control <- -5.

En la expresión anterior te devuelve el resultado con la función paste(), así como te hace una suma del valor usado de control + 1.

Si no pusiéramos la expresión final control <- control + 1 entraría en bucle indefinido porque siempre tendría el valor de 1, y esto siempre sería TRUE según la condición establecida.

Por eso es básico poner siempre una expresión de fin que haga en algún momento que sea FALSE.

# Introducimos la velocidad inicial
velocidad <- 80

# Preparamos el while loop
while (velocidad > 40) {
  print(paste("Tu velocidad es de", velocidad))
  if (velocidad > 60 ) {
    print("¡Pisa el freno! ¡Vas muy rápido!")
    velocidad <- velocidad - 10
  } else {
    print("¡Un poquito más despacio!")
    velocidad <- velocidad - 10
  }
}
## [1] "Tu velocidad es de 80"
## [1] "¡Pisa el freno! ¡Vas muy rápido!"
## [1] "Tu velocidad es de 70"
## [1] "¡Pisa el freno! ¡Vas muy rápido!"
## [1] "Tu velocidad es de 60"
## [1] "¡Un poquito más despacio!"
## [1] "Tu velocidad es de 50"
## [1] "¡Un poquito más despacio!"

3.2.1.1 Sentencia break para parar a while

La sentencia break simplemente rompe el while loop. Cuando R lo encuentra, abandona el look activo en ese momento. Y esto lo podemos introducir con un if.

control <- 1

while(control <= 5) {
  if(control %% 3 == 0) {
    break
  }
  print(paste("Control es", control))
  control <- control + 1
}
## [1] "Control es 1"
## [1] "Control es 2"

Analicemos el resultado anterior:

  • Cuando control <- 1, el resto de la división 1 %% 3 es 1. Como no es 0, hace el siguiente loop.
  • Cuando control <- 2, el resto de la división 2 %% 3 es 2. Como no es 0, hace el siguiente loop.
  • Cuando control <- 3, el resto de la división 3 %% 3 es 0. Como esta es la condición para el break, rompe el while loop y no sigue. Por eso no aparece la línea de Control es 3.

3.2.2 for loop

for loop funciona de un modo algo diferente al while loop. La expresión básica es:

for(var in seq) {
  expresión
}

Esto puede leerse del siguiente modo: para cada variable (var) de una secuencia (seq), ejecuta la expresión. Veámoslo con un ejemplo:

# Definimos un vector llamado yacimientos
yacimientos <- c("Gran Dolina", "Galería", "Elefante", "Sima de los Huesos", "Fantasma")

# Generamos el for loop diciendo que nos imprima de modo independiente cada objeto
for(yac in yacimientos) {
  print(yac)
}
## [1] "Gran Dolina"
## [1] "Galería"
## [1] "Elefante"
## [1] "Sima de los Huesos"
## [1] "Fantasma"

Como vemos, dentro de las opciones de for hemos incluido la variable con la que vamos a identificar a cada elemento del vector (yac) haciendo referencia a dónde estarán los objetos, en esta ocasión, en el vector yacimientos. Es destacable mencionar que el nombre yac lo hemos seleccionado nosotros para que el for sepa cómo llamar a los elementos, pero no hay que definirlo antes del for.

for lo primero que hace es identificar el vector, y observa que hay 5 elementos dentro de él. Después, coge el primer elemento y le asigna el nombre de yac y ejecuta la expresión. Una vez realizada, coge el segundo elemento dándole también el nombre de yac y vuelve a ejecutar la expresión. Así sucesivamente hasta que lo hace con todos los elementos.

Veamos un ejemplo numérico:

# Generamos un vector numérico llamado numero, con los valores entre 1 y 10
numero <- c(1:5)

# Queremos que el for nos dé elevado al cubo cada uno de los elementos
for(cubo in numero) {
  print(cubo^3)
}
## [1] 1
## [1] 8
## [1] 27
## [1] 64
## [1] 125

Más interesante si cabe, for loop no sólo trabaja con vectores, sino con cualquier objeto de R, incluyendo listas, matrices, data frames, etc.

3.2.2.1 Sentencias de control: break y next

La sentencia break hace lo mismo que en while, parar la ejecución del código y abandonar el loop. Veamos un ejemplo:

# Supongamos que queremos parar el for loop cuando un yacimiento tenga 7 letras
for(yac in yacimientos) {
  if(nchar(yac) == 7) {
    break
  }
  print(yac)
}
## [1] "Gran Dolina"

Como vemos, Galería tiene 7 letras, por lo que se ha parado ahí el for loop habiendo ejectuado solo el primer elemento, Gran Dolina.

La sentencia next hace que el for loop se salte el elemento que cumple esa condición.

# Supongamos que queremos saltar el for loop cuando un yacimiento tenga 7 letras
for(yac in yacimientos) {
  if(nchar(yac) == 7) {
    next
  }
  print(yac)
}
## [1] "Gran Dolina"
## [1] "Elefante"
## [1] "Sima de los Huesos"
## [1] "Fantasma"

En esta ocasión, el for loop se ha ejecutado completamente sin incluir Galería, que era el único elemento que tenía 7 letras.

3.2.2.2 Generar índices: [i], [j]

Imaginémonos ahora que nos interesa tener localizada exactamente la posición de cada elemento, para poder en un futuro hacer referencia o una llamada a ese elemento exclusivamente, es decir, hacer una especie de índice.

yacimientos <- c("Gran Dolina", "Galería", "Elefante", "Sima de los Huesos", "Fantasma")

for(i in 1:length(yacimientos)) { # esto es i in 1:5
  print(yacimientos[i])
}
## [1] "Gran Dolina"
## [1] "Galería"
## [1] "Elefante"
## [1] "Sima de los Huesos"
## [1] "Fantasma"

Lo que hacemos es definir dentro del for el índice i, que es el intervalo 1:5, ya que tenemos 5 yacimientos. En esta ocasión, al definir el índice i, no hace falta que generemos una nueva variable como antes yac. Simplemente añadimos en la expresión [i] asociada, en este caso, al nombre del vector yacimientos.

for(i in 1:length(yacimientos)) { # esto es i in 1:5
  print(paste(yacimientos[i], "está en la posición", i, "dentro del vector"))
}
## [1] "Gran Dolina está en la posición 1 dentro del vector"
## [1] "Galería está en la posición 2 dentro del vector"
## [1] "Elefante está en la posición 3 dentro del vector"
## [1] "Sima de los Huesos está en la posición 4 dentro del vector"
## [1] "Fantasma está en la posición 5 dentro del vector"

De este modo, ganamos control sobre la posición de cada elemento en el for loop.

Si tuviéramos más índices, y haciendo un encadenamiento de for loops, tendríamos una expresión como siguiente:

for (var1 in seq1) {
  for (var2 in seq2) {
    expr
  }
}

Donde var1 sería i y var2 sería j.

3.2.2.3 for en listas

Vamos a ver un ejemplo de cómo se comporta for con listas.

# Generamos la lista con información de Burgos obtenida de Wikipedia
burgos <- list(poblacion = 175623, 
            distritos = c("Centro-Norte", "Oeste", "Este", "Sur", "Periferia"), 
            capital = FALSE)

# Loop version 1 sin índice
for(b in burgos) {
  print(b)
}
## [1] 175623
## [1] "Centro-Norte" "Oeste"        "Este"         "Sur"         
## [5] "Periferia"   
## [1] FALSE
# Loop versión 2 con índice
for(i in 1:length(burgos)) {
  print(burgos[i])
}
## $poblacion
## [1] 175623
## 
## $distritos
## [1] "Centro-Norte" "Oeste"        "Este"         "Sur"         
## [5] "Periferia"   
## 
## $capital
## [1] FALSE
# Loop versión 3 con índice
for(i in 1:length(burgos)) {
  print(burgos[[i]])
}
## [1] 175623
## [1] "Centro-Norte" "Oeste"        "Este"         "Sur"         
## [5] "Periferia"   
## [1] FALSE

Cuando se utilizan índices [i], vemos que cada elemento de la lista viene definido de modo independiente con el símbolo de dólar $.

Cuando se utilizan índices [[i]], vemos que el resultado es el mismo que en la versión 1.

3.2.2.4 for en matrices

# Generamos una matriz con los datos del juego 3 en raya
resultado <- c("0", NA, "X", NA, "O", "O", "X", NA, "X")
tres_en_raya <- matrix(resultado, byrow=TRUE, nrow=3)

for (i in 1:nrow(tres_en_raya)) {
  for (j in 1:ncol(tres_en_raya)) {
    print(paste("En la fila", i, "y la columna", j, "el tablero contiene el valor", tres_en_raya[i, j]))
  }
}
## [1] "En la fila 1 y la columna 1 el tablero contiene el valor 0"
## [1] "En la fila 1 y la columna 2 el tablero contiene el valor NA"
## [1] "En la fila 1 y la columna 3 el tablero contiene el valor X"
## [1] "En la fila 2 y la columna 1 el tablero contiene el valor NA"
## [1] "En la fila 2 y la columna 2 el tablero contiene el valor O"
## [1] "En la fila 2 y la columna 3 el tablero contiene el valor O"
## [1] "En la fila 3 y la columna 1 el tablero contiene el valor X"
## [1] "En la fila 3 y la columna 2 el tablero contiene el valor NA"
## [1] "En la fila 3 y la columna 3 el tablero contiene el valor X"
# Visitas a nuestro perfil de linkedin por días
linkedin <- c(16, 9, 13, 5, 2, 17, 14)

# Código para el for loop con if/else
for (li in linkedin) {
  if (li > 10 ) {
    print("¡Eres popular!")
  } else {
    print("¡Sé más visible!")
  }
  print(li)
}
## [1] "¡Eres popular!"
## [1] 16
## [1] "¡Sé más visible!"
## [1] 9
## [1] "¡Eres popular!"
## [1] 13
## [1] "¡Sé más visible!"
## [1] 5
## [1] "¡Sé más visible!"
## [1] 2
## [1] "¡Eres popular!"
## [1] 17
## [1] "¡Eres popular!"
## [1] 14
# Ampliamos el for loop con break y next
for (li in linkedin) {
  if (li > 10) {
    print("You're popular!")
  } else {
    print("Be more visible!")
  }
  
  # Add if statement with break
  if(li > 16) {
  print("This is ridiculous, I'm outta here!")
  break
  }
  # Add if statement with next
  if(li < 5) {
    print("This is too embarrassing!")
    next
  }
  
  print(li)
}
## [1] "You're popular!"
## [1] 16
## [1] "Be more visible!"
## [1] 9
## [1] "You're popular!"
## [1] 13
## [1] "Be more visible!"
## [1] 5
## [1] "Be more visible!"
## [1] "This is too embarrassing!"
## [1] "You're popular!"
## [1] "This is ridiculous, I'm outta here!"

3.3 Funciones

Las funciones son típicamente como una caja negra, a la que se introducen unos datos, la caja negra los procesa, y te devuelve unos resultados. Típicamente sería:

Input -> Caja negra (función) -> Output

Una función en R tiene siempre esta estructura: nombre() acompañado de paréntesis. Dentro de este paréntesis están los argumentos, que son realmente las opciones que podemos introducir dentro de la función.

Vamos con un ejemplo, la función que calcula la desviación estándar sd().

3.3.1 Ayuda y argumentos de una función

Para acceder a la documentación de dicha función, podemos hacerlo de dos formas:

?sd      # con el símbolo de interrogación delante
help(sd) # o usando la función help con el nombre de la función en su interior

# Aunque si queremos ver solo los argumentos, podemos usar la función args()
args(sd)
## function (x, na.rm = FALSE) 
## NULL

3.3.2 Tipos de argumentos y modos de introducirlos

Los argumentos son de dos tipos:

  • Con valores sin predefinir. Vemos que en la función sd() tenemos dos argumentos. x es un argumento sin definir, y esto quiere decir que es obligatorio que introduzcamos su valor. En este caso se trata de los datos.
  • Con valores predefinidos. El segundo argumento, na.rm = FALSE está predefinido, y esto quiere decir que si al ejecutar la función no especificamos el valor de este argumento, de modo predeterminado ejecutará FALSE. Esto los convierte en argumentos opcionales.

Podemos introducir los argumentos de la función de dos modos:

  • Sin especificar el nombre del argumento. En este caso sigue el orden definido en la función.
  • Especificando el nombre del argumento. Podemos escribirlo en el orden que queramos.
datos <- c(3,5,2,6,7)

sd(x = datos)
## [1] 2.073644
sd(datos)
## [1] 2.073644
minutos <- c(16, 9, 13, 5, NA, 17, 14)

# Como hay datos sin valor, NA, el resultado de la media es NULL
mean(minutos)
## [1] NA
# Para cambiar este comportamiento, le indicamos que ignore los NA cambiando el argumento predeterminado de na.rm a TRUE (el predeterminado es FALSE)
mean(linkedin, na.rm = TRUE)
## [1] 10.85714

3.3.3 Encadenando funciones

Podemos introducir funciones dentro de funciones. De este modo las estamos encadenando.

minutos1 <- c(16, 9, 13, 5, NA, 17, 14)
minutos2 <- c(17, NA, 5, 16, 8, 13, 14)

# Calculmamos la media de las desviaciones absolutas encadenando varias funciones.
mean(abs(minutos1-minutos2), na.rm = TRUE)
## [1] 4.8

3.3.4 Escribiendo funciones

Vamos a ver la estructura básica de una función. Pero antes saber que la última expresión de la función es un return. Una cosa muy importante es que el nombre de los argumentos y de los objetos en el interior de la función solo tienen significado dentro de la función, no fuera. Por lo tanto, si definimos una x dentro de la función, al ejecutarla fuera por sí sola dará un error porque no la encuentra. Las funciones, dependiendo si le damos un nombre con lo que se convertirá en un objeto de R identificable o no, son de dos tipos: con nombre o anónimas.

3.3.4.1 Funciones con 1 argumento

# Función general
funcion <- function(arg1, arg2) {
  cuerpo
}

# La modificamos para generar una función que calcule el triple de un número
triple <- function(x) {
  x * 3
}

# Esto hace lo mismo
triple <- function(x) {
  y <- x * 3
  return(y)
}

# El resultado es 18
triple(6)
## [1] 18

3.3.4.2 Funciones con 2 ó más argumentos

# Vamos a verlo con dos argumentos
aritmetica <- function(a, b) {
  a*b + a/b
}

# Ponemos los dos valores en orden, 6 para *a* y 2 para *b*
aritmetica(6, 2)
## [1] 15
# Ahora vemos el error que sale cuando solo ponemos un valor, ya que no sabe cuál es el valor del segundo
# aritmetica(6)
# Nos sale este error: Error in aritmetica(6) : argument "b" is missing, with no default

# Para dar un valor predeterminado a un argumento, se pone en el paréntesis de function(). Le cambiamos el nombre a la función:
aritmetica_pred <- function(a, b=2) {
  a*b + a/b
}

# Ahora vemos que ya no da error
aritmetica_pred(6)
## [1] 15

3.3.4.3 Introducimos la función if()

# Ponemos el valor de 0 en *b*. Vemos que el resultado es Inf porque una división entre 0 es Inf
aritmetica(3, 0)
## [1] Inf
# Para evitar ese valor y que salga 0, hacemos lo siguiente. Una vez que ponemos *return()*, el resto no se ejecuta.
aritmetica <- function(a, b) {
  if(b == 0) {
    return(0)
  }
  a*b + a/b
}

aritmetica(3, 0)
## [1] 0
# Otro ejemplo
interpret <- function(num_views) {
  if (num_views > 15) {
    print("You're popular!")
    return(num_views)
  } else {
    print("Try to be more visible!")
    return(0)
  }
}

3.3.4.4 Funciones sin argumentos

En algunas ocasiones nos puede interesar crear funciones sin argumentos:

# Definimos la función hola
hola <- function() {
  print("¡Hola muchachos!")
  return(TRUE)
}

hola()
## [1] "¡Hola muchachos!"
## [1] TRUE

3.3.4.5 Combinando funciones, if() y for

# The linkedin and facebook vectors have already been created for you
linkedin <- c(16, 9, 13, 5, 2, 17, 14)
facebook <- c(17, 7, 5, 16, 8, 13, 14)

# The interpret() can be used inside interpret_all()
interpret <- function(num_views) {
  if (num_views > 15) {
    print("You're popular!")
    return(num_views)
  } else {
    print("Try to be more visible!")
    return(0)
  }
}

# Define the interpret_all() function
# views: vector with data to interpret
# return_sum: return total number of views on popular days?
interpret_all <- function(views, return_sum = TRUE) {
  count <- 0

  for (v in views) {
    count <- interpret(v) + count
  }

  if (return_sum == TRUE) {
    return(count)
  } else {
    return(NULL)
  }
}

# Call the interpret_all() function on both linkedin and facebook
interpret_all(linkedin)
## [1] "You're popular!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] "You're popular!"
## [1] "Try to be more visible!"
## [1] 33
interpret_all(facebook)
## [1] "You're popular!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] "You're popular!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] "Try to be more visible!"
## [1] 33

3.3.5 Paquetes de R

Los paquetes en R son conjuntos de funciones ya realizadas por programadores que van asociadas con su documentación, datos y con ejemplos para su correcto uso, de modo que sea fácilmente compartido con otros. De este modo, no tenemos por qué desarrollar las nuestras propias. El repositorio oficial de los miles de paquetes de R con sus funciones se encuentran en los servidores CRAN (Comprehensive R Archive Network).

De forma predeterminada, R carga al iniciarse 7 paquetes: stats, graphics, grDevices, utils, datasets, methods y base. Estos son los paquetes que nos permiten lanzar funciones como mean(), sd() o install.packages() sin tener que cargar explícitamente los paquetes que los contienen.

3.3.5.2 Función install.packages()

Esta función nos permite instalar paquetes introduciendo el nombre del mismo entre paréntesis.

# install.packages("ggplot2")

3.3.5.3 Función library()

Cuando los paquetes con las funciones que nos interesen no están cargados de forma predeterminada, tenemos que cargarlos para poder ejecutarlas. Eso lo conseguimos con library(). Esto quiere decir que no basta con instalarlos, sino que después tenemos que cargarlos para que sean operativos.

Se pueden cargar los paquetes poniéndolos con o sin comillas, por lo que el resultado es indistinto. Además, es importante señalar que solo se puede cargar un paquete cada vez.

# library(ggplot2)
# library("ggplot2")

# Como vemos, se puede poner con o sin comillas. El resultado es el mismo.

3.3.5.4 Función require()

Cumple la misma función que library(), es decir, cargar paquetes. Pero la principal diferencia es que cuando no está instalado un paquete, en vez de salir un error, nos aparece una advertencia (warning). Esto puede ser útil cuando estabamos con funciones largas y se necesitan paquetes concretos. Si no se encuentran, el error rompe la función, pero una advertencia no.

3.4 La familia apply

Esta familia de funciones hacen mucho más simple realizar tareas repetitivas e iterativas de un modo mucho más sencillo, ahorrando muchas líneas de código.

3.4.1 Función lapply()

La función lapply() trabaja con listas y vectores, pero siempre devuelve una lista. Veamos el ejemplo nuevamente de la lista de Burgos. Para poder repetir ese for con menos código, y puesto que estamos trabajando con una lista, usamos la función lapply.

Su estructura básica está compuesta por dos elementos: X es el vector o la lista y FUN es la función a aplicar sobre esos objetos. Sería así: lapply(X, FUN, ...).

lapply(burgos, print)
## [1] 175623
## [1] "Centro-Norte" "Oeste"        "Este"         "Sur"         
## [5] "Periferia"   
## [1] FALSE
## $poblacion
## [1] 175623
## 
## $distritos
## [1] "Centro-Norte" "Oeste"        "Este"         "Sur"         
## [5] "Periferia"   
## 
## $capital
## [1] FALSE

Por lo tanto, la estructura de las funciones apply siempre es: primero, ponemos el nombre de nuestros datos, y segundo, la función a aplicar.

# Generamos la función multi
multi <- function(x, factor) {
  x * factor
}

# Creamos una lista
precio <- list(3, 3.4, 2.6, 3.1, 1,6)

# Ahora ponemos la función lapply con el factor
lapply(precio, multi, factor = 4)
## [[1]]
## [1] 12
## 
## [[2]]
## [1] 13.6
## 
## [[3]]
## [1] 10.4
## 
## [[4]]
## [1] 12.4
## 
## [[5]]
## [1] 4
## 
## [[6]]
## [1] 24
# Ahora, en vez de una lista, lo queremos convertir en un vector. Usamos la función unlist()
# Aunque para simplificarlo en un vector podemos usar mejor sapply(), como veremos a continuación
unlist(lapply(precio, multi, factor = 4))
## [1] 12.0 13.6 10.4 12.4  4.0 24.0

Como vemos, los valores para introducir en una función, como en este caso factor, puede introducirse en lapply, dando en este caso el valor de 4.

# Usamos una función anónima y definimos los datos en su interior
lapply(list(1,2,3), function(x) { 3 * x })
## [[1]]
## [1] 3
## 
## [[2]]
## [1] 6
## 
## [[3]]
## [1] 9

3.4.2 Función sapply()

La función sapply() trabaja con listas y vectores e intenta simplificar una lista en un vector o una matriz. Si el objeto introducido es una lista, y no puede transformarla en una matriz o un vector, te devuelve una lista, pero debido a que para eso ya tenemos lapply(), no es recomendable utilizar sapply().

Su estructura básica está compuesta por dos elementos: X es el vector o la lista y FUN es la función a aplicar sobre esos objetos. Sería así: sapply(X, FUN, ...).

# Seleccionamos un vector (columna) y vemos el númnero de caracteres
sapply(burgos$distritos, nchar)
## Centro-Norte        Oeste         Este          Sur    Periferia 
##           12            5            4            3            9
sapply(burgos$distritos, nchar, USE.NAMES = FALSE) # quitamos los nombres
## [1] 12  5  4  3  9
# Ejemplo con temperaturas
temp <- list(dia1 = c(4, 6, 2, 9, -3),
             dia2 = c(-3, -4, 5, 3, 10, 6),
             dia3 = c(-4, -5, 3, 0, -1))
# temp is already available in the workspace

# Create a function that returns min and max of a vector: extremes
extremos <- function(x) {
  c(min = min(x), max = max(x))
}

# Apply extremes() over temp with sapply()
sapply(temp, extremos)
##     dia1 dia2 dia3
## min   -3   -4   -5
## max    9   10    3
# Apply extremes() over temp with lapply()
lapply(temp, extremos)
## $dia1
## min max 
##  -3   9 
## 
## $dia2
## min max 
##  -4  10 
## 
## $dia3
## min max 
##  -5   3

3.4.3 Función vapply()

La función vapply() es similar a sapply(), ya que funciona con listas o vectores. Hay que especificar explícitamente el formato de salida. Su estructura básica está compuesta por dos elementos: x es el vector o la lista y FUN es la función a aplicar sobre esos objetos. Sería así: vapply(X, FUN, FUN.VALUE, ..., UNE.NAMES = TRUE). El argumento nuevo aquí es FUN.VALUE, que es una plantilla general para devolver el valor de FUN. De hecho se considera a vapply() como la versión robusta de sapply(), porque se le restringe la salida de la función a aplicar. Es más, se considera una buena práctica convertir sapply() en vapply().

# Definition of basics()
basics <- function(x) {
  c(min = min(x), mean = mean(x), max = max(x))
}

# Apply basics() over temp using vapply()
vapply(temp, basics, numeric(3))
##      dia1      dia2 dia3
## min  -3.0 -4.000000 -5.0
## mean  3.6  2.833333 -1.4
## max   9.0 10.000000  3.0

3.5 Funciones útiles

3.5.1 Funciones matemáticas

  • abs(): te devuelve el valor absoluto.
  • sum(): te devuelve la suma.
  • mean(): te devuelve la media aritmética.
  • round(): te redondea los números. De modo predeterminado sin decimales.
  • rev(): da la vuelta al argumento.
vec1 <- c(1.5, 2.5, 8.4, 3.7, 6.3)
vec2 <- rev(vec1)
vec2
## [1] 6.3 3.7 8.4 2.5 1.5

3.5.2 Útil para datos

  • seq(): genera secuencias especificando from, to, y by.
  • rep(): Replicate elements of vectors and lists.
  • sort(): Sort a vector in ascending order. Works on numerics, but also on character strings and logicals.
  • rev(): Reverse the elements in a data structures for which reversal is defined.
  • str(): Display the structure of any R object.
  • append(): Merge vectors or lists.
  • is.*(): Check for the class of an R object.
  • as.*(): Convert an R object from one class to another.
  • unlist(): Flatten (possibly embedded) lists to produce a vector.

3.5.3 Expesiones regulares

En las expresiones regulares podemos buscar si un patrón existe dentro de una cadena de caracteres. Para eso podemos usar las funciones grepl() y grep(). Ambas expresiones tienen dos argumentos básicos: grepl(pattern, X).

  • grepl(). Es la función lógica de grep(). Devuelve TRUE cuando el patrón descrito aparece en los elementos, y FALSE cuando no.

  • grep(). Devuelve un índice de vectores de los elementos que tienen el patrón descrito.

Veámoslo con un ejemplo:

# The emails vector has already been defined for you
emails <- c("john.doe@ivyleague.edu", "education@world.gov", "dalai.lama@peace.org",
            "invalid.edu", "quant@bigdatacollege.edu", "cookie.monster@sesame.tv")

# Use grepl() to match for "edu"
grepl("edu", emails)
## [1]  TRUE  TRUE FALSE  TRUE  TRUE FALSE
# Use grep() to match for "edu", save result to hits
hits <- grep("edu", emails)

# Subset emails using hits
emails[hits]
## [1] "john.doe@ivyleague.edu"   "education@world.gov"     
## [3] "invalid.edu"              "quant@bigdatacollege.edu"

En el ejemplo anterior queríamos seleccionar los correos acabados en .edu. Sin embargo, con la función escrita, se seleccionaron también aquellos corresos que tenían en su interior, la cadena edu. Para poder filtrar estos datos, usamos los caracteres ^ (con el que indicamos que comience por edu) y $ (con el que indicamos que acabe por edu). Pero existen más:

  • @, because a valid email must contain an at-sign.
  • ., which matches any character (.) zero or more times (). Both the dot and the asterisk are metacharacters. You can use them to match any character between the at-sign and the “.edu” portion of an email address.
  • \.edu$, to match the “.edu” part of the email at the end of the string. The \ part escapes the dot: it tells R that you want to use the . as an actual character.
# Use grepl() to match for .edu addresses more robustly
grepl("@.*\\.edu$", emails)
## [1]  TRUE FALSE FALSE FALSE  TRUE FALSE
# Use grep() to match for .edu addresses more robustly, save result to hits
hits <- grep("@.*\\.edu$", emails)

# Subset emails using hits
emails[hits]
## [1] "john.doe@ivyleague.edu"   "quant@bigdatacollege.edu"

Las dos funciones vistas solo nos permiten localizar y filtrar. ¿Qué pasa si queremos cambiar un carácter por otro? En definitiva, reemplazar un patrón por otro. Para eso usamos las funciones sub() y gsub(). Su estructura es muy similar a las dos anteriores: gsub(pattern, replacement, X).

  • sub(). Sustituye un patró por otro, pero solo cuando lo encuentra la primera vez en la cadena de caracteres. Por ejemplo, si queremos cambiar la a por una o, cuando coge un elemento cambiar la primera a por la o, pero si hay más a en la palabra, no las cambia.
  • gsub(). Hace lo mismo que el anterior, pero en esta ocasión cambia todos los patrones encontrados en la palabra o cadena de caracteres, no solo la primera.

  • .*: A usual suspect! It can be read as “any character that is matched zero or more times”.
  • \s: Match a space. The “s” is normally a character, escaping it (\) makes it a metacharacter.
  • [0-9]+: Match the numbers 0 to 9, at least once (+).
  • ([0-9]+): The parentheses are used to make parts of the matching string available to define the replacement. The \1 in the replacement argument of sub() gets set to the string that is captured by the regular expression [0-9]+.

3.6 Horas y fechas

Para ver la fecha de hoy, usamos la función Sys.Date(), y para ver la hora y el día Sys.time(). Las fechas siempre son en AÑO-MES-DÍA.

Sys.Date()
## [1] "2019-10-07"
Sys.time()
## [1] "2019-10-07 08:56:28 CEST"
class(Sys.Date())
## [1] "Date"
class(Sys.time())
## [1] "POSIXct" "POSIXt"
# Para asignar una fecha concreta a un objeto tipo Date
nacimiento <- as.Date("1986-01-03")
class(nacimiento)
## [1] "Date"

Si queremos cambiar el orden de la fecha, como que primero sea el día, luego el mes y finalmente el año, tenemos que cambiar el formato. Y esto lo hacemos con el argumento format.

nacimiento <- as.Date("03-01-1986", format = "%d-%m-%Y")

Tenemos más opciones para dar formato a las fechas:

  • %Y: 4-digit year (1982)
  • %y: 2-digit year (82)
  • %m: 2-digit month (01)
  • %d: 2-digit day of the month (13)
  • %A: weekday (Wednesday)
  • %a: abbreviated weekday (Wed)
  • %B: month (January)
  • %b: abbreviated month (Jan)

Y del mismo modo para las horas:

  • %H: hours as a decimal number (00-23)
  • %I: hours as a decimal number (01-12)
  • %M: minutes as a decimal number
  • %S: seconds as a decimal number
  • %T: shorthand notation for the typical format %H:%M:%S
  • %p: AM/PM indicator
as.Date("1982-01-13")
## [1] "1982-01-13"
as.Date("Jan-13-82", format = "%b-%d-%y")
## [1] "1982-01-13"
as.Date("13 January, 1982", format = "%d %B, %Y")
## [1] "1982-01-13"
# Convertimos en objetos POSIX
mi_tiempo <- as.POSIXct("1986-01-03 19:45:00")

In R, dates are represented by Date objects, while times are represented by POSIXct objects. Under the hood, however, these dates and times are simple numerical values. Date objects store the number of days since the 1st of January in 1970. POSIXct objects on the other hand, store the number of seconds since the 1st of January in 1970.

The 1st of January in 1970 is the common origin for representing times and dates in a wide range of programming languages. There is no particular reason for this; it is a simple convention. Of course, it’s also possible to create dates and times before 1970; the corresponding numerical values are simply negative in this case.

# Definition of character strings representing times
str1 <- "May 23, '96 hours:23 minutes:01 seconds:45"
str2 <- "2012-3-12 14:23:08"

# Convert the strings to POSIXct objects: time1, time2
time1 <- as.POSIXct(str1, format = "%B %d, '%y hours:%H minutes:%M seconds:%S")
time2 <- as.POSIXct(str2, format = "%Y-%m-%d %H:%M:%S")

# Convert times to formatted strings
format(time1, "%M")
## [1] "01"
format(time2, "%I:%M %p")
## [1] "02:23 pm"

3.6.1 Aritmética con fechas

Lo bueno de que R identifique las fechas como tal, es que podemos hacer cálculos con ellas. Por ejemplo, ¿cuántos días llevo viviendo desde que nací?

días_nacimiento <- Sys.Date() - nacimiento
días_nacimiento
## Time difference of 12330 days
now <- Sys.time()
now + 3600          # add an hour
## [1] "2019-10-07 09:56:28 CEST"
now - 3600 * 24     # subtract a day
## [1] "2019-10-06 08:56:28 CEST"

Paquetes de R que trabajen con fechas son lubridate, zoo y xts.