Capítulo 3 Tipos de datos I: vectores

Bien, ya controlamos la calculadora. Vamos a ir más allá: ¿y si en lugar de tener un solo número tenemos una colección de elementos? En este capítulo vamos a ver un clásico de cualquier lenguaje de programación: los vectores o arrays, una concatenación de elementos (del mismo tipo). De hecho un número individual (por ejemplo, 1) es en realidad un vector de longitud uno (un solo elemento).

3.1 Vectores numéricos

La forma más sencilla de crear un vector en R es con el comando c() (de concatenar elementos), y basta con introducir sus elementos entre paréntesis, y separados por comas. Vamos a crear el vector con los tres primeros números naturales pares (el 0 no es natural, no seas bárbaro/a).

z <- c(2, 4, 6)
z
## [1] 2 4 6

Como ves ahora en el environment (entorno, arriba a la derecha) tenemos una colección de elementos, tres en concreto, guardados en una misma variable z. La longitud de un vector se puede calcular con el comando length() (nos devolverá el número de elementos de la variable que le pasemos como argumento).

## [1] 3

Además podemos concatenar a su vez vectores: vamos a concatenar el vector z consigo mismo, y añadiéndole al final un 8.

c(z, z, 8)
## [1] 2 4 6 2 4 6 8

Esta última concatenación lo que nos ha dado son, primero, los tres pares que ya teníamos en z, después de nuevo los tres primeros pares, y por último un 8.

3.2 Secuencias numéricas con un patrón

Muchas veces nos gustaría crear vectores de una forma mucho más rápida. Supongamos que queremos el vector de los primeros 21 números naturales. Si construyéramos el vector como antes, tendríamos que ejecutar el comando c(1, 2, 3, 4, 5, ...) hasta llegar al número 21. ¿Un poco largo, no?

Hay un atajo: el comando seq() nos permite crear una secuencia desde un elemento inicial hasta un elemento final, avanzando de uno en uno.

seq(1, 21) # secuencia desde 1 hasta 21 de uno en uno
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21

Es importante que no perdamos el foco de que programar es similar a escribir en un idioma, por lo que si hay algo que se puede decir de una forma más limpia y que se entienda mejor, ¿por qué no usarlo? Siempre que queramos definir secuencias entre dos números naturales (por ejemplo, entre 1 y un valor n), cuya distancia entre elementos consecutivos sea uno, el comando 1:n nos devuelve lo mismo que la orden seq(1, n). Además, si el elemento inicial es mayor que el final, R entenderá solo que la secuencia la queremo decreciente.

n <- 21
1:n # secuencia desde 1 hasta n (21) de uno en uno
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
n:1 # secuencia decreciente
##  [1] 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1

 

También podemos definir otro tipo de distancia entre dos elementos consecutivos (conocido como paso de discretización), por ejemplo de 0.5 en 0.5.

seq(1, 10, by = 0.5) # secuencia desde 1 a 10 de 0.5 en 0.5
##  [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5  8.0
## [16]  8.5  9.0  9.5 10.0

Otras veces nos interesará definir una secuencia entre un valor inicial y un valor final, pero nos da igual la distancia entre cada elemento: solo nos importa que tenga un número concreto de elementos (y que sea R el que decida la distancia entre elementos consecutivos para conseguirlo).

seq(1, 50, l = 11) # secuencia desde 1 a 50 de longitud 11
##  [1]  1.0  5.9 10.8 15.7 20.6 25.5 30.4 35.3 40.2 45.1 50.0

 

Otro atajo que podemos usar para definir secuencias de números con un patrón es definir vectores de elementos repetidos, por ejemplo un vector lleno de ceros. La función rep() nos permite repetir un elemento un número fijado de veces.

rep(0, 7) # vector de 7 ceros
## [1] 0 0 0 0 0 0 0

No solo podemos repetir un número sino que podemos repetir vectores enteros.

rep(c(0, 1, 2), 4) # repetimos el vector c(0, 1, 2) 4 veces
##  [1] 0 1 2 0 1 2 0 1 2 0 1 2

