Apuntes de R

Parte 2: Estadística descriptiva

Author

David Torres Irribarra e Isidora Naranjo López

Published

March 21, 2024

Introducción a gráficos en R

Ahora que conocemos los elementos básicos para operar en R vamos a empezar a aplicar lo aprendido a un ejemplo real. Para este ejemplo vamos a utilizar una base de datos de respuestas al cuestionario de los “Big 5”, basado en una influyente teoría de personalidad desarrollada originalmente por McCrae y Costa (1987).

El comando read.csv requiere un argumento principal, la dirección donde se encuentra el archivo. Esta dirección puede ser un lugar en el disco duro de la computadora local o una dirección web, como en el ejemplo a continuación:

# Leemos y guardamos la base de datos en el objeto big5
big5 <- read.csv("https://david-ti.github.io/introstats/data/big_five.csv")

Histogramas

Partamos por ver la distribución de los datos de la variable age (edad) en la base de datos big5. Un gráfico óptimo para examinar la distribución de una variable es un histograma, que podemos producir con la función hist. Es importante recordar que un histograma nos permite visualizar como están distribuidos los datos en un variable cuantitativa continua. De este modo, el grafico se construye a partir de la agrupación de datos, donde el eje horizontal del gráfico va a estar dividio en grupos y el eje vertical nos va a indicar la frecuencia de datos concentrados en cada grupo.

# Usa por defecto el método de Sturges
hist(big5$age)
Figure 1: Histograma de edad en la base big5.

Podemos también crear una versión con un número distinto de grupos usando el argumento breaks que controla este aspecto del histograma:

# Acá estamos pidiendo que R haga siete grupos
# Cuando se usa esta opción R lo considera como una "sugerencia"
hist(big5$age, breaks = 7)
Figure 2: Histograma de edad en la base big5.
# Acá estamos pidiendo usar el método de Freedman y Diaconis
hist(big5$age, breaks = "FD")
Figure 3: Histograma de edad en la base big5.

O podemos elaborar más nuestro gráfico usando argumentos adicionales:

hist(big5$age
    , breaks = "FD"           # Como decidir cuantos grupos usar
    , main = "Mi histograma"  # Título
    , axes = FALSE            # No dibujar los ejes 
    , xlab = "Edad"           # Etiqueta eje x
    , ylab = "Frequencia"     # Etiqueta eje y
    , col  = "dodgerblue4")   # Color a usar en el gráfico

axis(1)          # Dibujar el eje x
axis(2, las = 1) # Dibujar el eje y con etiquetas horizontales
Figure 4: Histograma de edad en la base big5.

Podemos también hacer un histograma de los promedios de cada persona en las preguntas de extroversión.

promedios_extro <- rowMeans(big5[,c("e1","e2","e3","e4","e5","e6","e7","e8","e9","e10")])

hist(promedios_extro
    , breaks = "FD"           # Como decidir cuantos grupos usar
    , main = "Promedios de extroversión"  # Título
    , axes = FALSE            # No dibujar los ejes 
    , xlab = "Promedios extroversión"   # Etiqueta eje x
    , ylab = "Frequencia"     # Etiqueta eje y
    , col  = "#4393C3")   # Color a usar en el gráfico

axis(1)          # Dibujar el eje x
axis(2, las = 1) # Dibujar el eje y con etiquetas horizontales
Figure 5: Histograma de promedios de extroversión.

Diagramas de dispersión

Ahora que hemos visto las distribuciones de dos variables, age y promedios_extro podemos usar un diagrama de dispersión para examinarlas en conjunto usando el comando plot que recibe como primer argumento los datos que irán en el eje x, y como segundo argumento los datos que se representarán en el eje y. Recordar que un diagrama de dispersión nos permite visualizar como se relacionan los datos de dos variables cuantitativas, permitiendo ver que tan relacionadas se encuentran.

plot(big5$age, promedios_extro)
Figure 6: Diagrama de dispersión de edad y extroversión.

Y podemos personalizar también este gráfico:

plot(big5$age, promedios_extro
    , main = "Diagrama de edad y extroversión"  # Título
    , axes = FALSE                      # No dibujar los ejes 
    , xlab = "Edad"                     # Etiqueta eje x
    , ylab = "Promedio de extroversión" # Etiqueta eje y
    , xlim = c(0,80)                    # Rango del eje x
    , ylim = c(0,5)                     # Rango del eje y
    , pch  = 18                         # Elegir símbolo
    , col  = "#4393C3")                 # Color 

