17 min read

Manejo de bases de datos de encuestas

Introducción

Las bases de datos provenientes de encuestas tienen algunos problemas que les son específicos y que requieren soluciones igualmente específicas. Aquí nos centramos en tres problemas:

  1. El manejo de etiquetas de variables y la creación de diccionarios de variables
  2. La combinación de pares de variables para facilitar la exploración bivariada
  3. Una solución a un problema de datos mal cargados: las variables separadas en múltiples columnas.

En los tres casos se utiliza extensamente la manipulación de listas y programación funcional. Son soluciones complejas a problemas (aparentemente) simples.

Conservar (y utilizar) los atributos de bases de datos de Stata o SPSS

Uno de los formatos de archivo en los que podemos encontrar bases de datos son los correspondientes a SPSS o Stata. Aunque no son formatos de archivo “nativos” de R es muy fácil importarlo y convertirlos a un objeto de la clase data.frame.

Los archivos de SPSS o Stata permiten utilizar dos tipos de metadatos que no están disponibles directamente en R: la Etiqueta de variable y las etiquetas de categorías y niveles para las variables categóricas.

Etiquetas de variables

En R las columnas de un data.frame tienen un nombre. Como usamos R desde la consola y tenemos que escribir esos nombres en general buscamos que sean cortos y que no contengan espacios. El problema es que esos nombres son difíciles de recordar y, al momento de comunicar los resultados de un análisis, son casi imposibles de interpretar por un lector o lectora que no está familiarizado con esos nombres cortos. SPSS y Stata resuelven el problema agregando un segundo atributo a cada columna de una base de datos: las etiquetas. De este modo tenemos un nombre corto para llamar a una variable en el código y uno más largo y descriptivo para usarlo como etiqueta en los gráficos, tablas, etc. Es una ventaja grande y en R no está disponible directamente. Esto no implica que no podamos aprovechar estos metadatos cuando están disponibles, aunque la solución requiera un poco de trabajo extra.

Cuando importamos un archivo .sav o .dta con alguna de las funciones de importación de la librería haven:: las etiquetas de cada variable también se importan y se registran en el atributo label, asociado a cada columna del data.frame. Aunque son muy pocas las funciones o librerías que aprovechan este atributo de manera transparente para el usuario podemos extraer esta información para crear un diccionario de variables. Así podemos consultar las equivalencias entre pares de nombre-etiqueta.

Como label es un atributo asociado a cada elemento de una lista no tiene el comportamiento vectorizado por defecto que esperamos de los vectores en R. Para obtener un vector de etiquetas usamos la función map_chr() de la librería purrr:: que se encarga de iterar la función attr en cada elemento del data.frame y organizar el output como una vector. Ubicamos ese vector como columna del data.frame diccionario. La otra columna la obtenemos con colnames(), que por defecto nos regresa un vector y no requiere el uso explícito de un iterador.

library(haven)
library(tidyverse)
## Registered S3 methods overwritten by 'ggplot2':
##   method         from 
##   [.quosures     rlang
##   c.quosures     rlang
##   print.quosures rlang
## ── Attaching packages ─────────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.1     ✔ purrr   0.3.2
## ✔ tibble  2.1.1     ✔ dplyr   0.8.3
## ✔ tidyr   1.0.0     ✔ stringr 1.4.0
## ✔ readr   1.3.1     ✔ forcats 0.4.0
## ── Conflicts ────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
religion <- read_sav("http://www.losmexicanos.unam.mx/religion/encuesta_nacional/base_datos/Encuesta_Nacional_de_Religion_Secularizacion_y_Laicidad.sav")