Esa repetición además podemos definirla también de forma intercalada: en lugar de repetir c(0, 1, 2) cuatro veces seguidas, queremos cuatro 0, después cuatro 1, y después cuatro 2.

rep(c(0, 1, 2), each = 4) # cuatro 0, luego cuatro 1, luego cuatro 2
##  [1] 0 0 0 0 1 1 1 1 2 2 2 2

3.3 Vectores de caracteres (texto)

Un error común es asociar el concepto de vectores solo a números: un vector es una colección o concatenación de elementos del mismo tipo pero no tienen porque ser necesariamente números. Vamos a crear una frase de ejemplo, un vector de 4 elementos de tipo texto (en R se llaman char): "Mi", "nombre", "es" "Javier".

Como ves las variables de tipo char o character van entre comillas dobles, ya que es un cadena de texto.

mi_nombre <- c("Mi", "nombre", "es", "Javier")
mi_nombre
## [1] "Mi"     "nombre" "es"     "Javier"

Ya tenemos nuestro primer vector de texto de longitud 4. Las cadenas de texto son un tipo especial de dato, con los que obviamente no podremos hacer operaciones aritméticas como la suma o la media, pero si podemos hacer operaciones propias de cadenas de texto como puede ser la función paste(). Dicha función nos permite convertir un vector de 4 palabras en una frase, decidiendo que caracter queremos que vaya entre palabra con el argumento collapse =.

paste(mi_nombre, collapse = "") # todo junto
## [1] "MinombreesJavier"
paste(mi_nombre, collapse = " ") # separados por un espacio
## [1] "Mi nombre es Javier"
paste(mi_nombre, collapse = ".") # separados por un punto .
## [1] "Mi.nombre.es.Javier"

Si queremos pegar los elementos de la cadena de texto sin ningún tipo de caracter, existe una forma más abreviada y limpia de ejecutar la orden paste(mi_nombre, collapse = ""), usando la función paste0()

paste0(mi_nombre) # todo junto sin nada separando
## [1] "Mi"     "nombre" "es"     "Javier"

Esta función es muy útil si queremos definir variables de texto que compartan por ejemplo un prefijo (variable_1, variable_2, …)

paste0("variable", 1:7) # a la palabra «variable» le pegamos los números del 1 al 7
## [1] "variable1" "variable2" "variable3" "variable4" "variable5" "variable6"
## [7] "variable7"
paste("variable", 1:7, sep = "_") # separado por una barra baja
## [1] "variable_1" "variable_2" "variable_3" "variable_4" "variable_5"
## [6] "variable_6" "variable_7"

3.3.1 Paquete glue

Otra forma más intuitiva de trabajar con textos y variables numéricas es usar el paquete glue, que nos permite pegar cadenas de texto a variables numéricas de forma simbólica.

edad <- 10:15 # edades
glue("La edad es de {edad} años")
## La edad es de 10 años
## La edad es de 11 años
## La edad es de 12 años
## La edad es de 13 años
## La edad es de 14 años
## La edad es de 15 años

También podemos hacer uso de dicha función sin tener los valores numéricos previamente guardados en variables.

# Otra forma sin definir variables a priori
glue("La edad es de {10:15} años")
## La edad es de 10 años
## La edad es de 11 años
## La edad es de 12 años
## La edad es de 13 años
## La edad es de 14 años
## La edad es de 15 años

Ya sabemos trabajar con textos :)

¿Y si queremos pasar todo a mayúscula? ¿O todo a minúscula? ¿Y si queremos sustituir un caracter (por ejemplo .) por otro en todos los elementos? R también nos proporciona algunas funciones muy sencillas (del paquete {base}) de usar para dichas tareas. Aquí un ejemplo de algunas de ellas.

texto <- c("Hola.", "qué", "ase?", "todo", "bien.", "y yo",
           "que", "ME", "ALEGRO")
toupper(texto) # todo a mayúscula
## [1] "HOLA."  "QUÉ"    "ASE?"   "TODO"   "BIEN."  "Y YO"   "QUE"    "ME"    
## [9] "ALEGRO"
tolower(texto) # todo a minúscula
## [1] "hola."  "qué"    "ase?"   "todo"   "bien."  "y yo"   "que"    "me"    
## [9] "alegro"

