Vectores

Son los tipos de datos que forman la base de la estructura de los tibbles. La gran mayoría de las funciones que manipulen a los tibbles se escriben a nivel de vecotres.

Los tipos:

  • Atómicos
    • Contienen datos homogéneos.
  • Listas
    • Pueden contener datos heterogéneos
  • NULL
    • Vector de longitud cero, utilizado para representar la ausencia de un vector.
  • Vectores aumentados.
    • Factores, fechas, dataframes y tibbles.

Vectores atómicos

  • Hay cuatro tipos de vectores atómicos:
    • Lógicos: Se pueden escribir (TRUE, FALSE) o (T, F). Donde NA es un valor especial.
    • Dobles: Se pueden especificar mediante decimales (0.1234) o en notación científica (1.23e4) o hexadecimal (0xcafe). Tienen cuatro valores especiales: Inf, -Inf, NaN y NA.
    • Enteros: Se escriben similar a los dobles pero seguidos de una L (1234L, 1e4L, 0xcafeL), solo tienen un valor especial NA.
    • Caracteres: Se escriben mediante comillas simples y dobles (“hola”) o (‘chau’).
lgl_var  <- c(TRUE, F, 1:10%%4==0, NA)
int_var  <- c(1L, 6L, 10L)
dbl_var  <- c(1, 2.5, 4.5)
chr_var  <- c("these are", "some strings") 

Cuando los parametros de c() son otros vectores atómicos, retorna un vector atómico:

c(c(1,2), c(3,4))
## [1] 1 2 3 4

Propiedades

Cada vector tiene dos propiedades claves.

Su tipo:

typeof(lgl_var)
## [1] "logical"
typeof(int_var)
## [1] "integer"
typeof(dbl_var)
## [1] "double"
typeof(chr_var)
## [1] "character"

Su longitud:

x  <- list("a", "b", 1:10)
length(x)
## [1] 3

Detalles de implementación

No existen escalares, sólo son vectores de longitud 1.

3[1]
## [1] 3

Los enteros son exactos, los dobles pueden no serlo.

sqrt(2)^2 == 2
## [1] FALSE

Para comparar valores de punto flotante (no es posible representarlos mediante una cantidad finita de memoria):

near(sqrt(2)^2, 2) #requiere dplyr
## [1] TRUE

La complejidad se propaga, en particular los vectores asumen el tipo del elemento más complejo.

typeof(c(1L, 2L, 3.5))
## [1] "double"

Implementación en la memoria

Si bien los objetos en R pueden dar la apariencia de mutabilidad, la gran mayoría no lo son. Inclusive en las asignaciones mas sencillas.

x  <- 1
obj_addr(x) # librery lobstr
## [1] "0x1fa8a458"
x  <- 2
obj_addr(x)
## [1] "0x1fa8a3b0"

Los objetos asignados a partir de otros son punteros a la misma celda de memoria.

x  <- 1
y  <- x
obj_addr(x)
## [1] "0x1fe77fb8"
obj_addr(y)
## [1] "0x1fe77fb8"

R crea copias al modificar los vectores.

x  <- c(1,2,3)
y <- x
y[3] <- 3

all(x==y) # x, y <- 1,2,3
## [1] TRUE
obj_addr(x)
## [1] "0x203f1b88"
obj_addr(y)
## [1] "0x204739a8"

Para listas y dataframes el comportamiento es diferente. En vez de guardar los valores mismos, guarda referencias a éstos.

l1 <- c(1,2,3)
l2 <- l1 # Comparten memroia.
l2[[3]] <- 4

Luego, si se modifica algún valor, los comunes siguen compartiendo memoria.

Algo similar pero más sutil ocurre con los dataframes y tibbles:

d1 <- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
d2 <- d1
d2[, 2] <- d[, 2] * 2

Si modificamos una columna, se crea una copia de la columna modificada y se le hace referencia. Siguen compartiendo memoria de las columnas en común.

En cambio, si modificamos cualquier fila del dataframe, se hace una copia entera del mismo. Esto puede llevar a complicaciones de saturación de memoria.

d1 <- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
d3 <- d1
d3[1, ] <- d3[1, ]*3

Vectores de caracteres