religion
## # A tibble: 1,200 x 268
##     Con1   edo  muni  loca ageb  hr_ini1 min_ini1 hr_ter1 min_ter1 dura1
##    <dbl> <dbl> <dbl> <dbl> <chr>   <dbl>    <dbl>   <dbl>    <dbl> <dbl>
##  1     1     2     2   289 414-9      12       23      12       42    19
##  2     2     2     2   289 414-9      12       22      12       41    19
##  3     3     2     2   289 414-9      12       22      12       45    23
##  4     4     2     2   289 414-9      12       48      13        4    16
##  5     5     2     2   289 414-9      12       50      13       11    21
##  6     6     2     2   289 414-9      12       43      13        2    19
##  7     7     2     2   289 414-9      13        6      13       29    23
##  8     8     2     2   289 414-9      13       18      13       36    18
##  9     9     2     2   289 414-9      13        4      13       22    18
## 10    10     2     2   289 414-9      13       34      13       53    19
## # … with 1,190 more rows, and 258 more variables: resul1 <dbl>,
## #   hr_ini2 <dbl>, min_ini2 <dbl>, hr_ter2 <dbl>, min_ter2 <dbl>,
## #   dura2 <dbl>, resul2 <dbl>, hr_ini3 <dbl>, min_ini3 <dbl>,
## #   hr_ter3 <dbl>, min_ter3 <dbl>, dura3 <dbl>, resul3 <dbl>,
## #   hr_ini4 <dbl>, min_ini4 <dbl>, hr_ter4 <dbl>, min_ter4 <dbl>,
## #   dura4 <dbl>, resul4 <dbl>, p1_1 <dbl+lbl>, p1_2 <dbl+lbl>,
## #   p1_3 <dbl+lbl>, p2 <dbl+lbl>, p3 <dbl+lbl>, p4 <dbl+lbl>,
## #   p5 <dbl+lbl>, p6 <dbl+lbl>, p7_1 <dbl+lbl>, p7_2 <dbl+lbl>,
## #   p7_3 <dbl+lbl>, p8_1 <dbl+lbl>, p8_2 <dbl+lbl>, p8_3 <dbl+lbl>,
## #   p8_4 <dbl+lbl>, p8_5 <dbl+lbl>, p9_1 <dbl+lbl>, p9_2 <dbl+lbl>,
## #   p9_3 <dbl+lbl>, p10 <dbl+lbl>, p11 <dbl+lbl>, p12 <dbl+lbl>,
## #   p13_1 <dbl+lbl>, p13_2 <dbl+lbl>, p13_3 <dbl+lbl>, p14 <dbl+lbl>,
## #   p15 <dbl+lbl>, p16_1 <dbl+lbl>, p16_2 <dbl+lbl>, p16_3 <dbl+lbl>,
## #   p16_4 <dbl+lbl>, p17_1 <dbl+lbl>, p17_2 <dbl+lbl>, p17_3 <dbl+lbl>,
## #   p17_4 <dbl+lbl>, p17_5 <dbl+lbl>, p17_6 <dbl+lbl>, p17_7 <dbl+lbl>,
## #   p17_8 <dbl+lbl>, p17_9 <dbl+lbl>, p17_10 <dbl+lbl>, p17_11 <dbl+lbl>,
## #   p17_12 <dbl+lbl>, p17_13 <dbl+lbl>, p17_14 <dbl+lbl>,
## #   p17_15 <dbl+lbl>, p17_16 <dbl+lbl>, p18 <dbl+lbl>, p19 <dbl+lbl>,
## #   p20 <dbl+lbl>, p21 <dbl+lbl>, p22_1 <dbl+lbl>, p22_2 <dbl+lbl>,
## #   p22_3 <dbl+lbl>, p23 <dbl+lbl>, p24 <dbl+lbl>, p25 <dbl+lbl>,
## #   p26 <dbl+lbl>, p27_1 <dbl+lbl>, p27_2 <dbl+lbl>, p27_3 <dbl+lbl>,
## #   p27_4 <dbl+lbl>, p27_5 <dbl+lbl>, p27_6 <dbl+lbl>, p27_7 <dbl+lbl>,
## #   p27_8 <dbl+lbl>, p28_1 <dbl+lbl>, p28_2 <dbl+lbl>, p28_3 <dbl+lbl>,
## #   p28_4 <dbl+lbl>, p28_5 <dbl+lbl>, p28_6 <dbl+lbl>, p28_7 <dbl+lbl>,
## #   p28_8 <dbl+lbl>, p28_9 <dbl+lbl>, p28_10 <dbl+lbl>, p28_11 <dbl+lbl>,
## #   p28ot1 <dbl+lbl>, p28ot2 <dbl+lbl>, p28ot3 <dbl+lbl>,
## #   p28ot4 <dbl+lbl>, …
# Atributo de etiqueta de una columna