El paquete {base} también nos permite buscar y reemplazar patrones concretos en cadenas de texta (por ejemplo, sustituir toda letra «o» por el caracter «*»).

gsub("o", "*", texto) # toda "o" en el texto será sustituida por *
## [1] "H*la."  "qué"    "ase?"   "t*d*"   "bien."  "y y*"   "que"    "ME"    
## [9] "ALEGRO"

 

3.4 Vectores lógicos (TRUE/FALSE)

  • [X] Variables numéricas (individuales)
  • [X] Vectores de números
  • [X] Vectores de caracteres
  • [ ] Vectores lógicos

Veamos un último tipo de vectores importante en todo lenguaje de programación: los vectores de valores lógicos. Un valor lógico puede tomar tres valores:

  • TRUE (guardado internamente como un 1).
  • FALSE (guardado internamente como un 0).
  • NA (dato ausente, son las siglas de not available, cuando pretendemos aplicar una comparación a un dato ya de por si ausente).

Estos valores son resultado de evaluar condiciones lógicas. Por ejemplo, imaginemos que definimos un vector de números x <- c(1.5, -1, 2, 4, 3, -4). ¿Qué numeros del vector son menores que 2? Basta con que ejecutemos la orden x < 2, que nos devolverá TRUE/FALSE en cada hueco, en función de si cumple (TRUE) o no (FALSE) la condición pedida.

x <- c(1.5, -1, 2, 4, 3, -4)
x < 2
## [1]  TRUE  TRUE FALSE FALSE FALSE  TRUE

El primer, segundo y sexto elemento del vector son los únicos elementos (estrictamente) menores que 2, de ahí que en el primer, segundo y sexto elemento aparezca un TRUE y en el resto un FALSE. Es importante recordar que al evaluar una condición lógica sobre un vector de longitud n, la salida sigue siendo un vector de longitud n pero con valores lógicos.

Dicha condición lógica puede hacerse con otros operadores como <=, > o >=.

x <= 2
## [1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE
x > 2
## [1] FALSE FALSE FALSE  TRUE  TRUE FALSE
x >= 2
## [1] FALSE FALSE  TRUE  TRUE  TRUE FALSE

 

También podemos comparar si es igual a otro elemento, para lo que usaremos el operador ==, pudiendo usar también su opuesto != («distinto de»).

x == 2
## [1] FALSE FALSE  TRUE FALSE FALSE FALSE
x != 2
## [1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE

Las condiciones pueden ser combinadas, principalmente de dos maneras:

  • Intersección: todas las condiciones concatenadas se deben cumplir (conjunción y, operador &) para devolver un TRUE.

  • Unión: basta con que una de las condiciones concatenadas se cumpla (conjunción o, operador |) para devolver un TRUE.

Por ejemplo, vamos a calcular qué elementos del vector c(1.5, -1, 2, 4, 3, -4) sean menores que 3 pero (y) mayores que 0, y los elementos menores que 2 o mayores que 3.

x <- c(1.5, -1, 2, 4, 3, -4)
x < 3 & x > 0 # Solo los que cumplen ambas condiciones
## [1]  TRUE FALSE  TRUE FALSE FALSE FALSE
x < 2 | x > 3 # Los cumplen al menos una de ellas
## [1]  TRUE  TRUE FALSE  TRUE FALSE  TRUE

3.5 Fechas

  • numeric: variables de tipo numéricas (algunas veces vendrán indicados como int o integer para enteros, y dbl o double para números con decimales).
  • character: variables de tipo caracter.
  • Date: variables de tipo fecha.

 

Hay un tipo muy especial de datos que aún no hemos visto que son los datos tipo fecha. Una fecha podría ser a priori una simple cadena de texto "2021-04-21" pero podemos usar la función as.Date() para que R entienda que esa cadena de texto representa un instante temporal. Fíjate la diferencia entre una fecha en texto y una fecha con as.Date().

fecha_char <- "2021-04-21"
fecha_date <- as.Date(fecha_char, format = "%Y-%m-%d")
fecha_char + 1
## Error in fecha_char + 1: argumento no-numérico para operador binario
fecha_date + 1
## [1] "2021-04-22"