x <- c("a", "a", "abc", "d") 

La idea:

La realidad:

ref(x, character = T) # Muestra las referencias en memoria.

R utiliza un grupo global de cadenas de caracteres, donde cada elemento de un vector de caracteres hace referencia a una cadena única dentro del grupo.

Valores especiales

Hay caracteres especiales para los infinitos, los valores faltantes y los números que no existen.

c(-1,0, 1, NA)/0
## [1] -Inf  NaN  Inf   NA

Los valores NaN y NA son infecciosos:

NA > 5
## [1] NA
10*NA
## [1] NA
!NaN
## [1] NA
NaN + 5
## [1] NaN

No siempre se propagan, pero no hay que apostar a esto:

NA ^ 0
## [1] 1
NaN ^ 0 
## [1] 1
NA | TRUE
## [1] TRUE
NA & FALSE
## [1] FALSE
NaN | TRUE
## [1] TRUE

Consideremos el siguiente caso:

x  <- c(NA, 5, NaN, NA, 10)
x == NA
## [1] NA NA NA NA NA

La lógica de esto es NA no tienen porque ser los mismos no hacer referencia al mismo valor no existente.

Testeo

¿Cómo los comparamos?

v  <- c(0, Inf, NA, NaN)
is.finite(v)
## [1]  TRUE FALSE FALSE FALSE
is.infinite(v)
## [1] FALSE  TRUE FALSE FALSE
is.na(v)
## [1] FALSE FALSE  TRUE  TRUE
is.nan(v)
## [1] FALSE FALSE FALSE  TRUE

La comparación de tipos:

is_logical(c(T, NA))
## [1] TRUE
is_atomic(c('caracter'))
## [1] TRUE
is_character(c('h', 3)) #Convierte el 3 a caracter.
## [1] TRUE
is_list(list('h',3)) #Preserva los tipos.
## [1] TRUE

Dimensiones

Podemos crear vectores multidimensionales mediante comandos o cambiando la dimensión.

z  <- 1:6 #1, 2, 3, 4, 5, 6
dim(z) <- 2:3
print(z)
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6

Usando los comandos array y matrix :

matrix(1:6, nrow=2, ncol=3)
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
array(1:12, c(2,3))
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6

Con array y dim podemos incluso crear tensores.

array(1:24, c(2,2,3))
## , , 1
## 
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## , , 2
## 
##      [,1] [,2]
## [1,]    5    7
## [2,]    6    8
## 
## , , 3
## 
##      [,1] [,2]
## [1,]    9   11
## [2,]   10   12

Coerción

Coerción explícita: Es preferible intentar arreglar el tipo previamente a forzar una conversión.

v  <- c(TRUE, FALSE, TRUE)
as.integer(v)
## [1] 1 0 1
as.character(v)
## [1] "TRUE"  "FALSE" "TRUE"
as.integer(c('caracter')) # Puede causar advertencias y NA: 
## [1] NA

Coerción implícita: Ocurre cuando se utiliza un vector en un contexto que espera otro tipo de vector.

v  <- c(T, F, T, T)
sum(v)
## [1] 3
if (length(3)){print(TRUE)} #En el otro sentido.
## [1] TRUE

Reciclado

R implícitamente coerciona las longitudes de los vectores para que las operaciones tengan sentido. Esto se llama recycling o broadcasting.

Vector y escalares:

sample(10) + 10
##  [1] 18 12 19 20 14 11 15 16 17 13
runif(10) > 0.5
##  [1] FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE

Vector y otros vectores:

rep(1,10)
##  [1] 1 1 1 1 1 1 1 1 1 1
rep(1,10) + 1:2 #Expande el vector de menor longitud.
##  [1] 2 3 2 3 2 3 2 3 2 3
rep(1,10) + 1:3 #Si el vector no es múltiplo, lo trunca.
##  [1] 2 3 4 2 3 4 2 3 4 2

Para evitar errores el reciclado de vectores con dimension mayor a 1, solo permite reciclar escalares.