attr(religion$p7_1, "label")
## [1] " 7. ¿En cuáles de los siguientes lugares recibió una educación religiosa? 1° MENCIÓN"
diccionario <- tibble(nombre = colnames(religion), 
                      etiqueta = map_chr(religion, attr, "label"))
diccionario
## # A tibble: 268 x 2
##    nombre   etiqueta             
##    <chr>    <chr>                
##  1 Con1     ID                   
##  2 edo      " Estado"            
##  3 muni     " Municipio"         
##  4 loca     " Localidad"         
##  5 ageb     AGEB                 
##  6 hr_ini1  " Hora inicio"       
##  7 min_ini1 " Minutos de inicio" 
##  8 hr_ter1  " Hora de termino"   
##  9 min_ter1 " Minutos de termino"
## 10 dura1    " Duración"          
## # … with 258 more rows
#View(diccionario)

Obtenemos una estructura de datos relacional de pares y valores. Como mínimo nos sirve para consultar la definición de una variable, pero también podemos usarlo como una tabla para cambiar de nombres a etiquetas. De este modo podemos hacer la mayor parte del procesamiento usando los nombres cortos y luego renombrar a las columnas justo antes de utilizar una función en la que sirvan los nombres largos.

La función renombrar() tiene como único argumento un data.frame (que puede ser un subconjunto del data.frame completo) y utiliza las equivalencias del diccionario para renombrar.

renombrar <- function(x) {
  rename_all(x, ~diccionario$etiqueta[match(., diccionario$nombre)])
  }

religion %>% 
  select(p43_1, p43_2) %>% 
  renombrar() 
## # A tibble: 1,200 x 2
##    ` 43. ¿Qué tan de acuerdo o en desa… ` 43. ¿Qué tan de acuerdo o en des…
##                               <dbl+lbl>                           <dbl+lbl>
##  1                    1 [De acuerdo]         1 [De acuerdo]                
##  2                    1 [De acuerdo]         2 [De acuerdo en parte (esp.)]
##  3                   98 [NS]                98 [NS]                        
##  4                    1 [De acuerdo]         1 [De acuerdo]                
##  5                    1 [De acuerdo]         1 [De acuerdo]                
##  6                    1 [De acuerdo]         1 [De acuerdo]                
##  7                    1 [De acuerdo]         1 [De acuerdo]                
##  8                    5 [En desacuerdo]      5 [En desacuerdo]             
##  9                    1 [De acuerdo]         1 [De acuerdo]                
## 10                    1 [De acuerdo]         2 [De acuerdo en parte (esp.)]
## # … with 1,190 more rows

También se puede utilizar left_join() para renombrar los valores de un data.frame en formato largo o formato de pares de clave-valor.

tolerancia <- religion %>% 
  select(p43_1, p43_2) %>% 
  as_factor() %>% 
  pivot_longer(cols = everything(), 
               names_to = "nombre") # "nombre" coincide con la columna en el diccionario

tolerancia %>% 
  left_join(diccionario)
## Joining, by = "nombre"
## # A tibble: 2,400 x 3
##    nombre value              etiqueta                                      
##    <chr>  <fct>              <chr>                                         
##  1 p43_1  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  2 p43_2  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  3 p43_1  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  4 p43_2  De acuerdo en par… " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  5 p43_1  NS                 " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  6 p43_2  NS                 " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  7 p43_1  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  8 p43_2  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
##  9 p43_1  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
## 10 p43_2  De acuerdo         " 43. ¿Qué tan de acuerdo o en desacuerdo est…
## # … with 2,390 more rows