axis(1)          # Dibujar el eje x
axis(2, las = 1) # Dibujar el eje y con etiquetas horizontales
Figure 7: Diagrama de dispersión de edad y extroversión.

En este último ejemplo cambiamos el tipo de símbolos con el argumento pch. Las opciones que pueden ser usadas en R son las siguientes:

Figure 8: Simbolos disponibles en R.

Gráficos de barra

Para ver ejemplos de gráficos de barra vamos a trabajar con una nueva base de datos que pueden descargar desde aquí. Esta base de datos tiene datos sobre los pasajeros del Titanic, y vamos a explorar los datos disponibles.

# Leemos y guardamos la base de datos en el objeto titanic
titanic <- read.csv("https://david-ti.github.io/introstats/data/titanic.csv")

Examinemos los datos en esta base de datos:

str(titanic)
'data.frame':   891 obs. of  12 variables:
 $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
 $ Survived   : int  0 1 1 1 0 0 0 0 1 1 ...
 $ Pclass     : int  3 1 3 1 3 3 1 3 3 2 ...
 $ Name       : chr  "Braund, Mr. Owen Harris" "Cumings, Mrs. John Bradley (Florence Briggs Thayer)" "Heikkinen, Miss. Laina" "Futrelle, Mrs. Jacques Heath (Lily May Peel)" ...
 $ Sex        : chr  "male" "female" "female" "female" ...
 $ Age        : num  22 38 26 35 35 NA 54 2 27 14 ...
 $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
 $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
 $ Ticket     : chr  "A/5 21171" "PC 17599" "STON/O2. 3101282" "113803" ...
 $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
 $ Cabin      : chr  "" "C85" "" "C123" ...
 $ Embarked   : chr  "S" "C" "S" "S" ...
head(titanic)
  PassengerId Survived Pclass
1           1        0      3
2           2        1      1
3           3        1      3
4           4        1      1
5           5        0      3
6           6        0      3
                                                 Name    Sex Age SibSp Parch
1                             Braund, Mr. Owen Harris   male  22     1     0
2 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female  38     1     0
3                              Heikkinen, Miss. Laina female  26     0     0
4        Futrelle, Mrs. Jacques Heath (Lily May Peel) female  35     1     0
5                            Allen, Mr. William Henry   male  35     0     0
6                                    Moran, Mr. James   male  NA     0     0
            Ticket    Fare Cabin Embarked
1        A/5 21171  7.2500              S
2         PC 17599 71.2833   C85        C
3 STON/O2. 3101282  7.9250              S
4           113803 53.1000  C123        S
5           373450  8.0500              S
6           330877  8.4583              Q

Esta base de datos tiene 891 filas y 12 variables. Entre las variables que incluye están:

  • Survived : indica con un 1 si la persona sobrevivió o 0 si murió
  • Pclass : indica la clase en la que el pasajero viaja
  • Sex : indica el sexo registrado de cada pasajera y pasajero
  • Age : edad

Partamos por ver cuántas personas sobrevivieron:

table(titanic$Survived)

  0   1 
549 342 
barplot(table(titanic$Survived)
      , names.arg = c("Murieron","Sobrevivieron")
      )
Figure 9: Gráfico de barras variable Survived.

Recordar que a diferencia de un histograma, un gráfico de barras permite visualizar la distribución de la variable categórica. Esto implica que el eje horizontal del gráfico de barras representa categorías y no rangos de una cantidad.

Podemos ver la proporción de cuantas personas sobrevivieron:

prop.table(table(titanic$Survived))

        0         1 
0.6161616 0.3838384 
barplot(prop.table(table(titanic$Survived))
      , names.arg = c("Murieron","Sobrevivieron")
      )
Figure 10: Gráfico de barras variable Survived.

Podemos ver que la proporción de sobrevivientes es aproximadamente 0.38, lo que quiere decir que aproximadamente 38% de los pasajeros del Titanic sobrevivió.

