Apuntes de R

Fundamentos de R

Estos son los apuntes del curso Data Science with R ofrecido por la universidad de Harvard a través de la plataforma HarvardX.

Lo más básico

Instalación de R

Instalar R en Debian es sencillo: apt install r-base r-recommended.

Se inicia un terminal de R con la orden R.

Instalar y cargar paquetes

Instalar paquetes aumenta la funcionalidad

install.packages("nombre_del_paquete")

Se pueden instalar en grupos, más de uno a la vez usando un vector como entrada de la orden de instalación, así:

install.packages(c("paquete1", "paquete2", "paquete3"))

En Debian, R intenta instalar los paquetes en /usr/lib y ese directorio no es escribible por el usuario. Se puede definir una ruta alternativa (p. ej. en /home/usuario/.R/library) pero para que R pueda compilar e instalar el código de los paquetes deseados es necesario instalar en el sistema el paquete libssl-dev.

Es interesante guardar un script con la instalación de todos los paquetes usados normalmente, de manera que es sencillo reinstalarlos cada vez que se quiera utilizar una instalación base de R o otro ordenador en el que no se haya trabajado anteriormente. La lista de paquetes instalados se puede recuperar con ìnstalled.packages().

Solo es necesario instalar los mismos paquetes otra vez si se utiliza R con otro usuario en el mismo ordenador u otro ordenador distinto. O, por supuesto, si se borra el directorio que contiene los paquetes descargados y compilados.

También es bueno empezar un script con la instalación y carga de los paquetes necesarios para su ejecución.

Una vez instalado, un paquete que no pertenezca a la instalación base de R necesita ser cargado con la instrucción library.

library(nombre_del_paquete)

La instalación se hace una vez, pero la carga del paquete se hace siempre que haga falta (una vez por sesión o script). Nótese la falta de comillas dentro de los paréntesis una vez que el paquete está instalado. Cuando se intenta cargar un paquete y se muestra un error, lo más probable es que sea necesario instalarlo primero.

Objetos

Los objetos son cosas guardadas en R. Se asignan valores a variables y eso es un objeto:

a <- 1

En este caso, atoma el valor 1 y se puede operar con a igual que con un 1: a+1=2. Pero los objetos pueden ser mucho más complicados, como funciones. Los valores se asignan con la flecha <-, aunque también sirve el signo igual =. No se recomienda usar = para evitar confusiones, solo la flecha.

Escribir tan solo el nombre de la variable hace que R muestre su valor, por ejemplo:

a
[1] 1

muestra un 1 como valor de la variable y, entre corchetes, que este es el primero de los datos mostrados. A veces, un vector consta de muchos datos en una lista y, al principio de cada línea de esta lista se muestra entre corchetes el número de orden del primer elemento de esa línea.

Si la variable (el objeto) no tiene un valor asignado, R mostrará un mensaje de error. Es decir, no se pueden recuperar valores de objetos aún no definidos. Por ejemplo:

a <- 1
x
Error: object 'x' not found.

Para definir una variable hay que seguir unas reglas sencillas: 1. Han de comenzar con una letra. 2. No pueden contener espacios. 3. No pueden coincidir con variables predefinidas en R (como pi o install.packages).

Una buena costumbre es usar nombres que sean explicativos y sustituir los espacios con guiones bajos (primera_solucion, datos2, etc.)

La orden ls() elabora una lista con los objetos que R tiene guardados en el espacio de trabajo (ver más abajo). Cada vez que se define una variable o se asignan valores a un vector, estos elementos aparecen en la salida del comando ls().

Espacios de trabajo

Cada vez que se define una variable u otro objeto, se está modificando el espacio de trabajo (workspace). Este puede ser guardado al finalizar la sesión y cargado al comienzo de la siguiente (o deshechado). La orden ls() muestra una lista de los objetos guardados en el espacio de trabajo. Hay órdenes (como save o load) para guardar y cargar espacios de trabajo, pero aún no sé cómo funcionan, no parece fácil al principio.

Estos objetos se muestran en RStudio (in IDE para R) en la pestaña Environment.

Funciones

Una vez definidas variables, el análisis de los datos se puede definir como una serie de funciones aplicadas a los datos. Algunas de estas funciones ya están predefinidas en R.

Las órdenes usadas antes como install.packages, library son funciones. Se pueden cargar más funciones que las predefinidas instalando y cargando paquetes.

Cuando se evalúa una función, que es lo mismo (más o menos) que ejecutarla, hay que incluir paréntesis aunque no haya nada entre ellos. Si se necesita pasarle parámetros a la función, han de incluirse entre los paréntesis. Si no se usan los paréntesis, R muestra el código fuente de la función.

Las funciones incorporan un documento de ayuda para aprender lo que hacen y cómo: qué parámetros son necesarios, etc. Por ejemplo, help("log") muestra la ayuda de la función que calcula el logaritmo de un número: log(x). Se consigue el mismo resultado con el signo de interrogación: ?log.

En la ayuda se especifican los parámetros necesarios, obligatorios u opcionales, y el orden en el que deben aparecer. Por eso, si se respeta el orden no es necesario definirlos antes. Por ejemplo:

log(x = 8, base = 2)

es equivalente a

log(8,2)

porque los parámetros están en el orden esperado. Pero también funciona lo siguiente:

log(base = 2, x = 8)

donde los parámetros no están en orden pero definidos.

La función args("nombre_de_la_funcion") devuelve los parámetros de una función; sirve para tener a mano un recordatorio rápido sin tener que acceder al documento completo de la ayuda.

Hay otros objetos predefinidos, como constantes matemáticas (pi (pi), infinito (inf) u otras) o incluso conjuntos de datos completos. Estos conjuntos de datos de ejemplo se pueden consultar con la instrucción data().

Las funciones pueden recibir tanto datos (numéricos o no, depende de la función) como variables, que serán interpretadas como los datos asignados a ellas. Por eso, si se asigna x <- 8, tanto log(8) como log(x) darán el mismo resultado.

Las funciones se pueden anidar, y entonces se usa el resultado de unas como parámetro para las siguientes. El orden es siempre de dentro afuera: se evalúan primero las más interiores, las más profundamente anidadas, y luego las más exteriores, las que contienen a las demás. log(sqrt(9)) calcula primero la raíz de 9 y luego el logaritmo natural de ese resultado. Ya se usó la anidación de funciones en un apartado anterior al instalar paquetes de R que venían dados por una lista asignada a un vector.

Escribir programas

Aunque definir variables y escribir fórmulas y funciones para operar con ellas desde luego que funciona, es mucho más eficaz escribir todo en un documento de texto y luego ejecutarlo. De esta manera, tan solo cambiando el valor de las variables pueden obtenerse diferentes resultados.

En un programa se pueden escribir comentarios, es decir, líneas que no serán ejecutadas y sirven para documentar el código, como por ejemplo:

# Asignar valores a las variables
a <- 1
b <- 2
c <- 3

# Fórmula para ser ejecutada
resultado <- sqrt(a+b+c)
resultado

Tipos de datos

La función class devuelve el tipo de objeto que se le pasa como parámetro:

a <- 1
class(a)
[1] numeric

Aunque se pueden asociar variables a números, no es la mejor ni más eficiente manera de manejar datos, lo más habitual es organizar un conjunto de datos (data frame). Así se pueden manejar diferentes tipos de datos en un solo objeto.

En el ejemplo se usa el conjunto de datos proporcionado para analizar el número de asesinatos en EE.UU. Es necesario tener acceso a él, cargarlo y comprobar de qué tipo de objeto se trata.

install.package("dslabs")
library(dslabs)
data("murders")
class(murders)

[1] "data.frame"

Para saber más acerca del conjunto de datos se usa str (de structure: str(murders)). Esta función devuelve una vista de la estructura de los datos con el número de observaciones (filas, entrevistados, etc.), el número de variables (columnas, preguntas, opciones, etc.) y los nombres de las variables con los primeros valores de cada una a modo de ejemplo.