Aparece la columnaetiqueta con la descripción de la variable. Sin embargo tiene un problema: hay un espacio delante y otro detrás de la descripción que luce un poco mal. Además el nombre es demasiado largo, lo que complicará el etiquetado si queremos usarlo para un gráfico.

tolerancia %>% 
  left_join(diccionario) %>% 
  mutate(etiqueta = str_remove(etiqueta, "43. ¿Qué tan de acuerdo o en desacuerdo está usted con las siguientes ideas\\? "), 
         etiqueta = str_trim(etiqueta), 
         etiqueta = str_wrap(etiqueta, 40)) %>% 
  group_by(value, etiqueta) %>% 
  count() %>% 
  ggplot(aes(x=str_wrap(value, 15), y = n)) + 
    geom_col() + 
    facet_wrap(vars(etiqueta), ncol = 1) + 
    theme_minimal() + 
    labs(title = "¿Qué tan de acuerdo o en desacuerdo está usted con las\nsiguientes ideas?", 
       y = NULL)
## Joining, by = "nombre"

¿Qué es dbl+lbl?

Algunas de las columnas de este data.frame pertenecen a una clase algo inusual: dbl+lbl. El signo + indica que pertenecen a dos clases al mismo tiempo, son double o número no enteros y son labelled o variable etiquetadas. Unas pocas funciones1 reconocen esta dualidad y la pueden manejar. La mayoría de las funciones base ignoran esa clase especial y consideran a estos vectores como numéricos, algo que no es necesariamente una buena idea para variable categóricas. La función as_factor()2 se encarga de convertir un vector dbl+lbl en un factor común y corriente de R. Esta función aprovecha la orientación a objetos de R y tiene métodos para diferentes clases de objetos, por lo que podemos usarla de igual modo para un data.frame completo y para un vector.

Combinaciones bivariadas

Cuando estamos explorando una base de datos puede ser interesante hacer un análisis bivariado de un conjunto de datos de interés. Pensemos en tres casos 1) hacer gráficos de pares, 2) producir las tablas de contingencia de todos los posibles pares de variables en un data.frame con datos categóricos o 3) estimar el coeficiente de correlación entre todos los posibles pares de variables.

Los casos 1 y 3 ya están contemplados en R. La función pairs o, mejor aún, GGally::ggpairs() resuelven el caso 1, generando un gráfico con todos los pares de variables. La función cor() por defecto produce una matriz de correlación en la que cada celda contiene el coeficiente de correlación para cada par de variables, resolviendo el caso 3.

¿Qué pasa con el caso 2? No hay una función que se encargue de resolver ese problema de manera genérica. Por lograrlo lo más simple, robusto y generalizable es crear una lista, en la que cada elemento es un par de variables. La función listar_pares(), que se define más abajo, hace exactamente eso: toman un data.frame y, por defecto, nos regresa una lista de data.frame con todas combinaciones únicas de dos columnas.

El argumento full = TRUE produce una lista con el producto cartesiano de todas las variables, incluyendo los cruces de cada variable consigo misma y cruces repetidos, en los que cada variable ocupa el primer lugar cada vez. Esto podría ser útil para generar una matriz completa de estadísticos o gráficos bivariados.

El resultado en ambos casos es, por defecto, una lista nombrada. El nombre de cada elemento de la lista es el concatenado de los nombres separado por el símbolo /. Si los nombres son innecesario o directamente un estorbo se puede utilizar la funciónunname() para eliminarlos.

Tomemos el bloque p44 de la base de datos religión. Para mantener las cosas simples nos enfocamos en las primeras 3, aunque podrías hacer esto mismo para muchas más variables.

#Subconjunto de datos sobre tolerancia