Una pregunta interesante de hacerse, es si esta proporción es igual para todas las clases de pasajeros; es decir, si se mantiene esta proporción de sobrevivientes en las clases 1, 2 y 3. ¿Por qué es interesante esta pregunta? Porque si la proporción de sobrevivientes es muy diferente en las distintas clases (posteriormente debemos generar análisis para ver si estas diferencias son efectivamente estadísticamente significativas) se podría hipotetizar porqué esto ocurrió.

Para calcular la proporción de sobrevientes según clase, primero haremos una tabla de frecuencia de supervivencia según clase, y luego una tabla de proporción. En esta tabla se podrá ver la proporción de sobrevientes del total según cada clase, es decir, la proporción de sobrevivientes para la clase 1, la clase 2 y la clase 3. (Como complemento, podemos observar la proporción de personas que murieron del total según cada clase).

# Tabla de frecuencia
table(titanic$Pclass,titanic$Survived)
   
      0   1
  1  80 136
  2  97  87
  3 372 119
# Tabla de proporciones dentro de cada clase
prop.table(table(titanic$Pclass,titanic$Survived)
      , margin = 1)
   
            0         1
  1 0.3703704 0.6296296
  2 0.5271739 0.4728261
  3 0.7576375 0.2423625
barplot(prop.table(table(titanic$Pclass,titanic$Survived), margin = 1)
      , beside = TRUE
      , legend = TRUE
      , names.arg = c("Murieron","Sobrevivieron")
      )
Figure 11: Gráfico de barras variable Survived y Pclass.

Para crear la tabla de proporciones y el gráfico de barras de esta, agregamos el argumento margin = 1, que quiere decir que el análisis de proporción se hará respecto al total de la fila. En este caso, las filas representan cada clase, por lo tanto la tabla mostrará la proporción de personas que sobrevivieron (y que murieron) en cada clase sobre el total de esa misma clase.

Podemos ver que si sumamos los valores por fila (hacia el lado) el resultado de esta sumatoria es 1. Y en el gráfico de barras, la suma de las barras de cada clase (representadas por los diferentes colores) es 1.

Este análisis de proporción también lo podemos hacer considerando como total el grupo de sobrevivientes y el grupo de personas que murieron. Así, nos podemos preguntar ¿De las personas que sobrevivieron, qué proporción era de primera clase? Para generar esta tabla usaremos el argumento margin = 2, que indica que queremos que considere como total la suma de los valores de cada columna. En este caso, columna 0 representa personas que murieron y columna 1 personas que sobrevivieron.

# Tabla de frecuencia
table(titanic$Pclass,titanic$Survived)
   
      0   1
  1  80 136
  2  97  87
  3 372 119
# Tabla de proporciones dentro de cada clase
prop.table(table(titanic$Pclass,titanic$Survived)
      , margin = 2)
   
            0         1
  1 0.1457195 0.3976608
  2 0.1766849 0.2543860
  3 0.6775956 0.3479532
barplot(prop.table(table(titanic$Pclass,titanic$Survived), margin = 2)
      , beside = TRUE
      , legend = TRUE
      , names.arg = c("Murieron","Sobrevivieron")
      )
Figure 12: Gráfico de barras variable Survived y Pclass.

Podemos ver que si sumamos los valores por columna (hacia abajo) el resultado de esta sumatoria es 1. Y en el gráfico de barras, la suma de todas las barras por grupo (murieron, sobrevivieron) es 1.

Tablas

Ya hemos visto como el comando table puede ser utilizado para generar tablas de frequencia o de proporciones de una o dos variables. La función addmargins agrega la suma de los datos correspondientes a las distintas categorías de la tabla construida.

# Tabla de frecuencias
addmargins(table(titanic$Pclass,titanic$Survived))
     
        0   1 Sum
  1    80 136 216
  2    97  87 184
  3   372 119 491
  Sum 549 342 891
# Tabla de proporciones dentro de cada clase
addmargins(prop.table(table(titanic$Pclass,titanic$Survived)
      , margin = 1))
     
              0         1       Sum
  1   0.3703704 0.6296296 1.0000000
  2   0.5271739 0.4728261 1.0000000
  3   0.7576375 0.2423625 1.0000000
  Sum 1.6551818 1.3448182 3.0000000