En el momento en que convertimos la cadena de texto a fecha, aunque se visualice como tal, internamente es un número, por lo que podemos restar fechas (días entre ambas), podemos sumar números a fechas (fecha días después), etc. Dentro del entorno tidyverse, el paquete lubridate tiene implementadas múltiples funciones para poder operar con fechas de forma sencilla e intuitiva.

Como ya hemos dicho, las fechas y momentos temporales no serán meras cadenas de caracter sino que tienen clases especiales asociadas y algunas funciones especiales que pueden sernos útiles: las fechas serán de tipo dates mientras que las horas será de tipo POSIXct o POSIXlt. En el primer caso, las fechas serán guardadas internamente como el número de días transcurridos desde el 1 de enero de 1970, y las horas como número de segundos desde el 1 de enero de 1970 (para la clase POSIXct) o una lista de segundos, minutos y horas (para la clase POSIXlt).

¿Cómo obtener automáticamente la fecha de hoy, por ejemplo? La función Sys.Date() nos devuelve directamente la fecha y hora en el momento de la ejecución de la orden.

fecha <- Sys.Date()
fecha
## [1] "2021-12-23"
fecha - 7 # una semana antes
## [1] "2021-12-16"
class(fecha) # de clase fecha
## [1] "Date"

Para convertir una cadena de texto a fecha, basta usar la función as.Date() del paquete {base}, pasándole como argumento la fecha en formato "yyyy-mm-dd" por defecto.

as.Date("2021-03-10") # formato por defecto
## [1] "2021-03-10"

Si introducimos otro tipo de formato, debemos especificárselo en un segundo argumento, para R sepa el formato de fecha que le estamos pasando

as.Date("10-03-2020", "%d-%m-%Y") # con día-mes-año (4 cifras)
## [1] "2020-03-10"
as.Date("10-03-20", "%d-%m-%y")  # con día-mes-año (2 cifras)
## [1] "2020-03-10"
as.Date("03-10-2020", "%m-%d-%Y") # con mes-día-año (4 cifras)
## [1] "2020-03-10"
as.Date("Octubre 21, 1995 21:24", "%B %d, %Y %H:%M") # fecha escrita
## [1] NA

 

ERROR: sin pasar a fecha no se puede operar

Si tenemos una fecha como caracter, nunca podremos hacer operaciones (por ejemplo, restarle una unidad temporal, en este caso un día).

"2021-03-10" - 1 # error
## Error in "2021-03-10" - 1: argumento no-numérico para operador binario

Fíjate la diferencia cuando lo convertimos en fecha

fecha <- as.Date("2021-03-10") - 1 # día previo
fecha
## [1] "2021-03-09"

Aunque aparentemente parezca una cadena de texto, prueba a ejecutar unclass(fecha) para comprobar que tiene internamente guardado.

unclass(fecha)
## [1] 18695
unclass(as.Date("1969-01-01")) # un año antes de la referencia a partir de la cual cuenta días
## [1] -365

 

Dentro del entorno de paquetes de tidyverse, el paquete lubridate tiene bastantes funciones útiles para trabajar con fechas como las siguientes

library(lubridate)
ymd_hms("2017-11-28T14:02:00") # convertir a fecha una cadena año-mes-día + hora
## [1] "2017-11-28 14:02:00 UTC"
ydm_hms("2017-22-12 10:00:00") # convertir a fecha una cadena año-día-mes + hora
## [1] "2017-12-22 10:00:00 UTC"
dmy_hms("1 Jan 2017 23:59:59") # convertir a fecha una cadena textual de fecha + hora
## [1] "2017-01-01 23:59:59 UTC"
mdy("July 4th, 2000") # convertir a fecha una cadena textual de fecha
## [1] "2000-07-04"
ymd(20170131)
## [1] "2017-01-31"

Además el paquete nos proporciona herramientas para extraer la fecha y hora actuales con las funciones today() y now()

## [1] "2021-12-23"
now()
## [1] "2021-12-23 17:55:07 CET"