religion %>% 
  select(p44_1:p44_3) %>% 
  as_factor() -> tolerancia


lista_pares <- function(df, full = FALSE) {
  if(!full) {
    nombres <- combn(names(df), 2, simplify = FALSE)
  }
  else
  {
    nombres <- lapply(apply(expand.grid(names(df), names(df), stringsAsFactors = FALSE), 1, as.list), as.character)
  }
  
  out <- lapply (nombres, function (x) {df[, c(x[1], x[2])]})
  names(out) <- lapply(nombres, paste, collapse = " / ")
  out
}


lista_pares(tolerancia)
## $`p44_1 / p44_2`
## # A tibble: 1,200 x 2
##    p44_1                              p44_2                             
##    <fct>                              <fct>                             
##  1 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  2 Tolerante                          Tolerante                         
##  3 NC                                 NC                                
##  4 Tolerante                          Tolerante                         
##  5 Tolerante                          Tolerante                         
##  6 Tolerante                          Tolerante                         
##  7 Tolerante                          Tolerante                         
##  8 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  9 Tolerante                          Tolerante                         
## 10 Tolerante                          Tolerante                         
## # … with 1,190 more rows
## 
## $`p44_1 / p44_3`
## # A tibble: 1,200 x 2
##    p44_1                              p44_3                             
##    <fct>                              <fct>                             
##  1 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  2 Tolerante                          Tolerante                         
##  3 NC                                 NC                                
##  4 Tolerante                          Tolerante                         
##  5 Tolerante                          Tolerante                         
##  6 Tolerante                          Tolerante                         
##  7 Tolerante                          Tolerante                         
##  8 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  9 Tolerante                          Tolerante                         
## 10 Tolerante                          Tolerante                         
## # … with 1,190 more rows
## 
## $`p44_2 / p44_3`
## # A tibble: 1,200 x 2
##    p44_2                              p44_3                             
##    <fct>                              <fct>                             
##  1 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  2 Tolerante                          Tolerante                         
##  3 NC                                 NC                                
##  4 Tolerante                          Tolerante                         
##  5 Tolerante                          Tolerante                         
##  6 Tolerante                          Tolerante                         
##  7 Tolerante                          Tolerante                         
##  8 Ni tolerante ni intolerante (esp.) Ni tolerante ni intolerante (esp.)
##  9 Tolerante                          Tolerante                         
## 10 Tolerante                          Tolerante                         
## # … with 1,190 more rows
lista_pares(tolerancia) %>% 
  map(table)