# Tabla de proporciones entre sobrevivientes o fallecidos (columnas)
addmargins(prop.table(table(titanic$Pclass,titanic$Survived)
      , margin = 2))
     
              0         1       Sum
  1   0.1457195 0.3976608 0.5433803
  2   0.1766849 0.2543860 0.4310708
  3   0.6775956 0.3479532 1.0255488
  Sum 1.0000000 1.0000000 2.0000000

Generando tablas de resumen con aggregate

Pero también podemos generar tablas de resumen que nos muestren resultados descriptivos para subgrupos. Una función que nos permite generar este tipo de tablas es la función agregate.

Esta función requiere de al menos tres argumentos:

  • Los datos que queremos resumir
  • Las categorías en las que queremos dividir los datos
  • La función que queremos aplicar a cada subgrupo

Generemos por ejemplo una tabla que nos muestre cuál es el promedio de edad de las personas que sobrevivieron y de las que fallecieron.

# titanic$Age es el dato que queremos resumir
# titanic$Survived es el dato en que queremos agrupar
# FUN es una abreviación de función, indicando lo que queremos hacer 
aggregate( titanic$Age
          , by = list(titanic$Survived)
          , FUN = mean)
  Group.1  x
1       0 NA
2       1 NA

Pero algo salió mal… podemos ver que en vez de mostrar resultados, R nos muestra solo NA que en R indica que el valor no está disponible. Esto suele ocurrir cuando entre nuestros datos hay valores perdidos. Una manera de ver si este es el caso es usar la función is.na (que nos permite evaluar si un dato es NA) en combinación con la función table.

# Evalúa cada observación en Age para ver si es NA
# Es lento revisar cada observación
is.na(titanic$Age)
  [1] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
 [13] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
 [25] FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE FALSE FALSE FALSE
 [37]  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE
 [49]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
 [61] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
 [73] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
 [85] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
 [97] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE
[109] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[121] FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE
[133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[145] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[157] FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[169]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[181]  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
[193] FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE
[205] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[217] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[229] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[241]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[253] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[265]  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE
[277] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[289] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
[301]  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
[313] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[325]  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE  TRUE
[337] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
[349] FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE  TRUE
[361] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE
[373] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[385]  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[397] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[409] FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
[421]  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE
[433] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[445]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
[457] FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE
[469]  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[481] FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
[493] FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
[505] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[517] FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE
[529] FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
[541] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[553]  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE
[565]  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE
[577] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[589] FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE
[601] FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
[613]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[625] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE
[637] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[649]  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE
[661] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE
[673] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[685] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
[697] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[709] FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[721] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
[733]  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE
[745] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[757] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[769]  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE
[781] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[793]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[805] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
[817] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE
[829]  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
[841] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE
[853] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE
[865] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[877] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[889]  TRUE FALSE FALSE
# Esto resume los resultados diciendo cuántos datos NA hay
table(is.na(titanic$Age))

FALSE  TRUE 
  714   177 

Dado que tenemos datos perdidos (177 en este caso), tenemos que agregar un argumento adicional a nuestra función llamado na.rm, que corresponde a una abreviación de “remover NAs”, la cual le indicará a la función que debe ignorar estos datos.

aggregate( titanic$Age
          , by = list(titanic$Survived)
          , FUN = mean
          , na.rm = TRUE)
  Group.1        x
1       0 30.62618
2       1 28.34369

Al agregar esta instrucción adicional podemos ver ahora los resultados. Una de las ventajas de usar aggregate es que podemos usarla para generar cualquier tipo de resultados cambiando la función indicada en el argumento FUN:

# Veamos la edad de la persona menor en cada categoría
aggregate( titanic$Age
          , by = list(titanic$Survived)
          , FUN = min
          , na.rm = TRUE)
  Group.1    x
1       0 1.00
2       1 0.42
# Veamos la edad de la persona mayor en cada categoría
aggregate( titanic$Age
          , by = list(titanic$Survived)
          , FUN = max
          , na.rm = TRUE)
  Group.1  x
1       0 74
2       1 80

Describiendo la distribución de variables

Existen indicadores que nos permiten caracterizar propiedades de las distribuciones de datos, siendo los más importantes la tendencia central y la dispersión.

Medidas de tendencia central

Media o promedio (y datos perdidos)

En clase discutimos tres medidas de tendencia central. La primera, el promedio, la hemos usado ya en múltiples oportunidades.

Si quiero promediar notas de 10 controles, puedo usar el comando mean entregando como argumentos el vector de notas:

notas_de_controles <- c(4.8,5.3,6.2,4.6,5.9,6.0,5.3,5.1,6.5,4.2)

mean(notas_de_controles)
[1] 5.39

El cálculo de la media es sencillo, pero en casos donde nuestros datos tienen datos perdidos, codificados con NA por R, el comando no funcionará directamente:

# Incluyamos un dato perdido
notas_de_controles_con_NA <- c(4.8,5.3,6.2,NA,5.9,6.0,5.3,5.1,6.5,4.2)

mean(notas_de_controles_con_NA)
[1] NA

Podemos ver que R nos arroja ahora un resultado NA. En general, cada vez que R encuentra un NA en los datos el programa considerará que no puede ejecutar el cálculo que se requiere (debido a que no tiene información respecto a ese dato).

Sin embargo, podemos indicar con un argumento adicional que R debe ignorar datos perdidos, realizando el cálculo solo considerando los datos disponibles. El argumento a utilizar en este caso es na.rm (NA remove o remover NAs), el cual debemos activar especificando el valor TRUE.

# Incluyamos un dato perdido
notas_de_controles_con_NA <- c(4.8,5.3,6.2,NA,5.9,6.0,5.3,5.1,6.5,4.2)

mean(notas_de_controles_con_NA, na.rm = TRUE)
[1] 5.477778

Podemos ver que ahora R ha calculado el promedio de las notas ignorando el dato perdido.

Finalmente, podemos gráficar la media en un histograma usando el comando abline (línea A B… como en las funciones lineales), especificando que queremos una línea vertical (argumento v) ubicada en la media, de color rojo (col), y vamos a querer una línea más gruesa (lwd: de line width, literalmente grosor de la línea).

# Dibujar el histograma
hist(notas_de_controles)

# Agregar una linea roja en el promedio
abline(v = mean(notas_de_controles)
  , col = 'red'
  , lwd = '3')

Mediana

La mediana puede ser calculada con el comando median de forma similar a como obtuvimos la media.

median(notas_de_controles)
[1] 5.3
median(notas_de_controles_con_NA, na.rm = TRUE)
[1] 5.3
# Se calcula promediando observaciones intermedias
numero_par_de_datos <- c(1,2,3,4,5,6)
median(numero_par_de_datos)
[1] 3.5
# Se calcula identificando el valor medio
numero_impar_de_datos <- c(1,2,3,4,5)
median(numero_impar_de_datos)
[1] 3

Al igual que con la media, podemos también agregar una línea vertical para la mediana en el mismo histograma que creamos al trabajar con la media.

# Dibujar el histograma
hist(notas_de_controles)

# Agregar una linea roja en el promedio
abline(v = mean(notas_de_controles)
  , col = 'red'
  , lwd = '3')

# Agregar una linea verde en la mediana
abline(v = median(notas_de_controles)
  , col = 'green'
  , lwd = '3')

Moda

Para ver un ejemplo de cálculo de la moda vamos a utilizar la base de datos sobre el Titanic. Examinemos la variable Sex de los pasajeros para determinar la moda entre las edades. Para esto podemos hacer una tabla:

table(titanic$Sex)

female   male 
   314    577 

Podemos ver que en el caso de esta variable, la moda es “male”.

Medidas de dispersión

Revisemos las medidas de dispersión que discutimos en clases.

Rango

El rango puede ser calculado fácilmente con las funciones max (dato máximo) y min (dato mínimo). Podemos también pedir ambos datos directamente usando el comando range.

max(notas_de_controles) 
[1] 6.5
min(notas_de_controles)
[1] 4.2
# recordemos que el rango es dato máximo menos mínimo
max(notas_de_controles) - min(notas_de_controles)
[1] 2.3
range(notas_de_controles)
[1] 4.2 6.5
range(notas_de_controles_con_NA, na.rm = TRUE)
[1] 4.2 6.5

Rango interquartil

El rango interquartil puede ser calculado directamente con la función IQR (Inter Quartile Range).

IQR(notas_de_controles) 
[1] 1.1

Desviación estándar y varianza

La desviación estándar y la varianza pueden ser calculadas respectivamente con los comandos sd (del inglés standard deviation) y var (varianza).

sd(notas_de_controles) 
[1] 0.7460265
var(notas_de_controles)
[1] 0.5565556
sd(notas_de_controles_con_NA, na.rm = TRUE)
[1] 0.7344688
var(notas_de_controles_con_NA, na.rm = TRUE)
[1] 0.5394444

Cuantiles y boxplots

R nos permite calcular cualquier medida de posición usando el comando quantile. Por ejemplo, si queremos ver los valores de los deciles podemos indicarlos en el argumento probs. Veamos los deciles (dividimos la distribución en 10 partes iguales) de la variable Age en la base sobre el Titanic.

Cuantiles

quantile(titanic$Age
  , probs = c(.1,.2,.3,.4,.5,.6,.7,.8,.9), na.rm = TRUE)
 10%  20%  30%  40%  50%  60%  70%  80%  90% 
14.0 19.0 22.0 25.0 28.0 31.8 36.0 41.0 50.0 

Si queremos ver los quintiles (dividimos la distribución en 5 partes iguales):

quantile(titanic$Age
  , probs = c(.2,.4,.6,.8), na.rm = TRUE)
 20%  40%  60%  80% 
19.0 25.0 31.8 41.0 

Si queremos ver los cuartiles (dividimos la distribución en 4 partes iguales):

quantile(titanic$Age
  , probs = c(.25,.5,.75), na.rm = TRUE)
   25%    50%    75% 
20.125 28.000 38.000 

Podemos comparar los resultados que obtenemos usando el comando IQR con el comando quantile:

IQR(titanic$Age, na.rm = TRUE)
[1] 17.875
tercer_cuartil <- quantile(titanic$Age
  , probs = c(.75), na.rm = TRUE)

primer_cuartil <- quantile(titanic$Age
  , probs = c(.25), na.rm = TRUE)

tercer_cuartil - primer_cuartil
   75% 
17.875 

Boxplots

Una representación gráfica muy útil para visualizar una distribución es conocida como diagrama de caja (y bigotes) o boxplot.

Este diagrama se basa en las medidas de posición, dibujando una caja en base al primer y tercer cuartil, indicando la mediana o segundo cuartil dentro de la caja. Usamos el argumento horizontal para mostrar el boxplot “acostado”.

boxplot(titanic$Age
  , horizontal = TRUE)
Figure 13: Boxplot Edad.

Si quiero comparar distribuciones de distintos, el comando boxplot ofrece la posibilidad de separar datos de acuerdo a una variable que indica los grupos. De esta manera, si quiero comparar las distribuciones de edad de los pasajeros en cada una de las tres clases del Titanic, puedo usar la virgulilla ~ (también conocida a veces como “cola de chancho”) para indicar de acuerdo a que grupo quiero separa la edad:

# "Genera un boxplot de la distribución de edad de acuerdo a clase"
boxplot(titanic$Age ~ titanic$Pclass)
Figure 14: Boxplot Edad y Clase.

Generando tablas de resumen con describe

Un comando alternativo para ver resúmenes de datos es el comando describe disponible en la librería psych. Instalemos y carguemos esta librería.

#install.packages("psych")
library(psych)

Podemos usar el comando describe ahora para ver detalles sobre una variable.

describe(titanic$Age)
   vars   n mean    sd median trimmed   mad  min max range skew kurtosis   se
X1    1 714 29.7 14.53     28   29.27 13.34 0.42  80 79.58 0.39     0.16 0.54

Y podemos usar el comando describeBy (describir por) para mostrar este mismo resumen por grupos con el argumento group.

describeBy(titanic$Age, group = titanic$Pclass)

 Descriptive statistics by group 
group: 1
   vars   n  mean   sd median trimmed   mad  min max range skew kurtosis   se
X1    1 186 38.23 14.8     37   37.98 16.31 0.92  80 79.08 0.12    -0.41 1.09
------------------------------------------------------------ 
group: 2
   vars   n  mean sd median trimmed   mad  min max range skew kurtosis   se
X1    1 173 29.88 14     29   29.87 10.38 0.67  70 69.33 0.13     0.17 1.06
------------------------------------------------------------ 
group: 3
   vars   n  mean   sd median trimmed   mad  min max range skew kurtosis   se
X1    1 355 25.14 12.5     24   24.84 10.38 0.42  74 73.58 0.48     0.94 0.66