Este post acompaña al ejercicio 5.2. La resolución de una de las consignas requería un tipo de transformación de datos que sirve para ese caso y para muchos otros.
Formato ancho y formato largo
En la clase hemos hablado de los datos tidy o prolijos. Se trata de datos que tienen dos propiedades:
Son rectangulares. Es decir, todas la columnas tienen el mismo largo y todas las filas tienen el mismo largo1. Si hay valores faltantes se rellenan, generalmente con
NA
.Cada columna es una variable y cada fila una observación.
A manera de ejemplo, los datos de la base de la Encuesta Nacional de Migración tienen este formato.
library(foreign)
library(tidyverse)
library(knitr)
# R permite camino hacia los datos como URL, es decir, podemos leer directamente un archivo desde una dirección a través de http.
# Sin embargo es recomendable crear una copia local de los datos 1) por velocidad y 2) para no abusar del servidor que nos ofrece los datos, 3) por si los datos desaparecen o cambian de ubicación.
migracion <- read.spss("http://www.losmexicanos.unam.mx/migracion/encuesta_nacional/base_datos/Encuesta_Nacional_de_Migracion.sav", to.data.frame = TRUE)
diccionario <- tibble(nombre = names(migracion),
etiquetas = str_trim(attr(migracion, "variable.labels")))
migracion <- as.tibble(migracion)
migracion %>%
sample_n(3) %>%
select(edo, starts_with("p7_"), Pondi2) %>%
kable(caption = "Cada columna es una variable, cada fila una observación.")
edo | p7_1 | p7_2 | p7_3 | p7_4 | p7_5 | p7_6 | p7_7 | p7_8 | p7_9 | p7_10 | Pondi2 |
---|---|---|---|---|---|---|---|---|---|---|---|
15 | No | No | No | No | No | No | No | No | No | No | 38032 |
15 | Sí | Sí | Sí | Sí | No | Sí | Sí | Sí | Sí | Sí | 65612 |
26 | Sí | Sí | Sí, en parte | Sí | Sí | Sí | Sí | Sí | Sí | Sí | 27811 |
Esta formato de datos es el más usado y tener una convención para su creación y uso podemos tener expectativas razonables sobre como se van a comportar los inputs y outputs. Sin embargo para algunas operaciones es más práctico dar a los datos un tipo de diferente de formato: pares2 de claves y valores. El formato básico de claves y valores tiene dos columnas –y sólo dos columnas, independientemente de la cantidad de variables con la que estemos tratando. Una columna registra las claves, en este caso los nombres de cada variable. La otra registra el valor para esa variable. En este contexto usamos de manera intercambiable “pares de clave-valor” y “formato largo”.
migracion %>%
sample_n(3) %>%
select(edo, starts_with("p7_"), Pondi2) %>%
gather() %>%
kable(caption = "Los nombre de columna ahora están en filas.")
key | value |
---|---|
edo | 28 |
edo | 16 |
edo | 15 |
p7_1 | Sí |
p7_1 | Sí |
p7_1 | Sí |
p7_2 | Sí |
p7_2 | Sí |
p7_2 | Sí |
p7_3 | Sí |
p7_3 | Sí |
p7_3 | Sí |
p7_4 | Sí |
p7_4 | Sí |
p7_4 | No |
p7_5 | Sí |
p7_5 | Sí |
p7_5 | Sí |
p7_6 | No |
p7_6 | Sí |
p7_6 | No |
p7_7 | Sí |
p7_7 | Sí |
p7_7 | Sí |
p7_8 | Sí |
p7_8 | Sí |
p7_8 | Sí |
p7_9 | Sí |
p7_9 | Sí |
p7_9 | Sí |
p7_10 | Sí |
p7_10 | Sí |
p7_10 | No |
Pondi2 | 190556 |
Pondi2 | 37800 |
Pondi2 | 31209 |
Los datos son exactamente los mismos y podemos revertir el proceso, regresando los datos al formato original. Lo que hicimos fue pasar de unos datos en formato ancho –muchas columnas, pocas filas- a uno largo –pocas columnas, muchas filas, la información de los nombres de columa ahora pasó a una fila. Como se ve en el código lo hicimos con la función gather()
, del paquete tidyr
que se encuentra a su vez en el metapaquete tidyverse
.
¿Para qué usar formato largo?
El formato largo es muy útil en varios escenarios en los que necesitamos hacer explícita la información sobre los niveles de agrupamiento de nuestros datos. En la práctica transformamos nuestros datos a formato largo antes de aplicar operaciones agrupadas con group_by
a través de mutate()
o summarise()
o para gráficos de ggplot
en los que vamos a usar las propiedades de agrupación de los datos con facet_wrap()
, fill()
o color
. Las funciones que mencionamos aprovechan muy bien a los datos largos, ya que podemos utilizar a la/s columna/s de clave/s para crear grupos y aplicar funciones sobre esos grupos.
Aplicación al ejercicio 5.2.
En el caso de los gráficos es muy útil cuando nos interesa graficar más de una variable y esas variables se encuentran originalmente es más de una columna. En lugar de graficar cada columna por separado las unimos alargándolas y las graficamos como una sólo columna (los valores) separada en grupos por las claves. En este caso en lugar de generar una par de claves y valores vamos a necesitar un trío de claves y valores: como estamos trabajando con datos ponderados es necesario convervar los ponderadores cuando hacemos la transformación a formato largo.
Formato original de los datos del ejercicio 5.2.
En el formato original los datos son prolijos: cada fila una observación, cada columna una variable. En este caso fila es una persona encuestada, cada columna una pregunta y cada intersección de fila/columna la respuesta de una persona encuestada a una pregunta.
migracion %>%
head() %>%
select(starts_with("p7_"), Pondi2) %>%
kable()
p7_1 | p7_2 | p7_3 | p7_4 | p7_5 | p7_6 | p7_7 | p7_8 | p7_9 | p7_10 | Pondi2 |
---|---|---|---|---|---|---|---|---|---|---|
Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | 58676 |
NS | NS | NS | NS | NS | NS | NS | NS | NS | NS | 11725 |
Sí | Sí | Sí | NS | No | No | Sí | No | No | No | 34727 |
Sí, en parte | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | 56490 |
Sí | Sí | Sí | Sí | Sí, en parte | Sí, en parte | Sí | Sí | Sí | Sí | 113882 |
Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | 58275 |
Sin embargo para hacer el gráfico aprovechando los agrupamientos necesitamos que los datos tengan un formato con tres columnas: una para las preguntas, otra para las respuestas y una tercera para el ponderador. De este modo podemos posteriormente agrupar por preguntas y respuestas y generar todos los conteos de manera prolija, obteniendo el resultado en un data.frame listo para pasar a ggplot
.
Formato largo: toma 1.
La primera aproximación es pasar gather
con los argumentos por defecto3 y ver que pasa. Reduciré el número de preguntas para que el resultado sea más facil de visualizar y comprender.
migracion %>%
head %>% #La cabeza, sólo las primeras filas
select(p7_1, p7_2, Pondi2) %>%
gather() %>%
kable(caption = "El ponderador no va ahí")
key | value |
---|---|
p7_1 | Sí |
p7_1 | NS |
p7_1 | Sí |
p7_1 | Sí, en parte |
p7_1 | Sí |
p7_1 | Sí |
p7_2 | Sí |
p7_2 | NS |
p7_2 | Sí |
p7_2 | Sí |
p7_2 | Sí |
p7_2 | Sí |
Pondi2 | 58676 |
Pondi2 | 11725 |
Pondi2 | 34727 |
Pondi2 | 56490 |
Pondi2 | 113882 |
Pondi2 | 58275 |
¿Lo logramos? Sí, en parte
. Las preguntas ahora están en la columna key
^[clave, es el nombre en inglés que por defecto utiliza gather
) y las respuestas en value
4. Sin embargo el ponderador también está en filas a aparte. Para poder aplicarlo a momento de hacer los conteos necesitamos que el ponderador sea una columna separada, aparte de key
y value
, que repita su magnitud para cada par de pregunta/respuesta. Necesitamos un trío de claves y valores. gather
contempla esta situación y tiene un operador muy simple para indicarle que no queremos que una variable se convierta en pares de clave-valor y se conserve en la estructura original, repitíendose para da grupo. El operador es el signo menos -
, que antecede el nombre de columna o columnas para las que queremos este comportamiento.
Por defecto gather
utiliza los nombres key
y value
para las columnas de claves y valor. Sin embargo es buena idea no usar estos nombres genéricos no son muy útiles –podrían ser cualquier cosa- y es buena práctica usar nombres significativos, relacionados con nuestros datos. En este caso podrías llamar a esas columnas Pregunta
y Respuesta
, ya que contienen preguntas y respuestas.
Cuando queremos excluir una columna de la transformación a formato largo es necesario suministrar nombres para las columnas de claves y valores. El operador
-
no funciona si no suministramos los nombres. Los nombres de clave y valor son los dos primeros argumentos de la función, las variables excluidas con-
son los argumentos sucesivos.
Formato largo: toma 2.
migracion %>%
head %>%
select(p7_1, p7_2, Pondi2) %>%
gather(Pregunta, Respuesta, -Pondi2) %>%
kable(caption = "El ponderador va en columna aparte.")
Pondi2 | Pregunta | Respuesta |
---|---|---|
58676 | p7_1 | Sí |
11725 | p7_1 | NS |
34727 | p7_1 | Sí |
56490 | p7_1 | Sí, en parte |
113882 | p7_1 | Sí |
58275 | p7_1 | Sí |
58676 | p7_2 | Sí |
11725 | p7_2 | NS |
34727 | p7_2 | Sí |
56490 | p7_2 | Sí |
113882 | p7_2 | Sí |
58275 | p7_2 | Sí |
Conteos agrupados y ponderados
Con la estructura de datos correcta podemos hacer los conteos de las respuestas a cada pregunta aplicando el ponderador muestal. Seguimos con el ejemplo con pocas columnas para facilitar la lectura de las salidas, pero podemos escalar este procedimiento a tantas columnas como querramos, la disponibilidad de memoria RAM es el límite.
Pasos:
- Aplicamos los agrupamientos a los transformados. En este caso queremos un grupo para cada combinación de pregunta/respuesta.
- Aplicamos el conteo indicando a
Pondi2
comowt=
o ponderador muestral. - Asignamos un nombre al data.frame con los conteos. No es estrictamente necesario, pero en este caso corta el código y hace más legible la sintaxis posterior del gráfico.
migracion %>%
select(p7_1, p7_2, Pondi2) %>%
gather(Pregunta, Respuesta, -Pondi2) %>%
group_by(Pregunta, Respuesta) %>%
count(wt = Pondi2) -> conteos_p7
kable(conteos_p7, caption = "Conteos finales, casi listos para graficar")
Pregunta | Respuesta | n |
---|---|---|
p7_1 | NC | 715096 |
p7_1 | No | 13580012 |
p7_1 | NS | 2021106 |
p7_1 | Sí | 43552653 |
p7_1 | Sí, en parte | 19951882 |
p7_2 | NC | 835828 |
p7_2 | No | 11550564 |
p7_2 | NS | 2964105 |
p7_2 | Sí | 42893890 |
p7_2 | Sí, en parte | 21576362 |
Etiquetas de las variables 1
En el bootstrap del ejercicio 5 se crea un diccionario de variables con la siguiente estructura:
diccionario %>%
filter(str_detect(nombre, "p7_")) %>%
kable(caption = "Relación de nombres cortos y largos del bloque p7.")
nombre | etiquetas |
---|---|
p7_1 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_2 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
p7_3 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas indígenas? |
p7_4 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas gais? |
p7_5 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas con ideas políticas distintas a las suyas? |
p7_6 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas enfermas de sida? |
p7_7 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas personas con alguna discapacidad? |
p7_8 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otro país (extranjeras)? |
p7_9 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas con una cultura distinta? |
p7_10 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas lesbianas? |
Los datos largos tienen otra características interesante, están listos para hacer joins de datos. De este podemos reemplazar facilmente los códigos de las preguntas (p*_*) por los nombres largos, mucho más significativos para quién vaya a leer nuestro reporte/paper/tesis.
Pasos:
- Cambiar el nombre de la columna
Pregunta
deconteos_p7
anombre
, de modo que coincida con la variable con igual información endiccionario
. - Hacer un
left_join
, de modo que se conserven solamente las filas de la izquierda, es decir, deconteos_p7
.
conteos_p7 %>%
rename("nombre" = Pregunta) %>%
kable(caption = "Al cambiar el nombre aquí no necesito especificar claves de unión en el join")
nombre | Respuesta | n |
---|---|---|
p7_1 | NC | 715096 |
p7_1 | No | 13580012 |
p7_1 | NS | 2021106 |
p7_1 | Sí | 43552653 |
p7_1 | Sí, en parte | 19951882 |
p7_2 | NC | 835828 |
p7_2 | No | 11550564 |
p7_2 | NS | 2964105 |
p7_2 | Sí | 42893890 |
p7_2 | Sí, en parte | 21576362 |
conteos_p7 %>%
rename("nombre" = Pregunta) %>%
left_join(diccionario) %>%
kable(caption = "La columna etiqueta tiene los nombres largos que usaré para graficar")
nombre | Respuesta | n | etiquetas |
---|---|---|---|
p7_1 | NC | 715096 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_1 | No | 13580012 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_1 | NS | 2021106 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_1 | Sí | 43552653 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_1 | Sí, en parte | 19951882 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra religión? |
p7_2 | NC | 835828 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
p7_2 | No | 11550564 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
p7_2 | NS | 2964105 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
p7_2 | Sí | 42893890 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
p7_2 | Sí, en parte | 21576362 | 7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de otra raza (negro, chino,etc.)? |
Etiquetas de variables 2
Los nombres largos de las variables –las preguntas tal como fueron formuladas- son muy informativas. Quizás demasiado. En el gráfico van a superponerse y van a ser difíciles de leer.
Aproximación 1: eliminar el texto repetido.
Como la primera parte de la pregunta se repite una aproximación a este problema es eliminarla y mencionarla una vez para todo el gráfico, conservando solamente la parte que cambia.
Advertencia vamos a usar expresiones regulares. La RegEx no se entienden, se usan.
Pasos:
- Usando
mutate
aplicamos la funciónstr_remove()
, que elimina la cadena de caracteres (patrón o pattern enhelp(str_remove)
) de la columna. En este caso vamos a eliminar la cadena “7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de” - Algunos símbolos (
?*.^|&
entre otros, están reservados por las expresiones regulares y es necesario “escaparlos”. Un caracter se escapa usando la barra invertida \. Como la barra invertida es a su vez un caracter reservado por R tenemos que escaparlo, así que utilizamos la doble barra invertida \\. Como markdown reserva las barras invertidas para que en este documento salgan 2 tuve que escribir 4. Y así hasta que se torna peligroso
conteos_p7 %>%
rename("nombre" = Pregunta) %>%
left_join(diccionario) %>% #Hasta acá repito lo que hice en el bloque anterior.
mutate(etiquetas = str_remove(etiquetas, "7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas de ")) %>%
kable(caption = "Más legible")
nombre | Respuesta | n | etiquetas |
---|---|---|---|
p7_1 | NC | 715096 | otra religión? |
p7_1 | No | 13580012 | otra religión? |
p7_1 | NS | 2021106 | otra religión? |
p7_1 | Sí | 43552653 | otra religión? |
p7_1 | Sí, en parte | 19951882 | otra religión? |
p7_2 | NC | 835828 | otra raza (negro, chino,etc.)? |
p7_2 | No | 11550564 | otra raza (negro, chino,etc.)? |
p7_2 | NS | 2964105 | otra raza (negro, chino,etc.)? |
p7_2 | Sí | 42893890 | otra raza (negro, chino,etc.)? |
p7_2 | Sí, en parte | 21576362 | otra raza (negro, chino,etc.)? |
Aproximación 2: agregar saltos de línea.
Pasos:
- Usando
mutate()
aplicamos la funciónstr_wrap()
, que agrega saltos de líneas cada determinado número caracteres respetando los límites de palabra. De ese modo en lugar de tener una línea muy larga tenemos varias líneas cortas. La expresión literal de una salto de línea en R es\\n
, una barra invertida antes de unan
minúscula.
- A
str_wrap
se le puede indicar el ancho aproximado para los saltos de línea: cuanto más pequeño el ancho más frecuentes los saltos de línea.
conteos_p7 %>%
rename("nombre" = Pregunta) %>%
left_join(diccionario) %>% #Hasta acá repito lo que hice en el bloque anterior.
mutate(etiquetas = str_wrap(etiquetas, 20))
## # A tibble: 10 x 4
## # Groups: nombre, Respuesta [10]
## nombre Respuesta n etiquetas
## <chr> <chr> <dbl> <chr>
## 1 p7_1 NC 715096 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 2 p7_1 No 13580012 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 3 p7_1 NS 2021106 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 4 p7_1 Sí 43552653 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 5 p7_1 Sí, en parte 19951882 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 6 p7_2 NC 835828 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 7 p7_2 No 11550564 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 8 p7_2 NS 2964105 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 9 p7_2 Sí 42893890 "7 ¿Estaría dispuesto\no no estaría\ndisp…
## 10 p7_2 Sí, en parte 21576362 "7 ¿Estaría dispuesto\no no estaría\ndisp…
Gráfico
Ya tenemos todo preparado para hacer el gráfico.
- Los conteos agrupados y ponderados.
- Los nombre de etiqueta larga propiamente formateados para que hacerlos legibles.
Pasos:
- Ubicar la etiqueta de cada pregunta en el eje x y los conteos de respuestas en el eje y.
- Utilizar el argumento
fill =
deggplot
para que cada respuesta tenga un color de relleno5 igual - Especificar
position = "dodge"
6 ageom_bar
para barras lado a lado. Por defectoposition = "stack"
y se generan barras apiladas. - Ubicar en el subtítulo del gráfico la parte repetida de la pregunta.
library(hrbrthemes)
migracion %>%
select(starts_with("p7_"), Pondi2) %>%
gather(Pregunta, Respuesta, -Pondi2) %>%
group_by(Pregunta, Respuesta) %>%
count(wt = Pondi2) %>%
rename("nombre" = Pregunta) %>%
left_join(diccionario) %>%
mutate(etiquetas = str_remove(etiquetas,
"7 ¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas "),
etiquetas = str_wrap(etiquetas, 15)) %>%
ungroup ()%>%
mutate(Respuesta = factor(Respuesta, levels = c("Sí", "Sí, en parte", "No", "NS", "NC"))) ->
conteos_p7_etiquetados
conteos_p7_etiquetados %>%
ggplot(aes(x = etiquetas, y = n, fill = Respuesta)) +
geom_col(position = "dodge") +
labs(title = "Actitudes de tolerancia en México",
subtitle = "¿Estaría dispuesto o no estaría dispuesto a permitir que en su casa vivieran personas...",
caption = "Elaboración propia\nDatos Encuesta Nacional de Migración",
x = NULL,
y = NULL) +
scale_y_continuous(labels = scales::comma) +
theme_ipsum_rc()
Aunque esto no significa que filas y columnas tienen el mismo, en ese caso serían datos cuadrados. Todos los datos cuadrados son rectangulares pero no todos los datos rectangulares son cuadrados.↩
O tríos, cuartetos, etc.↩
Para conocer los argumentos por defecto de
gather
usehelp(gather)
.↩Valor, ibid.↩
El argumento
color =
controla el color del contorno, no del relleno.↩Sí, yo también detesto esa sintaxis con comillas, pero es lo que hay↩