## $`p44_1 / p44_2`
##                                     p44_2
## p44_1                                Tolerante
##   Tolerante                                704
##   Ni tolerante ni intolerante (esp.)        27
##   Intolerante                               12
##   NA                                         1
##   NS                                         1
##   NC                                         0
##                                     p44_2
## p44_1                                Ni tolerante ni intolerante (esp.)
##   Tolerante                                                         150
##   Ni tolerante ni intolerante (esp.)                                162
##   Intolerante                                                        18
##   NA                                                                  1
##   NS                                                                  0
##   NC                                                                  1
##                                     p44_2
## p44_1                                Intolerante  NA  NS  NC
##   Tolerante                                   24   3   4   0
##   Ni tolerante ni intolerante (esp.)           8   0   1   0
##   Intolerante                                 53   1   4   0
##   NA                                           1   6   0   0
##   NS                                           0   0  11   0
##   NC                                           0   0   0   7
## 
## $`p44_1 / p44_3`
##                                     p44_3
## p44_1                                Tolerante
##   Tolerante                                591
##   Ni tolerante ni intolerante (esp.)        39
##   Intolerante                               10
##   NA                                         3
##   NS                                         1
##   NC                                         1
##                                     p44_3
## p44_1                                Ni tolerante ni intolerante (esp.)
##   Tolerante                                                         202
##   Ni tolerante ni intolerante (esp.)                                135
##   Intolerante                                                        19
##   NA                                                                  0
##   NS                                                                  0
##   NC                                                                  0
##                                     p44_3
## p44_1                                Intolerante  NA  NS  NC
##   Tolerante                                   71   0  16   5
##   Ni tolerante ni intolerante (esp.)          17   0   6   1
##   Intolerante                                 54   0   5   0
##   NA                                           0   6   0   0
##   NS                                           0   0  11   0
##   NC                                           0   0   0   7
## 
## $`p44_2 / p44_3`
##                                     p44_3
## p44_2                                Tolerante
##   Tolerante                                563
##   Ni tolerante ni intolerante (esp.)        69
##   Intolerante                                9
##   NA                                         2
##   NS                                         2
##   NC                                         0
##                                     p44_3
## p44_2                                Ni tolerante ni intolerante (esp.)
##   Tolerante                                                         122
##   Ni tolerante ni intolerante (esp.)                                214
##   Intolerante                                                        17
##   NA                                                                  2
##   NS                                                                  1
##   NC                                                                  0
##                                     p44_3
## p44_2                                Intolerante  NA  NS  NC
##   Tolerante                                   43   0  12   5
##   Ni tolerante ni intolerante (esp.)          42   0   6   1
##   Intolerante                                 56   0   4   0
##   NA                                           0   6   0   0
##   NS                                           1   0  16   0
##   NC                                           0   0   0   7
lista_pares(tolerancia) %>% 
  map(set_names, c("izq", "der")) %>% 
  bind_rows(.id = "cruce") %>% 
  group_by(cruce, izq, der) %>% 
  count()
## # A tibble: 64 x 4
## # Groups:   cruce, izq, der [64]
##    cruce       izq                         der                            n
##    <chr>       <fct>                       <fct>                      <int>
##  1 p44_1 / p4… Tolerante                   Tolerante                    704
##  2 p44_1 / p4… Tolerante                   Ni tolerante ni intoleran…   150
##  3 p44_1 / p4… Tolerante                   Intolerante                   24
##  4 p44_1 / p4… Tolerante                   NA                             3
##  5 p44_1 / p4… Tolerante                   NS                             4
##  6 p44_1 / p4… Ni tolerante ni intolerant… Tolerante                     27
##  7 p44_1 / p4… Ni tolerante ni intolerant… Ni tolerante ni intoleran…   162
##  8 p44_1 / p4… Ni tolerante ni intolerant… Intolerante                    8
##  9 p44_1 / p4… Ni tolerante ni intolerant… NS                             1
## 10 p44_1 / p4… Intolerante                 Tolerante                     12
## # … with 54 more rows

Convertir una matriz de indicadores en un factor

En algunos casos cuando importamos datos categóricos las variables no están en una solo columna, sino que tienen la forma de una matriz de indicadores: cada categoría es una variable y un registro (TRUE, 1, o la etiqueta correspondiente a la categoría) indican en datos. Este es un formato incómodo para trabajar en R, lo ideal es tener a cada variable en una columna.

Aunque el problema puede tener diferentes casos una buena alternativa para resolver este problema, que puede adaptarse a diferentes situaciones, es utilizar la función coalesce().

Para poder ejemplificar el proceso comenzaremos por simular unos datos con esta estructura. Tendremos la Pregunta 1, que simula una escala de Likert con cuatro categorías y la Pregunta 2, con idénticas categorías. Ambas se generan con un proceso aleatorio independiente y se reúnen en el mismo data.frame. El proceso de simular los datos es algo complicado, lo importante es que el resultado de el data.frame que vamos a limpiar juntando las cuatro columnas en una sola.

Nota Si alguien piensa que este es un ejemplo innecesariamente retorcido jamás ha recibido una base de datos cargada en Excel.

categorías <- c("Muy de acuerdo", "De acuerdo", "En desacuerdo", "Muy en desacuerdo")
pregunta1 <- sample(categorías, 
            size = 100,  
            prob = c(0.2, 0.2, 0.5, 0.1), 
            replace = TRUE)

