R Principiantes
R Principiantes
Este libro está dirigído a personas que nunca han usado R o ningún otro
lenguaje de programación. No es necesario conocimiento previo de
estadística.
El propósito es que el lector:
Dado que S y sus estándares son propiedad de los Laboratorios Bell, lo cual
restringe su uso, Ross Ihaka y Robert Gentleman, de la Universidad de
Auckland en Nueva Zelanda, decidieron crear una implementación abierta y
gratuita de S. Este trabajo, que culminaría en la creación de R inició en
1992, teniendo una versión inicial del lenguaje en 1995 y en el 2000 una
versión final estable.
Aunque R está diseñado para análisis estadístico, con el paso del tiempo los
usuarios de este lenguaje han creado extensiones a R, llamadas paquetes,
que han ampliado su funcionalidad. En la actualidad es posible realizar en R
minería de textos, procesamiento de imagen, visualizaciones interactivas de
datos y procesamiento de Big Data, entre muchas otras cosas.
Referencias
Level (2017). How Big Companies Are Using R for Data Analysis.
Recuperado en septiembre de 2017 de:
[Link]
using-r-data-analysis/
Microsoft (2014). Companies using R in 2014. Recuperado en
septiembre de 2017 de:
[Link]
[Link]
Bhalla, D. (2016) Companies using R. Recuperado en septiembre de
2017 de: [Link]
R FAQ. Recuperado en Septiembre de 2017 de: [Link]
[Link]/doc/FAQ/[Link]#What-is-R_003f
TIOBE Index for September 2017. Recuperado en Septiembre de 2017
de: [Link]
Adesanya, T. (2017). A Gentler Introduction to Programming.
Recuperado en Septiembre de 2017 de:
[Link]
programming-707453a79ee8
2 Instalación
La manera de instalar R cambia dependiendo del sistema operativo utilices
pero todas tienen en común el uso de CRAN.
[Link]
2.1 Windows
Para instalar R en Windows, la forma más simple es descargar la versión
más reciente de R base desde el siguiente enlace de CRAN:
[Link]
[Link]
2.3 Linux
En Linux, como suele ser el caso para casi todo, hay una manera fácil y una
difícil de instalar R.
[Link]
[Link]
3.1 La consola de R
Lo primero que nos encontramos al ejecutar R es una pantalla que nos muestra la
versión de este lenguaje que estamos ejecutando y un prompt:
>_
Cuando decimos que R nos devuelve algo, es que ha realizado algo que le hemos
pedido, es decir, nos está dando una salida.
3.3 Objetos
En R, todo es un objeto. Todos los datos y estructuras de datos son objetos. Además,
todos los objetos tienen un nombre para identificarlos.
Las constantes y variables en R tienen nombres que nos permiten hacer referencia a
ellas en operaciones.
Las constantes ya están establecidas por R, mientras que nosotros podemos crear
variables, asignándoles valores a nombres.
En R usamos <- para hacer asignaciones. De este modo, podemos asignar el valor 3 a
la variable radio
radio <- 3
Los nombres de las variables pueden incluir letras, números, puntos y guiones bajos.
Deben empezar siempre con una letra o un punto y si empiezan con un punto, a este
no puede seguirle un número.
Finalmente, cuando te encuentres con un renglón de código que inicia con un gato
(hashtag), esto representa un comentario, es código que no se ejecutará, sólo se
mostrará.
# Este es un comentario
Cuando llamamos una función, se realizan las operaciones que contiene, usando los
argumentos que hemos establecido.
mean()
quantile()
summary()
density()
c()
Al igual que con las variables, se recomienda que los nombres de las funciones sean
claros, no ambiguos y descriptivos. Idealmente, el nombre de una función describe lo
que hace. De hecho, es probable que adivines qué hacen casi todas funciones de la
lista de arriba a partir de su nombre.
Las funciones son un tema que revisamos más adelante. Por el momento, recuerda que
una función realiza operaciones y nos pide argumentos para poder llevarlas a cabo.
3.6 Documentación
Las funciones de R base y aquellas que forman parte de paquete tienen un archivo de
documentación.
Este archivo describe qué hace la función, sus argumentos, detalles sobre las
operaciones que realiza,los resultados que devuelve y ejemplos de uso.
?mean()
help("mean")
Por ejemplo, la documentación del paquete stats, instalado por defecto en R base.
help(package = "stats")
Puedes encontrar cuál es tu directorio de trabajo con la función getwd(). Sólo tienes
que escribir la función en la consola y ejecutarla.
getwd()
## [1] "C:/Users/JuanBosco/Documents/GitHub/r-principiantes-bookdown"
setwd("C:\otro_directorio")
# Ver archivos
[Link]()
# Ver directorios
[Link]()
3.7.1 Sesión
Todos los objetos y funciones creadas en una sesión, permanecen sólo en ella, no son
compartidos entre sesiones, sin embargo una sesión puede tener el mismo directorio
de trabajo que otra sesión.
Para conocer los objetos y funciones que contiene nuestra sesión, usamos la función
ls(), que nos devolverá una lista con los nombres de todo lo guardado en la sesión.
ls()
## [1] "arboles" "area_cuad" "area_prisma"
## [4] "banco" "bato" "bcancer"
## [7] "bmi" "cara" "coco_jambo"
## [10] "columna" "columnas" "conteo"
## [13] "correlaciones" "crear_histograma" "dado"
## [16] "desv_est" "df" "edades"
## [19] "estatura" "i" "ingreso"
## [22] "iris_csv" "iris_excel" "iris_txt"
## [25] "lista_recursiva" "mat_cuant" "mat_media"
## [28] "matriz" "matriz_t" "matriz2"
## [31] "matriz3" "media" "mi_array"
## [34] "mi_df" "mi_lista" "mi_lista_importado"
## [37] "mi_matriz" "mi_vector" "mi_vector_1"
## [40] "mi_vector_2" "mi_vector_3" "mi_vector_mezcla"
## [43] "mi_vector_nuevo" "nivel" "nombre"
## [46] "nombres" "num" "numero"
## [49] "peso" "posicion" "promedio"
## [52] "ptab_banco" "radio" "resultado"
## [55] "tab_banco" "tablas" "tiempo_final"
## [58] "tiempo_inicial" "trees_excel" "trees_max"
## [61] "umbral" "valor" "variacion_tiempo"
## [64] "variacion_velocidad" "vector" "vector_1"
## [67] "vector_2" "vector_3" "vector_4"
## [70] "vector1" "vector2" "velocidad_final"
## [73] "velocidad_inicial"
Con que recuerdes que cada sesión de R tiene su propio entorno global, eso será
suficiente.
3.8 Paquetes
R puede ser expandido con paquetes. Cada paquete es una colección de funciones
diseñadas para atender una tarea específica. Por ejemplo, hay paquetes para trabajo
visualización geoespacial, análisis psicométricos, mineria de datos, interacción con
servicios de internet y muchas otras cosas más.
Estos paquetes se encuentran alojados en CRAN, así que pasan por un control
riguroso antes de estar disponibles para su uso generalizado.
[Link]("readr")
Una vez concluida la instalación de un paquete, podrás usar sus funciones con la
función library(). Sólo tienes que llamar esta función usando como argument oel
nombre del paquete que quieres utilizar
library(readr)
Es importante que tengas en mente que debes hacer una llamada a library() cada
que inicies una sesión en R. Aunque hayas importado las funciones de un paquete con
anterioridad, las sesiones de R se inician "limpias", sólo con los objetos y funciones de
base.
[Link]("un_paquete_falso")
Los paquetes que hemos importado en nuestra sesión actual aparecen al llamar
sessionInfo().
Los datos de tipo lógico sólo tienen dos valores posibles: TRUE (verdadero)
y FALSE(falso). Este tipo de datos es esencial para trabajar con álgebra
booleana.
Las cadenas de texto son diferentes a los factores aunque suelen lucir igual
en la consola. Un factor puede ser descrito como un dato numérico
representado por una etiqueta. Cada uno de las etiquetas o valores que
puedes asumir un factor se conoce como nivel.
Por ejemplo, podemos tener un conjunto de datos de tipo factor con dos
niveles, femenino (1) y masculino (2). Para nuestra computadora,
femenino tiene un valor de 1, pero a nosotros se nos muestra la palabra
femenino. Por su parte, las cadenas de texto no son representaciones de
datos numéricos, son
class(3)
## [1] "numeric"
class("3")
## [1] "character"
class(TRUE)
## [1] "logical"
4.1 Coerción
En R, los datos pueden ser coercionados (forzados) para convertirlos de un
tipo a otro. Cuando pedimos a R que ejecute una operación, R intentará
coercionar los datos al tipo correcto que permita realizarla. Si no lo logra,
nos devuelve como resultado un error.
Como los datos de tipo lógico sólo admiten dos valores (TRUE y FALSE),
estos son los más restrictivos; mientras que los datos de cadena de texto, al
admitir cualquier cantidad y combinación de caracteres, son los más
flexibles.
[Link](5)
## [1] "5"
Estas funciones siguen las reglas de coerción de R, así que no podemos
hacer forzar coerciones no permitidas.
[Link]("palabra")
## [1] NA
[Link](5)
## [1] TRUE
[Link](5)
## [1] FALSE
5 Operadores
Los operadores son los símbolos que le indican a R que debe realizar una tarea.
Combinando datos y operadores es que logramos que R haga su trabajo.
Existen operadores específicos para cada tipo de tarea. Los tipos de operadores
principales son los siguientes:
Aritméticos
Relacionales
Lógicos
De asignación
15 * 3
## [1] 45
Cuando intentas realizar una operación aritmética con otro tipo de dato, R primero
intentará coercionar ese dato a uno numérico. Si la coerción tiene éxito se realizará la
operación normalmente, si falla, el resultado será un error.
4 + "tres"
El mensaje "non-numeric argument for binary operator" aparece siempre que intentas
realizar una operación aritmética con un argumento no numérico. Si te encuentras un un
error que contiene este mensaje, es la primera pista para que identifiques donde ha
ocurrido un problema.
Cualquier operación aritmética que intentemos con un dato NA, devolverá NA como
resultado.
NA - 66
## [1] NA
21 * NA
## [1] NA
NA ^ 13
## [1] NA
Entre los operadores aritméticos, el de división entera o módulo requiere una explicación
adicional sobre su uso. La operación que realiza es una división de un número entre otro,
pero en lugar de devolver el cociente, nos devuelve el residuo.
Por ejemplo, si hacemos una división entera de 4 entre 2, el resultado será 0. Esta es una
división exacta y no tiene residuo.
4 %% 2
## [1] 0
En cambio, si hacemos una división entera de 5 entre 2, el resultado será 1, pues este es el
residuo de la operación.
5 %% 2
## [1] 1
Sin embargo, al usar los operadores >, >=, < y <= con cadenas de texto, estos tienen un
comportamiento especial.
## [1] TRUE
Este resultado se debe a que se ha hecho una comparación por orden alfabético. En este
caso, la palabra "casa" tendría una posición posterior a "barco", pues empieza con "c" y
esta letra tiene una posición posterior a la "b" en el alfabeto. Por lo tanto, es verdadero
que sea "mayor".
## [1] NA
Estos operadores pueden ser usados con estos con datos de tipo numérico, lógico y
complejo. Al igual que con los operadores relacionales, los operadores lógicos siempre
devuelven TRUE o FALSE.
Para realizar operaciones lógicas, todos los valores numéricos y complejos distintos a 0
son coercionados a TRUE, mientras que 0 siempre es coercionado a FALSE.
Por ejemplo, 5 | 0 resulta en TRUE y 5 & FALSE resulta en FALSE. Podemos comprobar lo
anterior con la función isTRUE().
5 | 0
## [1] TRUE
5 & 0
## [1] FALSE
isTRUE(0)
## [1] FALSE
isTRUE(5)
## [1] FALSE
!(FALSE | FALSE)
## [1] TRUE
También podemos combinar operadores lógicos y relacionales, dado que esto últimos dan
como resultado TRUE y FALSE.
## [1] TRUE
Operador Operación
<- Asigna un valor a una variable
= Asigna un valor a una variable
Aunque podemos usar el signo igual para una asignación, a lo largo de este libro
utilizaremos <-, por ser característico de R y fácil de reconocer visualmente.
Además, esta operación nos permite "guardar" el resultado de operaciones, de modo que
podemos recuperarlos sin necesidad de realizar las operaciones otra vez. Basta con llamar
el nombre de la variable en la consola
estatura
## [1] 1.73
peso
## [1] 83
peso / estatura ^ 2
## [1] 27.7323
peso <- 76
peso
## [1] 76
peso / estatura ^ 2
## [1] 25.39343
peso / estatura ^ 2
## [1] 19.72387
bmi
## [1] 19.72387
Como podrás ver, es posible asignar a una variable valores de otra variable o el resultado
de operaciones con otras variables.
tiempo_inicial <- 0
tiempo_final <- 15
variacion_velocidad / variacion_tiempo
## [1] 5
En la tabla siguiente se presenta el orden en que ocurren las operaciones que hemos
revisado en este capítulo.
Orden Operadores
1 ^
2 */
3 +-
4 < > <= >= == !=
5 !
6 &
7 |
8 <-
Si deseamos que una operación ocurra antes que otra, rompiendo este orden de
evaluación, usamos paréntesis.
Podemos tener paréntesis anidados.
6 Estructuras de datos
Las estructuras de datos son objetos que contienen datos. Cuando trabajamos con R, lo que estamos haciendo es
manipular estas estructuras.
Las estructuras tienen diferentes características. Entre ellas, las que distinguen a una estructura de otra son su
número de dimensiones y si son homogeneas o hereterogeneas.
6.1 Vectores
Un vector es la estructura de datos más sencilla en R. Un vector es una colección de uno o más datos del mismo
tipo.
Tipo. Un vector tiene el mismo tipo que los datos que contiene. Si tenemos un vector que contiene datos de
tipo numérico, el vector será también de tipo numérico. Los vectores son atómicos, pues sólo pueden
contener datos de un sólo tipo, no es posible mezclar datos de tipos diferentes dentro de ellos.
Largo. Es el número de elementos que contiene un vector. El largo es la única dimensión que tiene esta
estructura de datos.
Atributos. Los vectores pueden tener metadatos de muchos tipos, los cuales describen características de los
datos que contienen. Todos ellos son incluidos en esta propiedad. En este libro no se usarán vectores con
metadatos, por ser una propiedad con usos van más allá del alcance de este libro.
Cuando una estructura únicamente puede contener datos de un sólo tipo, como es el caso de los vectores, decimos
que es homogénea, pero no implica que necesariamente sea atómica. Regresaremos sobre esto al hablar de
matrices y arrays.
Como los vectores son la estructura de datos más sencilla de R, datos simples como el número 3, son en realidad
vectores. En este caso, un vector de tipo numérico y largo igual a 1.
## [1] 3
[Link](3)
## [1] TRUE
## [1] 1
Lo mismo ocurre con los demás tipos de datos, por ejemplo, con cadenas de texto y datos lógicos.
[Link]("tres")
## [1] TRUE
[Link](TRUE)
## [1] TRUE
Llamamos esta función y le damos como argumento los elementos que deseamos combinar en un vector, separados
por comas.
# Vector numérico
c(1, 2, 3, 5, 8, 13)
## [1] 1 2 3 5 8 13
# Vector lógico
c(TRUE, TRUE, FALSE, FALSE, TRUE)
Si deseamos agregar un elemento a un vector ya existente, podemos hacerlo combinando nuestro vector original
con los elementos nuevos y asignando el resultado a nuestro vector original.
mi_vector
mi_vector_3
## [1] 1 3 5 2 4 6
Si intentamos combinar datos de diferentes tipos en un mismo vector, R realizará coerción automáticamente. El
vector resultante será del tipo más flexible entre los datos que contenga, siguiendo las reglas de coerción.
class(mi_vector)
## [1] "numeric"
Si intentamos agregar un dato de tipo cadena de texto, nuestro vector ahora será de tipo cadena de texto.
class(mi_vector_nuevo)
## [1] "character"
Como las cadenas de texto son el tipo de dato más flexible, siempre que creamos un vector que incluye un dato de
este tipo, el resultado será un vector de texto.
class(mi_vector_mezcla)
## [1] "character"
Podemos crear vectores de secuencias numéricas usando :. De un lado de los dos puntos escribimos el número de
inicio de la secuencia y del otro el final.
1:10
## [1] 1 2 3 4 5 6 7 8 9 10
10:1
## [1] 10 9 8 7 6 5 4 3 2 1
Las secuencias creadas con : son consecutivas con incrementos o decrementos de 1. Estas secuencias pueden
empezar con cualquier número, incluso si este es negativo o tiene cifras decimales
# Número negativo
-43:-30
## [1] -43 -42 -41 -40 -39 -38 -37 -36 -35 -34 -33 -32 -31 -30
Si nuestro número de inicio tiene cifras decimales, estas serán respetadas al hacer los incrementos o decrementos
de uno en uno. En contraste, si es nuestro número de final el que tiene cifras decimales, este será redondeado.
56.007:50
968:960.928
Existen algunas operaciones al aplicarlas a un vector, se aplican a cada uno de sus elementos. A este proceso le
llamamos vectorización.
Por ejemplo, las operaciones aritméticas pueden vectorizarse. Si las aplicamos a un vector, la operación se
realizará para cada uno de los elementos que contiene.
# Nuestro vector
mi_vector <- c(2, 3, 6, 7, 8, 10, 11)
# Las operaciones
mi_vector + 2
## [1] 4 5 8 9 10 12 13
mi_vector * 2
## [1] 4 6 12 14 16 20 22
mi_vector / 2
mi_vector ^ 2
mi_vector %% 2
## [1] 0 1 0 1 0 0 1
Esta manera de aplicar una operación es muy eficiente. Comparada con otros procedimientos, requiere de menos
tiempo de cómputo, lo cual a veces es considerable, en particular cuando trabajamos con un número grande de
datos.
Aunque el nombre de este proceso es vectorización, también funciona, en ciertas circunstancias, para otras
estructuras de datos.
En un sentido estricto, las matrices son una caso especial de un array, que se distingue por tener específicamente
dos dimensiones, un "largo"" y un "alto". Las matrices son, por lo tanto, una estructura con forma rectangular, con
renglones y columnas.
Como las matrices son usadas de manera regular en matemáticas y estadística, es una estructura de datos de uso
común en R común y en la que nos enfocaremos en este libro.
Los arrays, por su parte, pueden tener un número arbitrario de dimensiones. Pueden ser cubos, hipercubos y otras
formas. Su uso no es muy común en R, aunque a veces es deseable contar con objetos n-dimensionales para
manipular datos. Como los arrays tienen la restricción de que todos sus datos deben ser del mismo tipo, no
importando en cuántas dimensiones se encuentren, esto limita sus usos prácticos.
En general, es preferible usar listas en lugar de arrays, una estructura de datos que además tienen ciertas ventajas
que veremos más adelante.
Creamos matrices en R con la función matrix(). La función matrix() acepta dos argumentos, nrow y ncol. Con
ellos especificamos el número de renglones y columnas que tendrá nuestra matriz.
## [1] 1 2 3 4 5 6 7 8 9 10 11 12
## [,1]
## [1,] 1
## [2,] 2
## [3,] 3
## [4,] 4
## [5,] 5
## [6,] 6
## [7,] 7
## [8,] 8
## [9,] 9
## [10,] 10
## [11,] 11
## [12,] 12
Los datos que intentemos agrupar en una matriz serán acomodados en orden, de arriba a abajo, y de izquierda a
derecha, hasta formar un rectángulo.
Cuando intentamos acomodar un número diferente de elementos y celdas, ocurren dos cosas diferentes.
Si el número de elementos es mayor al número de celdas, se acomodarán todos los datos que sean posibles y los
demás se omitirán.
Si, por el contrario, el número de celdas es mayor que el número de elementos, estos se reciclaran. En cuanto los
elementos sean insuficientes para acomodarse en las celdas, R nos devolverá una advertencia y se empezaran a
usar los elementos a partir del primero de ellos
## Warning in matrix(1:12, nrow = 5, ncol = 4): data length [12] is not a sub-
## multiple or multiple of the number of rows [5]
Otro procedimiento para crear matrices es la unión vectores con las siguientes funciones:
cbind() para unir vectores, usando cada uno como una columna.
rbind() para unir vectores, usando cada uno como un renglón.
De este modo podemos crear cuatro vectores y unirlos para formar una matriz. Cada vector será un renglón en esta
matriz.
Usamos rbind() para crear un matriz, en la que cada vector será un renglón.
# Resultado
matriz
# Resultado
matriz
Al igual que con matrix(), los elementos de los vectores son reciclados para formar una estructura rectangular y
se nos muestra un mensaje de advertencia.
# Resultado
matriz
# Resultados
matriz
Como NA representa datos perdidos, puede estar presente en compañía de todo tipo de de datos.
No obstante que las matrices y arrays son estructuras que sólo pueden contener un tipo de datos, no son atómicas.
Su clase es igual a matriz (matrix) o array segun corresponda.
class(mi_matriz)
## [1] "matrix"
Obtenemos el número de dimensiones de una matriz o array con la función dim(). Esta función nos devolverá
varios números, cada uno de ellos indica la cantidad de elementos que tiene una dimensión.
## [1] 4 3
Cabe señalar que si usamos dim() con un vector, obtenemos NULL. Esto ocurre con todos los objetos
unidimensionales
dim(mi_vector)
## NULL
Finalmente, las operaciones aritméticas también son vectorizadas al aplicarlas a una matriz. La operación es
aplicada a cada uno de los elementos de la matriz.
# Resultado
mi_matriz
# Suma
mi_matriz + 1
# Multiplicación
mi_matriz * 2
# Potenciación
mi_matriz ^ 3
Si intentamos vectorizar una operación utilizando una matriz con NAs, esta se aplicará para los elementos válidos,
devolviendo NA cuando corresponda.
# Resultado
matriz
matriz / 2
# Resultado
matriz
## [,1] [,2]
## [1,] 1 4
## [2,] 2 5
## [3,] 3 6
# Resultado
matriz_t
Podemos entender a los data frames como una versión más flexible de una matriz. Mientras que en una matriz
todas las celdas deben contener datos del mismo tipo, los renglones de un data frame admiten datos de distintos
tipos, pero sus columnas conservan la restricción de contener datos de un sólo tipo.
En términos generales, los renglones en un data frame representan casos, individuos u observaciones, mientras que
las columnas representan atributos, rasgos o variables. Por ejemplo, así lucen los primeros cinco renglones del
objeto iris, el famoso conjunto de datos Iris de Ronald Fisher, que está incluido en todas las instalaciones de R.
## [Link] [Link] [Link] [Link] Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
Los primeros cinco renglones corresponden a cinco casos, en este caso flores. Las columnas son variables con los
rasgos de cada flor: largo y ancho de sépalo, largo y ancho de pétalo, y especie.
Para crear un data frame usamos la función [Link](). Esta función nos pedirá un número de vectores igual al
número de columnas que deseemos. Todos los vectores que proporcionemos deben tener el mismo largo.
Más adelante se hará evidente porque esta característica de un data frame es sumamente importante y también,
cómo podemos sacarle provecho.
Además, podemos asignar un nombre a cada vector, que se convertirá en el nombre de la columna. Como todos los
nombres, es recomendable que este sea claro, no ambiguo y descriptivo.
mi_df
## [1] 4 4
## [1] 4
## [1] "function"
Si los vectores que usamos para construir el data frame no son del mismo largo, los datos no se reciclaran. Se nos
devolverá un error.
[Link](
"entero" = 1:3,
"factor" = c("a", "b", "c", "d"),
"numero" = c(1.2, 3.4, 4.5, 5.6),
"cadena" = [Link](c("a", "b", "c", "d"))
)
## Error in [Link](entero = 1:3, factor = c("a", "b", "c", "d"), numero = c(1.2, : arguments
Verificamos el resultado
class(df)
## [1] "[Link]"
# Resultado
df
## V1 V2 V3 V4
## 1 1 4 7 10
## 2 2 5 8 11
## 3 3 6 9 12
Al igual que con una matriz, si aplicamos una operación aritmética a un data frame, esta se vectorizará.
Los resultados que obtendremos dependerán del tipo de datos de cada columna. R nos devolverá todas las
advertencias que ocurran como resultado de las operaciones realizadas, por ejemplo, aquellas que hayan requerido
una coerción.
mi_df * 2
6.4 Listas
Las listas, al igual que los vectores, son estructuras de datos unidimensionales, sólo tienen largo, pero a diferencia
de los vectores cada uno de sus elementos puede ser de diferente tipo o incluso de diferente clase, por lo que son
estructuras heterogéneas.
Podemos tener listas que contengan datos atómicos, vectores, matrices, arrays, data frames u otras listas. Esta
última característica es la razón por la que una lista puede ser considerada un vector recursivo, pues es un objeto
que puede contener objetos de su misma clase.
Para crear una lista usamos la función list(), que nos pedirá los elementos que deseamos incluir en nuestra lista.
Para esta estructura, no importan las dimensiones o largo de los elementos que queramos incluir en ella.
Al igual que con un data frame, tenemos la opción de poner nombre a cada elemento de una lista.
Por último, no es posible vectorizar operaciones aritméticas usando una lista, se nos devuelve un error como
resultado.
mi_lista
## $un_vector
## [1] 1 2 3 4 5 6 7 8 9 10
##
## $una_matriz
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $un_df
## num let
## 1 1 a
## 2 2 b
## 3 3 c
# Resultado
lista_recursiva
## $lista1
## $lista1$un_vector
## [1] 1 2 3 4 5 6 7 8 9 10
##
## $lista1$una_matriz
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $lista1$un_df
## num let
## 1 1 a
## 2 2 b
## 3 3 c
##
##
## $lista2
## $lista2$un_vector
## [1] 1 2 3 4 5 6 7 8 9 10
##
## $lista2$una_matriz
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $lista2$un_df
## num let
## 1 1 a
## 2 2 b
## 3 3 c
length(lista_recursiva)
## [1] 2
Dado que una lista siempre tiene una sola dimensión, la función dim() nos devuelve NULL.
dim(lista_recursiva)
## NULL
Las listas tienen clase list, sin importar qué elementos contienen.
class(lista_recursiva)
## [1] "list"
Finalmente, no es posible vectorizar operaciones aritméticas usando listas. Al intentarlo nos es devuelto un error.
mi_lista / 2
Si deseamos aplicar una función a cada elemento de una lista, usamos lapply(), como veremos en el capítulo 10.
7 Subconjuntos
En R, podemos obtener subconjuntos de todas las estructuras de datos. Esto es, podemos extraer datos de
las estructuras que tengan una o más características en especial.
Para esta extracción usaremos índices, operadores lógicos y álgebra Booleana. Aunque cada estructura de
datos de R es diferente, existen procedimientos para obtener subconjuntos que pueden usarse con todas
ellas. Por supuesto, hay otras que funcionan sólo con algunas estructuras.
7.1 Índices
Crer subconjuntos usando índices es el procedimiento más universal en R, pues funciona con todas las
estructuras de datos.
Los índices en R son posicionales. Cuando usamos este método le pedimos a R que extraiga de una
estructura los datos que se encuentran en una o varias posiciones específicas.
Escribimos corchetes [] después de un objeto para obtener subconjuntos con índices. Dentro de los
corchetes escribimos el o los números que corresponden a la posición que nos interesa extraer del objeto.
Por ejemplo:
objeto[3]
lista[4:5]
dataframe[c(2, 7), ]
Veamos un ejemplo con un vector. Creamos un vector que contiene los nombres de distintos niveles
educativos.
nivel
length(nivel)
## [1] 5
¿Cómo obtendríamos el tercer elemento de este vector usando índices? ¿Y del primer al cuarto elemento?
¿O el segundo y quinto elemento?
Sabemos que los índices son posicionales y que se usan corchetes para realizar la operación de
extracción, por lo tanto, las respuestas a las preguntas anteriores son relativamente intuitivas.
Para extraer el tercer elemento hacemos lo siguiente.
nivel[3]
## [1] "Secundaria"
Por lo tanto, para extraer del primer al cuarto elemento de un vector, usamos los números del 1 al 4.
nivel[1:4]
Cuando deseamos extraer elementos en posiciones no consecutivas, usamos vectores. Por ejemplo, para
extraer el segundo y quinto elemento del vector *nivel, lo siguiente no funciona.
nivel[2, 5]
nivel[c(2, 5)]
¿Porqué no funcionó usar nivel[2, 5] para extraer dos elementos no consecutivos en nuestro vector?
El mensaje de error nos da una pista muy importante. Al usar una coma dentro de los corchetes estamos
pidiendo a R que busque los índices solicitados en más de una dimensión.
Entonces, al llamar nivel[2, 5] le pedimos a R que extraiga el elemento que se encuentA en la posición
2 de la primera dimensión del vector, y el elemento en la posición 5 de su segunda dimensión. Como los
vectores son unidimensionales, es imposible cumplir esta instrucción y se produce un error.
En cambio, usar nivel[c(2, 5)] funciona porque estamos dando un vector con dos números, pero
ambos en una misma dimensión de nuestro objeto.
Para estructuras con más de una dimensión, los índices hacen referencia a posiciones para cada una de
ellas. En estructuras de dos dimensiones (matrices y data frames), el primer índice es para los
renglones y la segunda para las columnas.
Este es un tipo de operación muy común al trabajar con data frames y matrices
mi_df
Confirmamos que nuestro data frame tiene dos dimensiones: tres renglones y tres columnas.
dim(mi_df)
## [1] 4 4
Con índices podemos extraer un dato que se encuentra en un renglón y columna específico.
## [1] M
## Levels: H M
## [1] 20
## [1] Elsa
## Levels: Armando Elsa Ignacio Olga
# Extraer dato en
mi_df[1:2, 3:4]
## sexo grupo
## 1 H 0
## 2 M 1
# Si dejamos vacio el índice para una dimensión, nos son devueltos todos
# los datos que contiene
mi_df[ , 1]
mi_df[1, ]
## nombre edad sexo grupo
## 1 Armando 20 H 0
## nombre grupo
## 1 Armando 0
## 3 Ignacio 1
Para objetos de tres o más dimensiones se siguen las mismas reglas, aunque ya no es tan fácil hablar de
renglones y columnas. Por ejemplo, un array de cuatro dimensiones.
## [1] 2 2 2 2
## , , 1, 1
##
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## , , 2, 1
##
## [,1] [,2]
## [1,] 5 7
## [2,] 6 8
##
## , , 1, 2
##
## [,1] [,2]
## [1,] 9 11
## [2,] 10 12
##
## , , 2, 2
##
## [,1] [,2]
## [1,] 13 15
## [2,] 14 16
# Un par de subconjuntos
mi_array[1, 2, 1, 2]
## [1] 11
mi_array[ , 1, 1, 1]
## [1] 1 2
Finalmente, conviene saber que la posición de los elementos en una estructura se determinan en su
creación.
## [1] "b"
vector2[2]
## [1] "c"
Esto es suficiente por ahora. La siguiente ocasión continuaremos con más métodos para crear
subconjuntos.
Esta ocasión terminaremos de revisar las formas de extraer subconjuntos de nuestros datos. Puede parecer
que le estamos dedicando mucho tiempo a este tema, pero es crucial para ahorrarnos dolores de cabeza en
el futuro.
Al igual que con los índices, podemos usar corchetes cuadrados ([ ]) para obtener subconjuntos, pero en
lugar de escribir un número de índice, escribimos el nombre del elemento que deseamos extraer como
una cadena de texto, es decir, entre comillas.
mi_df["nombre"]
## nombre
## 1 Armando
## 2 Elsa
## 3 Ignacio
## 4 Olga
mi_df["grupo"]
## grupo
## 1 0
## 2 1
## 3 1
## 4 0
En una data frame, cada uno de sus elementos representa una columna en los datos, así que en realidad
estamos pidiendo a R que nos devuelva las columnas con los nombres que le indicamos.
Podemos extraer más de un elemento si en lugar de una cadena de texto escribimos un vector de texto
entre los corchetes.
# Esto funciona
mi_df[c("edad", "sexo")]
## edad sexo
## 1 20 H
## 2 24 M
## 3 22 M
## 4 30 H
## [1] <NA>
## Levels: H M
## sexo edad
## 1 H 20
## 2 M 24
## 3 M 22
## 4 H 30
mi_df["calificacion"]
Para una lista, el procedimiento es el mismo que con un data frame. En lugar de obtener columnas,
obtenemos los elementos contenidos en la lista para los que hemos proporcionado un nombre.
Una diferencia importante con los data frame es que si pedimos un nombre que no existe en la lista, se
nos devuelve NULL en lugar de un error.
mi_lista["dos"]
## $dos
## [1] "2"
mi_lista[c("cuatro", "tres")]
## $cuatro
## [1] 1 2 3 4
##
## $tres
## [1] 3
## Levels: 3
mi_lista["cinco"]
## $<NA>
## NULL
También podemos usar el signo de dólar $ para extraer subconjuntos usando nombres.
Este método permite extraer sólo un elemento a la vez y en un data frame, siempre devolverá una
columna. Si lo deseamos, podemos escribir el nombre que nos interesa obtener sin comillas.
# Esto funciona
mi_df$nombre
# Esto no funciona
mi_df$c("nombre", "edad")
Notarás que la salida al usar este método es diferente que si usamos corchetes. Si revisas la Tarea 01,
verás más ejemplos de este comportamiento.
En pocas palabras, distintos métodos para obtener subconjuntos pueden devolver resultados
diferentes. Más adelante en este documento veremos porqué ocurre esto
** Nombres para extraer renglones De igual manera que con los índices, si escribimos dentro de un
corchete nombres separados por comas, R interpretará esto como que estamos buscando elementos en
más de una dimensión.
En un data frame, el primer nombre corresponderá a renglones y el segundo a columnas. Esto funciona
porque en un data frame los renglones también pueden tener nombre.
Por ejemplo, en el conjunto de datos iris los nombres de los renglones son igual a su número de renglón.
iris["110", "Species"]
## [1] virginica
## Levels: setosa versicolor virginica
## [Link] [Link]
## 15 5.8 4
## [Link] [Link]
## 88 6.3 2.3
## 96 5.7 3.0
Esta es una herramienta muy poderosa al manipular datos pues nos proporciona una flexibilidad para
extrar subconjuntos.
iris[5:6, "Species"]
iris["76", 2:3]
## [Link] [Link]
## 76 3 4.4
## [Link] Species
## 1 0.2 setosa
## 2 0.2 setosa
## 149 2.3 virginica
## 150 1.8 virginica
Esto no funciona con las listas, Por ser estructuras de datos unidimensionales.
** Subconjuntos con su clase y tipo original: usando el signo de dólar $ y el corchete doble [[ ]]
mi_df["nombre"]
## nombre
## 1 Armando
## 2 Elsa
## 3 Ignacio
## 4 Olga
mi_df$nombre
Para entender que está ocurriendo, recordemos que un data frame está formado por vectores. Estos
vectores nunca dejan de ser vectores, aunque estén contenidos dentro de un data frame, por lo tanto, es
posible extraerlos de esta estructura de datos.
Además, necesitamos entender que cuando extraemos un subconjunto de un objeto usando corchetes,
obtenemos como resultado un objeto de su misma clase.
Esto suena a un trabalenguas, pero todo lo que quiere decir es que extraemos un subconjunto de una lista,
obtenemos una lista; si lo hacemos de un data frame, obtenemos un data frame; y lo mismo para todas las
estructuras de datos.
class(mi_df)
## [1] "[Link]"
class(mi_df["nombre"])
## [1] "[Link]"
Esto cambia cuando usamos el signo de dólar $ para extraer un subconjunto. Si usamos un signo de
dolar, obtenemos un objeto de la misma clase y tipo que era ese elemento originalmente.
En el caso de un data frame, usar el signo de dólar siempre resulta en vectores atómicos.
class(mi_df$nombre)
## [1] "factor"
class(mi_df$edad)
## [1] "numeric"
class(mi_df$sexo)
## [1] "factor"
## [1] "list"
## [1] "matrix"
## [1] "[Link]"
Otra manera de extraer elementos de un objeto con su clase original es usar corchetes dobles [[ ]].
La ventaja de usar este método es que podemos usar índices y nombres dentro de los corchetes dobles, lo
que nos da acceso a una mayor flexibilidad para extraer subconjuntos, además de que nos permite usarlos
en estructuras de datos que tienen elementos sin nombre.
mi_df[["edad"]]
## [1] 20 24 22 30
mi_df[[2]]
## [1] 20 24 22 30
mi_matriz
## [1] 4
Sin embargo, no podemos usar vectores dentro los corchetes dobles para extraer subconjuntos, pues este
método busca un elemento a la vez.
## Error in mi_matriz[[c(1, 3), 2]]: attempt to select more than one element in get1index
Obtener un objeto del tipo correcto al extraer un subconjunto es sumamente importante, en particular si
deseamos usar este subconjunto para realizar otras operaciones.
Por ejemplo, si queremos obtener la media de la columna [Link] del conjunto de datos iris, usamos
la función mean( ). Sin embargo, esta función nos pide que demos como argumento un vector, de modo
que debemos estar atentos a cómo extraemos la columna que nos interesa.
## [1] NA
# Esto sí funciona
mean(iris$[Link])
## [1] 3.057333
## [1] 3.057333
Por ejemplo, queremos obtener todos los datos de una encuesta que corresponden a mujeres, o a personas
que viven en una entidad específica o que tienen un ingreso superior a la media.
Si tenemos columnas que contengan esa información en nuestro conjunto de datos, podemos extraer
subconjuntos usando condicionales dentro de los corchetes.
** objeto[condicion, columnas_devueltas] **
Veamos un ejemplo.
Extraeremos del conjunto iris...
... todos los casos en los que el ancho del pétalo es mayor a 2.
iris[iris["[Link]"] > 2, ]
iris[iris[["Species"]] == "setosa", ]
... la especie de los casos en que los que el ancho del sépalo sea menor a 3.
... el ancho y largo del pétalo de los casos en los que el largo del sépalo es mayor o igual a 7.2.
## [Link] [Link]
## 106 6.6 2.1
## 108 6.3 1.8
## 110 6.1 2.5
## 118 6.7 2.2
## 119 6.9 2.3
## 123 6.7 2.0
## 126 6.0 1.8
## 130 5.8 1.6
## 131 6.1 1.9
## 132 6.4 2.0
## 136 6.1 2.3
Si no indicamos qué columnas queremos que se nos devuelvan, obtendremos todas. De esta manera
podemos extraer subconjuntos que cumplen una condición, pero sólo para una columna específica.
Para entender porqué escribimos una operación relacional aplicada a un subconjunto dentro de los
corchetes, nos conviene saber que las operaciones relacionales también se vectorizan.
Si al vector iris$[Link] aplicamos la operación > 6, esta se aplicará a todos sus elementos,
devolviendo TRUE o FALSE, según corresponda.
[Link](iris["[Link]"] > 6)
## [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [23] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [34] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [45] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [56] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [67] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [78] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [89] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [100] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [111] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [122] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [144] FALSE FALSE FALSE FALSE FALSE FALSE FALSE
Así, lo que pedimos antes de la coma dentro de un corchete, son todos los renglones para los que la
condición es verdadera.
Si quisieras, podrías elegir manualmente renglones de un data frame usando TRUE y FALSE.
# EL vector de datos lógicos se reciclará, así que obtendremos uno de cada cinco elemento
iris[c(TRUE, FALSE, FALSE, FALSE, FALSE), ]
Sin embargo, es comun que necesitemos realizar tareas para las que no existe una función específica o que para
encontrar solución necesitemos combinarl o utilizar funciones en sucesión, lo cual puede complicar nuestro
código.
Esto es sencillo de resolver pues contamos con la función hist() que hace exactamente esto. Sólo tenemos que
dar un vector numérico como argumento para generar una gráfica (veremos esto con más detalle en el capítulo 12).
Primero, generaremos datos aleatorios sacados de una distribución normal con la función rnorm(). Esta función
tiene los siguientes argumentos:
Además, llamaremos [Link]() para que estos resultados sean replicables. Cada que llamamos rnorm() se
generan número aleatorios diferentes, pero si antes llamamos a [Link](), con un número específico como
argumento obtendremos los mismos resultados.
[Link](173)
edades <- rnorm(n = 1500, mean = 15, sd = .75)
edades[1:10]
Ahora, sólo tenemos que ejecutar hist() con el argumento x igual a nuestro vector y obtendremos un histograma.
# Histograma
hist(x = edades)
Estupendo. Hemos logrado nuestro objetivo.
Nuestro jefe está satisfecho, pero le gustaría que en el histograma se muestre la media y desviación estándar de los
datos, que tenga un título descriptivo y que los ejes estén etiquetados en español, además de que las barras sean de
color dorado.
Suena complicado, pero podemos calcular la media de los datos usando la función mean(), la desviación estándar
con sd() y podemos agregar los resultados de este cálculo al histograma usando la función abline(). Para agregar
título, etiquetas en español y colores al histograma sólo basta agregar los argumentos apropiados a la función
hist().
No te preocupes mucho por los detalles de todo esto, lo veremos más adelante.
Agregamos líneas con abline(), para la media de rojo y desviación estándar con azúl. También ajustamos los
argumentos de hist().
Para cumplir con esta tarea podríamos usar el código que ya hemos escrito. Simplemente lo copiamos y pegamos
cincuenta veces, cambiando los valores para cada una de variables que nos han pedido.
Pero hacer las cosas de este modo propicia errores y es difícil de corregir y actualizar.
Para empezar, si copias el código anterior cincuenta veces, tendrás un script con más de 400 líneas. Si en algún
momento te equivocas porque escribiste "Enceusta" en lugar de "Encuesta", incluso con las herramientas de
búsqueda de RStudio, encontrar donde está el error será una tarea larga y tediosa.
Y si tu jefe en esta ejemplo quiere que agregues, quites o modifiques tu histograma, tendrás que hacer el cambio
cincuenta veces, una para cada copia del código. De nuevo, con esto se incrementa el riesgo de que ocurran
errores.
Es en situaciones como esta en las que se hace evidente la necesidad de crear nuestras propias funciones, capaces
de realizar una tarea específica a nuestros problemas, y que pueda usarse de manera repetida. Así reducimos
errores, facilitamos hacer correcciones o cambios y nos hacemos la vida más fácil, a nosotros y a quienes usen
nuestro código después.
Cuando asignamos una función a un nombre decimos que hemos definido una función.
El nombre que asignamos a una función nos permite ejecutarla y hacer referencias a ella. Podemos asignar la
misma función a diferentes nombres o cambiar una función a la que ya le hemos asignado un nombre. Es
recomendable elegir nombres claros, no ambiguos y descriptivos.
Una vez que la función tiene nombre, podemos llamarla usando su nombre, al igua lque con las funciones por
defecto de R.
Los argumentos son las variables que necesita la función para realizar sus operaciones. Aparecen entre paréntesis,
separados por comas. Los valores son asignados al nombre del argumento por el usuario cada vez que ejecuta una
función. Esto permite que usemos nuestras funciones en distintas situaciones con diferentes datos y
especificaciones.
Los argumentos pueden ser datos, estructuras de datos, conexiones a archivos u otras funciones y todos deben
tener nombres diferentes.
El cuerpo de la función contiene, entre llaves, todas las operaciones que se ejecutarán cuando la función sea
llamada. El cuerpo de una función puede ser tan sencillo o complejo como nosotros deseemos, incluso podemos
definir funciones dentro de una función (y definir funciones dentro de una función dentro de otra función, aunque
esto se vuelve confuso rápidamente).
Si el código del cuerpo de la función tiene errores, sus operaciones no se realizarán y nos será devuelto un mensaje
de error al ejecutarla. R no avisa si nuestra función va a funcionar o no hasta que intentamos correrla.
Una ventaja de usar RStudio es que nos indica errores de sintaxis en nuestro código, lo cual puede prevenir
algunos errores. Sin embargo, hay alguno que no detecta, como realizar operaciones o coerciones imposibles.
Para ver esto en acción, crearemos una función sencilla para obtener el área de un cuadrilátero.
Podemos convertir esto a operaciones de R y asignarlas a una función llamada area_cuad de la siguiente manera:
Nombre: area_cuad.
Argumentos: lado1, lado2. Estos son los datos que necesita la función para calcular el área, representan el
largo de los lados de un cuadrilátero.
Cuerpo: La operación lado1 * lado2, escrita de manera que R pueda interpretarla.
Ejecutaremos nuestra función para comprobar que funciona. Nota que lo único que hacemos cada que la llamamos
es cambiar la medida de los lados del cuadrilátero para el que calcularemos un área, en lugar de escribir la
operación lado1 * lado2 en cada ocasión.
area_cuad(lado1 = 4, lado2 = 6)
## [1] 24
## [1] 1296
En cada llamada a nuestra función estamos asignando valores distintos a los argumentos usando el signo de igual.
Si no asignamos valores a un argumento, se nos mostrará un error
area_cuad(lado1 = 14)
## Error in area_cuad(lado1 = 14): argument "lado2" is missing, with no default
En R, podemos especificar los argumentos por posición. El orden de los argumentos se determina cuando creamos
una función.
En este caso, nosotros determinamos que el primer argumento que recibe area_cuad es lado1 y el segundo es
lado2. Así, podemos escribir lo siguiente y obtener el resultado esperado.
area_cuad(128, 64)
## [1] 8192
Podemos crear ahora una función ligeramente más compleja para calcular el volumen de un prisma rectangular
Siguiendo la misma lógica de transformar un algoritmo a código de R, podemos crear una función con el
algoritmo: arista x arista x arista.
## [1] 162
# Probemos la función
area_prisma(3, 6, 9)
## [1] 162
Con esto estamos listos para definir una función para crear histogramas con las características que nos pidió
nuestro jefe hipotético.
}
Para que esta función realice lo que deseamos necesitamos:
datos
nombre
Ya sabemos las operaciones realizaremos, sólo tenemos que incluirlas al cuerpo de nuestro función.
Reemplazaremos las variables que hacen referencia a un objeto en particular por el nombre de nuestros
argumentos. De esta manera será generalizable a otros casos.
En este ejemplo, cambiamos la referencia a la variable edades por referencias al argumento datos y la referencia a
Edades, que usaremos como título del histograma, por una referencia al argumento nombre.
Probemos nuestra función usando datos distintos, generados de manera similar a las edades, con la función
rnorm().
Generaremos datos de ingreso, con una media igual a 15000 y una desviación estándar de 4500.
# Resultado
ingreso[1:10]
crear_histograma(ingreso, "Ingreso")
Luce bien. Probemos ahora con datos sobre el peso de las personas. siguiendo el mismo procedimiento.
crear_histograma(peso, "Peso")
Las funciones definidas por el usuario pueden devolvernos errores. Por ejemplo, si introducimos datos que no son
apropiados para las operaciones a realizar, nuestra función no se ejecutará correctamente.
crear_histograma("Cuatro", ingreso)
## Error in [Link](datos, main = nombre, xlab = "Datos", ylab = "Frecuencia", : 'x' must be
Por esta razón es importante crear documentación para las funciones que hayas creado. Puede ser tan sencilla
como una explicación de qué hace la función y qué tipo de datos necesita para realizar sus operaciones.
La primera persona beneficiada por esto eres tu, pues tu yo de un mes en el futuro puede haber olvidado por
completo la lógica de una función específica, así que la documentación es una manera de recordar tu trabajo.
# crear_histograma
# Devuelve un histograma con lineas indicando la media y desviación estándar de un vector de dat
# Argumentos:
# - datos: Un vector numérico.
# - nombre: Una cadena de texto.
8.4.1 Ejecutando
Ahora, podremos cumplir con la solicitud de nuestro jefe ficticio usando cincuenta llamadas a una función en lugar
de correr más de cuatrocientas líneas de código y que hemos reducido la probabilidad de cometer errores.
Además, si a nuestro jefe se le ocurren nuevas características para los histogramas, basta con cambiar el cuerpo de
nuestra función una vez y esto se verá reflejado en nuestro cincuenta casos al correr de nuevo el código.
Por ejemplo, supongamos que nuestro jefe también quiere que el histograma muestre la mediana de nuestros datos
y que las barras sean de color naranja. Basta con hacer un par de cambios.
# Resultado
crear_histograma(peso, "Peso con mediana")
Quizás estés pensando que escribir una función cincuenta veces de todos modos es demasiada repetición y aún se
presta a cometer errores. Lo cual es cierto, pero podemos hacer más breve nuestro código y menos susceptible a
equivocaciones con la familia de funciones apply, que revisaremos en el capítulo 10.
9 Estructuras de control
Como su nombre lo indica, las estructuras de control nos permiten controlar la manera en
que se ejecuta nuestro código.
Las estructuras de control establecen condicionales en nuestros código. Por ejemplo, qué
condiciones deben cumplirse para realizar una operación o qué debe ocurrir para ejecutar
una función.
Esto es de gran utilidad para determinar la lógica y el orden en que ocurren las operaciones,
en especial al definir funciones.
else (de otro modo) es usado para indicarle a R qué hacer en caso de la condición de un if
no se cumpla.
Un if es la manera de decirle a R:
if(Condición) {
operaciones_si_la_condición_es_TRUE
}
## [1] "Verdadero"
else complementa un if, pues indica qué ocurrirá cuando la condición no se cumple, es
falsa (FALSE), en lugar de no hacer nada.
if(condición) {
operaciones_si_la_condición_es_TRUE
} else {
operaciones_si_la_condición_es_FALSE
}
## [1] "Verdadero"
## [1] "Falso"
Para ilustrar el uso de if else definiremos una función que calcule el promedio de
calificaciones de un estudiante y, dependiendo de la calificación calculada, nos devuelva un
mensaje específico.
promedio(c(6, 7, 8, 9, 8))
## [1] 7.6
promedio(c(5, 8, 5, 6, 5))
## [1] 5.8
Ahora deseamos que esta función nos muestre si un estudiante ha aprobado o no.
Si asumimos que un estudiante necesita obtener 6 o más de promedio para aprobar, podemos
decir que:
if(media >= 6) {
print("Aprobado")
} else {
print("Reprobado")
}
}
## [1] "Aprobado"
promedio(c(5, 8, 5, 6, 5))
## [1] "Reprobado"
Está funcionando, aunque los resultados podrían tener una mejor presentación.
Usaremos la función paste0() para pegar el promedio de calificaciones, como texto, con el
resultado de "Aprobado" o "Reprobado". Esta función acepta como argumentos cadenas de
texto y las pega (concatena) entre sí, devolviendo como resultado una nueva cadena.
if(media >= 6) {
print(paste0(texto, "aprobado"))
} else {
print(paste0(texto, "reprobado"))
}
}
promedio(c(6, 7, 8, 9, 8))
promedio(c(5, 8, 5, 6, 5))
Por supuesto, como lo vimos en el capítulo sobre funciones, podemos hacer aún más
compleja a promedio(), pero esto es suficiente para conocer mejor las aplicaciones de if
else.
9.1.2 ifelse
La función ifelse( ) nos permite vectorizar if, else. En lugar de escribir una línea de
código para cada comparación, podemos usar una sola llamada a esta función, que se
aplicará a todos los elementos de un vector.
if(1:10 < 3) {
"Verdadero"
}
## Warning in if (1:10 < 3) {: the condition has length > 1 and only the first
## element will be used
## [1] "Verdadero"
Este mensaje nos dice que sólo se usará el primer elemento del vector para evaluar su la
condición es verdadera y lo demás será ignorado.
En cambio, con ifelse se nos devolverá un valor para cada elemento de un vector en el que
la condición sea TRUE, además nos devolverá otro valor para los elementos en que la
condición sea FALSE.
Si intentamos el ejemplo anterior con ifelse(), se nos devolverá un resultado para cada
elemento del vector, no sólo del primero de ellos.
## [1] "Verdadero" "Verdadero" "Falso" "Falso" "Falso"
## [6] "Falso" "Falso" "Falso" "Falso" "Falso"
De este modo podemos usar ifelse() para saber si los números en un vector son pares o
nones.
Por ejemplo, pedimos sólo los números que son exactamente divisibles entre 2 y 3.
num <- 1:20
num
9.2 for
La estructura for nos permite ejecutar un bucle (loop), realizando una operación para cada
elemento de un conjunto de datos.
Su estructura es la siguiente:
for(elemento in objeto) {
operacion_con_elemento
}
Al escribir un bucle for la parte que corresponde al elemento la podemos llamar como
nosotros deseemos, pero la parte que corresponde al objeto debe ser el nombre de un objeto
existente.
Los dos bucles siguientes son equivalentes, sólo cambia el nombre que le hemos puesto al
elemento.
for(elemento in objeto) {
operacion_con_elemento
}
for(i in objeto) {
operacion_con_elemento
}
Tradicionalmente se usa la letra i para denotar al elemento, pero nosotros usaremos nombres
más descriptivos en este capítulo.
Vamos a obtener el cuadrado de cada uno de los elementos en un vector numérico del 1 al 6,
que representa las caras de un dado.
for(cara in dado) {
dado ^ 2
}
Notarás que al ejecutar el código anterior parece que no ha ocurrido nada. En realidad, sí se
han realizado las operaciones, pero R no ha devuelto sus resultados.
Las operaciones en un for se realizan pero sus resultados nunca son devueltos
automáticamente, es necesario pedirlos de manera explícita.
Una solución para mostrar los resultados de un bucle for es usar la función print().
for(cara in dado) {
print(cara ^ 2)
}
## [1] 1
## [1] 4
## [1] 9
## [1] 16
## [1] 25
## [1] 36
Comprobamos que la operación ha sido realizada a cada elemento de nuestro objeto. Sin
embargo, usar print() sólo mostrará los resultados de las operaciones en la consola, no los
asignará a un objeto.
for(numero in 1:10) {
print(numero)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
En nuestro ejemplo, pasamos por los valores de dado, cara por cara. La primera cara será
igual a 1, la segunda a 2, y así sucesivamente hasta el 6.
Podemos usar estos valores para asignar cada valor resultante de nuestras operaciones a una
posición específica en un vector, incluso si este está vacio.
for(cara in dado) {
mi_vector[cara] <- cara ^ 2
}
Aunque no fueron mostrados en la consola, los resultados han sido asignados al objeto
mi_vector.
mi_vector
## [1] 1 4 9 16 25 36
dado ^ 2
## [1] 1 4 9 16 25 36
Dado que en R contamos con vectorización de operaciones, que podemos usar las funciones
de la familia apply (discutido en siguiente capítulo) en objetos diferentes a vectores y que la
manera de recuperar los resultados de un for es un tanto laboriosa, este tipo de bucle no es
muy popular en R.
Sin embargo, es conveniente que conozcas esta estructura de control, pues hay ocasiones en
la que es la mejor herramienta para algunos problemas específicos.
9.3 while
Este es un tipo de bucle que ocurre mientras una condición es verdadera (TRUE). La
operación se realiza hasta que se se llega a cumplir un criterio previamente establecido.
while(condicion) {
operaciones
}
Probemos sumar +1 a un valor, mientras que este sea menor que 5. Al igual que con for,
necesitamos la función print() para mostrar los resultados en la consola.
umbral <- 5
valor <- 0
while(valor < umbral) {
print("Todavía no.")
valor <- valor + 1
}
¡Ten cuidado con crear bucles infinitos! Si ejecutas un while con una condición que nunca
será FALSE, este nunca se detendrá.
Si corres lo siguiente, presiona la tecla ESC para detener la ejecución, de otro modo, correra
por siempre y puede llegar a congelar tu equipo.
while(1 < 2) {
print("Presiona ESC para detener")
}
El siguiente es un error común. Estamos sumando 1 a i con cada iteración del bucle, pero
como no estamos asignando este nuevo valor á i, su valor se mantiene igual, entonces la
condición nunca se cumplirá y el bucle será infinito.
i <- 0
while(i < 10) {
i + 1
}
Un uso común de while es que realice operaciones que queremos detener cuando se cumple
una condición, pero desconocemos cuándo ocurrirá est
Supongamos que, por alguna razón queremos sumar calificaciones, del 1 al 10 al azar, hasta
llegar a un número que mayor o igual a 50. Además nos interesa saber cuántas calificaciones
sumaron y cuál fue el resultado al momento de cumplir la condición.
Para obtener números al azar del 1 al 10, usamos la función sample(). Esta función va a
tomar una muestra al azar de tamaño igual a 1 (argumento size) de un vector del 1 al 10
(argumento x) cada vez que se ejecute.
Por lo tanto, cada vez que corras el ejemplo siguiente obtendrás un resultado distinto, pero
siempre llegarás a un valor mayor a 50.
Primero, tomará un número al azar del 1 al 10, y lo sumará a valor. Segundo, le sumará 1 a
conteo cada que esto ocurra, de esta manera sabremos cuántas iteraciones ocurrieron para
llegar a un valor que no sea menor a 50.
Aunque no son mostrados en la consola los resultados son asignados a los objetos valory
conteo
valor
## [1] 52
conteo
## [1] 8
Por último, si intentamos ejecutar un while para el que la condición nunca es igual a TRUE,
este no realizará ninguna operación.
conteo <- 0
while("dado" == "ficha") {
conteo <- conteo + 1
}
conteo
## [1] 0
Para interrumpir un bucle con break, necesitamos que se cumpla una condición. Cuando
esto ocurre, el bucle se detiene, aunque existan elementos a los cuales aún podría aplicarse.
for(i in 1:10) {
if(i == 3) {
break
}
print(i)
}
## [1] 1
## [1] 2
numero <- 20
while(numero > 5) {
if(numero == 15) {
break
}
numero <- numero - 1
}
numero
## [1] 15
Como habrás notado, la aplicación de break es muy similar a while, realizar una operación
hasta que se cumple una condición, y ambos pueden usarse en conjunto.
Por su parte, usamos next para "saltarnos" una iteración en un bucle. Cuando la condición se
cumple, esa iteración es omitida.
for(i in 1:4) {
if(i == 3) {
next
}
print(i)
}
## [1] 1
## [1] 2
## [1] 4
Estas dos estructuras de control nos dan un control fino sobre nuestro código. aunque los
dos ejemplos de arriba son con for, también funcionan con while y repeat.
9.5 repeat
Este es un bucle que se llevará a cabo el número de veces que especifiquemos, usando un
break para detenerse. repeat asegura que las operaciones que contiene sean iteradas al
menos en una ocasión.
repeat {
operaciones
un_break_para_detener
}
Por ejemplo, el siguiente repeat sumará +1 a valor hasta que este sea igual a cinco,
entonces se detendrá.
valor <- 0
mi_vector <- NULL
repeat{
valor <- valor + 1
if(valor == 5) {
break
}
}
# Resultado
valor
## [1] 5
Este tipo de bucle es quizás el menos utilizado de todos, pues en R existen alternativas para
obtener los mismos resultados de manera más sencilla y sin el riesgo de crear un bucle
infinito. Sin embargo, puede ser la mejor alternativa para problemas específicos.
10 La familia apply
La familia de funciones apply es usada para aplicar una función a cada elemento de una
estructura de datos. En particular, es usada para aplicar funciones en matrices, data
frames, arrays y listas.
Con esta familia de funciones podemos automatizar tareas complejas usando poca líneas
de código y es una de las características distintivas de R como lenguaje de programación.
La familia de funciones apply es una expresión de los rasgos del paradigma funcional de
programación presentes en R. Sobre esto no profundizaremos demasiado, pero se refiere
saber que en R las funciones son "ciudadanos de primera", con la misma importancia que
los objetos, y por lo tanto, operamos en ellas.
La familia de funciones apply no sólo recibe datos como argumentos, también recibe
funciones.
Hay operaciones que, si las aplicamos a un vector, son aplicadas a todos sus elementos.
mi_vector
## [1] 1 2 3 4 5 6 7 8 9 10
mi_vector ^ 2
## [1] 1 4 9 16 25 36 49 64 81 100
Lo anterior es, generalmente, preferible a escribir una operación para cada elemento o a
usar un bucle for, como se describió en el capítulo sobre estructuras de control.
Como todo lo que ocurre en R es una función, podemos decir que al vectorizar estamos
aplicando una función a cada elemento de un vector. La familia de funciones apply
nos permite implementar esto en estructuras de datos distintas a los vectores.
10.0.2 Las funciones de la familia apply
apply()
eapply()
lapply()
mapply()
rapply()
sapply()
tapply()
vapply()
Es una familia numerosa y esta variedad de funciones se debe a que varias de ellas tienen
aplicaciones sumamente específicas.
Todas las funciones de esta familia tienen una característica en común: reciben como
argumentos a un objeto y al menos una función.
Hasta ahora, todas las funciones que hemos usado han recibido como argumentos
estructuras de datos, sean vectores, data frames o de otro tipo. Las funciones de la familia
apply tienen la particularidad que pueden recibir a otra función como un argumento. Lo
anterior puede sonar confuso, pero es más bien intuitivo al verlo implementado.
Nosotros trabajaremos con las funciones más generales y de uso común de esta familia:
apply()
lapply()
Estas dos funciones nos permitirán solucionar casi todos los problemas a los que nos
encontremos. Además, conociendo su uso, las demás funciones de la familia apply serán
relativamente fáciles de entender.
10.1 apply
apply aplica una función a todos los elementos de una matriz.
10.1.1 ¿Qué es X
X es una matriz o cualquier otro objeto que sea posible coercionar a una matriz. Esto es,
principalmente, vectores y data frames.
mi_df
## v1 v2
## 1 1 4
## 2 2 5
## 3 3 6
# Coerción a matriz
mi_matriz <- [Link](mi_df)
## [1] TRUE
# Resultado
mi_matriz
## v1 v2
## [1,] 1 4
## [2,] 2 5
## [3,] 3 6
Aunque también podemos coercionar listas y arrays a matrices, los resultados que
obtenemos no siempre son apropiados para apply(), por lo que no es recomendable usarl
estos objetos como argumentos.
Para MARGIN:
1 es renglones.
2 es columnas.
Por ejemplo, podemos usar apply() para obtener la sumatoria de los elementos de una
matriz, por renglón.
Aplicamos apply(), dando la función sum() el argumento FUN, nota que sólo necesitamos
el nombre de la función, sin paréntesis.
Por último, damos el argumento MARGIN = 1, para aplicar la función por renglón.
## [1] 28 32 22 26
sum(matriz[1, ])
## [1] 28
sum(matriz[2, ])
## [1] 32
sum(matriz[3, ])
## [1] 22
sum(matriz[4, ])
## [1] 26
sum(vector_1)
## [1] NA
sum(vector_2)
## [1] NA
sum(vector_3)
## [1] 15
sum(vector_4)
## [1] 58
Estamos aplicando una función a cada elemento de nuestra matriz. Los elementos
son los renglones. Cada renglón es un vector. Cada vector es usado como argumento
de la función.
## [1] 10 26 42 30
En este caso, la función sum() ha sido aplicado a cada elementos de nuestra matriz, los
elementos son las columnas, y cada columna es un vector.
FUN es un argumento que nos pide el nombre de una función que se se aplicarla a
todos los elementos de nuestra matriz.
El ejemplo de la sección anterior aplicamos las funciones mean() y sum() usando sus
nombres, sin paréntesis, esto es, sin especificar argumentos.
Podemos dar como argumento cualquier nombre de función, siempre y cuando ésta acepte
vectores como argumentos.
apply(matriz, 1, mean)
apply(matriz, 2, mean)
# Desviación estándar
apply(matriz, 1, FUN = sd)
# Máximo
apply(matriz, 1, FUN = max)
## [1] 13 14 11 12
# Cuantiles
apply(matriz, 1, FUN = quantile)
Por lo tanto, el primer argumento que espera la función, será la X del apply().
Para ilustrar esto, usaremos la función quantile(). Llama ?quantile en la consola para
ver su documentación.
?quantile
quantile() espera siempre un argumento x, que debe ser un vector numérico, además
tener varios argumentos adicionales.
probs es un vector numérico con las probabilidades de las que queremos extraer
cuantiles.
[Link], si le asignamos TRUE quitará de x los NA y NaN antes de realizar operaciones.
names, si le asignamos TRUE, hará que el objeto resultado de la función tenga
nombres.
type espera un valor entre 1 y 9, para determinar el algoritmo usado para el cálculo
de los cuantiles.
Esto funcionará siempre y cuando los argumentos sean apropiados para la función. Si
proporcionamos un argumento inválido, la función no se ejecutará y apply fallará.
Por ejemplo, intentamos obtener cuantiles de las columnas de una matriz, en la que una de
ellas es de tipo caracter.
# Resultado
apply(matriz2, 2, quantile)
## Error in (1 - h) * qs[i]: non-numeric argument to binary operator
Por lo tanto, apply sólo puede ser usado con funciones que esperan vectores como
argumentos.
10.1.5 ¿Qué pasa si deseamos utilizar los demás argumentos de una función
con apply?
En los casos en los que una función tiene recibe más de un argumento, asignamos los
valores de estos del nombre de la función, separados por comas, usando sus propios
nombres (a este procedimiento es al que se refiere el argumento ... descrito en la
documentación de apply).
Si además deseamos que el resultado aparezca sin nombres, entonces definimos el valor
del argumento names de la misma manera.
De este modo es posible aplicar funciones complejas que aceptan múltiples argumentos,
con la ventaja que usamos pocas líneas de código.
class(mat_media)
## [1] "numeric"
class(mat_cuant)
## [1] "matrix"
Este comportamiento se debe a que apply() nos devolverá objetos del mismo tipo que
la función aplicada devuelve. Dependiendo de la función, será el tipo de objeto que
obtengamos.
Sin embargo, este comportamiento puede causarte algunos problemas. En primer lugar,
anterior te obliga a conocer de antemano el tipo del resultado que obtendrás, lo cual no
siempre es fácil de determinar, en particular si las funciones que estás utilizando son poco
comunes o tienen comportamientos poco convencionales.
Cuando estás trabajando en proyectos en los que el resultado de una operación será usado
en operaciones posteriores, corres el riesgo de que en alguna parte del proceso, un
apply() te devuelva un resultado que te impida continuar adelante.
Con algo de práctica es más o menos sencillo identificar problemas posibles con los
resultados de apply(), pero es algo que debes tener en cuenta, pues puede explicar por
qué tu código no funciona como esperabas.
En este sentido, lapply() tiene la ventaja de que siempre devuelve una lista.
10.2 lapply
lapply() es un caso especial de apply(), diseñado para aplicar funciones a todos los
elementos de una lista. La l de su nombre se refiere, precisamente, a lista.
lapply() intentará coercionar a una lista el objeto que demos como argumento y después
aplicará una función a todos sus elementos.
lapply siempre nos devolverá una lista como resultado. A diferencia de apply, sabemos
que siempre obtendremos ub objeto de tipo lista después de aplicar una función, sin
importar cuál función sea.
Dado que en R todas las estructuras de datos pueden coercionarse a una lista, lapply()
puede usarse en un número más amplio de casos que apply(), además de que esto nos
permite utilizar funciones que aceptan argumentos distintos a vectores.
lapply(X, FUN)
En donde:
Estos argumentos son idéntico a los de apply(), pero a diferencia aquí no especificamos
MARGIN, pues las listas son estructuras con una unidimensionales, que sólo tienen largo.
trees contiene datos sobre el grueso, alto y volumen de distinto árboles de cerezo negro.
Cada una de estas variables está almacenada en una columna del data frame.
trees[1:5, ]
Dado que un data frame está formado por columnas y cada columna es un vector atómico,
cuando usamos lapply() , la función es aplicada a cada columna. lapply(), a diferencia
de apply() no puede aplicarse a renglones.
class(arboles)
## [1] "list"
Esto es muy conveniente, pues la recomendación para almacenar datos en un data frame
es que cada columna represente una variable y cada renglón un caso (por ejemplo, el
enfoque tidy de Wickham (2014)). Por lo tanto, con lapply() podemos manipular y
transformar datos, por variable.
Al igual que con apply(), podemos definir argumentos adicionales a las funciones que
usemos, usando sus nombres, después del nombre de la función.
## $Girth
## 80%
## 16.3
##
## $Height
## 80%
## 81
##
## $Volume
## 80%
## 42.6
Si usamos lapply con una matriz, la función se aplicará a cada celda de la matriz, no a
cada columna.
Creamos una matriz.
# Resultado
matriz
Llamamos a lapply().
## [[1]]
## 80%
## 1
##
## [[2]]
## 80%
## 2
##
## [[3]]
## 80%
## 3
##
## [[4]]
## 80%
## 4
##
## [[5]]
## 80%
## 5
##
## [[6]]
## 80%
## 6
##
## [[7]]
## 80%
## 7
##
## [[8]]
## 80%
## 8
##
## [[9]]
## 80%
## 9
Para usar una matriz con lapply() y que la función se aplique a cada columna, primero la
coercionamos a un data frame con la función [Link]()
## $V1
## 80%
## 2.6
##
## $V2
## 80%
## 5.6
##
## $V3
## 80%
## 8.6
Si deseamos aplicar una función a los renglones de una matriz, una manera de lograr es
transponer la matriz con t() y después coercionar a un data frame.
## $V1
## 80%
## 5.8
##
## $V2
## 80%
## 6.8
##
## $V3
## 80%
## 7.8
Con vectores como argumento, lapply() aplicará la función a cada elementos del vector,
de manera similar a una vectorización de operaciones.
Por ejemplo, usamos lapply() para obtener la raíz cuadrada de un vector numérico del 1
al 4, con la función sqrt().
lapply(mi_vector, sqrt)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 1.414214
##
## [[3]]
## [1] 1.732051
##
## [[4]]
## [1] 2
De hecho, lapply() está haciendo lo mismo que un for(), está iterando una operación en
todos los elementos de una estructura de datos.
for(numero in mi_vector) {
resultado[posicion] <- sqrt(numero)
posicion <- posicion + 1
}
resultado
... nos dará los mismos resultados que el siguiente código con lapply().
resultado
## [[1]]
## [1] 2.44949
##
## [[2]]
## [1] 2.645751
##
## [[3]]
## [1] 2.828427
##
## [[4]]
## [1] 3
##
## [[5]]
## [1] 3.162278
##
## [[6]]
## [1] 3.316625
##
## [[7]]
## [1] 3.464102
El código con lapply() es mucho más breve y más sencillo de entender, al menos para
otros usuarios de R.
El inconveniente es que obtenemos una lista como resultado en lugar de un vector, pero
eso es fácil de resolver usando la función [Link]() para hacer coerción a tipo
numérico.
[Link](resultado)
El siguiente código es la manera en la que usamos for() si deseamos aplicar una función
a todas sus columnas, tiene algunas partes que no hemos discutido, pero es sólo para
ilustrar la diferencia simplemente usar trees_max <- lapply(trees, max).
for(i in 1:columnas) {
trees_max[i] <- max(trees[, i])
i <- i +1
}
trees_max
Hasta hora hemos hablado de usar lapply() con objetos que pueden coercionarse a una
lista, pero ¿qué pasa si usamos esta función con una lista que contiene a otros objetos?
Pues la función se aplicará a cada uno de ellos. Por lo tanto, así podemos utilizar
funciones que acepten todo tipo de objetos como argumento. Incluso podemos aplicar
funciones a listas recursivas, es decir, listas de listas.
Por ejemplo, obtendremos el coeficiente de correlación de cuatro data frames contenidos
en una sola lista. Esto no es posible con apply(), porque sólo podemos usar funciones
que aceptan vectores como argumentos, pero con lapply() no es ningún problema.
Empezaremos creando una lista de data frames. Para esto, usaremos las función rnorm(),
que genera números al azar y [Link](), para que obtengas los mismos resultados aquí
mostrados.
# Fijamos seed
[Link](seed = 2018)
# Resultado
tablas
## $df1
## a b c
## 1 -0.42298398 -0.2647112 -0.6430347
## 2 -1.54987816 2.0994707 -1.0300287
## 3 -0.06442932 0.8633512 0.7124813
## 4 0.27088135 -0.6105871 -0.4457721
## 5 1.73528367 0.6370556 0.2489796
##
## $df2
## d e f
## 1 -1.0741940 1.2638637 -0.2401222
## 2 -1.8272617 0.2501979 -1.0586618
## 3 0.0154919 0.2581954 0.4194091
## 4 -1.6843613 1.7855342 -0.2709566
## 5 0.2044675 -1.2197058 -0.6318248
##
## $df3
## g h i
## 1 -0.2284119 -0.4897908 -0.3594423
## 2 1.1786797 1.4105216 -1.2995363
## 3 -0.2662727 -1.0752636 -0.8698701
## 4 0.5281408 0.2923947 1.0543623
## 5 -1.7686592 -0.2066645 -0.1486396
Para obtener el coeficiente de correlación usaremos la función cor().
Esta función acepta como argumento una data frame o una matriz. Con este objeto,
calculará el coeficiente de correlación R de Pearson existente entre cada una de sus
columnas. Como resultado obtendremos una matriz de correlación.
cor(iris[1:4])
Con lapply aplicaremos cor() a cada uno de los data frames contenidos en nuestra lista.
El resultado será una lista de matrices de correlaciones.
## $df1
## a b c
## a 1.0000000 -0.4427336 0.6355358
## b -0.4427336 1.0000000 -0.1057007
## c 0.6355358 -0.1057007 1.0000000
##
## $df2
## d e f
## d 1.0000000 -0.6960942 0.4709283
## e -0.6960942 1.0000000 0.2624429
## f 0.4709283 0.2624429 1.0000000
##
## $df3
## g h i
## g 1.0000000 0.6228793 -0.1472657
## h 0.6228793 1.0000000 -0.1211321
## i -0.1472657 -0.1211321 1.0000000
De esta manera puedes manipular información de múltiples data frames, matrices o listas
con muy pocas líneas de código y, en muchos casos, más rápidamente que con las
alternativas existentes.
## a b c
## a 1.0000000 -0.4427336 0.6355358
## b -0.4427336 1.0000000 -0.1057007
## c 0.6355358 -0.1057007 1.0000000
11 Importar y exportar datos
Hasta ahora, hemos trabajado con datos ya existentes en R base o que hemos generado nosotros mismos, sin
embargo, lo usual es que usemos datos almacenados en archivos fuera de R.
R puede importar datos de una amplia variedad de tipos de archivo con las funciones en base además de que esta
capacidad es ampliada con el uso de paquetes específicos.
Cuando importamos un archivo, estamos guardando su contenido en nuestra sesión como un objeto. Dependiendo
del procedimiento que usemos será el tipo de objeto creado.
De esta manera tendremos acceso a una vasta diversidad de fuentes de datos. Entre otras, podrás descargar los
archivos
La función [Link]() nos pide como argumento url, la dirección de internet del archivo que queremos
descargar y destfile el nbombre que tendrá el archivo en nuestra computadora. Ambos argumentos como cadenas
de texto, es decir, entre comillas.
Por ejemplo, para descargar una copia del set iris disponible en el UCI Machine Learning Repository usamos la
siguiente dirección como argumento url:
[Link]
[Link](
url = "[Link]
destfile = "[Link]"
)
Este método funciona con prácticamente todo tipo de archivos, aunque en algunos casos será necesario agregar el
argumento method = "wb", por asegurar que el archivo obtenido funcione correctamente.
R cuenta con la función genérica [Link](), que puede leer cualquier tipo de archivo que contenga una tabla.
La condición para que R interprete un archivo como una tabla es que tenga renglones y en cada renglon, los datos
estén separados por comas, o algún otro caracter, indicando columnas. Es decir, algo que luzca de la siguiente
manera.
1, 20, 8, 5
1, 31, 6, 5
2, 18, 9, 5
2, 25, 10, 5
Por supuesto, en lugar de comas podemos tener puntos y coma, dos puntos, tabuladores o cualquier otro signo de
puntuación como separador de columnas.
La función [Link]() acepta un número considerable de argumentos. Los más importantes son los siguientes.
file: La ruta del archivo que importaremos, como cadena de texto. Si el archivo se encuentra en nuestro
directorio de trabajo, es suficientedar el nombre del archivo, sin la ruta completa.
header: Si nuestro archivo tiene encabezados, para ser interpretados como nombres de columna, definimos
este argumento como TRUE.
sep: El caracter que es usado como separador de columnas. Por defecto es ";".
[Link]: Un vector opcional, de tipo caracter, con los nombres de las columnas en la tabla.
stringsAsFactors: Esta función convierte automáticamente los datos de texto a factores. Si este no es el
comportamiento que deseamos, definimos este argumento como FALSE.
Puedes consultar todos los argumentos de esta función ejecutando ?[Link] en la consola.
Es importante señalar que el objeto obtenido al usar esta función es siempre un data frame.
Probemos con un archivo con extensión ".data", descargado desde el respositorio de Github de este libro.
[Link](
url = "[Link]
dest = "[Link]"
)
Estos datos pertenecen a una base de diagnósticos de cancer mamario de la Universidad de Wisconsin, usado para
probar métodos de aprendizaje automático. Puedes encontrar la información completa sobre este conjunto de datos
en el siguiente enlace:
[Link]
Nos damos cuenta de que hemos tenido éxito en la descarga si aparece un mensaje en la consola de R indicando
los resultados de nuesta operación.
head(bcancer)
## V1
## 1 1000025,5,1,1,1,2,1,3,1,1,2
## 2 1002945,5,4,4,5,7,10,3,2,1,2
## 3 1015425,3,1,1,1,2,2,3,1,1,2
## 4 1016277,6,8,8,1,3,4,3,7,1,2
## 5 1017023,4,1,1,3,2,1,3,1,1,2
## 6 1017122,8,10,10,8,7,10,9,7,1,4
Nuestros datos no lucen particularmente bien. Necesitamos ajustar algunos parámetros al importarlos.
No hay datos de encabezado, por lo que header será igual a FALSE y el separador de columnas es una coma, así
que el valor de sep será ",". No conocemos cuál es el nombre de las columnas, así que por el momento no
proporcionaremos uno.
# Resultado
head(bcancer)
## V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11
## 1 1000025 5 1 1 1 2 1 3 1 1 2
## 2 1002945 5 4 4 5 7 10 3 2 1 2
## 3 1015425 3 1 1 1 2 2 3 1 1 2
## 4 1016277 6 8 8 1 3 4 3 7 1 2
## 5 1017023 4 1 1 3 2 1 3 1 1 2
## 6 1017122 8 10 10 8 7 10 9 7 1 4
Luce mejor, pero los nombres de las columnas son poco descriptivos. Si no damos nombres de variables, cada
columna tendrá como nombre "V" seguida de números del 1 adelante.
Para este ejemplo, contamos con un archivo de información, que describe el contenido de los datos que hemos
importado.
[Link]
[Link]
Si descargas este archivo, puedes abrirlo usando el bloc o navegador de internet de tu computadora.
Guardaremos en un vector las abreviaturas de los nombres de columna descritos en el documento anterior.
Ahora usaremos este vector como argumento [Link] en [Link](), para importar nuestros datos con
nombres de columna.
# Resultado
head(bcancer)
## id clump_t u_csize u_cshape m_adh spcs b_nuc b_chr n_nuc mit class
## 1 1000025 5 1 1 1 2 1 3 1 1 2
## 2 1002945 5 4 4 5 7 10 3 2 1 2
## 3 1015425 3 1 1 1 2 2 3 1 1 2
## 4 1016277 6 8 8 1 3 4 3 7 1 2
## 5 1017023 4 1 1 3 2 1 3 1 1 2
## 6 1017122 8 10 10 8 7 10 9 7 1 4
Nuestros datos han sido importados correctamente. Además, el objeto resultante es un data frame, listo para que
trabajemos con él.
class(bcancer)
## [1] "[Link]"
11.2.1 Archivos CSV
Un caso particular de las tablas, son los archivos separados por comas, con extensión .csv, por Comma Separated
Values, sus siglas en inglés. Este es un tipo de archivo comunmente usado para compartir datos, pues es compatible
con una amplia variedad de sistemas diferentes además de que ocupa relativamente poco espacio de
almacenamiento.
Probemos descargando los mismos datos que en el ejemplo anterior, pero almacenados en un archivo con
extensión .csv.
[Link](
url = "[Link]
dest = "[Link]"
)
Podemos usar [Link]() con los mismos argumentos que en el ejemplo anterior, con la excepción de que este
archivo sí tiene encabezados de columna, por lo que cambiamos header de FALSE a TRUE.
# Resultado
head(bcancer)
## id clump_t u_csize u_cshape m_adh spcs b_nuc b_chr n_nuc mit class
## 1 1000025 5 1 1 1 2 1 3 1 1 2
## 2 1002945 5 4 4 5 7 10 3 2 1 2
## 3 1015425 3 1 1 1 2 2 3 1 1 2
## 4 1016277 6 8 8 1 3 4 3 7 1 2
## 5 1017023 4 1 1 3 2 1 3 1 1 2
## 6 1017122 8 10 10 8 7 10 9 7 1 4
Una ventaja de usar documentos con extensión .csv es la posibilidad de usar la función [Link](). Esta es una es
una versión de [Link](), optimizada para importar archivos .csv.
[Link]() acepta los mismos argumentos que [Link](), pero al usarla con un archivo .csv, en casi todo los
casos, no hara falta especificar nada salvo la ruta del archivo.
# Resultado
head(bcancer)
## id clump_t u_csize u_cshape m_adh spcs b_nuc b_chr n_nuc mit class
## 1 1000025 5 1 1 1 2 1 3 1 1 2
## 2 1002945 5 4 4 5 7 10 3 2 1 2
## 3 1015425 3 1 1 1 2 2 3 1 1 2
## 4 1016277 6 8 8 1 3 4 3 7 1 2
## 5 1017023 4 1 1 3 2 1 3 1 1 2
## 6 1017122 8 10 10 8 7 10 9 7 1 4
Por ejemplo, intentamos abrir el archivo con extensión .csv que importamos antes.
[Link]("[Link]")
R intentará usar el programa que en nuestro equipo, por defecto, abre el tipo de archivo que le hemos indicado. Si
no tenemos un programa configurado para abrir el tipo de archivo que deseamos, nuestro sistema operativo nos
pedira que elijamos uno.
Lo anterior puede ocurrir si intentas abrir el archivo con extensión .data que hemos importado en este capítulo.
[Link]("[Link]")
Podemos usar la función readLines() para leer un archivo línea por línea. Establecemos el argumento n = 4 para
obtener sólo los primeros cuatro renglones del documento.
readLines("[Link]", n = 4)
Observando la salida de readLines() podremos determinar si el archivo que nos interesa puede ser importado
usando con los métodos que hemos revisado o necesitaremos de herramientas diferentes.
El documento "R Data Import/Export" (R Core Team, 2018) contiene una guía avanzada sobre el proceso de
importar y exportar todo tipo de datos. Puedes consultarlo en el siguiente enlace:
[Link]
Dependiendo del tipo de estructura de dato en el que se encuentran contenidos nuestros datos son las opciones que
tenemos para exportarlos.
Si nuestros datos se encuentran contenidos en una estructura de datos rectangular, podemos exportarlos con
diferentes funciones.
De manera análoga a [Link](), la función [Link]() nos permite exportar matrices o data frames, como
archivos de texto con distintas extensiones.
Probemos exportando el objeto iris a un documento de texto llamado [Link] a nuestro directorio de trabajo,
usando como separador la coma, con nombres de columnas y sin nombre de renglones.
# Resultado
head(iris_txt)
También podemos exportar datos a archivos con extensión .csv con la función [Link]().
Vamos a exportar iris como un documento .csv. En este caso, sólo especificamos que no deseamos guardar los
nombres de los renglones con [Link] = FALSE.
# Resultado
head(iris_csv)
11.4.2 Listas
La manera más sencilla de exportar listas es guardarlas en archivos RDS. Este es un tipo de archivo nativo de R
que puede almacenar cualquier objeto a un archivo en nuestro disco duro.
Además, RDS comprime los datos que almacena, por lo que ocupa menos espacio en disco duro que otros tipos de
archivos, aunque contengan la misma información.
Para exportar un objeto a un archivo RDS, usamos la función saveRDS() que siempre nos pide dos argumentos:
Creamos una lista de ejemplo que contiene dos vectores y dos matrices
# Resultado
mi_lista
## $a
## [1] TRUE FALSE TRUE
##
## $b
## [1] "a" "b" "c"
##
## $c
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $d
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
Aunque podemos intentar [Link]() para exportar listas, por lo general obtendremos un error como
resultado.
## Error in (function (..., [Link] = NULL, [Link] = FALSE, [Link] = TRUE, : arguments
Si deseamos importar un archivo RDS a R, usamos la función readRDS(), indicando la ruta en la que se encuentra
el archivo que deseamos.
Vamos el resultado.
mi_lista_importado
## $a
## [1] TRUE FALSE TRUE
##
## $b
## [1] "a" "b" "c"
##
## $c
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $d
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
## [1] "list"
Los objetos immportados usando un archivo RDS conservan los tipos y clases que tenían originalmente, lo cual
previene pérdida de información.
R base no tiene una función para importar archivos almacenados en archivos con extensión .xsl y .xslx, creados
con Excel.
Para importar datos desde este tipo de archivos, necesitamos instalar el paquete readxl, que contiene funciones
específicas para realizar esta tarea.
[Link]("readxl")
library(readxl)
Para probar estas funciones, descargaremos una hoja de cálculo de prueba. Nota que hemos establecido el
argumento mode = "wb" para asegurar que el archivo se descargue correctamente.
[Link](
url = "[Link]
destfile = "data_frames.xlsx",
mode = "wb"
)
Si intentamos leer las primeras cinco líneas de data_frames.xlsx, confirmamos que este es un archivo que no tiene
forma rectangular, de tabla.
readLines("data_frames.xlsx", n = 5)
## [1] "PK\003\004\024"
## [2] "\177߉YTU,B õ’(±çmöL\177¸jêd\t\001\215³¹èf\035‘\200-œ6v–‹¯É{ú$\022$eµª\235…\\¬\001Åpp\177
En caso de que tengamos instalado Excel o algún otro programa compatible con archivos de hoja de cálculo, como
LibreOffice Calc o Number, podemos pedir a R que abra este archivo con [Link](). De este modo podemos
explorar su contenido.
[Link]("data_frames.xlsx")
excel_sheets("data_frames.xlsx")
Intentaremos importar la pestaña iris con read_excel(). Esta función tiene los siguientes argumentos principales.
path: La ruta del archivo a importar. Si no especificamos una ruta completa, será buscado en nuestro
directorio de trabajo.
sheet: El nombre de la pestaña a importar. Si no especificamos este argumento, read_excel() intentará leer
la primera pestaña de la hoja de cálculo.
range: Cadena de texto con el rango de celdas a importar, escrito con el formato usado en Excel. Por ejemplo,
"[Link]".
col_names: Con este argumento indicamos si la pestaña que vamos a importar tiene encabezados para usar
como nombres de columna. Por defecto su valor es TRUE. Si no tenemos encabezados, podemos dar un vector
con nombres para asignar a las columnas.
Probemos read_excel().
iris_excel
## # A tibble: 150 x 5
## [Link] [Link] [Link] [Link] Species
## <dbl> <dbl> <dbl> <dbl> <chr>
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
## 7 4.6 3.4 1.4 0.3 setosa
## 8 5 3.4 1.5 0.2 setosa
## 9 4.4 2.9 1.4 0.2 setosa
## 10 4.9 3.1 1.5 0.1 setosa
## # ... with 140 more rows
Si los datos en la hoja de cálculo tienen forma de tabla, read_excel() no tendrá problemas para importarlos.
Cuando este no es el caso, usamos el argumento range para extraer sólo la información que nos interesa.
# Resultado
trees_excel
## # A tibble: 34 x 6
## `Datos trees` X__1 X__2 X__3 X__4 X__5
## <chr> <dbl> <dbl> <dbl> <lgl> <chr>
## 1 <NA> NA NA NA NA <NA>
## 2 <NA> 8.3 70 10.3 NA <NA>
## 3 <NA> 8.6 65 10.3 NA <NA>
## 4 <NA> 8.8 63 10.2 NA Los nombres de las variables son~
## 5 <NA> 10.5 72 16.4 NA <NA>
## 6 <NA> 10.7 81 18.8 NA <NA>
## 7 <NA> 10.8 83 19.7 NA <NA>
## 8 <NA> 11 66 15.6 NA <NA>
## 9 <NA> 11 75 18.2 NA <NA>
## 10 <NA> 11.1 80 22.6 NA <NA>
## # ... with 24 more rows
Los resultados no lucen bien porque los datos en la pestaña no tienen forma de tabla.
Ajustamos los argumentos de read_excel() para leer correctamente la infomación de la pestaña. Al explorar
manualmente el archivo [Link], podemos localizar el rango en el que se encuentran los datos (de las
celdas B3 a D33) y los nombres de las columnas (Girth, Height y Volume).
# Resultado
trees_excel
## # A tibble: 31 x 3
## Girth Height Volume
## <dbl> <dbl> <dbl>
## 1 8.3 70 10.3
## 2 8.6 65 10.3
## 3 8.8 63 10.2
## 4 10.5 72 16.4
## 5 10.7 81 18.8
## 6 10.8 83 19.7
## 7 11 66 15.6
## 8 11 75 18.2
## 9 11.1 80 22.6
## 10 11.2 75 19.9
## # ... with 21 more rows
Esta vez hemos tenido éxito y los datos importados son los correctos.
El paquete readxl tiene más funciones para trabajar con hojas de cálculo además de read_excel() y
excel_sheets(), pero revisar cada una de ellas sale del alcance de este libro. Puedes conocer más sobre ellas en la
documentación de readxl, llamando help(package = "readxl").
Por ejemplo, en Psicología el paquete SPSS Statistics de IBM es el paquete estadístico comercial más usado. Si
eres psicólogo o psicóloga, o colaboras con psicólogos, es áltamente probable que te encuentres con datos
contenidos en archivos con extensión .sav, el tipo de archivo nativo de SPSS Statistics.
Por lo tanto, es conveniente ser capaces de importar y exportar datos almacenados en archivos compatibles con
paquetes estadísticos comerciales, pues esto nos permitirá usar datos ya existentes compatibles con ellos y
colaborar con otras personas.
[Link]("haven")
library(haven)
Las siguientes funciones de haven son usadas para importar datos. Todas estas funciones nos piden como
argumento file la ruta y nombre del archivo a importar, si no especificamos ruta, será buscado en nuestro
directorio de trabajo.
También podemos exportar nuestros data frames creados en R como archivos compatibles con estos programas con
las siguientes funciones. Todas piden el argumento file, con la ruta y nombre del archivo a crear. Es muy
importante que demos como nombre de archivo uno con la extensión correcta para cada paquete.
Como siempre, puedes leer sobre las demás funciones en el paquete haven en su documentación, llamando
help(package = "haven").
12 Gráficas
R cuenta con un sistema de generación de gráficas poderoso y flexible. Si nembargo, tener estar cualidades hace
que este sistema sea un tanto complejo para aprender.
En este capítulo revisaremos como crear las gráficas más comunes con R base, así como algunos de los parámetros
que podemos ajustar para mejorar su presentación.
Al crear gráficas, notarás que ponemos en práctica todo lo que hemos visto en los capítulos anteriores, incluyendo
importar datos, hacer subconjuntos de un objeto y uso de funciones.
Usaremos un conjunto de datos llamado "Bank Marketing Data Set", que contiene información de personas
contactadas en una campaña de marketing directo puesta en marcha por un banco de Portugal.
Comenzamos con la descarga de la copia del archivo csv desde el sitio de Github de este libro.
[Link](
url = "[Link]
destfile = "[Link]"
)
readLines("[Link]", n = 4)
## [1] "\"age\";\"job\";\"marital\";\"education\";\"default\";\"balance\";\"housing\";\"loan\";\"
## [2] "30;\"unemployed\";\"married\";\"primary\";\"no\";1787;\"no\";\"no\";\"cellular\";19;\"oct
## [3] "33;\"services\";\"married\";\"secondary\";\"no\";4789;\"yes\";\"yes\";\"cellular\";11;\"m
## [4] "35;\"management\";\"single\";\"tertiary\";\"no\";1350;\"yes\";\"no\";\"cellular\";16;\"ap
Por la estructura de los datos, podremos usar la función [Link](), con el argumento sep = ";" para importarlos
como un data frame.
Vemos las primeras líneas del conjunto con head(), el número de renglones y columnas con dim().
# Primeros datos
head(banco)
# Dimensiones
dim(banco)
## [1] 4521 17
Usamos lapply() con la función class() para determinar el tipo de dato de cada columna en banco. Conocer esto
nos será muy útil más adelante.
lapply(banco, class)
## $age
## [1] "integer"
##
## $job
## [1] "factor"
##
## $marital
## [1] "factor"
##
## $education
## [1] "factor"
##
## $default
## [1] "factor"
##
## $balance
## [1] "integer"
##
## $housing
## [1] "factor"
##
## $loan
## [1] "factor"
##
## $contact
## [1] "factor"
##
## $day
## [1] "integer"
##
## $month
## [1] "factor"
##
## $duration
## [1] "integer"
##
## $campaign
## [1] "integer"
##
## $pdays
## [1] "integer"
##
## $previous
## [1] "integer"
##
## $poutcome
## [1] "factor"
##
## $y
## [1] "factor"
Y por último, pedimos un resumen de nuestros datos con la función summary(). Esta función acepta cualquier tipo
de objeto como argumento y nos devuelve un resumen descriptivo de los datos de cada uno de sus elementos.
summary(banco)
Esta función tiene un comportamiento especial, pues dependiendo del tipo de dato que le demos como argumento,
generará diferentes tipos de gráfica. Además, para cada tipo de gráfico, podremos ajustar diferentes parámetros
que controlan su aspecto, dentro de esta misma función.
Puedes imaginar a plot() como una especie de navaja Suiza multifuncional, con una herramienta para cada
ocasión.
plot() siempre pide un argumento x, que corresponde al eje X de una gráfica. x requiere un vector y si no
especificamos este argumento, obtendremos un error y no se creará una gráfica.
El resto de los argumentos de plot() son opcionales, pero el más importante es y. Este argumento también
requiere un vector y corresponde al eje Y de nuestra gráfica.
Dependiendo del tipo de dato que demos a x y y será el gráfico que obtendremos, de acuerdo a las siguientes
reglas:
x y Gráfico
Continuo Continuo Diagrama de dispersión (Scatterplot)
Continuo Discreto Diagrama de dispersión, y coercionada a numérica
Continuo Ninguno Diagrama de dispersión, por número de renglón
x y Gráfico
Discreto Continuo Diagrama de caja (Box plot)
Discreto Discreto Gráfico de mosaico (Diagrama de Kinneman)
Discreto Ninguno Gráfica de barras
Ninguno Cualquiera Error
Además de plot(), hay funciones que generan tipos específicos de gráfica. Por ejemplo, podemos crear una
gráfica de barras con plot() pero existe también la función barplot(). También existen también casos como el de
los histogramas, que sólo pueden ser creados con la función hist().
Cuando llamas a la función plot() o alguna otra similar, R abre una ventana mostrando ese gráfico. Si estás
usando RStudio, el gráfico aparece en el panel Plot. Si llamas de nuevo la función plot(), el gráfico generado más
reciente reemplazará al más antiguo y en RStudio se creará una nueva pestaña en en el planel Plot. El gráfico
reemplazado se perderá.
Por lo tanto, a menos que nosotros los indiquemos, nuestros gráficos se pierden al crear uno nuevo. Al final de este
capítulo veremos cómo exportar gráficos de manera más permanente.
12.3 Histogramas
Un histograma es una gráfica que nos permite observar la distribución de datos numéricos usando barras. Cada
barra representa el número de veces (frecuencia) que se observaron datos en un rango determinado.
Para crear un histograma usamos la función hist(), que siempre nos pide como argumento x un vector numérico.
El resto de los argumentos de esta función son opcionales. Si damos un vector no numérico, se nos devolverá un
error.
Ya hemos trabajado con esta función en el capítulo 8, pero ahora profundizaremos sobre ella.
Probemos creando un histograma con las edades (age) de las personas en nuestro data frame banco. Sabemos que
age
Daremos como argumento a hist() la columna age como un vector, extraido de banco usando el signo de dolar $,
aunque también podemos usar corchetes e índices.
hist(x = banco$age)
Nuestro histograma luce bastante bien para habernos costado tan poco trabajo crearlo, aunque puede mejorar su
presentación.
Podemos agregar algunos argumentos a la función hist() para modificar ciertos parámetros gráficos.
Vamos a cambiar el título del gráfico con el argumento main, y el nombre de los ejes X y Y con xlab y ylab,
respectivamente.
Estos argumentos requiere una cadena de texto y pueden agregados también a gráficos generados con plot().
Probemos cambiando el color de las barras del histograma agregando el argumento col. Este argumento acepta
nombres de colores genéricos en inglés como "red", "blue" o "purple"; y también acepta colores hexadecimales,
como "#00FFFF", "#08001a" o "#1c48b5".
Puedes ver una lista de los nombres de colores válidos en R en el siguiente enlace:
[Link]
El tema de los colores hexadecimales sale del alcance de este libro, pero en el siguiente enlace encontrarás una
web app para generar y elegir fácilmente colores de este tipo.
[Link]
Creamos ahora un histograma con los mismos argumentos, pero con los datos de la columna "duration", con barras
de color marfil ("ivory") y los títulos apropiados.
La función plot() puede generar gráficos de barra si damos como argumento x un vector de factor o cadena de
texto, sin dar un argumento y.
Por ejemplo, creamos una gráfica de barras de la variable educación ("education") de banco
plot(x = banco$education)
Al igual que con los histogramas, obtenemos un resultado aceptable no obstante el esfuerzo mínimo que hemos
hecho para generar nuestra gráfica de barras.
Podemos ajustar los parámetros gráficos con los argumentos main, xlab, ylab y col. En este caso, podemos darle a
col un vector de colores, uno por barra, para que cada una sea distinta.
Sin embargo, hay ocasiones en las que deseamos usar gráficas de barras para presentar proporciones, que
deseamos barras apiladas. Para esos casos, usamos la función barplot().
Además de usar plot(), podemos crear gráficas de barra con la función barplot().
barplot pide como argumento una matriz, que represente una tabla de contingencia con los datos a graficar. Este
tipo de tablas pueden ser generadas con la función table().
table() pide como argumento uno o más vectores, de preferencia variables discretas. Si damos sólo un vector
como argumento, devuelve un conteo, si damos dos o más variables, devuelve tablas de contingencia.
table(banco$education)
##
## primary secondary tertiary unknown
## 678 2306 1350 187
Si damos como argumentos la variable education y la variable loan (préstamo), obtenemos una tabla de
contingencia, que asignaremos al objeto tab_banco.
# Resultado
tab_banco
##
## primary secondary tertiary unknown
## no 584 1890 1176 180
## yes 94 416 174 7
Damos como argumento tab_banco a barplot() y nos devuelve una gráfica de barras apiladas.
barplot(tab_banco)
Si deseamos graficar proporciones en lugar de conteos, usamos la función [Link]().
Esta función nos pide como argumento una tabla de contingencia generada por table(). y un número para margin.
El argumento margin es similar a MARGIN de apply() (como vimos en el capítulo 10).
Si damos como argumento 1, las proporciones se calcularán agrupadas por renglón. La suma de proporciones
por renglón será igual a 1.
Si damos como argumento 2, las proporciones se calcularán agrupadas por columna. La suma de proporciones
por columna será igual a 1
Si no damos ningún argumento, las proporciones se calcularán usando toda la tabla como grupo. La suma de
proporciones de todas las celdas en la tabla será igual a 1.
Para ilustrar esto, veamos los tres casos para margin usando como argumento nuestro objeto tab_banco.
##
## primary secondary tertiary unknown
## no 0.15248042 0.49347258 0.30704961 0.04699739
## yes 0.13603473 0.60202605 0.25180897 0.01013025
##
## primary secondary tertiary unknown
## no 0.86135693 0.81960104 0.87111111 0.96256684
## yes 0.13864307 0.18039896 0.12888889 0.03743316
##
## primary secondary tertiary unknown
## no 0.12917496 0.41804910 0.26011944 0.03981420
## yes 0.02079186 0.09201504 0.03848706 0.00154833
Nosotros queremos obtener las proporciones por columna, así que usaremos margin = 2.
ptab_banco <- [Link](tab_banco, margin = 2)
barplot(ptab_banco)
Hemos obtenido el resultado esperado, pero podemos mejorar la presentación. Nota que con barras apiladas el
argumento col se puede usar para colorear las categorias al interior de las barras.
Luce bien, pero tenemos un problema: no sabemos qué representan las categorías en nuestras barras apiladas
viendo sólamente nuestra gráfica.
Nosotros podemos consultar directamente con los datos, pero una persona que vea por primera vez esta gráfica no
tendrá esa opción, reduciendo con ello su utiidad.
Para solucionar este problema, usamos leyendas.
12.5 Leyendas
Las leyendas son usadas para identificar con mayor claridad los distintos elementos en un gráfico, tales como
colores y formas.
En R usamos la función legend() para generar leyendas. Esta función debe ser llamada después de crear un
gráfico. En cierto modo es una anotación a un gráfico ya existente. legend() es una función relativamente
compleja, así que sólo revisaremos lo esencial.
legend: Las etiquetas de los datos que queremos describir con la leyenda. Por ejemplo, si tenemos cuatro
categorías a describir, proporcionamos un vector de cuatro cadenas de texto.
fill: Los colores que acompañan a las etiquetas definidas con legend. Estos colores tienen que coincidir con
los que hemos usado en el gráfico.
x y y: Las coordenadas en pixeles, en las que estará ubicada la leyenda. Podemos dar como argumento a x
alguno de los siguientes, para ubicar automáticamente la leyenda: "bottomright", "bottom", "bottomleft",
"left", "topleft", "top", "topright", "right", "center".
title: Para poner título a la leyenda.
Además, tenemos muchos otros argumentos opcionales, que puedes consultar en la documentación llamando ?
legend().
Vamos a agregar una leyenda a la última gráfica de barras que creamos en la sección anterior de este capítulo.
Entonces necesitamos conocer las etiquetas que daremos como argumento legend y a qué colores corresponden al
vector banco$loan.
Usamos la función unique para determinar cuántos valores únicos hay en este vector. Cada uno de estos valores
corresponde a una etiqueta. Esta función, si la aplicamos a un vector de tipo factor, nos devuelve sus niveles.
unique(banco$loan)
## [1] no yes
## Levels: no yes
Tenemos dos etiquetas, "no" y "yes" (no y sí, respectivamente), en ese orden, por lo que ese será nuestro
argumento legend.
Nosotros determinamos los colores en la sección anterior como "royalblue" y "grey", en ese orden. Por lo tanto,
tendremos que "no" será coloreado con "royalblue", y "yes" con "grey". como vamos a rellenar una barra, esto
colores los daremos al argumento fill.
Por último, daremos como "topright" como argumento x para que nuestra leyenda se unique en la parte superior
derecha de nuestro gráfico.
En las secciones siguientes agregaremos leyendas a otros gráficos, con lo cual quedará un poco más claro el uso de
legend().
Para generar un diagrama de dispersión, damos vectores numéricos como argumentos x y y a la función plot().
Tenemos algunos datos extremos tanto en balance. Para fines de tener una gráfica más informativa, vamos a
recodificarlos usando ifelse(), cambiando todos los valores mayores a 15 000.
banco$balance <- ifelse(banco$balance > 15000, 15000, banco$balance)
En los diagramas de dispersión, podemos usar el argumento col para camiar el color de los puntos usando como
referencia una tercera variable.
Nos sería de utilidad una leyenda para interpretar más fácilmente los colores.
Ya sabemos que los niveles de loan son "no" y "yes", además de que los colores han sido rojo y nregro, así que
agregar una leyenda será relativamente fácil.
Si usamos diagramas de dispersión con iris obtendremos gráficos mucho más interesantes.
Creamos un gráfico con las medidas de pétalo, aplicando lo que hemos visto para generar diagramas de dispersión.
Una gráfica de este tipo dibuja un rectángulo cruzado por una línea recta horizontal. Esta linea recta representa la
mediana, el segundo cuartil, su base representa el pimer cuartil y su parte superior el tercer cuartil. Al rango entre
el primer y tercer cuartil se le conoce como intercuartílico (RIC). Esta es la caja.
Además, de la caja salen dos líneas. Una que llega hasta el mínimo valor de los datos en la variable o hasta el
primer cuartil menos hast 1.5 veces el RIC; y otra que llegar hasta el valor máximo de los datos o el tercer cuartil
más hasta 1.5 veces el RIC. Estos son los bigotes.
Usamos la función plot() para crear este tipo de gráfico, dando como argumento x un vector de factor o cadena de
texto, y como argumento y un vector numérico.
Una ventaja de este tipo de gráfico es que podemos comparar las distribución de una misma variable para
diferentes grupos.
Vamos a ver cómo se distribuye la edad por nivel de educación en nuestro objeto banco, esto es, las variables
education y age.
Podemos ver que las personas con menor nivel educativo tienden a tener una edad mayor. La mayoría de las
personas con educación primaria tienen entre 40 y 50 años, mientras que la mayoría con educación terciaria tiene
entre 35 y 45 años, aproximadamente.
En la primera manera, si damos como argumento x un vector numérico, nos dará un diagrama de caja de esa
variable.
boxplot(x = banco$age)
formula: Para esta función las fórmulas tienen el formato y ~ x, donde x es el nombre de la variable continua
a graficar, y la x es la variable que usaremos como agrupación.
data: Es el data frame del que serántomadas las variables.
Por ejemplo, para mostrar diagramas de caja por nivel educativo, nuestra variable y es age y nuestra variable x es
education, por lo tanto, formula será age ~ education.
Este tipo de grafico recibe su nombre porque consiste en una cuadricula, en la que cada rectángulo representa el
numero de casos que corresponden a un cruce específico de variables. Entre más casos se encuentren en ese cruce,
más grande será el rectángulo.
Para obtener un gráfico de mosaico, damos como vectores de factor o cadena de texto como argumentos x y y a la
función plot().
Por ejemplo, intentemos graficar el estado marital con el nivel educativo de las personas en banco
Podemos cambiar el color de los mosaicos con el argumento col. Debemos proporcionar un color por cada nivel
del vector en el eje Y.
plot(x = banco$marital, y = banco$education,
col = c("#99cc99", "#cc9999", "#9999cc", "#9c9c9c"))
De esta manera es más claro que el grupo más numeroso de personas son las casadas con educación secundaria y
el más pequeño, divorciadas con educación primaria.
Cuando llamamos una de estas funciones, le estamos indicando a R que "mande" nuestro gráfico a un dispositivo
gráfico (graphic device) en nuestra computadora, donde podemos verlo, que por defecto es una ventana en
nuestro escritorio o el panel Plot si estás usando RStudio.
Una consecuencia de esto es que si creas y lo mandas a un dispositivo gráfico en uso, el gráfico nuevo reemplazará
al anterior. Por ejemplo, si usas plot() para crear un gráfico, se mostrará en una ventana de tu escritorio, pero si
usas plot() de generar un gráfico distinto, el contenido de esta ventana será reemplazada con este nuevo gráfico.
Lo mismo pasa con todos los dispositivos gráficos.
Además, los gráficos no pueden ser guardados en un objetos para después ser exportados. Es necesario mandar
nuestros gráficos a un dispositivo como JPG, PNG o algún otro tipo de archivo que pueda ser almacenado en
nuestro disco duro.
Para exportar un gráfico usamos alguna de las siguientes funciones, cada una corresponde con un tipo de archivo
distinto. No son las únicas, pero son las más usadas.
bpm()
jpeg()
pdf()
png()
tiff()
Cada una de estas funciones tiene los siguientes argumentos tres argumentos principales.
filename: El nombre y ruta del archivo de imagen a crear. Si no especificamos una ruta completa, entonces el
el archivo será creado en nuestro directorio de trabajo.
width: El ancho del archivo de imagen a crear, por defecto en pixeles.
height: El alto del archivo de imagen a crear, por defecto en pixeles.
La manera de utilizar estas funciones llamándolas antes de llamar a una función que genere una gráfica. Al hacer
esto, le indicamos a R que en lugar de mandar nuestro gráfico a una ventana del escritorio, lo mande a un
dispositivo gráfico distinto.
Finalmente, llamamos a la función [Link](), para cerrar el dispositivo gráfico que hemos elegido, de este modo
se creará un archivo y podremos crear más gráficos después.
Por ejemplo, para exportar un gráfico con leyenda como un archivo PNG llamamos lo siguiente. Nota que tenemos
que dar la misma extensión de archivo que la función que estamos llamando, en este caso .png.
title = "Loan")
[Link]()
## png
## 2
null device 1
[Link]("loan_age.png")