También tenemos disponibles en dicho paquete funciones para extraer facilmente algunas variables temporales como el día de la semana, el mes o el cuatrimestre, con las funciones year(), months(), day() o wday() (día de la semana).

fecha <- now()
year(fecha)
## [1] 2021
month(fecha)
## [1] 12
day(fecha)
## [1] 23
wday(fecha, week_start = 1) # Día de la semana (empezando por el lunes)
## [1] 4
hour(fecha)
## [1] 17
minute(fecha)
## [1] 55
second(fecha)
## [1] 7.542684
week(fecha) # Número de semana (del año)
## [1] 51

Al igual que podemos realizar operaciones aritméticas sencillas con las fechas, también podemos realizar comparaciones, por ejemplo, si el día actual es menor o mayor que otra fecha dada.

fecha_actual <- now()
fecha_actual > ymd(20170131) # Actual vs 2017-01-31
## [1] TRUE
fecha_actual > ymd(21000131) # Actual vs 2100-01-31
## [1] FALSE

 

ERROR: sumar números a caracteres sin convertir tipos de datos

A veces la lectura de variables numéricas de nuestros archivos puede hacer que un número, por ejemplo 1, sea leído como la cadena de texto "1", con la que no podemos operar como un número. Las funciones as.numeric(), as.character() y as.logical() nos permiten convertir una variable en tipo numérico, caracter o lógico, respectivamente.

"1" + 1
## Error in "1" + 1: argumento no-numérico para operador binario
as.numeric("1") + 1
## [1] 2
## [1] "1"
as.logical(c(0, 1))
## [1] FALSE  TRUE

3.6 Consejos

CONSEJOS

 

Recuperar un comando y autocompletar

Si haces click con el ratón en la consola y pulsas la flecha «arriba» del teclado, te irá apareciendo todo el historial de órdenes ejecutadas. Es una manera de ahorrar tiempo para ejecutar órdenes similares a las ya ejecutadas. Si empiezas a escribir el nombre de una variable pero no te acuerdas exactamente de su nombre, pulsando tabulador te autocompletará solo.

 

Expresiones regulares

Dentro del entorno de paquetes tidyverse, el paquete stringr permite un manejo más complejo de cadenas de texo (como el uso de expresiones regulares).

Paquete stringr para manejar cadenas de texto más complejas

Imagen/gráfica 3.1: Paquete stringr para manejar cadenas de texto más complejas

 

Tabla 3.1: Paquetes mencionados hasta ahora
paquetes descripción
{base} utilidades básicas
{bookdown} creación de libros, manuales e informes en R
{glue} manejo de texto literal
{stringr} manejo complejo de cadenas de texto

3.7 📝 Ejercicios

(haz click en las flechas para ver soluciones)

📝Ejercicio 1: define un vector que contenga los números 1, 10, -1 y 2, y guárdalo en una variable llamada vector_num.

  • Solución:
# Vector de números
vector_num <- c(1, 10, -1, 2)
vector_num
## [1]  1 10 -1  2

 

📝Ejercicio 2: crea un vector con las palabras “Hola”, “me”, “llamo” (y tu nombre y apellidos), y pega luego sus elementos de forma que la frase esté correctamente escrita en castellano. Tras hacerlo, añade “y tengo 30 años”.

  • Solución:
# Definiendo el vector
vector_char <- c("Hola", "me", "llamo", "Javier",
                 "Álvarez", "Liébana")

# Pegamos
paste(vector_char, collapse = " ")
## [1] "Hola me llamo Javier Álvarez Liébana"
# Añadimos frase
paste0(paste(vector_char, collapse = " "), " y tengo 30 años.")
## [1] "Hola me llamo Javier Álvarez Liébana y tengo 30 años."

 

📝Ejercicio 3: obten la fecha de hoy, define la fecha de tu cumpleaños, y calcula la diferencia de días

  • Solución:
# Hoy
hoy <- Sys.Date()

# Cumple (diferentes formatos de entrada)
cumple <- as.Date("1989-09-10") # por defecto
cumple <- as.Date("10-09-1989", "%d-%m-%Y")

# Diferencia
hoy - cumple
## Time difference of 11792 days