df1 <- lapply(categorías, function(x) ifelse(pregunta1 == x, x, NA)) %>% 
  as.data.frame() %>% 
  setNames(paste0("Pregunta1", categorías)) 

pregunta2 <- sample(categorías, 
            size = 100,  
            prob = c(0.2, 0.2, 0.5, 0.1), 
            replace = TRUE)

df2 <- lapply(categorías, function(x) ifelse(pregunta2 == x, x, NA)) %>% 
  as.data.frame() %>% 
  setNames(paste0("Pregunta2", categorías)) 

df_final <- cbind(df1, df2) 
as_tibble(df_final)
## # A tibble: 100 x 8
##    `Pregunta1Muy d… `Pregunta1De ac… `Pregunta1En de… `Pregunta1Muy e…
##    <fct>            <fct>            <fct>            <fct>           
##  1 <NA>             <NA>             En desacuerdo    <NA>            
##  2 Muy de acuerdo   <NA>             <NA>             <NA>            
##  3 Muy de acuerdo   <NA>             <NA>             <NA>            
##  4 <NA>             <NA>             En desacuerdo    <NA>            
##  5 <NA>             <NA>             En desacuerdo    <NA>            
##  6 <NA>             De acuerdo       <NA>             <NA>            
##  7 <NA>             <NA>             En desacuerdo    <NA>            
##  8 <NA>             <NA>             En desacuerdo    <NA>            
##  9 <NA>             <NA>             En desacuerdo    <NA>            
## 10 <NA>             <NA>             <NA>             Muy en desacuer…
## # … with 90 more rows, and 4 more variables: `Pregunta2Muy de
## #   acuerdo` <fct>, `Pregunta2De acuerdo` <fct>, `Pregunta2En
## #   desacuerdo` <fct>, `Pregunta2Muy en desacuerdo` <fct>

Buscamos una data.frame con dos columnas, Pregunta 1 y Pregunta 2. La función coalesce() hará el trabajo pesado: juntar los vectores reemplazando los NA (missing) con un registro válido del otro vector. Como esta función trabaja solamente con pares es necesario aplicarla con un funcional que recorra una lista resolviendo el primer par y luego, sobre el resultado de ese par, resolverlo con el vector siguiente. Y así recursivamente hasta agotar la lista. Esta operación llamada reducción es frecuente en la programación funcional. La función reduce() es una de las aplicaciones en R de la reducción de listas.

Como tenemos dos grupos de columnas hay un paso previo necesario: separar a cada grupo y ubicarlo como elemento de una lista. La función split.default() combinada con una expresión regular que nos permite separar la parte variable del nombre de columnas (en este caso Pregunta1 y Pregunta2) de la parte constante (la categoría de respuesta).

df_final %>% 
  mutate_all(as.character) %>%  #coalesce fallará con factores
  split.default(str_remove(names(.), 
#subnombres separados por | (o en expresiones regulares)
                           "Muy de acuerdo|De acuerdo|En desacuerdo|Muy en desacuerdo")) %>% 
  map_df(reduce, coalesce)
## # A tibble: 100 x 2
##    Pregunta1         Pregunta2        
##    <chr>             <chr>            
##  1 En desacuerdo     En desacuerdo    
##  2 Muy de acuerdo    En desacuerdo    
##  3 Muy de acuerdo    Muy de acuerdo   
##  4 En desacuerdo     De acuerdo       
##  5 En desacuerdo     En desacuerdo    
##  6 De acuerdo        En desacuerdo    
##  7 En desacuerdo     De acuerdo       
##  8 En desacuerdo     En desacuerdo    
##  9 En desacuerdo     Muy en desacuerdo
## 10 Muy en desacuerdo En desacuerdo    
## # … with 90 more rows

  1. Pero importantes, la mayoría de las de la librería dplyr::

  2. No confundir con as.factor.