La función head (head(murders)) muestra los primeros 6 elementos de cada variable en forma de tabla, con las variables como columnas y las observaciones (las seis primeras( en las filas.

Acceder a las variables

Para poder saber algo acerca de los datos contenidos en el objeto hay que estudiar las variables por separado y relacionar unas con otras. Para acceder a los datos de las variables se usa el signo $ en el nombre del conjunto de datos: murders$population muestra los datos contenidos en esa variable, 51 números en total, uno por estado (los estados son, en este caso, las observaciones). Los datos mostrados (y esto es importante) siguen el orden de las observaciones tal cual están registradas en el conjunto de datos, fila por fila.

Los nombres de las variables aparecen como una lista en la salida de la función str() y como las cabeceras de las columnas en la salida de la función head(), pero también pueden consultarse específicamente con la función names().

Pero, empezando por el principio, lo más sencillo es entender primero los vectores.

Vectores: numéricos, alfanuméricos y lógicos

Tipos de vectores (los básicos y más usuales): 1. Numéricos. 2. Alfanuméricos o texto. 3. Lógicos. 4. Factores.

Ciertos objetos, como la información contenida en la variable murders$population, no constan de un valor numérico sino de muchos (51 en el ejemplo). Este tipo de objetos es un vector. Teóricamente, un número es un vector de longitud 1.

La función length informa de la cantidad de datos que hay en un vector. En el siguiente ejemplo se asigna el vector a un objeto y se comprueba la longitud de ese vector. También se puede comprobar directamente: ambas instrucciones son equivalentes.

pop <- murders$population
length(pop)
length(murders$population)
[1] 51

Los vectores pueden almacenar números, como murders$population. La función class aplicada a este vector devolverá numeric. Estos son vectores numéricos.

También pueden almacenar texto, como el vector formado por la columna (o variable) state en este mismo conjunto de datos. Así, class(murders$state) informará de que el tipo de vector es alfanumérico, o sea, texto: character.

Un tercer tipo de vectores es el vector lógico, que puede contener solo los valores TRUE o FALSE.

z <- 3 == 2
z
 [1] FALSE

class(z)
 [1] "logical"

En este ejemplo se ve el operador == que «pregunta» si 3 es igual a 2. Usando = se asigna un valor a una variable.

Los factores son vectores alfanuméricos pero que almacenan solo un número limitado de cadenas diferentes. En el conjunto de datos del ejemplo, el vector murders$regions es un vector de factores porque almacena categorías, no diferentes datos (solo cuatro regiones diferentes en todo el conjunto de datos guardadas como 2 4 4 2 4 4 1 2… según se ve en str(murders)).

La función levels muestra las categorías de que se compone el vector de factores.

levels(murders$region)

Este ejemplo devuelve cuatro valores: Northeast, South, North Central y West.1 No importa el número de filas (observaciones) ni el número de veces que aparezca cada una de las regiones en el conjunto de datos, estos son todos los tipos que existen. Anidando funciones, se puede saber el número de niveles (valores) únicos de la variable de tipo factor con la función length() así: length(levels(murders$regions)). Esta función anidada devuelve el valor 4: hay cuatro diferentes factores en todo el conjunto de datos.

Las confusiones entre factores y vectores alfanuméricos son una fuente de problemas en los programas.

Trabajar con un conjunto de datos (data frame)

Un data frame o conjunto de datos es, básicamente, una tabla en la que pueden convivir datos numéricos, alfanuméricos, lógicos y de cualquier tipo.

Para los ejemplos a continuación se usará un conjunto de datos de ejemplo que consiste en los ratios de asesinatos por arma de fuego en los EE.UU. para cada estado, junto a su población y otros detalles. Para acceder a este conjunto de datos hay que instalar el paquete correspondiente y cargarlo para que sus datos sean accesibles. Luego cargar el conjunto de datos en sí.

install.packages("dslabs")
library(dslabs)
data(murders)

Una vez que el paquete ha sido instalado no es necesario volver a hacerlo en el mismo ordenador. A partir de ese momento es suficiente con cargar la librería y el conjunto de datos en cada sesión.

La función str(objeto) es útil para analizar la estructura del objeto, pues muestra un resumen. Por ejemplo:

str(murders)

mostrará que es un conjunto de datos (data.frame), el número de observaciones (filas) y variables (columnas), y un resumen del contenido de estas variables.

La función names devuelve los nombres de las columnas de un conjunto de datos. Serían los nombres de las variables o columnas contenidas en cada observación o fila. En el caso del ejemplo, sería names(murders).

Más información se puede extraer con head(murders). Esta función muestra las seis primeras filas del conjunto de datos.

Cada variable tiene una clase, que puede ser numérica, alfanumérica o lógica. Se puede consultar la clase con la función class(). Pero para extraer los datos de una sola variable, es necesario usar el símbolo $ (llamado accesor ‘accessor’) antes del nombre de la variable. Así class(murders$abb) muestra la clase de datos en el vector formado por los valores de la variable abb dentro del conjunto de datos murders. Como se dijo, para conocer los nombres de las variables incluidas en un conjunto de datos (las columnas), se puede usar la función names.

Los valores de una variable en un conjunto de datos se pueden extraer también con el uso de corchetes: x <- murders[["population"]].

a <- murders$population
b <- murders[["population"]]
identical(a, b)

Si en lugar de corchetes dobles se usan corchetes sencillos, el objeto al que se asocie será un subconjunto de datos de clase data.frame en lugar de un vector.

Dentro de un conjunto de datos puede haber variables de tipo factor. Son valores numéricos de un rango determinado que se asocian con un nombre o valor, como el valor “Region” en el ejemplo (solo cuatro regiones diferentes en todo el conjunto de datos guardadas como 2 4 4 2 4 4 1 2… según se ve en str(murders)). Los valores (niveles) que puede tomar la variable de tipo factor se pueden consultar con la función levels().

levels(murders$region)

Este ejemplo devuelve cuatro valores: Northeast, South, North Central y West. No importa el número de filas (observaciones) ni el número de veces que aparezca cada una de las regiones en el conjunto de datos, estos son todos los tipos que existen. Anidando funciones, se puede saber el número de niveles (valores) únicos de la variable de tipo factor con la función length() así: length(levels(murders$regions)). Esta función anidada devuelve el valor 4.

¿Cuántos estados hay por región? La función table() muestra el número de observaciones en cada nivel de la variable de tipo factor, con su nombre asociado. En este caso, table(murders$region).

Vectores

Un vector es la unidad básica de almacenamiento de datos en R. Es una list de datos. Los conjuntos de datos complejos se suelen poder separar en vectores individuales. Por ejemplo, en el conjunto murders, cada una de las variables es a su vez un vector (cada uno de una clase diferente, etc.).

La forma más sencilla de crear un vector es con la función c, de concatenate. Se pueden crear vectores de clase numérica tanto como alfanumérica:

codes <- c(380, 124, 818)
country <- c("italy", "canada", "egypt")

Es importante saber que se pueden otorgar nombres a los elementos de un vector numérico, y se puede hacer de varias maneras: definiéndolo en el mismo vector, o creando objetos con datos y objetos con nombres para los datos, y uniéndolos después. Ejemplos:

codes <- c(italy = 380, canada = 124, egypt = 818)
codes <- c("italy" = 380, "canada" = 124, "egypt" = 818)

codes <- c(380, 124, 818)
country <- c("italy","canada","egypt")
names(codes) <- country

Otra manera de crear un vector es con la función seq(a, b) (viene de sequence ‘secuencia’). Esta función muestra los enteros consecutivos de a a b. Aunque el comportamiento por defecto es mostrar los enteros de uno en uno, un tercer parámetro permite establecer el valor de la diferencia entre elementos. Así, seq(1, 10, 2) mostrará los impares desde 1 (el comienzo) hasta 10 (el final): 1, 3, 5, 7, 9.

También es válido el siguiente atajo: [1:10] muestra los números enteros consecutivos entre 1 y 10.

Acceder a elementos de un vector

Los corchetes sirven para acceder a elementos específicos dentro de un vector. Por ejemplo, para los vectores anteriores, los siguientes códigos muestran los datos, dentro del vector codes definido antes, de la posición 2, de las posiciones 1 y 3, de las posiciones 1 a 2 (no 1 Y 2), los correspondientes al nombre canada y a los nombres egypt e italy2:

codes[2]
codes[c(1,3)]
codes[1:2]
codes["canada"]
codes[c("egypt","italy")]

Nótese que el parámetro que se le pasa al nombre del objeto (codes) es en sí mismo un vector también, por lo que han de «aglutinarse» con la función concatenate: c(1. 3).

Coerción (forzado) de vectores

La coerción es el intento de R de ser flexible con los tipos de datos utilizados. Cuando los datos introducidos no concuerdan con lo esperado se intenta entender lo que se quiso decir antes de mostrar un mensaje de error.

Coerción3 significa lo mismo que ‘extorsión’ o ‘coacción’: Presión ejercida sobre alguien para forzar su voluntad o su conducta; y también Represión, inhibición, restricción. Coerción en este contexto se refiere a la manera en que R restringe el uso de un solo tipo de datos en un vector. Si hay datos numéricos, son numéricos; si hay datos alfanuméricos, son alfanuméricos; si son lógicos, son lógicos. Pero cuando hay varios tipos mezclados la norma es:

Por eso se pueden hacer operaciones con vectores lógicos, porque los TRUE son tratados como 1 y los FALSE tratados como 0. De esta manera, se pueden conocer la suma de TRUE en un vector o la media de los valores (1 sería la media si todos los valores fueran TRUE y 0 sería la media si todos los valores fueran FALSE; si fueran mitad y mitad, la media sería 0.5).

De manera que el código x <- c(1, "Canadá", 3), aunque tiene valores numéricos, será un vector alfanumérico, pues tanto 1 como 3 pueden ser tratados como caracteres en lugar de números. En efecto, class(x)devolverá "character", y los valores almacenados en x son "1" "Canadá" "3", con los números entre comillas porque son tratados como caracteres.

R tiene funciones para forzar un tipo específico de coerción. La función as.character() fuerza que los números sean considerados caracteres, es decir, cadenas alfanuméricas.

x <- 1:5
y <- as.carachter(x)
y
 [1] "1" "2" "3" "4" "5"

La operación inversa se logra con la función as.numeric. as.numeric(y) devuelve 1 2 3 4 5.

Esto es muy útil cuando los datos son presentados en formas que hacen que R interprete números como caracteres o al revés (códigos postales, por ejemplo).

Datos que faltan (NA)

En R, los datos que faltan en un vector, algo muy habitual en conjuntos de datos reales, se muestran como NA, de not available. Cuando R intenta forzar un vector pero no puede, aparecen datos de esta manera. Por ejemplo, intentado convertir letras en números:

x <- c("1", "b", "3")
as.numeric(x)
 [1] 1 NA 3
 Warning message:
 NAs introduced
 by coercion

Lo que pasa es que los vectores deben guardar el mismo tipo de datos, como se dijo. Sin embargo, R trata de entender lo que “significan” los datos y ajustarlos en la medida de lo posible. Por ejemplo, convertirá el vector c(1, "canada", 3) en un vector alfanumérico "1" "canada" "3". Esto en ocasiones puede causar confusión cuando se cometen errores y los datos son convertidos por fuerza (coerced) en datos de otro tipo.

Las funciones as.character() y as.numeric() convierten vectores a sus respectivos tipos de datos. Cuando la conversión no es posible, como en el ejemplo anterior, en el que "canada" no puede ser convertido a números, R asigna el valor NA (Not Available) al dato que falta y avisa de que la coerción ha introducido valores “NA”.

Ordenar datos de un vector

Hay varias funciones útiles para ordenar datos en un vector, o también podría decirse que para ordenar un vector en función de sus datos.

Las funciones sort, order y rank proporcionan utilidades para ordenar los datos dentro de un vector. En realidad no modifican nada, solo muestran los datos con una ordenación determinada.

Función sort

Esta función simplemente ordena los datos de menor a mayor. De manera que si se asocia un vector a un objeto y luego se requiere el primer elemento de ese objeto se obtiene el valor más bajo de todo el vector. Ejemplo:

library(dslabs)
data(murders)
pop <- murders$population
pop <- sort(pop)
pop[1]

Función order

Esta función devuelve el vector de índices necesario para ordenar el vector en la función sort. Es decir, es un vector compuesto por los números de orden necesarios para ordenar los elementos del vector sort. Un ejemplo:

Vector: 23, 1, 56, 57, 12
Sort: 1, 12, 23, 56, 57
Order: 2, 5, 1, 3, 4

Los datos de vector están ordenados así: el segundo, el quinto, el primero, el tercero y finalmente el cuarto (de menor a mayor). Es útil para encontrar los números de fila que cumplan ciertas propiedades, como la fila correspondiente al estado con la menor población:

library(dslabs)
data("murders")
pop <- murders$population
ord <- order(pop)
ord[1]

En realidad esto mismo se puede hacer con la función which.min, que busca la posición (la fila, el índice) del valor mínimo de un vector: which.min(murders$population).

Función rank

La función rankdevuelve el vector formado por las posiciones de los elementos en el vector ordenado por la función sort. Es decir, es un ranking de cada dato, posición a posición. Con un ejemplo se ve más claro:

data vector sort order rank
31 4 2 3
4 15 3 1
15 31 1 2
92 65 5 5
65 92 4 4

En la columna rank el primer elemento del vector de datos (31) es el tercero más bajo (es el tercero en el vector ordenado sort); el segundo elemento del vector de datos (4) es el menor, el más bajo, es el primero en el vector ordenado sort; etc.

Operaciones con vectores

Es útil ordenar los datos, extraer el valor máximo o el mínimo, pero para sacar partido a un conjunto de datos, suele ser necesario manipular los vectores en base a otros vectores.

Por ejemplo, en el apartado anterior se vio que California es el estado con mayor número de asesinatos: murders$state[which.max(murders$total)]. Pero también es el estado con mayor población total: `murdersstate[which.max(murderspopulation)] (más de 37 millones de habitantes).

Por lo tanto, para hacernos una idea más realista de lo peligroso (o no) que es el estado de California deberíamos medir el número de asesinatos per cápita.

Las operaciones con vectores (y entre vectores) se hacen elemento por elemento. Es decir, si se multiplica el vector c(1, 3, 5, 7, 9) por 2 se obtiene el vector 2, 6, 10, 14, 18, que es cada uno de los elementos multiplicado por 2. Pero cuando las operaciones se hacen con dos vectores de igual longitud, las operaciones se siguen haciendo elemento por elemento, de manera similar a una hoja de cálculo (la primera celda de la primera columna se suma con la primera celda de la segunda columna, y así en cada fila). Por eso, un código como el siguiente:

library(dslabs)
data("murders")
murder_rate <- murders$total/murders$population*100000

dará como resultado una nueva variable (o columna) murder_rate (el ratio de asesinatos por 100,000 habitantes en cada estado) en el conjunto de datos con los valores, para cada estado, de la población total dividida por la población total del estado y multiplicado por 100,000.

Indexación de vectores

Las posibilidades en R a la hora de manipular vectores son muy potentes y útiles. Por ejemplo, se pueden manipular vectores basándose en características de otros vectores.

Siguiendo con el ejemplo, ahora que hemos calculado el índice de asesinatos por cada 100,000 habitantes para cada estado (murder_rate), lo usaremos para escoger un estado al que mudarnos si, por ejemplo, venimos de Italia, que tiene un índice de asesinatos por cada 100,000 habitantes de 0.71. Para ello, usaremos una característica muy potente de R, que es indexar vectores basándose en valores lógicos (TRUE/FALSE). Esto se aplica a las operaciones lógicas, en las que la comparación con un término aplica la operación a cada uno de los elementos del vector. Está más claro con el ejemplo a continuación. El siguiente código:

index <- murder_rate < 0.71

creará un vector lógico en el que habrá valores TRUE en las observaciones (filas) en las que el índice de asesinatos sea menor que 0.71 y FALSE en las que sea igual o mayor. El operador <= dará el mismo resultado pero será TRUE cuando el valor de «murder_rate» sea menor o igual que 0.71. Pero esta es una lista de «trues» y «falses» que no dice mucho. Pero filtrar los nombres basándose en esta lista es mucho más útil: murders$state[index] muestra únicamente los valores Hawaii, Iowa, New Hampshire, North Dakota y Vermont.

El vector lógico index es forzado (‘coerced’) en uno numérico en el que FALSE es 0 y TRUE es 1. Por eso, sum(index) da como resultado 5, porque suma todo y solo hay 5 unos, los correspondientes a los estados de Hawaii, Iowa, NH, ND y Vermont.

Ahora ya sabemos qué estados tienen un índice de asesinatos menor o igual que el de Italia. Ahora bien, si además queremos mudarnos a un estado en el oeste será necesaria una nueva operación de filtrado.

Digamos que el límite de asesinatos por 100,000 habitantes es 0.71 y que la región es el oeste. Por lo tanto, pueden definirse las siguientes condiciones:

safe <- murder_rate <= 0.71
west <- murders$region == "West"
# El operador "&" creará un vector lógico en el que serán "TRUE" los
# elementos que satisfagan AMBAS condiciones
index <- safe & west

murders$state[index]
[1] Hawaii

Funciones útiles para la indexación

Tres nuevas funciones muy útiles para manipular vectores son which, match e %in%.

Which

which devuelve las entradas (el número de índice, el número de fila) de un vector lógico que son ciertas (es decir, son TRUE).

x <- c(FALSE, TRUE, FALSE, TRUE, TRUE, FALSE)
which(x)
[1] 2 4 5

Por ejemplo, para ver el índice de asesinatos de Massachusetts, se puede asignar el valor de which a una variable basándose en un operador lógico, así:

index <- which(murders$state == "Massachusetts")
index
[1] 22
murder_rate[index]
[1] 1.802

Por supuesto, no es la única manera de lograrlo, con lo que se sabe hasta ahora, se puede conocer un índice de asesinatos en concreto filtrando el vector con el operador lógico directamente: index <- murders$state == "Massachusetts" y, a continuación el filtrado murder_rate[index]. O, también, directamente: murder_rate[murders$state == "Massachusetts"].

Match

Busca entradas en un vector y devuelve los índices (o filas) en las que se puede acceder a esos datos. Como parámetro se puede pasar, a su vez, un vector con datos que se quieren comprobar. Un ejemplo: para conocer el índice de asesinatos de varios estados en lugar de uno solo se puede crear un vector con «el cruce» de la información de dos vectores: el vector de nombres y el vector de estados a consultar. Así:

index <- match(c("New York", "Florida", "Texas"), murders$state)
index
[1] 33 10 44
murder_rate[index]
[1] 2.667960 3.398069 3.201360
# Comprobación de si los índices dados por la función equivalen a los
# estados que queremos consultar
murders$state[index]
[1] "New York" "Florida" "Texas"

%in%

Esta función lo que hace es comprobar si cada elemento de un vector existe en otro vector dado. Una muestra: si se define x como las seis primeras letras x <- c("a", "b", "c", "d", "e") e y como otra secuencia de letras diferente y <- c("a", "d", "f"), la función %in% devolverá los valores lógicos resultantes de comparar ambos vectores: y %in% x será TRUE, TRUE, FALSE, pues a está en x, d está en x y f, no.

El conjunto de datos con el que estamos trabajando permite comprobar si los estados de Dakota, Boston y Washington son realmente estados o no. ¿Están los tres elementos de esta lista contenidos en la lista de los 52 estados de EE.UU.? Es decir, c("Boston", "Dakota", "Washington") %in% murders$state. La respuesta es FALSE FALSE TRUE porque ninguno de los dos primeros es un estado, pero Washington sí lo es.

Manejo básico de datos (basic data wrangling)

Hasta ahora se han manipulado vectores reordenándolos y agrupando sus elementos al indexar el vector de varias maneras (operadores lógicos, datos de otros vectores, etc.). Pero para profundizar en el análisis de datos se necesitan manipular conjuntos de datos enteros, tablas de datos. Para eso es necesario el paquete dplyr.4 Este paquete proporciona las funciones más comunes para el manejo de tablas.

Se pueden añadir columnas (variables) a una tabla o cambiar una columna que ya existe con mutate; se pueden filtrar los datos usando filter o se pueden hacer subconjuntos de datos seleccionando columnas concretas con la función select. Por supuesto, se pueden generar combinaciones de funciones pasando el resultado de una función como parámetro para otra. Para eso se usa la tubería %>%.

Mutate

Esta función modifica columnas en un conjunto de datos o tabla de datos. Necesita dos parámetros: el conjunto de datos con el que va a trabajar y el nombre y valor de la columna que se va a añadir mutate(datos, variable = valor). Tanto la variable como los datos (en el caso de que se obtengan manipulando variables ya existente tal y como pasará a continuación) se extren directamente de la tabla de datos y no del espacio de trabajo, así que es suficiente con el nombre de la variable (population, state, etc.). No es necesario especificar el conjunto de datos una y otra vez (murderspopulation, murdersstate, etc.).

Por lo tanto, para añadir el índice de asesinatos al conjunto de datos como una columna más se usará: murders <- mutate(murders, rate = total/population*100000). Nótese la falta de murders$ delante de cada nombre de variable. Dado que ya se había definido la variable murder_rate, también sería válido un código como murders <- mutate(murders, rate = murder_rate).

Ahora, la estructura del conjunto de datos ya incluye esta columna. Se puede comprobar fácilmente con str(murders).

NOTA IMPORTANTE: Esta función ha modificado el objeto murders, que existe en el espacio de trabajo. Si en algún momento se vuelve a cargar este desde el paquete original (library(dslabs)), el objeto se sobrescribirá y se perderán los cambios hechos.

Filter

Por ejemplo, si queremos visualizar solo los datos para los que el índice de asesinatos es menor de 0.71. El primer argumento es la tabla o conjunto de datos y la condición que queremos filtrar, el segundo.

filter(murders, rate <=0.71)

Igual que con mutate, los parámetros son las variables de la tabla directamente, no objetos del espacio de trabajo, por lo que no hay que escribir murders$ constantemente.

Select

La función select permite trabajar con solo el conjunto de variables que se seleccione como segundo argumento (el primero es el nombre de la tabla). En este ejemplo solo hay seis columnas, pero muchas tablas hay cientos. Como ejemplo, crearemos un nuevo objeto con tres columnas (nombre del estado, región a la que pertenece e índice de asesinatos) y luego aplicaremos un filtro:

new_table <- select(murders, state, region, rate)
filter(new_table, rate <= 0.71)

De nuevo, tanto la tabla como sus variables no son objetos del espacio de trabajo.

Las tuberías (pipe): %>%

Tal y como hacen las tuberías, se puede canalizar el resultado de una función para que haga las veces de entrada de datos de la siguiente.

Así, funciones como las anteriores pueden ser escritas en una sola orden. Primero se toma un origen de datos, una tabla murders. Estos datos se «canalizan» hacia una segunda función en la que se seleccionan solo ciertas variables:

murders %>% select(state, region, rate) %>% filter(rate <= 0.71)

El operador de canalización (%>%) entiende que lo que sea que se canaliza es sobre lo que hay que efectuar las operaciones, de ahí que no sea necesario ese primer parámetro que especifica el origen de datos como en los ejemplos de las funciones una por una dados antes. También se evita el objeto intermedio new_table.

Si no se carga la biblioteca dplyr antes, estas funciones (incluida la tubería) no estarán disponibles y se mostrará un error.

Crear tablas de datos

Para muchos de los análisis que se pueden hacer con el paquete dplyr puede que necesitemos crear una tabla de datos con valores. Una tabla de datos o data frame se puede crear con la función data.frame. En ella se especifican unas variables y valores que asignar a esas variables, como en el ejemplo siguiente:

notas <- data.frame (nombres = c("Juan", "Pedro", "María", "Raquel",
                    "Margarita"),
                    examen_1 = c(2.5, 4, 6.5, 8, 4),
                    examen_2 = c(5.5, 6, 7, 7.5, 3),
                        stringsAsFactors = FALSE)
notas

# nombres    examen_1   examen_2
# Juan       2.5        5.5
# Pedro      4          6
# María      6.5        7
# Raquel     8          7.5
# Margarita  4          3

OJO Por defecto, data.frame hace que las entradas alfanuméricas sean tratadas como factores (ver más arriba), por lo que será necesario (si lo es, que puede que en algún caso sea conveniente) añadir el último de los parámetros del código anterior: stringsAsFactors. Si no se incluye, el resultado de class(notas$nombres) será factor. Si se incluye, será character.

Gráficos básicos

Los gráficos son tremendamente útiles a la hora de inferir algo de los conjuntos de datos de manera intuitiva, mucho más que las listas de números, como pasa con el conjunto de datos del ejemplo, murders.

R es muy potente a la hora de hacer gráficos y mostrar datos.

La función plot hace gráficos de manera muy sencilla.

population_in_millions <- murders$population/10^6
total_gun_murders <- murders$total

plot(population_in_millions, total_gun_murders)

Es decir, plot(datos_en_el_eje_x, datos_en_el_eje_y).

Una vez que se escriba esta orden en una consola de R, R mostrará el gráfico y no parece que haya manera humana de deshacerse de él. En los IDE, como RStudio, hay una sección especialmente dedicada a los gráficos, de manera que es más sencillo gestionarlos (los gráficos). Trabajando en consola, parece que la única manera de cerrar el gráfico es cerrar la ventana que lo contiene.

Creo que hay una manera de guardar el gráfico a disco, pero aún no lo tengo claro.

Los histogramas son muy útiles y se describirán más tarde en el programa, y se consiguen con la función hist, así: hist(murders$rate).

Una manera más detallada de mostrar datos son los diagramas de caja o boxplot ver más información aquí. Un ejemplo de diagrama de caja (boxplot en inglés) es la siguiente: boxplot(rate~regions, data = murders). Esta representación de datos da mucha más información acerca de cómo se distribuyen los datos de una muestra.

Programación básica

Condicionales (lo básico)

if-else

La sentencia condicional más básica es if-else. La fórmula general es la siguiente:

if(condición lógica){
   expresión_si_es_cierta
   } else {
   expresión_alternativa
   }

Un ejemplo con el conjunto de datos de asesinatos por arma de fuego en EE.UU. que hemos usado hasta ahora:

# cargar los datos (por recordar, más que nada)
library(dslabs)
data(murders)

# definir el índice por cada 100,000 habitantes
murder_rate <- murders$total/murders$population*100000

# objeto intermedio para encontrar el menor índice de asesinatos de
# todo el conjunto de datosæ
ind <- which.min(murder_rate)

# sentencia condicional
if(murder_rate[ind]) < 0.5){
   print(murders$state[ind]
   } else {
   print("Ningún estado tiene un índice tan bajo. Vete a Japón,
   pringao")
   }

En esta función condicional se usa un objeto intermedio (ind) que almacena el número de índice (el número de fila) del menor de todos los valores de murder_rate. Después, en la condición propiamente dicha, se pregunta si el valor de murder_rate que corresponde a esa fila es menor de 0.5. Nótese que ind es un número de fila, mientras que murder_rate[ind] es un valor concreto de número de asesinatos por cada 100,000 habitantes, por eso se pregunta por el valor de la variable murder_rate filtrada por el número de fila del valor mínimo. Eso devuelve el valor mínimo.

Si la condición es cierta, se muestra en pantalla (print) el valor almacenado en la variable murders$state en la posición (número de fila) correspondiente a ind (el valor mínimo de murder_rate).

Si no es cierta (else), se muestra un mensaje. Perfectamente se podría mostrar otra cosa, como el valor máximo, la lista completa o lo que nos dé la gana, esto es solo un ejemplo. No tiene por qué ser un mensaje.

El ejemplo muestra [1] "Vermont", porque el índice de asesinatos en Vermont es murder_rate[ind], o sea, 0.3196. Si en lugar de poner una condición a 0.5, esta se pone a 0.1, el resultado será el mensaje alternativo propuesto: Ningún estado tiene un índice tan bajo, ¡vete a Japón, pringao!

ifelse

La función ifelse está relacionada con la anterior, pero no es lo mismo. Se escribe todo junto, en una sola palabra, y tiene tres parámetros obligatorios: una condición lógica y dos respuestas posibles (si se cumple la condición y si no se cumple).

Un ejemplo que devuelve el recíproco de un número x5 solo si x es positivo es el siguiente:

x <- 0
ifelse(x > 0, 1/x, NA)

La utilidad de esta función reside en el hecho de que funciona con vectores. Efectúa las comprobaciones elemento por elemento, tal y como se ha visto anteriormente en otras funciones. Por ejemplo, si se define a como un vector con números tanto positivos como negativos, la función ifelse comprobará cada uno de los elementos del vector:

a <- c(1, 2, -4, 5)
ifelse(a > 0, 1/a, NA)

#[1] 1, 0.5, NA, 0.2

Se toma un elemento, se comprueba la condición y se devuelve una de las dos respuestas dependiendo de si la condición se cumple o no. Es muy útil (esta función) para sustituir los NA por ceros, por ejemplo. Reemplazar los valores que faltan (NA) por otro valor es un uso muy habitual de esta función. En el paquete dslabs hay otro conjunto de datos de ejemplo con este propósito: na_example.

library(dslabs)
data(na_example)

# La suma de NA se consigue preguntando por los NA contenidos en el
# conjunto de datos y sumándolos.
sum(is.na(na_example))
#[1] 145

# Nuevo vector en el que se sustituyen todos los NA por 0
no_nas <- ifelse(is.na(na_example), 0, na_example)

# Nueva comprobación:
sum(is.na(no_nas))
#[1] 0

En este ejemplo, la función ifelse comprueba si el dato del vector na_example es NA. Si lo es entonces la condición is.na(na_example es TRUE y aplica la primera respuesta, es decir, 0. Si no lo es, aplica la segunda respuesta, es decir, el dato de na_example sin modificar.

any

Esta función comprueba todos los elementos de un vector lógico y devuelve TRUE si cualquiera de los elementos (al menos uno) es TRUE.

z <- c(TRUE, TRUE, FALSE)
y <- c(FALSE, FALSE, FALSE)
any(z)
#[1] TRUE
any(y)
#[1] FALSE

all

Esta función comprueba todos los elementos de un vector lógico y devuelve TRUE si todos los elementos son TRUE.

z <- c(TRUE, TRUE, FALSE)
y <- c(TRUE, TRUE, TRUE)
all(FALSE)
#[1] FALSE
all(y)
#[1] TRUE

Creación de funciones

A medida que se avanza en el análisis de datos, será necesario repetir la misma operación una y otra vez, como por ejemplo calcular la media de una serie de datos. Se puede hacer a mano, por supuesto, dividiendo la suma de los elementos sum(x) entre la cantidad de elementos length(x). Pero sería mucho mejor escribir una función que lo hiciese, de manera que con escribir media(x) hiciese el cálculo completo.

En realidad no es necesario, porque esa función ya existe, es mean(x), pero otras funciones que aún no existen pueden ser necesarias.

Para ello está la función function, que dice a R que se está definiendo una nueva función, con sus parámetros necesarios u opcionales, configuraciones por defecto, etc.

El uso básico de function es el siguiente:

media <- function(x){
    s <- sum(x)
    n <- length(x)
    s/n
    }

Se almacena en media (ese será el nombre de la nueva función) una nueva función con un parámetro obligatorio entre paréntesis (x) sobre el que se efectúan las operaciones entre llaves: se asigna la suma de los elementos a s, se asigna la longitud del vector a n y se efectúa la división de s entre n. Lo que aparezca en la última línea será la salida de la nueva función, que puede ser un valor a devolver, como en este caso, o una modificación de una tabla, una asignación de valor, el borrado de un dato, etc.

Nota importante: Las asignaciones a variables u objetos que se efectúen dentro de la función no afectan al espacio de trabajo. Es decir, que si hay un objeto s con un valor en el espacio de trabajo porque en algún momento se ha definido s <- 1, este valor no se ve en absoluto afectado por la asignación a s de valores diferentes a 1 (el valor en el ejemplo) durante la operación media. De igual manera, la operación media no tiene en cuenta los valores que s o n pudieran tener en el espacio de trabajo y por tanto no le afectan para nada.

La función general es:

función_nueva <- function(x){
      operaciones a realizar sobre x
      resultado
      }

Puede ser que sean necesarios varios parámetros, en cuyo caso se especifican entre los paréntesis: function(x, y, z){. Por ejemplo, si se piden tres valores de una ecuación cuadrática para dar las soluciones. En ese caso, las operaciones entre llaves se efectuarán sobre los tres elementos aportados por el usuario.

También es posible que los parámetros tengan opciones por defecto. El siguiente ejemplo calcula la media aritmética o geométrica dependiendo de un parámetro a elección del usuario. Este parámetro tiene un valor predefinido que será tomado por defecto:

media <- function(x, aritmetica = TRUE){
    n <- length(x)
    ifelse(aritmetica, sum(x)/n, prod(x)^(1/n))
    }

En la última línea, una sentencia ifelse hace que, dependiendo de si el parámetro aritmetica existe o no (es decir, si es 1 o 0, si es TRUE o FALSE) devuelve la media aritmética (es la primera opción de ifelse, la que se efectúa si la condición es cierta), y este es el comportamiento por defecto, pues por defecto el parámetro aritmetica se ha establecido como TRUE. Si, por el contrario, hay una definición explícita del aritmetica como FALSE, entonces ifelse escoge la segunda respuesta y devuelve la media geométrica.

Bucles (bucles for)

Los bucles se crean para repetir la misma operación una y otra vez sobre un conjunto de datos en el que se varía el dato sobre el que se opera en cada una de las iteraciones del bucle.

Por ejemplo, para calcular la suma de una progresión. La progresión de los números naturales desde 1 hasta n 1+2+3+4+5+…+n se puede calcular mediante la fórmula [n(n+1)1]/2. Para comprobar si la fórmula es correcta, se pueden efectual las sumas desde 1 hasta n solo escogiendo un número n.

Una función que haga el cálculo es sencillo después de ver el apartado anterior sobre creación de funciones:

suma_n <- function(n){
     x <- 1:n
     sum(x)
     }

La función pide un parámetro y asigna al objeto x un vector formado por los números naturales desde 1 hasta n. A continuación calcula la suma de los elementos de ese vector.

Ahora bien, si lo que necesitamos no es conocer la suma hasta un n concreto sino cómo evoluciona esta suma desde n=1 hasta n=25 es necesario hacer el mismo cálculo 25 veces, cada vez con un valor diferente de n. Para eso vale un bucle for.

La forma general es:6

for(i in rango_de_valores){
    operaciones a realizar sobre i a medida que varía el valor de i
    }

Un bucle extremadamente sencillo (e inutil) es el siguiente:

for(i in 1:5){
   print(i)
   }

Da como resultado los números de uno a cinco. Lo que hace es asignar valores a i dentro de los definidos entre paréntesis (de uno a cinco) y luego ejecuta el código sobre ese valor de i. En este caso es solamente mostrar i.

Nota importante: el último valor de i (o cualquier otra variable que se use en el bucle for) se mantiene después del cálculo. Con el ejemplo anterior, el código i devolverá 5, porque es el último valor de i con el que se ejecutó el bucle.

Para el ejemplo propuesto, la suma de los primeros n números naturales, se necesita primero crear un vector que almacene los n resultados de las n sumas desde 1 hasta n: m <- 25 para definir las sumas desde 1 hasta 25, y s_n <- vector(length = m) para crear un vector vacío de esa longitud.

Ahora viene el bucle en sí. Nótese que suma_n(n) es la función creada anteriormente.

for(n in 1:m){
   s_n[n] <- suma_n(n)
   }

Este código define un valor cambiante de n desde 1 hasta m y asigna el valor de la función suma_n sobre ese valor de n. La función suma_n definida anteriormente calcula la suma desde 1 hasta n y la almacena en la posición n en el vector s_n. Ese vector tiene una longitud m para poder almacenar tantos valores de n como se definan en la variable m.

Un gráfico mostrará hasta qué punto la secuencia lograda es correcta: plot(1:m, s_n). Además, se puede superponer la funcíon lines para mostrar el resultado de la fórmula y comparar la superposición de ambos resultados tal que así: lines(n, n*(n+1)/2). Sin embargo, en consola al menos no funciona (plot muestra el gráfico, pero lines da una pantalla en blanco. Hay que probar en RStudio u otro IDE.

Otras funciones útiles

Este curso no las cubrirá, pero funciones útiles que es provechoso aprender a utilizar son:

Lo que se aprenda de ellas irá apareciendo aquí.

Visualización de datos

Mostrar y guardar los gráficos

Trabajando en la consola de R los gráficos salen directamente a la pantalla y no se puede hacer nada con ellos, salvo matar la ventana y cerrarlos. Para guardar las imágenes hay que hacer lo siguiente:

  1. Llamar a la función correspondiente a la imagen que se quiere crear.
  2. Ejecutar el gráfico, que no aparece.
  3. Liberar el control y devolverlo a la pantalla.

Hay varias funciones para cada uno de los tipos de gráfico: jpeg(), png(), bmp() y tiff() para mapas de bits.

jpeg(file = "gráfico_1.jpeg")
plot(murders$region)
dev.off()

Se puede especificar la ruta completa para no guardar la imagen en el directorio actual. También se puede especificar el tamaño de la imagen con width y height. Por defecto es 480x480. Por ejemplo, guardando como png.

png(file = "gráfico_2.png",
    width = 640,
    height = 640
    )
plot(murders$region)
    dev.off()

Argumentos posibles son las unidades en las que se mide el tamaño (mm, cm, in) y la resolución (en ppp), como en

png(file = "gráfico_3.png",
    width = 45
    height = 30
    units = "mm"
    res = 300
    )

Otra opción es guardar como pdf con pdf(file = "archivo.pdf") o como PostScript con postscript(file = "archivo.ps").

Introducción a la visualización de datos y distribuciones

Introducción a la visualización de datos

Corresponde al capítulo 6 del libro

Normalmente los datos en bruto no son muy útiles a la hora de derivar ideas de una tabla o un vector. Los números por sí solos no dicen mucho a la mayoría de las personas. Sin embargo, un gráfico puede ofrecer mucha información fácilmente comprensible de un vistazo, de manera que se pueden extraer conclusiones de manera más sencilla que mirando tablas. En ocasiones, una imagen es tan demostrativa que no hace falta ningún análisis más.

Últimamente, cada vez hay más datos disponibles y herramientas informáticas para analizarlos. Incluso en el mundo del periodismo se aprecia un aumento de los gráficos basados en datos para apoyar afirmaciones.

La visualización correcta de los datos es la herramienta más poderosa del análisis exploratorio. Y este es a su vez la parte más importante del análisis de datos.

Introducción a la distribución normal

Corresponde al capítulo 8 del libro Es habitual resumir conjuntos de datos numéricos con un solo valor (la media) y, en ocasiones, con dos valores (la media y la desviación típica): el rendimiento de una clase con el valor medio de la nota de un examen (5.2 o 5.2±0.4).

En ocasiones estos dos números son suficientes para entender los datos. Las técnicas de visualización de datos ayudan a ver cuándo estos resúmenes son adecuados y proporcionan herramientas y alternativas cuando no lo son.

Lo más básico para analizar un conjunto numérico es la distribución. Una vez que un vector ha sido resumido como una distribución, hay técnicas que permiten extrer información muy útil de ese vector.

Tipos de datos

En un conjunto de datos (data set), hay datos catalogados como registros (cada una de las mediciones hechas, como cada persona que responde una encuesta, las filas de la tabla) y como variables (cada uno de los datos que cada registro genera, como las preguntas de una encuesta, las columnas de la tabla).

Las variables pueden ser de dos tipos:

Cuando una variable tiene un número muy limitado de valores, se dice que es categórica. Ejemplo: sexo (hombre, mujer), regiones en EE.UU. (South, West, NorthEast, Central).

Algunos datos categóricos pueden ser ordenados aunque no sean números, como los grados de temperatura de un producto: frío, templado, caliente. Pueden ser conocidos como datos ordinales (se pueden ordenar).

Las variables numéricas pueden ser continuas o discretas. Una variable continua es una que puede tomar cualquier valor, como la altura de una persona (1.82, 1.772). Los valores que puede tomar dependen únicamente de la precisión con que se mida. Podría ser considerada categórica solo si se toman los valores alto, mediano y bajo.

Una variable discreta es una que solo puede tomar un cierto número de valores a números redondos, como la altura en centímetros (182, 177) o una población (233,845 personas). Según algunos textos, se pueden considerar ordinales también, pero puede ser confuso. Es mejor ceñirse a la nomenclatura de variables ordinales como aquellas que tienen un rango muy pequeño de valores. Por ejemplo, el número de paquetes de cigarrillos que una persona fuma al día puede ser considerada ordinal (valores 0, 1, 2 o 3), pero el número de cigarrillos será una variable numérica (de 0 a 60).

Se puede conocer el nombre de las variables de un conjunto de datos con la función names.

install.package("dslabs")
library(dslabs)
data(heights)
names(heights)

# [1] "sex"  "height"

Se pueden conocer los valores que puede tomar una variable observando los primeros registros de una tabla con la función head:

library(dslabs)
data(heights)
head(heights)

#   sex  height
#  1   Male     75
#  2   Male     70
#  3   Male     68
#  4   Male     74
#  5   Male     61
#  6 Female     65

COMPLETAR

Un primer ejemplo

Un problema artificial pero ilustrativo para entender los conceptos detrás de la idea de distribución: describir la estatura de un grupo de personas a ET (un extraterrestre que nunca ha visto un humano). Lo primero es recolectar los datos. Eso lo consideramos hecho con el conjunto de datos heights ya incluido en el paquete dslabs.

El modo más sencillo es enviar el conjunto completo de datos con sus 1,050 entradas. Pero hay maneras más efectivas.

La distribución es el resumen estadístico básico de una lista de objetos o números, es una descripción compacta de la lista. Esto es muy sencillo con datos en categorías, como la lista de sexos en heights: male y female. Así que el resumen podría ser simplemente la proporción de ambos. El código prop.table(table(heights$sex)) dará las proporciones en tanto por uno, o sea Female será 0.227 y Male será 0.773.7 Y no hay más.

Para categorías más complejas, un gráfico de barras describe las proporciones de cada categoría. Son ejemplos de cómo convertir un vector en un elemento visual que resuma sus características.

Con datos numéricos es algo más complejo, pues como cada elemento es único (o casi, la frecuencia de repetición suele ser muy baja), mostrar la frecuencia de aparición no es muy efectivo. Sería mejor definir una función que mostrase la proporción de datos por debajo de un valor A para todos los posibles valores de A (esto es una distribución acumulativa o CDF por sus siglas en inglés (Cumulative Distribution Function). Es decir, cuántos valores hay de A entre el inicio y A. Comprobando el valor de la curva en x = 72 se ve que el valor es 0.839, es decir, el 83.9% de los elementos de la curva tiene una altura igual o menor a 72 pulgadas. ¿Cuántos alumnos tienen una altura entre 66 y 72 pulgadas? Son el 83.9% menos el 13.8% = 70.1%. Es por tanto muy útil para decidir la probabilidad de que una medida tenga un valor determinado (un 70% de que el alumno tenga una altura entre 66 y 72 pulgadas)

Gráfico de distribución acumulativa

Sin embargo, no es un gráfico muy popular porque obvia datos interesantes como el valor en el que se centra la lista de datos, si es simétrica o no respecto a ese centro, etc. Los datos están, pero no son fácilmente identificables.

Este gráfico se puede calcular con el siguiente código:

# Dado un conjunto de datos llamado *datos*
# se define el rango de valores que abarcan los datos
a <- seq(min(datos), max(datos), length = 100)

# se calcula la probabilidad para un valor
cdf_function <- function(x){
    mean(datos <= x)
}
cdf_values <- sapply(a, cdf_function)
plot(a, cdf_values)

Histogramas

Una manera correcta y muy exacta de representar datos es con una gráfico de distribución acumulada como el anterior, pero no es muy práctico en la vida real. Lo más esclarecedor es un histograma. En un histograma se divide una secuencia continua de datos (como las alturas individuales de un grupo de personas) en tramos discretos en los que se cuenta cada una de las apariciones de datos (por ejemplo, entre 176 y 178 centímetros hay 48 personas, entre 178 y 180 hay 30, etc.).

Histograma

Es más sencillo ver cómo se distribuyen los datos en la muestra usando un histograma: rango de datos, rango en el que se agrupa la mayoría de elementos, simetría de los datos, etc.

Pero se pierde algo de información porque dentro de cada intervalo considerado (el ancho de las barras) todas las medidas son consideradas iguales. En el gráfico del ejemplo más arriba cada barra representa intervalos de 5°C. Todas las medidas entre 70 y 75, por ejemplo, son consideradas iguales aunque no lo sean en realidad. Las implicaciones prácticas de esta normalización pueden ser irrelevantes dependiendo del caso.

Curva de densidad

Una curva de densidad (Smooth Density Plot) es mejor para una interpretación rápida de la distribución de datos. Es una estimación de cómo se vería un histograma con una cantidad extremadamente elevada de observaciones y, por tanto, de barras. Es como aumentar enormemente la resolución con la que se puede ver un histograma.

Curva de densidad

En este tipo de gráficos, el eje Y muestra la densidad de aparición de un valor determinado en el conjunto de datos. Es decir, para conocer la frecuencia de aparición de un cierto valor dentro de un intervalo, hay que conocer el porcentaje del área bajo la curva (la integral de la función) dentro del dicho intervalo en el eje X.

Es útil para comparar dos muestras. Pero tiene como contrapartida que es una estimación que puede ser ajustada y por tanto se presta a interpretación. Su utilidad depende en parte de la habilidad del analista.

Su origen es la asunción de que la lista de datos (en el ejemplo es la lista de alturas de alumnos varones en pulgadas contenida en el paquete dslabs) proviene de una lista hipotética que contiene todas las alturas de todos los estudiantes varones de todo el mundo medidas con asombrosa precisión. Y esta es la lista que queremos mostrar a ET, que proporcionará valores más precisos y generales que la lista que tenemos. Pero para eso hay que asumir algo. La precisión con la que se miden los datos y el número enorme de datos permite hacer un histograma con las barras extremadamente delgadas, de manera que no hay saltos entre barras (de ahí lo de smooth ‘suave’).

Pero no tenemos infinitas ni un millón de observaciones sino 812 en nuestra lista. Por eso se hace un histograma contando frecuencias de valores en lugar de contar valores. Se dibuja entonces una curva que conecte los picos superiores de las barras del histograma. Cómo se ajusta la curva a la forma del histograma original es algo, como se dijo, que puede ser ajustado con un parámetro en la función ggplot, y depende del analista. En nuestro caso, se puede asumir que las proporciones de medidas contiguas serán similares, de manera que el gráfico tenderá a ser más suave que en otros casos.

La distribución normal

Tanto los histogramas como la curva de densidad son excelentes resúmenes de una distribución de datos. Para resumir y explicar los datos aún mejor y de manera más resumida se usan la media y la desviación típica (así que se puede resumir la distribución con solo dos números). Esto es gracias a la distribución normal.

La distribución normal también se conoce como «Campana de Gauss» o «Curva de Bell», y es uno de los conceptos matemáticos más usados de la historia porque es una distribución que se repite en multitud de ocasiones de manera más o menos exacta (en resultados de apuestas, estatura de un grupo, presion arterial, medición de errores experimentales, etc.).

La distribución normal no se basa en datos empíricos sino en una fórmula matemática. Esta formula se controla en el intervalo entre a y b con tan solo dos parámetros, m y s (la media y la desviación típica). El resto son constantes conocidas (pi y e).

Lo más importante es que la distribución es simétrica alrededor de la media (igual hacia un lado que hacia el otro) y el 95% de los valores están dentro de ±2 desviaciones típicas de la media. Este hecho, que la formula y por tanto la curva puede ser descrita con solo dos parámetros (m y s) hace que cuando un conjunto de datos es aproximadamente como la distribución normal, toda la información necesaria para describir este conjunto de datos y las inferencias que se pueden extraer se pueden codificar con esos dos parámetros, la media y la desviación típica que (ahora sí) se extraen empíricamente de la muestra.

La media es la suma de los elementos dividida por el número de elementos. En un vector x la media es media <- sum(x) / length(x), aunque existe una función ya definida para ello: mean(x).

La desviación típica es la raíz cuadrada de la suma de los cuadrados de las diferencias entre los valores y la media, todo dividido por el número de elementos. Es decir, es la media (cuadrado y raíz cuadrada se anulan pero se usan para eliminar los negativos) de la distancia entre los valores y la media. En R se calcula con:

desv_tip <- sqrt(sum((x-media)^2) / length(x))`

Es importante saberlo porque la función definida en R no divide por length(x) sino por length(x) - 1.8 Cuando el número de elementos es grande no tiene ninguna importancia un elemento arriba o abajo, así que se puede usar la función sd sin problema. Cuando son pocos, esta diferencia influirá en el resultado.

Calculemos la media y la desviación típica para el conjunto de datos heights, pero solo los varones. Se guarda en x el vector de estaturas para los varones:

library(dslabs)
data(heights)
index <- heights$sex == "Male"
x <- heights$height[index]
# se da una vuelta para mostrar los resultados como una sencilla
# tabla, pero valdría con las dos funciones tal cual.
media <- mean(x)
desv_tip <- sd(x)
c(media=media, desv_tip=desv_tip)

#   media   desv_tip
#   69.31       3.61

Un gráfico de densidad muestra que la forma de la curva es prácticamente igual a la distribución normal calculada con la media de 69.31 y la desviación típica de 3.61.

Unidades estándar (estándar units)

Es conveniente hablar de unidades estándar cuando se trata de conjuntos de datos que se aproximan bastante a la distribución normal. Una unidad estándar informa de cuántas desviaciones típicas se separa un dato contando desde la media. Se calcula como

z = (x - m)/s

De esta manera, z se puede usar como medida de cómo de apartado está un valor de la media y, por tanto, cuán extraño es en la muestra. Si z es mayor de 2 o menor de -2, se puede considerar que la estatura es alta o baja (no está dentro de los valores que tiene el 95% de la muestra, que son ±2 desviaciones típicas desde la media -ver arriba-). Si los valores de z son más extremos, pues más alejado el elemento de la media. Cuando el valor de z se acerca a 0, el valor está dentro de la media.

Otra ventaja es que estos valores de ±2 s alrededor de la media no tienen nada que ver con las unidades originales (pulgadas, mmHg, °C, kg, etc.)

En R se calcula z con la función scale, así: z <- scale(x). Se puede calcular cuántos alumnos de la muestra están dentro de ±2 desviaciones típicas calculando el valor absoluto de z < 2 y hallando su media (luego lo explico): mean(abs(z) < 2). El valor resultante es 0.9495, ~0.95, o sea, el 95% de las estaturas están dentro del rango de ±2 desviaciones típicas.

Explicación: en este código se usa la media de los valores absolutos porque el resultado de lo que está entre paréntesis es un vector lógico en el que hay valores TRUE cuando el valor absoluto de z es menor de 2 y valores FALSE cuando es 2 o mayor. Dado que ambos se asimilan a 1 y 0, respectivamente, la media muestra la proporción de unos respecto al total de elementos (suma de unos dividida por el número total de elementos). Si todos fuesen TRUE, la media sería 1.

De manera que se pueden predecir las proporciones de elementos dentro de un rango sin ni siquiera mirar los datos.

Sin embargo, para confirmar la exactitud de las observaciones se deben explorar otros indicadores, como los cuantiles. Pero antes, algunos datos más sobre la distribución normal.

La función acumulativa normal y pnorm

Ya se ha hablado de la distribución normal como aproximación súper útil para muchas distribuciones del mundo real.

La función aculumativa de la distribución normal viene dada por una fórmula matemática, igual que la curva de Bell, que en R se obtiene con la función pnorm. Decimos que un valor aleatorio a está normalmente distribuido con media m y desviación típica s si su distribución de probabilidad (ver la función acumulativa o eCDF más arriba) está definida por *F(a) = pnorm(a, m, s).

Otra vez, esto es sumamente útil porque para calcular la probabilidad de que una observación esté contenida en un rango no hace falta todo el conjunto de datos, solo la media y la desviación típica.

Por ejemplo, para saber la probabilidad de que un estudiante sea más alto que 70.5 pulgadas habrá que calcular 1 - pnorm(70.5, mean(x), sd(x)), donde x es el vector obtenido anteriormente de las alturas de solo los varones. Sin restar de 1 el resultado será la probabilidad de que la altura sea menor de 70.5.

La distribución normal se calcula matemáticamente solo a partir de la media y la desviación típica, y está definida solo para variables continuas, no tiene sentido en variables discretas. Esto es importante, porque no se puede «preguntar» a los datos acerca de la probabilidad de que la medida sea 70 pulgadas; la probabilidad de que sea 70 pulgadas exactamente es la misma que cualquier otra medida. Hay varias medidas de 70 pulgadas y solo una de 69.6850393700787, pero esto es solo porque la mayoría conoce su altura en pulgadas y redondea al valor entero más cercano (seguro que redondean hacia arriba, nadie quiere ser bajito). Pero este valor tan exacto es 177 cm convertido a pulgadas (177/2.54). De manera que, medidos con infinita precisión, todos y cada uno de los valores de la serie de datos serían únicos.

De lo anterior se deduce que lo que sí tiene sentido es preguntar, en cambio, cuál es la probabilidad de que la medida esté entre 69 y 70 pulgadas. Es decir, las probabilidades se definen para intervalos.

En casos como heights, donde los datos están redondeados, la aproximación normal es particularmente útil cuando el intervalo incluye uno de los valores después del redondeo, por ejemplo, el intervalo 69.5 a 70.5

# probabilidades en los datos reales con rangos de 1 pulgada que
# contienen un valor entero:
mean(x <= 68.5) - mean(x <= 67.5)
mean(x <= 69.5) - mean(x <= 68.5)
mean(x <= 70.5) - mean(x <= 69.5)

# probabilidades calculadas en la distribución normal concuerdan:
pnorm(68.5, mean(x), sd(x)) - pnorm(67.5, mean(x), sd(x))
pnorm(69.5, mean(x), sd(x)) - pnorm(68.5, mean(x), sd(x))
pnorm(70.5, mean(x), sd(x)) - pnorm(69.5, mean(x), sd(x))

Ejecutando el código anterior se ve que las probabilidades en el conjunto de datos reales no están muy alejadas del conjunto normal definido con las mismas media y desviación típica.

Sin embargo, el siguiente código hace lo mismo para un intervalo que no incluye un valor entero (70.1 y 70.9). La cosa cambia. El valor devuelto (lo probabilidad de encontrar una medida en el intervalo) es 0.0221 para el conjunto de datos, mientras que la distribución normal espera una probabilidad de 0.0836:

mean(x <= 70.9) - mean(x <= 70.1)
pnorm(70.9, mean(x), sd(x)) - pnorm(70.1, mean(x), sd(x))

Esto suele llamarse discretización. Aunque la distribución es continua, los valores tienden a agruparse en valores discretos debido al redondeo. Aún así, lo distribución normal como aproximación es muy útil, siempre y cuando se tenga esto en cuenta.

Ejemplos de cómo usar pnorm en un conjunto de datos, en est caso para estimar la proporción de datos entre 69 y 72 pulgadas en el conjunto heights:

library(dslabs)
data(heights)
x <- heights$height[heights$sex=="Male"]
# Calcular la media (avg) y la desviación típica (stdev) del conjunto
# de datos
avg <- mean(x)
stdev <- sd(x)
# Calcular la proporción sin el conjunto de datos, solo con los
# valores de la media y la desv.típ. que los definen:
pnorm(72, avg, stdev) - pnorm(69, avg, stdev)

Sin embargo, cuando nos acercamos a los bordes de la distribución, puede haber valores en el conjunto de datos donde la distribución normal no los espera, de manera que las diferencias empiezan a ser grandes: el siguiente código muestra que cuando se calcula la proporción de valores entre 79 y 81 pulgadas, el valor real es 1.6 veces el valor normal:

library(dslabs)
data(heights)
x <- heights$height[heights$sex == "Male"]
exact <- mean(x > 79 & x <= 81)
approx <- pnorm(81, mean(x), sd(x)) - pnorm(79, mean(x), sd(x))
exact / approx

Como se dijo antes, pnorm calcula la proporción de valores menores o iguales a un valor dado x. Para saber la proporción de valores mayores hay que restar de 1: 1 - pnorm(x, mu, sigma).

Cuando se usan proporciones para calcular poblaciones (como en ¿Cuántos hombres en el mundo son más altos que x si la proporción que muestra la función pnorm es 0.04?). En este ejemplo, hay que multiplicar la proporción por el número de hombres, pero no se pueden medir decimales, así que hay que redondear con round:

# Proporción *p* para el valor 84 en una distribución normal de media *mu*
# 69 y desviación típica *sigma* 3:
p <- 1 - pnorm(84, 69, 3)

# Redondeo para aplicar la proporción a una población de 10^9
# elementos (población de varones entre 18 y 40 años):
round(p * 10^9)

Eso son 287 hombres (entre 18 y 40 años) más altos de 84 pulgadas (7 pies o, lo que es lo mismo, 213 cm.). Curiosidad: Si hay 10 jugadores de la NBA más altos de 213 cm, ¿qué proporción de la población mundial tan alta está en la NBA? Pues 10 / round(p * 10^9), es decir, un 3.48%.

Aparte de esta curiosidad, ahora se repite el cálculo para la altura de Lebron James, 6 pies y 8 pulgadas o 203 cm, sabiendo que hay 150 jugadores en lugar de 10 que son tan altos como él. Hay un 0.12% de hombres de esa altura en el mundo jugando en la NBA. Conclusión: es más fácil entrar en la NBA si eres muy alto. FALSO. Como se vio antes, la distribución normal tiende a subestimar las medidas en los extremos, o sea que es muy probable que haya más hombres de 7 pies o más en el mundo de los que espera la distribución normal.

Cuantiles, percentiles y diagramas de cajas (y bigotes)

Los cuantiles son puntos de corte que dividen un conjunto de datos en intervalos de una probabilidad dada. El cuantil q-ésimo es un valor del que el q% de las observaciones es igual o menor. El cuantil q se puede calcular con la función quantile(data, q). El valor q debe proporcionarse en tanto por uno, es decir, el 78% de las observaciones es igual o menor a 71.11 pulgadas, de manera que quantile(heights$height, 0.78) devolverá 71.11 como resultado. Valores mayores que 1 darán error.

Por lo tanto, el cuantil 0 es el valor mínimo, el cuantil 0.5 es la mediana y el cuantil 1 es el valor máximo (el 100% de las observaciones son iguales o menores a ese valor).

Dentro de los cuantiles (que es la fórmula general) se pueden encontrar varias maneras de agrupar los datos. Las más usadas son:

Los percentiles son los cuantiles que dividen un conjunto de datos en 100 intervalos, cada uno con un 1% de probabilidad. Si se define una secuencia desde 0.01 (= 1%) hasta 0.99 en intervalos de 0.01 y se calculan los cuantiles correspondientes a cada elemento de esa serie se tendrá una lista completa de los percentiles de un conjunto de datos. Ejemplo:

p <- seq(0.01, 0.99, 0.01)
quantile(data, p)

Los cuartiles son lo mismo pero para cuantiles 0.25, 0.50 y 0.75. También conocidos como primer cuartil, mediana y tercer cuartil. Una visión general de los cuartiles se puede lograr con la función summary: summary(heights$height) devolverá el valor mínimo (el cuantil 0), el primer cuartil, la mediana, la media, el tercer cuartil y el valor máximo.

Encontrar cuantiles con qnorm

La función qnorm da el valor teórico del cuantil de probabilidad p de una distribución de media mu y desviación típica sigma. Por defecto, mu es 0 y sigma es 1, por lo que si se escribe sin argumentos qnorm() da el valor del cuantil de una distribución normal teórica estándar. En efecto, qnorm(0.5) devuelve 0, porque en una distribución normal estándar la media mu coincide con la mediana (cuantil 0.5). Es decir, hay tantos valores por encima de la media como por debajo. Para especificar la media y la desviación típica se hace por este orden: qnorm(p, mu, sigma).

Si pnorm ofrece la probabilidad de que un valor sea menor o igual a z, qnorm es la función inversa y ofrece el valor que tiene una probabilidad p.

Como qnorm devuelve los valores de los cuantiles teóricos, se puede usar para cerciorarse de si la distribución de valores del conjunto de datos es aproximadamente normal o no. Un ejemplo: conociendo mu y sigma para un conjunto de datos, como el de las alturas de alumnos, los cuantiles teóricos se pueden calcular de la siguiente manera:

#Elaborar una secuencia desde 1% a 99% en tanto por uno, de uno en uno
#y asignarla al vector *p*
p <- seq(0.01, 0.99, 0.01)

#Calcular los cuantiles almacenados en *p* de una distribución
#(teórica) con *mu* y *sigma* como valores definitorios.
cuantiles_teoricos <- qnorm(p, 63, 3)

Diagramas q-q o cuantil-cuantil y diagramas de cajas

Para saber si la distribución normal es una buena aproximación a la distribución de los datos en el conjunto estudiado se puede hacer una gráfica comparando los cuantiles teóricos con los observados. Si ambos coinciden (más o menos), se puede usar una distribución normal de media mu y desviación típica sigma como resumen del conjunto de datos completo.

Antes de crear un gráfico, se pueden comparar dos conjuntos de datos con una sencilla tabla en la que poner lado a lado varios percentiles de ambos conjuntos de datos. Por ejemplo, comparar los percentiles 10º, 30º, 50º, 70º y 90º de las alturas de alumnos varones y mujeres.

# Cargar los datos
library(dslabs)
data(heights)

# Asociar los datos filtrados a sendos vectores
male <- heights$height[heights$sex == "Male"]
female <- heights$height[heights$sex == "Female"]

# Definir un vector con los percentiles que se van a comparar
percntls <- seq(0.1, 0.9, 0.2)  # de 10% a 90% en saltos de 20

# Calcular los percentiles y asociarlos a vectores
female_percentiles <- quantile(female, percntls)
male_percentiles <- quantile(male, percntls)

# Crear un objeto que contenga un conjunto de datos (data frame)
df <- data.frame(female = female_percentiles, male = male_percentiles)

# Comprobar el resultado
df

Ahora sí, el código necesario para crear un gráfico que permita la comparación es el siguiente:

# Cargar los datos
library(dslabs)
data(heights)

# Asociar datos a un vector y filtrar
index <- heights$sex == "Male"
x <- heights$height[index]
z <- scale(x)

# calcular los cuantiles teóricos y reales
p <- seq(0.05, 0.95, 0.05)
observed_quantiles <- quantile(x, p)
theoretical_quantiles <- qnorm(p, mean = mean(x), sd = sd(x))

# Dibujar el gráfico
plot(theoretical_quantiles, observed_quantiles)
abline(0, 1)

# Gráfico con valores escalados (en desviaciones típicas desde la
# media)
observed_quantiles <- quantile(z, p)
theoretical_quantieles <- qnorm(p)
plot(theoretical_quantiles, observed_quantiles)
abline(0, 1)

Si el gráfico se dibuja en una consola de R (no en un IDE), se puede volver a la ventana de terminal e introducir la orden abline(0, 1) mientras el gráfico está en otra ventana. La línea será dibujada en el gráfico directamente.

Análisis exploratorio

Haciendo lo mismo que se hizo con las alturas de los estudiantes varones, pero ahora con las alturas de las mujeres, se ve que hay variaciones importantes respecto a la distribución normal: hay un aplanamiento de la curva en torno a un valor superior a la media y en un gráfico q-q se ve que hay más mujeres muy altas de lo esperado.

Esto nos puede ayudar a detectar problemas en el conjunto de datos. Por ejemplo, en el formulario on-line en el que los estudiantes introducían su altura, la opción por defecto era Sexo: mujer y hubo chicos que detallaron su altura pero no cambiaron la opción en cuanto al sexo.

Los cálculos básicos para saber algo de los datos disponibles son: 1. La media mean(x). 2. La mediana median(x). 3. La desviación típica sd(x). 4. La desviación absoluta respecto a la mediana9 mad(x).

Tanto la media como la desviación típica son muy sensibles a las variaciones de valores fuera del rango típico (outliers), por ejemplo errores de tecleo, como comerse puntos decimales. La mediana y la desviación absoluta respecto a la mediana son mucho menos sensibles y se denominan medidas robustas.

Estos valores que se salen del rango claramente son detectables con un diagrama de cajas, un histograma o un gráfico q-q.

Se puede escribir una función para comprobar cómo estos valores fuera de rango o outliers afectan a la media de una serie de datos. Se toma una serie de datos, se varía uno de ellos y luego se comprueba la media, por ejemplo con un valor verídico pero sin coma decimal (es decir, multiplicado por 10).

# Cargar los datos
library(dslabs)
data(heights)
index <- heights$sex == "Male"
x <- heights$height[index]

# Escribir la función
error_avg <- function(k){
  index <- heights$sex == "Male"
  x_err <- heights$height[index]
  x_err[1] <- k
  mean(x_err)

# Comprobar el resultado (69.5 * 10)
error_avg(695) - mean(x)

En este caso, la media sube más de una pulgada y media con solo un valor erróneo en todo el conjunto.

Introducción a ggplot2

ggplot2 es relativamente sencillo de usar para principiantes, comparado con otras herramientas para crear gráficos. Se instala con el conjunto de paquetes tidyverse: library(tidyverse) o puede cargarse ggplot2 en exclusiva con library(ggplot2). Antes de cargar las bibliotecas puede ser necesario instalarlas: install.packages("tidyverse"). Al hacerlo, atención a los mensajes de R: en mi caso he necesitado instalar primero tidyr y broom.

ggplot está diseñado para trabajar exclusivamente con conjuntos de datos ordenados en tablas, donde las filas son observaciones y las columnas son las distintas variables.

Componentes del gráfico

Hay varias partes en las que se puede dividir un gráfico para ver más fácilmente cómo se construye y qué órdenes se incluyen en la función ggplot2 para lograr un resultado conveniente.

  1. Datos: el conjunto de datos incluido en el gráfico.
  2. Geometría: diagrama de dispersión, diagrama de barras, histograma, etc.).
  3. Aspecto: valores de los ejes, colores de puntos, barras o líneas, etc.
  4. Otros componentes como escala de los ejes, estilo, leyenda, títulos, etc.

Crear un nuevo gráfico

Lo primero que hay que hacer es definir un «objeto ggplot» con la función ggplot. Esta función asocia un conjunto de datos con el gráfico que se va a crear (el componente número 1, datos, visto en el apartado anterior). Pero aún no hay nada dibujado, tan solo un objeto definido y los datos correspondientes asociados a él. En realidad, el código sí dibuja un gráfico, pero lo único que se ve es un fondo gris, falta la geometría, el aspecto y otros componentes necesarios.

Para definir el objeto, las líneas de código siguientes son equivalentes:

ggplot(data = murders)
murders %>% ggplot()

p <- ggplot(data = murders)
p

En las dos primeras se llama directamente a ggplot, bien definiendo los datos como argumento de la función, bien usando una tubería que «canalice» los datos desde el objeto murders hasta la función.

Las dos últimas líneas asocian el resultado de la función ggplot a una variable p. Para ver el gráfico (repito: aún no se ve nada) se muestra el valor de p10. Si no se asocia el gráfico a una variable, la función muestra el gráfico directamente, que es lo que sucede con cualquiera de las dos primeras líneas del ejemplo.

Resumir con dplyr

Gapminder

Principios de visualización de datos

Functions

En general, las funciones se evalúan con el uso de los paréntesis.

Las funciones no son el único objeto predefinido en R, también hay conjuntos de datos con los que practicar y evaluar el uso de las funciones. Se pueden conocer los conjuntos de datos disponibles con data().

Variables

Cuando se definen variables en R, se deben seguir algunas pautas: - Deben comenzar por una letra - No pueden contener espacios - Deben ser nombres característicos que hagan el código legible más adelante. - No pueden (y no deben) ser iguales que nombres de funciones definidas o predefinidas en R.

Es habitual y una buena prácitca escribir los nombres de variables en minúsculas y con guiones bajos en lugar de espacios.

Comentarios

Cualquier línea que comienze con # no será evaluada y servirá para escribir recordatorios que ayuden a entender el código escrito.


  1. El orden en el que aparecen los factores (las cuatro categorías o, en este caso, regiones) no respeta el orden de aparición dentro del vector, sino que aparecen en orden alfabético. Se pueden reordenar con la función reorder, pero se verá en la segunda parte: visualización de datos.↩︎

  2. Porque el vector tiene asociados nombres a sus elementos (con la función names).↩︎

  3. Creo que coerción está bien, pero cuando se trata de un adjetivo (como en El vector coercionado es el último de la lista) se entiende mejor forzado.↩︎

  4. Puede que haya que instalarlo: install.packages("dplyr") y cargarlo luego con library(dplyr).↩︎

  5. El recíproco de x es 1/x o x^-1.↩︎

  6. Por supuesto, i es una convención, se puede usar cualquier variable.↩︎

  7. La funcíon table muestra la frecuencia de aparición de cada valor individual de un conjunto de datos.↩︎

  8. ¿Por qué? Buena pregunta.↩︎

  9. Hay que estudiar esto más a fondo. No sé lo que es.↩︎

  10. También valdría print(p).↩︎