tibble(x=1:4, y=2)
## # A tibble: 4 x 2
##       x     y
##   <int> <dbl>
## 1     1     2
## 2     2     2
## 3     3     2
## 4     4     2
try(tibble(x=1:4, y=1:2))
## Error : Tibble columns must have compatible sizes.
## * Size 4: Existing data.
## * Size 2: Column `y`.
## i Only values of size one are recycled.
matrix(1, nrow=3, ncol=3) + 2
##      [,1] [,2] [,3]
## [1,]    3    3    3
## [2,]    3    3    3
## [3,]    3    3    3
#matrix(1, 3, 3) + 1:2) #ERROR

Nombrar vectores

Se puede nombrar cualquier tipo de vector, el nombrarlos facilita la selección y creación de subconjuntos.

Al crearlos:

c(x = 1, y =2, z = 4)
## x y z 
## 1 2 4

Después de crearlos utilizando purrr::set_names :

v <- 1:3
set_names(v, c("a", "b", "c"))
## a b c 
## 1 2 3

Subconjuntos y selección (subsetting)

Al igual que filter para las filas de un tibble, se puede seleccionar subconjuntos un vector. Ésta se hace mediante los corchetes rectos [ . Hay cuatro formas de utilizar ésto.

  1. Utilizando un vector de enteros. Si los enteros son positivos, devuelve los elementos tales que los indices correspondan al valor de los enteros.
x <- c("uno", "dos", "tres", "cuatro", "cinco")
x[c(3,2,5)]
## [1] "tres"  "dos"   "cinco"

Se puede incluso repetir indices, generando vectores de longitud mayor al original.

x[c(1,1,1,5,5,5,3,3)] #Creamos un vector de 
## [1] "uno"   "uno"   "uno"   "cinco" "cinco" "cinco" "tres"  "tres"

Los enteros negativos eliminan elementos en las posiciones especificadas. No se permite mezclar enteros positivos con negativos.

x[c(-1,-3,-5)]
## [1] "dos"    "cuatro"

2. Podemos crear subconjuntos mediante vectores lógicos, preserva sólo los índices que corresponden al valor TRUE

x <- c(10, 3, NA, 5, 8, 1, NA, 9 , NaN)
x[c(!is.na(x))]
## [1] 10  3  5  8  1  9
x[c(TRUE,FALSE)] # Porque funciona?
## [1]  10  NA   8  NA NaN
x[x %% 2 == 0] # Los pares o NA..
## [1] 10 NA  8 NA NA
  1. Si el vector tiene nombres, podemos usarlos para seleccionar.
x <- c(abc = 1, def = 2, xyz = 5)
x[c("xyz", "def")]
## xyz def 
##   5   2
x[rep("xyz", 10)]
## xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz 
##   5   5   5   5   5   5   5   5   5   5

4. Usando [ ], nada. Nos permite seleccionar todos los elementos en la dimensión no especificada. Es particularmente util en datos multi dimensionados.

x <- array(1:27, c(3,3,3)) 
x[c(T,F),  , c(1,3)] # Recicla la primer coordenada!
## , , 1
## 
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    3    6    9
## 
## , , 2
## 
##      [,1] [,2] [,3]
## [1,]   19   22   25
## [2,]   21   24   27

Listas

Son una estructura más complejas que los vectores atómicos ya que pueden contener otras listas en su interior. Se pueden utilizar para crear estructuras jerárquicas como árboles.

UUtilizamos str() para obtener información de la lista sin despleguar todos sus contenidos.

x_named <- list(a = 1, b = 2, c =3, d = 4) 
str(x_named) 
## List of 4
##  $ a: num 1
##  $ b: num 2
##  $ c: num 3
##  $ d: num 4

Pueden contener otros tipos, vectores e inclusive listas y listas de listas.

l <- list("a", 1L, 1.5, TRUE, c(1,2), list(a=1,b=2, c=list(1,3))) 
str(l)
## List of 6
##  $ : chr "a"
##  $ : int 1
##  $ : num 1.5
##  $ : logi TRUE
##  $ : num [1:2] 1 2
##  $ :List of 3
##   ..$ a: num 1
##   ..$ b: num 2
##   ..$ c:List of 2
##   .. ..$ : num 1
##   .. ..$ : num 3

Subconjuntos de listas

Hay tres formas de seleccionar los elementos de una lista.

  1. El corchete simple [

Extrae una sublista, el resultado siempre es una lista, al igual que con los vectores se pueden utilizar indices enteros o vectores lógicos:

a <- list(a = 1:3, b= "una cadena", c = pi, d = list(-1, -5))
str(a[1:2])
## List of 2
##  $ a: int [1:3] 1 2 3
##  $ b: chr "una cadena"
str(a[c(T, F, F)])
## List of 2
##  $ a: int [1:3] 1 2 3
##  $ d:List of 2
##   ..$ : num -1
##   ..$ : num -5

2. El doble corchete [[

Extrae un solo componente de una lista, eliminando un nivel de jerarquía. El resultado tiene el tipo del elemento que retorna.

a <- list(a = 1:3, b= "una cadena", c = pi, d = list(-1, -5))

str(a[[4]])
## List of 2
##  $ : num -1
##  $ : num -5
a[[3]] # Es un doble
## [1] 3.141593
typeof(a[[3]])
## [1] "double"

a$b # El simbolo $ permite seleccionar al que hace referencia, es un atajo de seleccion.
## [1] "una cadena"

Atributos

Podemos agregarla metadatos cualesquiera a un vector mediante el uso de atributos (attr).

x <- 1:10

attr(x, "saludo") # Al campo "saludo" le asigna NULL ya que no se especificó.
## NULL
attr(x, "saludo") <- "hola!" # Al campo "saludo" le asigna la cadena "hola". 
attr(x, "despedida") <- "Chau!"

attributes(x) 
## $saludo
## [1] "hola!"
## 
## $despedida
## [1] "Chau!"

Atributos

Hay 3 atributos principales utilizados comúnmente las diferentes partes de R.

  • Los nombres.
  • Las dimensiones.
  • La clase.
    • Las clases son parte de la programación orientada a objetos y nos permite crear estructuras y funciones cuyos comportamiento varie bajo distintas condiciones. Las clases más comunes de R son S3 (sencillas, fáciles usar pero con poca estructura formal), S4 (son como S3, pero enforza el formalismo, son más dificiles de usar, pero menos propensas a causar errores en proyectos grandes) y R6 (no son nativas a R, son clases de referncia, la principal ventaja es que NO se copian al modificar)

Un ejemplo:

methods("as.Date")
## [1] as.Date.character   as.Date.default     as.Date.factor     
## [4] as.Date.numeric     as.Date.POSIXct     as.Date.POSIXlt    
## [7] as.Date.vctrs_sclr* as.Date.vctrs_vctr*
## see '?methods' for accessing help and source code

Vectores Aumentados

Los vectores aumentados son abstracciones que se construyen sobre los vectores y listas. Son los tibbles, data frames, arrays, factores, fechas, y muchas más.

Factores: Como ya vimos, están diseñados para representar datos categoricos. Se construyen sobre los vectores enteros y tienen un atributo de nivel .

x <-  factor(c("ab", "cd", "ab"), levels = c("ab", "cd", "ef"))
x
## [1] ab cd ab
## Levels: ab cd ef
typeof(x)
## [1] "integer"
attributes(x)
## $levels
## [1] "ab" "cd" "ef"
## 
## $class
## [1] "factor"

Las fechas y fechas hora son vectores numericos que representan el número de dias desde el 1ro de enero de 1970.

x <- as.Date("1971-01-01") 
unclass(x)
## [1] 365
typeof(x)
## [1] "double"
x <- lubridate::ymd_hm("1970-01-01 01:00")
unclass(x)
## [1] 3600
## attr(,"tzone")
## [1] "UTC"

Los tibbles son listas aumentadas, tienen las clases tbl_df, tbl y data.frame. Sus atributos son los nombres de las columnas ( names ) y los nombres de las filas row.names .

tb <- tibble::tibble(x = 1:5, y = 5:1) 
df <- data.frame(x = 1:5, y = 5:1) 
typeof(tb)
## [1] "list"
typeof(df)
## [1] "list"

attributes(tb)
## $names
## [1] "x" "y"
## 
## $row.names
## [1] 1 2 3 4 5
## 
## $class
## [1] "tbl_df"     "tbl"        "data.frame"