20 Vectores

20.1 Introducción

Hasta ahora este libro se ha enfocado en tibbles y sus paquetes correspondientes. Pero como empezaste a escribir tus propias funciones, y a profundizar en R, es que necesitas aprender sobre vectores, es decir, sobre los objetos que soportan los tibbles. Por esto, es mejor empezar con tibbles ya que inmediatamente puedes ver su utilidad, y luego trabajar a tu manera con los componentes que están debajo, los vectores. Los vectores son particularmente importantes, al igual que la mayoría de las funciones que escribirás y utilizarás con dichos vectores. Es posible desarrollar funciones que trabajen con tibbles (como ggplot2, dplyr and tidyr) pero las herramientas que necesitas para ello son peculiares e inmaduras. Por esto, estoy desarrollando un mejor enfoque, el cual puedes consultar en https://github.com/hadley/lazyeval, pero este no estará listo a tiempo para la publicación del libro. Incluso aún cuando esté completo, de todas maneras necesitarás entender el concepto de vectores, esto solo facilitará la escritura de capas finales que sean user-friendly (amigables al usuario).

20.1.1 Pre-requisitos

Este capítulo se enfoca en las estructuras de datos de R base, por lo que no es esencial cargar ningún paquete. Sin embargo, usaremos un conjunto de funciones del paquete purrr para evitar algunas insonsistencias en R Base.

20.2 Vectores básicos

There are two types of vectors:

  1. _Hay dos tipos de vectores:
  2. Vectores atómicos, de los cuales existen seis tipos: lógico o booleano, entero, doble o real, caracter, complejo y raw (que consisten en datos sin procesar). Los vectores de tipo entero y doble son ampliamente conocidos como vectores númericos.
  3. Las listas, las cuales son denominadas en ciertas ocasiones como vectores recursivos debido a que pueden contener otras listas..

La diferencia principal entre vectores atómicos y listas es que los vectores atomicos son homogéneos, mientras las listas pueden ser heterogéneas. Existe, otro objeto relacionado: Null (nulo). El nulo es a menudo usado para representar la ausencia de un vector (como el opuesto a NA el cual es usado para representar la ausencia de un valor en un vector.) Null típicamente se comporta como un vector de longitud cero 0. Figura @ref (fig:datatypes) resume las interrelaciones.

The hierarchy of R's vector types

Figure 20.1: The hierarchy of R’s vector types

Cada vector tiene dos propiedades claves:

  1. Su tipo (type), el cual puedes determinarlo con la sentencia typeof() (del inglés tipode).
  1. Su longitud (length), la cual puedes determinarla con la sentencia length() (del ingés longitud).

Los vectores pueden contener también arbitrariamente metadata adicional en forma de atributos. Estos atributos son usados para crear vectores aumentados los cuales implican un comportamiento distinto. Existen tres tipos de vectores aumentados:  Los factores (factors) construidos a partir de vectores de enteros.  Las fechas y fechas-tiempo (date-time) construidos a partir de vectores numéricos.  Los Dataframes y tibbles construidos a partir de listas. Este capítulo te introducirá a lo temas más importantes de vectores, desde lo más simple a lo más complicado. Comenzarás con vectores atómicos, luego seguirás con listas, y finalizarás con vectores aumentados.

20.3 Tipos importantes de vectores atómicos

Los cuatro tipos más importantes de vectores atómicos son lógico, entero, real y carácter. Los tipos raw y complejo son raramente usados durante el análisis de datos, por lo tanto no discutiremos sobre ellos aquí.

20.3.1 Lógico o Booleano

Los vectores de tipo lógico o booleano son el tipo más sencillo de vectores atómicos porque ellos solo pueden tomar tres valores posibles: falso, verdadero y Na. Los vectores lógicos son construidos usualmente con operadores de comparación, como se describe en comparaciones. También puedes crearlos manualmente con la función c ():

20.3.2 Numérico

Los vectores compuestos por enteros o reales son conocidos ampliamente como vectores numéricos. En R, los números son por defecto, reales. Por lo que, para generar un entero, debes colocar una L después del número:

La distinción entre enteros y reales no es realmente importante aunque existen dos diferencias relevantes de las que debes ser consciente: 1. Los números dobles o reales son aproximaciones. Los mismos representan números de punto flotante que no pueden ser precisamente representados con un monto fijo de memoria. Esto significa que debes considerar que todos los reales sean aproximaciones. Por ejemplo, ¿cuál es el cuadrado de la raíz cuadrada de dos?

Este compartamiento es común cuando trabajas con números de punto flotante: la mayoría de los cálculos incluyen algunos errores de aproximación. En lugar de comparar números de punto flotante usando ==, debes usar ´dplyr::near()`, el cual provee tolerancia numérica.

  1. Los números reales tienen cuatro tipos de valores posibles: NA, NaN, Inf and –Inf. Estos tres valores especiales NaN, Inf and -Inf pueden surgir a partir de la división de c(-1, 0, 1) / 0 Evita usar == para chequear estos valores especiales. En su lugar usa la funciones de ayuda is.finite(), is.infinite(), y is.nan():
0 Inf NA NaN
is.finite() x
is.infinite() x
is.na() x x
is.nan() x

20.3.3 Caracter

Los vectores compuestos por carácteres son los tipos más complejos de vectores atómicos, porque cada elemento del mismo es un string, y un string puede contener una cantidad arbitraria de datos. Ya has aprendido un montón acerca de cómo trabajar con strings en [strings]. En este punto quiero mencionar una característica importante y fundamental en la implementación de un string: R usa una reserva global de strings. Esto significa que cada string solo es almacenado en memoria una vez, y en cada uso de puntos del string a la representación. Esto reduce la cantidad de memoria necesaria por strings duplicados. Puedes ver este comportamiento en práctica con pryr::object_size():

y no utiliza más de 1000x ni tanta memoria como x, porque cada elemento de y es sólo un puntero al mismo string. Un puntero utiliza 8 bytes, entonces 1000 punteros a 136 B string es igual a 8 * 1000 + 136 = 8.13 kB.

20.3.4 Valores perdidos (Missing values)

Nota que cada tipo de vector atómico tiene su propio valor perdido (o missing value):

Normalmente no necesitas saber sobre los diferentes tipos porque puedes siempre usar el valor NA (not Available), es decir el valor faltante, y se convertirá al tipo correcto usando las reglas de la coerción implícitas. Sin embargo, existen algunas funciones que son estrictas acerca de sus inputs, por lo tanto es útil tener presente este conocimiento así puedes ser especifico cuando lo necesites.

###Ejercicios

  1. Describe la diferencia entre is.finite(x) y !is.infinite(x).
  2. Lee el código fuente de dplyr:: near() (Consejo: para ver el código fuente, escribe lo siguiente ()) ¿Funcionó?
  3. Un vector de tipo lógico puede tomar 3 valores posibles. ¿Cuántos valores posibles puede tomar un vector de tipo entero? ¿Cuántos valores posibles puede tomar un vector de tipo real? Usa google para realizar buscar información respecto a lo planteado anteriormente.
  4. Idea al menos 4 funciones que te permitan convertir un vector del tipo real a entero. ¿En qué difieren las funciones? Sé preciso.
  5. ¿Cuáles funciones del paquete readr te permiten convertir un vector del tipo string en un vector del tipo lógico, entero y doble?

20.4 Usando vectores atómicos

Ahora que conoces los diferentes tipos de vectores atómicos, es útil repasar algunas herramientas importantes para así poder utilizarlas. Esto incluye: 1. Cómo realizar una conversión de un determinado tipo a otro, y en cuáles casos esto sucede automáticamente. 2. Cómo decidir si un objeto es un tipo específico de un vector. 3. Qué sucede cuando trabajas con vectores de diferentes longitudes. 4. Cómo nombrar los elementos de un vector 5. Cómo obtener los elementos de interés de un vector.

20.4.1 Coerción

Existen dos maneras de convertir, o coercer, un tipo de vector a otro: 1. La Coerción explicita sucede cuando defines a una función como as.logical(), as.integer(), as.double(), o as.character(). Cuando te encuentres usando coerción explicita, siempre debes comprobar que sea posible realizar la corrección en sentido ascendente, de esta manera, en primer lugar, estamos seguros que ese vector nunca tuvo tipos incorrectos. Por ejemplo, quizás necesites la especificación de col_types (‘tipos de columna’) del paquete readr.

  1. La Coerción implícita sucede cuando usas un vector en un contexto especifico del cual se espera un cierto tipo de vector. Por ejemplo, cuando usas un vector del tipo lógico con la función numérica ‘summary’ (del inglés resumen), o cuando usas un vector del tipo doble donde se espera un vector del tipo entero. Porque la coerción explicita es usada raramente, y es ampliamente fácil de entender, enfocaré sobre la coerción implicita aquí. Anteriormente vimos el tipo más importante de coerción implicita: usando un vector de tipo lógico en un contexto numérico. En ese caso, el valor TRUE (‘VERDADERO’) es convertido a 1 y ‘FALSE’ (‘FALSO’) convertido a 0. Esto significa que la suma de un vector de tipo lógico es el número de los valores verdaderos, y el significado de un vector de tipo lógico es la proporción de valores verdaderos:

Quizás veas algún código (tipicamente más antiguo) basado en la coerción implicita pero en la dirección opuesta, es decir, de un valor entero a uno lógico

En este caso, 0 es convertido a FALSO y todo lo demás es convertido a VERDADERO. Pienso que esto hace más dificil entender el código, por lo que no lo recomiendo. En su lugar, de ser explicito, sugiero utilizar: length(x) > 0.

Es también importante entender que pasa cuando creas un vector que contiene múltiples tipos con c(): los tipos más complejos siempre ganan.

Un vector atómico no puede contener un mix de diferentes tipos porque el tipo es una propiedad de un vector completo, no de elementos individuales. Si necesitas un mix de múltiples tipos en el mismo vector, entonces debes usar una lista, la cual aprenderás en breve.

20.4.2 Funciones de test

Algunas veces quieres hacer las cosas de una manera diferente basadas en el tipo de vector. Una de las opciones es el uso de la sentencia typeof(). Otra es usar una función test la cual devuelva un valor TRUE o ‘FALSO’ . R base provee varias funciones como is.vector() y is.atomic(), pero estas a menudo devuelven resultados inesperados. En su lugar, es más acertado usar las funciones is_* provistas por el paquete purrr, las cual están resumidas en la tabla que se muestra a continuación.

lgl int dbl chr list
is_logical() x
is_integer() x
is_double() x
is_numeric() x x
is_character() x
is_atomic() x x x x
is_list() x
is_vector() x x x x x

Cada predicado además viene con una version para “escalares”, donde la función is_scalar_atomic(), chequea que la longitud sea 1. Esto es útil, por ejemplo, si quieres chequear en algún argumento que tu función sea un solo valor lógico.

20.4.3 Escalares y reglas de reciclado

Así como implicitamente se coercionan los tipos de vectores que son compatibles, R también implicitamente coerciona la longitud de los vectores. Esto se denomina vector recycling, o reciclado de vectores, debido a que el vector de menor longitud se repite, o recicla, hasta igualar la longitud del vector de mayor longitud. Esto es generalmente lo más útil cuando estás trabajando con vectores y “escalares”. Los escalares están puestos en notas porque R en realidad no tiene definido los escalares: en su lugar, un solo número conforma un vector de longitud 1. Debido a que no existen los escalares, la mayoría de las funciones están construidas como vectorizadas, esto significa que operan sobre un vector del tipo númerico. Esto es así porque, por ejemplo, este código funciona:

En R, las operaciones matemáticas básicas funcionan con vectores. Lo que significa que no necesitarás la ejecución de una interación explicita cuando realices cálculos matemáticos sencillos. Es intuitivo lo que debería pasar si agregas dos vectores de la misma longitud, o un vector y un “escalar”, pero ¿qué sucede si agregas dos vectores de diferentes longitudes?

Aquí, R expandirá el vector de menor longitud a la misma longitud del vector de mayor longitud, a esto es lo que denominamos reciclaje o reutilización de un vector. Esto es una excepción cuando la longitud del vector de mayor longitud no es un múltiplo entero de la longitud del vector más corto:

Mientras el vector reciclado puede ser usado para crear código claro y conciso, también puede ocultar problemas de manera silenciosa. Por esta razón, las funciones vectorizadas en tidyverse mostrarán errores cuando recicles cualquier otra cosa que no sea un escalar. Si quieres reutilzar, necesitarás hacerlo tu mismo con la sentencia rep():

20.4.4 Nombrando vectores

Todos los tipos de vectores pueden ser nombrados. Puedes asignarles un nombre al momento de crearlos con c():

O después de la creación con purrr::set_names():

Los vectores con nombres son más útiles para subconjuntos, como se describe a continuación.

20.4.5 Subsetting (Subdivisión o creación de subconjuntos) {#vector-subsetting, subdivisión de vectores}

Hasta ahora hemos usado dplyr::filter() para filtrar filas en una TIBBLE. La sentencia filter() sólo funciona con TIBBLES, por lo que necesitaremos una nueva herramienta para trabajar con vectores: [. [ representa a la función Subdivisión (Subsetting), la cual nos permite crear subconjuntos o subdivisiones a partir de vectores, y se indica como x[a]. Existen cuatro tipos de cosas en las que puedes subdividir un vector:

  1. Un vector numérico contiene sólo enteros. Los enteros deben ser todos positivos, todos negativos, o cero.

La Subdivisión con enteros positivos mantiene los elementos en aquellas posiciones:

Repitiendo una posición, puedes en realidad generar un output de mayor longitud que el input::

Los valores negativos eliminan elementos en posiciones especificas:

Es un error mezclar valores positivos y negativos:

El mensaje menciona subdivisiones utilizando cero, lo cual no returna valores.

Esto a menudo no es útil, pero puede ser de ayuda si quieres crear estructuras de datos inusuales para testear tus funciones.

  1. La subdivisión de un vector lógico mantiene/almacena todos los valores correspondientes al valor TRUE VERDADERO. Esto es a menudo mayormente útil en conjunto con las funciones de comparación.
  1. Si tienes un vector con nombre, puedes subdivirlo en un vector de tipo caracter.

Como con los enteros positivos, también puedes usar un vector del tipo caracter para duplicar entradas individuales.

  1. El tipo más sencillo de subsetting es el valor vacío, x[], el cual retorna el valor completo de x. Esto no es útil para vectores subdivididos, aunque si lo es para matrices subdivididas(y otras estructuras de grandes dimensiones) ya que te permite seleccionar toda las filas o todas las columnas, dejando el indice en blanco. Por ejemplo, si x está en la segunda posición, x[1, ] selecciona la primera fila y todas las columnas, y la expresión x[, -1] selecciona todas las filas y todas las columnas excepto la primera.

Para aprender más acerca de las aplicaciones de subsetting, puedes leer el capítulo de Subsetting de R Avanzado: http://adv-r.had.co.nz/Subsetting.html#applications.

Existe una importante variación de [, la cual consiste en [[. Esta expresión [[ sólo extrae un único elemento, y siempre omite nombres. Es una buena idea usarla siempre que quieras dejar en claro que estás extrayendo un único item, como en un bucle for. La diferencia entre [ y[[ es más importante para las listas (lists), como veremos en breve.

20.4.6 Ejercicios

  1. La expresión mean(is.na(x)), ¿qué dice acerca del vector ‘x’? ¿y qué sucede con la expresión sum(!is.finite(x))?

  2. Detenidamente lee la documentación de is.vector(). ¿Para qué se prueba la función realmente? ¿Por qué la función is.atomic() no concuerda con la definición de vectores atómicos vista anteriormente?

  3. Compara y contraste setNames() con purrr::set_names().

  4. Crea funciones que tomen un vector como entrada y devuelva:
    1. El último valor. ¿Deberás usar [ o [[?.
    2. Los elementos en posiciones pares.
    3. Cada elemento excepto el último valor.
    4. Sólo las posiciones pares (y sin valores perdidos (missing values)).
    5. ¿Por qué x[-which(x > 0)] no es lo mismo que x[x <= 0]?
    6. ¿Qué sucede cuando realizas un subset (subdivisión) con un entero positivo que es mayor que la longitud del vector? ¿Qué sucede cuando realizas un subset (subdivisión) con un nombre que no existe?

20.5 Vectores Recursivos (listas)

Las listas son un escalon más en complejidad partiendo de los vectores atómicos, debido a que las listas pueden contener otras listas. Lo cual las hace adecuadas para representar una estructura jerárquica o de tipo árbol. Puedes crear una lista con ´list()´:

Un herramienta muy útil para trabajar con listas es ´str()´ ya que se enfoca en la estructura, no en los contenidos.

A diferencia de los vectores atómicos, el objeto ´list()´ puede contener una variedad de diferentes objetos:

¡Incluso las listas pueden contener otras listas!

20.5.1 Visualizando listas

Para explicar funciones de manipulacion de listas más complicadas, es útil tener una representacion visual de las mismas. Por ejemplo, defino estas tres listas:

Y a continuación, las grafico:

Existen tres principios, al momento de observer el gráfico anterior: 1. Las listas tienen esquinas redondeadas, en cambio, los vectores atómicos tienen esquinas cuadradas. 2. Los hijos son representados dentro de sus listas padres, y tienen un fondo ligeramente más oscuro para facilitar la visualización de la jerarquía. 3. No es importante la orientación de los hijos (p.ej. las filas o columnas), entonces utilizaré la orientacion de una fila o columna para almacenar espacio o incluso para ilustrar una propiedad importante en el ejemplo.

Subdivisión (Subsetting) Existen tres maneras de subdividir una lista, lo cual ilustraré con una lista denominada ´a´:

El corchete simple ´[´ extrae una sub-lista. Por lo que, el resultado siempre será una lista.

Al igual que con vectores, puedes subdividirla, en un vector lógico, de enteros o caracteres. El doble corchete ´[[´ extrae un solo componente de una lista. Y remueve un nivel de la jerarquía de la lista.

El símbolo $ es un atajo para extraer elementos con nombres de una lista. Este funciona de modo similar al doble corchete´[[´ excepto que en el primer caso no es necesario el uso de comillas dobles.

La diferencia entre el corchete simple [ y el doble [[ es realmente importante para las listas, porque el doble [[ profundiza en una lista mientras que el simple [ retorna una nueva, lista más pequeña. Compara el código y el output de arriba con la representacion visual de la Figura @ref(fig:lists-subsetting).

lists-subsetting, echo = FALSE, out.width = "75%", fig.cap = "Subdividir una lista, de manera visual."}
knitr::include_graphics("diagrams_w_text_as_path/es/lists-subsetting.png")

20.5.2 Listas de Condimentos

La diferencia entre ambos [ y [[ es muy importante, pero es muy fácil confundirlos. Para ayudarte a recordar, permiteme mostrarte un pimientero inusual

Si este pimientero es tu lista x, entonces, `x[1] es un pimientero que contiene un simple paquete de pimienta:

La expresión x[2] luciría del mismo modo, pero podría contener el segundo paquete. La expresión x[1:2] sería un pimientero que contiente dos paquetes de pimienta.

La expresión x[[1]] es: Si quisieras obtener el contenido del paquete de pimiento, necesitarías utilizar la siguiente expresión `x[[1]][[1]:

20.5.3 Ejercicios

1.Dibuja las listas siguientes como sets anidados:

 1. `list(a, b, list(c, d), list(e, f))`
 1. `list(list(list(list(list(list(a))))))`

1.¿Qué pasaría si subdividieras un tibble como si fuera una lista? ¿Cuáles son las principales diferencias entre una lista y un tibble?

20.6 Atributos

Cualquier vector puede contener metadata arbitraria adicional mediante sus atributos. Puedes pensar en los atributos como una lista de vectores que pueden ser adjuntadas a cualquier otro objeto. Puedes obtener y setear valores de atributos individuales con attr() o verlos todos al mismo tiempo con attributes().

Existen tres atributos muy importantes que son utilizados para implementar partes fundamentals de R: 1. Los Nombres son utilizados para nombrar los elementos de un vector. 2. Las Dimensiones (o dims, denominación más corta) hacen que un vector se comporte como una matriz o arreglo. 3. Una Clase es utilizada para implementar el sistema S3 orientado a objetos. A los atributos nombres los vimos arriba, y no cubriremos las dimensiones porque no se usan matrices en este libro. Resta describir el atributo clase, el cual controla como las funciones genéricas funcionan. Las funciones genéricas son clave para la programacion orientada a objetos en R, porque ellas hacen que las funciones se comporten de manera diferente de acuerdo a las diferentes clases de inputs. Una discusión más profunda sobre programacion orientada a objetos no está contemplada en el ámbito de este libro, pero puedes leer más al respecto en el documento R Avanzado en: http://adv-r.had.co.nz/OO-essentials.html#s3. Así es como una función genérica típica luce:

La llamada al método “UseMethod” significa que esta es una función genérica, y llamará a un metódo específico, una función, basada en la clase del primer argumento. (Todos los métodos son funciones; no todas las funciones son métodos). Puedes listar todos los métodos existentes para una función genérica utilizando la función: methods():

Por ejemplo, si x es un vector de caracteres, as.Date() llamará a as.Date.character(); si es un factor, llamará a as.Date.factor().

Puedes ver la implementación específica de un método con: getS3method():

Lo mas importante del S3 genérico; sistema OO, es decir, orientado a objetos; es la función print(): el cual controla como el objeto es impreso cuando tipeas su nombre en la consola. Otras funciones genéricas importantes son las funciones de subdivisión [, [[, and $.

20.7 Vectores Aumentados

Los vectores atómicos y las listas son los bloques sobre los que se construyen otros tipos importantes de vectores como los tipos factores y fechas (dates). A estos vectores , los llamo __ vectores aumentados __, porque son vectores con attributos adicionales, incluyendo la clase. Los vectores aumentados tienen una clase, por ello se comportan de manera diferente a los vectores atómicos sobre los cuales son construidos. En este libro, hacemos uso de cuatro importantes vectores aumentados: • Los Factores • Las Dates (Fechas) • Los Date-times (Fecha-tiempo) • Los Tibbles Estos son descriptos a continuación: Factores Los factores son diseñados para representar datos categoricos que pueden tomar un set fijo de valores posibles, son construidos sobre los enteros, y tienen

20.7.1 Dates and date-times (Fechas y Fecha – Hora)

Las vectores del tipo date en R son vectores numéricos que representan el número de días desde el 1° de enero de 1970.

Los vectores date-time son vectores numéricos de clase POSIXct que representan el número de segundos desde el 1° de enero de 1970. (En caso de que te preguntes sobre “POSIXct”; “POSIXct” significa “Portable Operating System Interface, lo que significa “Interfaz portable de sistema operativo”; tiempo de calendario.)

El atributo tzone es opcional. Este controla como se muestra la hora, y no hace referencia al tiempo en términos absolutos.

Existe otro tipo de vector date-time llamado POSIXlt. Éstos son construidos en base a listas con nombres (named lists).

Los vectores POSIXlts son pocos comunes dentro del paquete tidyverse. Surgen en base a R, porque son necesarios para extraer components específicos de una fecha, como el año o el mes. Desde que el paquete lubridate te provee helpers para efectuar dicha extracción, los vectores POSIXlts no son necesarios. Siempre es más sencillo trabajar con loa vectores POSIXct’s, por lo tanto si tenés un vector POSIXlt, deberías convertirlo a un vector regular del tipo data-time con lubridate::as_date_time().

20.7.2 Tibbles

Los Tibbles son listas aumentadas: los cuales tienen las clases “tbl_df”, “tbl” y “data.frame”, y los atributos names (para nombrar una columna) y row.names (para nombrar una fila):

La diferencia entre un tibble y una lista, consiste en que todos los elementos de un data frame deben ser vectores con la misma longitud. Por lo tanto, todas las funciones que utilizan tibbles refuerzan esta condición.

Además, los data.frames tradicionales tienen una estructura muy similar a los tibbles:

Se puede decir, que la diferencia principal entre ambos es la clase. Debido a que un tibble incluye el tipo “data.frame” lo que significa que los tibbles heredan el comportamiento regular de un data frame por defecto. Ejecicios: 1. ¿Qué valor retorna la siguiente expresión hms::hms(3600)? ¿Cómo se muestra? ¿Cuál es la tipo primario sobre el cual se basa el vector aumentado? ¿Qué atributos utiliza el mismo? 2. Intenta y crea un tibble que tenga columnas con diferentes longitudes ¿Qué es lo que ocurre? 3. Teniendo en cuenta