Sobre este documento Este R Markdown corresponde al Script 1 del pipeline POGIT aplicado a la marca Adidas. Está basado directamente en el code1.R entregado por la directora de tesis (caso Apple). El script carga el survey de participantes del panel Amazon, construye las variables dummy de raza y eventos de vida, e integra todo en un objeto survey_enriched que será consumido por el Script 2.

Modificaciones respecto a code1.R (Apple):

Etiqueta Bloque Descripción
MOD-01 Bloque 9 Nombre del PDF de salida cambiado a survey_barplots_adidas.pdf

Todos los demás bloques son idénticos al original porque el survey es el mismo para Apple y para Adidas: los mismos participantes, las mismas preguntas demográficas.


1 Bloque 1 — Carga de paquetes

1.1 ¿Qué hace este bloque y por qué es necesario?

Antes de ejecutar cualquier análisis, R necesita cargar las librerías que contienen las funciones especializadas del pipeline. Este bloque define el listado completo de paquetes y los carga mediante una función personalizada load_pkg(), que verifica que todos hayan sido instalados correctamente.

La razón de centralizar la carga aquí es que si algún paquete falta, el error aparece desde el inicio y no a mitad del análisis. Los paquetes cubren cuatro grandes áreas: manipulación de datos (tidyverse, janitor, lubridate), visualización (ggplot2, scales, patchwork), modelación (pscl, tidymodels, caret) y cómputo paralelo (parallel, furrr, optimParallel), este último necesario para la estimación del modelo POGIT en el Script 3.

1.2 Términos nuevos o poco comunes

  • vapply(): Versión segura de sapply(). Aplica una función a cada elemento y garantiza que el resultado sea del tipo especificado (aquí logical). Falla explícitamente si el tipo no coincide, lo que lo hace más robusto en producción.
  • suppressPackageStartupMessages(): Silencia los mensajes de bienvenida que algunos paquetes muestran al cargarse. No afecta el funcionamiento.
  • require(): Similar a library() pero devuelve TRUE o FALSE en lugar de lanzar un error si el paquete no existe. Esto permite que load_pkg() capture el fallo y genere un mensaje más informativo.
  • optimParallel: Versión paralela del optimizador optim() de R base. Se usa en el Script 3 para la estimación por Máxima Verosimilitud (MLE) del modelo POGIT, distribuyendo el cómputo en múltiples núcleos del procesador.
  • numDeriv: Paquete para cálculo numérico de derivadas. Se usa para obtener los errores estándar de los coeficientes del modelo POGIT a partir de la matriz Hessiana de la función de log-verosimilitud.
packages <- unique(c(
  "tidyverse", "lubridate", "janitor", "skimr", "snakecase",
  "stringr", "dplyr", "gridExtra", "scales", "viridis",
  "patchwork", "fastDummies", "readxl", "openxlsx", "tidymodels",
  "Matrix", "caret", "pscl", "xgboost", "Metrics",
  "numDeriv", "optimParallel", "parallel", "furrr", "progressr",
  "tibble", "readr", "forcats", "ggplot2", "tidyr"
))

load_pkg <- function(pkgs) {
  ok <- vapply(pkgs, function(p) {
    suppressPackageStartupMessages(require(p, character.only = TRUE))
  }, logical(1))
  if (any(!ok)) {
    stop("Packages not installed/loaded: ", paste(pkgs[!ok], collapse = ", "))
  }
  invisible(ok)
}
load_pkg(packages)

Output esperado: Si todos los paquetes están instalados, este bloque no produce ningún mensaje visible. Si alguno falta, R arrojará un error indicando cuál paquete no pudo cargarse. Instalarlo con install.packages("nombre_paquete") y reintentar.


2 Bloque 2 — Configuración visual y paleta de colores

2.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque establece dos configuraciones globales: el locale del sistema y la paleta de colores. Ambas deben definirse antes de la función bar_plot() del Bloque 3, porque bar_plot() usa pal como valor por defecto de su argumento fill_pal.

El locale (es_ES.UTF-8) configura R para interpretar fechas, separadores y caracteres especiales en español. Si el sistema operativo no lo soporta (común en Windows), R emite una advertencia pero continúa sin interrumpirse.

La paleta pal es un vector nombrado de 10 colores en formato hexadecimal seleccionados de la paleta Tableau, diseñada para ser distinguible tanto en pantalla como en impresión en escala de grises. Definirla como objeto global garantiza que todos los gráficos del pipeline compartan el mismo esquema visual, y su posición antes de bar_plot() es crítica porque la función la recibe como argumento por defecto.

2.2 Términos nuevos o poco comunes

  • Locale: Configuración regional del sistema que determina idioma, formato de fechas y separador decimal. Se configura con Sys.setlocale().
  • identical(loc_ok, ""): Verifica si la asignación del locale falló. Sys.setlocale() devuelve una cadena vacía cuando no puede aplicar el locale. identical() es más estricto que == porque también verifica el tipo de dato.
  • Vector nombrado: Vector donde cada elemento tiene nombre asignado (pal["azul_1"]). Permite referenciar colores por nombre en lugar de por posición.
  • Formato hexadecimal (#RRGGBB): Representación de colores en base 16. Por ejemplo, #4E79A7 es un azul medio.
loc_ok <- base::Sys.setlocale("LC_ALL", "es_ES.UTF-8")
if (identical(loc_ok, "")) {
  warning(
    "Could not set locale 'es_ES.UTF-8'; ",
    "separators and month names will use the default locale."
  )
}

pal <- c(
  azul_1   = "#4E79A7",
  naranja  = "#F28E2B",
  rojo     = "#E15759",
  turquesa = "#76B7B2",
  verde    = "#59A14F",
  amarillo = "#EDC948",
  morado   = "#B07AA1",
  rosa     = "#FF9DA7",
  cafe     = "#9C755F",
  gris     = "#BAB0AC"
)

Output esperado: Si el locale se configura correctamente, no aparece nada. En Windows es frecuente la advertencia de locale, pero el script continúa. El objeto pal queda disponible en el entorno global con 10 colores nombrados.


3 Bloque 3 — Función bar_plot() y funciones auxiliares

3.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque define la función bar_plot() y dos funciones auxiliares de uso general en el pipeline: mode_safe() y reorder_drop_id().

bar_plot() genera gráficas de barras horizontales estandarizadas para cualquier variable categórica del survey. Encapsula la lógica de visualización siguiendo el principio DRY (Don’t Repeat Yourself): en lugar de repetir el código 20 veces, una sola función recibe el dataframe, la variable, los títulos y parámetros opcionales y produce el gráfico. La función calcula frecuencias y porcentajes, ordena las barras de mayor a menor con fct_reorder(), asigna colores cíclicamente de la paleta, y usa coord_flip() para barras horizontales que facilitan la lectura de etiquetas largas.

mode_safe() calcula la moda de un vector ignorando NAs. Es “safe” porque no falla si el vector está vacío (devuelve NA_character_). Se usará en el Script 2 para determinar el estado de envío más frecuente por cliente.

reorder_drop_id() filtra un dataframe a un conjunto de response_id y los reordena para coincidir exactamente con el orden de referencia. Esta alineación exacta es crítica en el Script 2 cuando se construyen las matrices V y W.

3.2 Términos nuevos o poco comunes

  • {{ var }}: Operador embracing de rlang. Permite que bar_plot() reciba el nombre de una columna sin comillas y lo evalúe correctamente dentro de dplyr. Es la sintaxis moderna para tidy evaluation.
  • forcats::fct_reorder(var_chr, n): Reordena los niveles de un factor según otra variable (n = frecuencia). Al combinarse con coord_flip(), produce barras ordenadas de mayor a menor de arriba hacia abajo.
  • coord_flip(): Invierte los ejes X e Y. Convierte barras verticales en horizontales, facilitando la lectura de etiquetas largas.
  • rep(unname(fill_pal), length.out = nrow(counts)): Repite los colores cíclicamente hasta tener exactamente tantos como categorías. unname() elimina los nombres del vector para evitar conflictos en scale_fill_manual().
  • sprintf("%.1f%%", pct): Formatea el porcentaje con un decimal y símbolo %. Produce cadenas como “34.2%”.
  • tabulate(match(x, ux)): Cuenta frecuencias eficientemente. match() devuelve la posición de cada elemento en el vector único; tabulate() cuenta cuántas veces aparece cada posición.
bar_plot <- function(df, var, title, x_lab,
                     fill_pal = pal, text_size = 3) {
  
  counts <- df %>%
    dplyr::filter(!is.na({{ var }})) %>%
    dplyr::count({{ var }}, name = "n") %>%
    dplyr::mutate(
      pct     = n / sum(n) * 100,
      var_chr = as.character({{ var }})
    ) %>%
    dplyr::arrange(dplyr::desc(n)) %>%
    dplyr::mutate(var_chr = forcats::fct_reorder(var_chr, n))
  
  fill_vals <- rep(unname(fill_pal), length.out = nrow(counts))
  names(fill_vals) <- levels(counts$var_chr)
  
  ggplot2::ggplot(counts, ggplot2::aes(x = var_chr, y = n, fill = var_chr)) +
    ggplot2::geom_col() +
    ggplot2::geom_text(
      ggplot2::aes(label = sprintf("%.1f%%", pct), y = n),
      hjust = -0.1, size = text_size, colour = "black"
    ) +
    ggplot2::coord_flip() +
    ggplot2::scale_y_continuous(
      expand = ggplot2::expansion(mult = c(0, 0.15))
    ) +
    ggplot2::scale_fill_manual(values = fill_vals, guide = "none") +
    ggplot2::labs(title = title, x = x_lab, y = "Number of participants") +
    ggplot2::theme_minimal()
}

mode_safe <- function(x) {
  x <- x[!is.na(x) & x != ""]
  if (length(x) == 0) return(NA_character_)
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

reorder_drop_id <- function(df, ref_ids) {
  df %>%
    dplyr::filter(response_id %in% ref_ids) %>%
    dplyr::slice(match(ref_ids, response_id))
}

Output esperado: Ningún output visible. Las tres funciones quedan registradas en el entorno global. Verificable con exists("bar_plot"), que devolverá TRUE.


4 Bloque 4 — Carga del survey

4.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque carga el archivo principal de datos demográficos: el survey aplicado a los participantes del panel Amazon MTurk/Prolific. Es la fuente de todas las variables de control demográfico que entrarán al modelo como covariables: edad, género, nivel educativo, ingresos, tamaño del hogar, entre otras.

readr::read_csv() es más rápida que read.csv() de R base para archivos grandes, maneja mejor la codificación UTF-8, e infiere automáticamente los tipos de datos. janitor::clean_names() estandariza todos los nombres de columna a snake_case inmediatamente después de la carga, evitando errores por mayúsculas o espacios en los nombres durante el resto del pipeline.

El dataset contiene aproximadamente 5,027 participantes y es la fuente de todas las covariables declaradas por encuestado descritas en la Sección 3 del paper (Construction of Predictor Variables).

4.2 Términos nuevos o poco comunes

  • janitor::clean_names(): Convierte nombres de columna a snake_case limpio. Ejemplo: "Q_Demos_Age""q_demos_age".
  • show_col_types = FALSE: Suprime el mensaje informativo sobre tipos de dato inferidos. No afecta la lectura.
  • Panel MTurk/Prolific: Participantes reclutados en plataformas de crowdsourcing que consintieron en compartir su historial de compras de Amazon. La columna q_prolific_mturk identifica la plataforma de origen de cada participante.
survey <- readr::read_csv(
  "data/survey-data/survey.csv",
  show_col_types = FALSE
) %>%
  janitor::clean_names()

message("Survey cargado: ", nrow(survey), " filas, ", ncol(survey), " columnas")
## Survey cargado: 6325 filas, 41 columnas

Output esperado: Un mensaje del tipo Survey cargado: 5027 filas, 41 columnas. Si aparece el error does not exist in current working directory, ajustar la ruta en knitr::opts_knit$set(root.dir = ...) del chunk de setup para que apunte a la carpeta donde existe data/survey-data/survey.csv.


5 Bloque 5 — Procesamiento de la pregunta de raza (multi-respuesta)

5.1 ¿Qué hace este bloque y por qué es necesario?

La pregunta de raza en el survey es de selección múltiple: un participante puede identificarse con varias razas simultáneamente (ej: "Asian, White or Caucasian"). La columna q_demos_race contiene cadenas de texto con múltiples valores separados por coma, lo que impide aplicar one-hot encoding directamente.

El pipeline de transformación sigue cinco pasos: normalización del texto (espacios irregulares alrededor de separadores), expansión con separate_rows() que convierte cada combinación en una fila por raza (formato largo), asignación a uno de los 6 niveles oficiales mediante factor con levels explícitos, conversión a columnas binarias con pivot_wider() (una por raza, prefijo race_), y verificación de que las 6 columnas esperadas existen — creando con 0 las que no aparezcan en los datos.

Desde el punto de vista del modelo POGIT, las variables race_* entran en la matriz V (covariables de asignación del SoW). A diferencia de variables categóricas estándar, estas dummies de raza no tienen categoría de referencia eliminada porque son multi-selección: un participante puede tener varios “1” simultáneos.

5.2 Términos nuevos o poco comunes

  • separate_rows(race_raw, sep = ",\\s*"): Expande una columna con múltiples valores separados por coma en múltiples filas. El patrón ",\\s*" matchea una coma seguida de cero o más espacios. Transforma el formato multi-valor a formato largo de una observación por fila.
  • factor(race_raw, levels = race_levels): Convierte la columna a factor con los 6 niveles oficiales predefinidos. Cualquier valor que no coincida queda como NA. Garantiza que solo se crean dummies para las 6 categorías del estudio.
  • pivot_wider(names_prefix, values_fill = 0L): Convierte formato largo a ancho, una columna por categoría. values_fill = 0L asigna 0 a las combinaciones participante-raza no presentes en los datos.
  • janitor::make_clean_names(race_levels): Convierte los nombres de los niveles de raza al mismo formato que clean_names(). Se usa para construir los nombres esperados de columnas y verificar que todas existen.
  • race_dummies[setdiff(needed_race, names(race_dummies))] <- 0L: Crea con valor 0 cualquier columna de raza que no aparezca en los datos. Garantiza que el dataframe siempre tiene las 6 columnas esperadas.
race_levels <- c(
  "White or Caucasian",
  "Black or African American",
  "Asian",
  "American Indian/Native American or Alaska Native",
  "Native Hawaiian or Other Pacific Islander",
  "Other"
)

race_long <- survey %>%
  dplyr::select(response_id, race_raw = q_demos_race) %>%
  dplyr::filter(!is.na(race_raw) & race_raw != "") %>%
  dplyr::mutate(
    race_raw = race_raw %>%
      stringr::str_replace_all("\\s*/\\s*", "/") %>%
      stringr::str_replace_all("\\s*,\\s*", ", ") %>%
      stringr::str_squish()
  ) %>%
  tidyr::separate_rows(race_raw, sep = ",\\s*") %>%
  dplyr::mutate(race_raw = stringr::str_trim(race_raw))

race_dummies <- race_long %>%
  dplyr::mutate(
    race  = factor(race_raw, levels = race_levels),
    value = 1L
  ) %>%
  dplyr::select(-race_raw) %>%
  tidyr::pivot_wider(
    names_from   = race,
    values_from  = value,
    names_prefix = "race_",
    values_fill  = 0L
  ) %>%
  janitor::clean_names()

needed_race <- paste0("race_", janitor::make_clean_names(race_levels))
race_dummies[setdiff(needed_race, names(race_dummies))] <- 0L

message("race_dummies: ", ncol(race_dummies) - 1, " columnas | ",
        nrow(race_dummies), " participantes con respuesta de raza")
## race_dummies: 6 columnas | 6325 participantes con respuesta de raza

Output esperado: Mensaje con 6 columnas de raza y el número de participantes con respuesta válida. Las columnas serán: race_white_or_caucasian, race_black_or_african_american, race_asian, race_other, race_american_indian_native_american_or_alaska_native, race_native_hawaiian_or_other_pacific_islander.


6 Bloque 6 — Procesamiento de la pregunta de cambios de vida (multi-respuesta)

6.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque aplica exactamente el mismo pipeline de transformación del Bloque 5, pero a la pregunta sobre cambios de vida en 2021: pérdida de empleo, mudanza, divorcio, nacimiento de un hijo, embarazo. Esta pregunta también es de selección múltiple.

La consistencia del procedimiento no es accidental: ambas preguntas comparten la misma estructura multi-respuesta, por lo que el mismo patrón (separate_rowsfactorpivot_widerclean_names → verificación) aplica directamente. Esta consistencia también facilita la comprensión y auditoría del código.

Desde el ángulo del modelo POGIT, las variables life_* entran también en la matriz V como variables de control: eventos de vida recientes pueden afectar el comportamiento de compra de manera transitoria. La Sección 8 del paper reporta que life_became_pregnant tiene efecto negativo significativo sobre la intensidad de consumo en tecnología (λᵢ) — resultado que podría replicarse o diferir en el caso de Adidas, siendo un hallazgo relevante para comparar entre ambas marcas.

6.2 Términos nuevos o poco comunes

  • life_levels: Vector con los 5 niveles oficiales de la pregunta de cambios de vida. Define los únicos valores válidos que se convertirán en columnas dummy.
  • Variable de control: Variable incluida en el modelo para aislar efectos confundidores, no por ser de interés primario. Permite estimar el efecto de la marca con mayor precisión.
  • Confounding: Sesgo estadístico donde una tercera variable (como un cambio de vida) afecta tanto la variable de interés como la dependiente, generando una asociación espuria si no se controla.
  • needed_life: Vector con los nombres esperados de las 5 columnas dummy de eventos de vida. Se usa para verificar completitud y crear las faltantes con 0.
life_levels <- c(
  "Lost a job",
  "Moved place of residence",
  "Divorce",
  "Had a child",
  "Became pregnant"
)

life_long <- survey %>%
  dplyr::select(response_id, life_raw = q_life_changes) %>%
  dplyr::filter(!is.na(life_raw) & life_raw != "") %>%
  dplyr::mutate(
    life_raw = life_raw %>%
      stringr::str_replace_all("\\s*/\\s*", "/") %>%
      stringr::str_replace_all("\\s*,\\s*", ", ") %>%
      stringr::str_squish()
  ) %>%
  tidyr::separate_rows(life_raw, sep = ",\\s*") %>%
  dplyr::mutate(life_raw = stringr::str_trim(life_raw))

life_dummies <- life_long %>%
  dplyr::mutate(
    life  = factor(life_raw, levels = life_levels),
    value = 1L
  ) %>%
  dplyr::select(-life_raw) %>%
  tidyr::pivot_wider(
    names_from   = life,
    values_from  = value,
    names_prefix = "life_",
    values_fill  = 0L
  ) %>%
  janitor::clean_names()

needed_life <- paste0("life_", janitor::make_clean_names(life_levels))
life_dummies[setdiff(needed_life, names(life_dummies))] <- 0L

message("life_dummies: ", ncol(life_dummies) - 1, " columnas | ",
        nrow(life_dummies), " participantes con respuesta de cambios de vida")
## life_dummies: 5 columnas | 2012 participantes con respuesta de cambios de vida

Output esperado: Mensaje con 5 columnas de eventos de vida y el número de participantes que declararon al menos un cambio. Las columnas serán: life_lost_a_job, life_moved_place_of_residence, life_divorce, life_had_a_child, life_became_pregnant.


7 Bloque 7 — Merge: construcción de survey_enriched

7.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque integra las variables dummy de raza y eventos de vida al survey principal mediante left_join, creando el objeto survey_enriched. Este es el entregable principal del Script 1: el dataframe completo que será consumido por el Script 2 para construir las matrices V y W.

Antes de cada join se aplica distinct(response_id, .keep_all = TRUE) a los tres dataframes para garantizar que no haya response_id duplicados. El argumento relationship = "one-to-one" en left_join() hace esta verificación explícita: si hubiera duplicados, R lanzaría un error en lugar de continuar silenciosamente con datos multiplicados.

El left_join desde survey (izquierda) garantiza que todos los participantes del survey original estén en survey_enriched, aunque no hayan respondido las preguntas de raza o eventos de vida (en cuyo caso sus columnas race_* y life_* serán NA). Estos NA se manejarán en el Script 2.

7.2 Términos nuevos o poco comunes

  • distinct(response_id, .keep_all = TRUE): Elimina filas duplicadas de response_id conservando la primera ocurrencia de todas las columnas. Previene relaciones many-to-many en los joins.
  • relationship = "one-to-one": Verificación explícita en left_join() de que la unión es 1:1. Si un response_id aparece más de una vez en alguno de los dataframes, R lanza un error informativo.
  • left_join(): Une dos dataframes conservando todas las filas del izquierdo. Si un response_id del survey no aparece en race_dummies, las columnas de raza quedan como NA.
  • survey_enriched: Objeto final del Script 1. Tiene las mismas filas que survey más las 11 columnas de dummies (6 de raza + 5 de vida). Es el input del Script 2.
survey       <- survey       %>% dplyr::distinct(response_id, .keep_all = TRUE)
race_dummies <- race_dummies %>% dplyr::distinct(response_id, .keep_all = TRUE)
life_dummies <- life_dummies %>% dplyr::distinct(response_id, .keep_all = TRUE)

survey_enriched <- survey %>%
  dplyr::left_join(race_dummies, by = "response_id",
                   relationship = "one-to-one") %>%
  dplyr::left_join(life_dummies, by = "response_id",
                   relationship = "one-to-one")

message("survey_enriched: ", nrow(survey_enriched), " filas, ",
        ncol(survey_enriched), " columnas")
## survey_enriched: 6325 filas, 52 columnas
colnames(survey_enriched)
##  [1] "duration_in_seconds"                                  
##  [2] "recorded_date"                                        
##  [3] "response_id"                                          
##  [4] "q_prolific_mturk"                                     
##  [5] "q_demos_age"                                          
##  [6] "q_demos_hispanic"                                     
##  [7] "q_demos_race"                                         
##  [8] "q_demos_education"                                    
##  [9] "q_demos_income"                                       
## [10] "q_demos_gender"                                       
## [11] "q_sexual_orientation"                                 
## [12] "q_demos_state"                                        
## [13] "q_amazon_use_howmany"                                 
## [14] "q_amazon_use_hh_size"                                 
## [15] "q_amazon_use_how_oft"                                 
## [16] "q_substance_use_1"                                    
## [17] "q_substance_use_2"                                    
## [18] "q_substance_use_3"                                    
## [19] "q_personal_1"                                         
## [20] "q_personal_2"                                         
## [21] "q_life_changes"                                       
## [22] "q_control"                                            
## [23] "q_altruism"                                           
## [24] "q_bonus_05"                                           
## [25] "q_bonus_20"                                           
## [26] "q_bonus_50"                                           
## [27] "q_data_value_05"                                      
## [28] "q_data_value_20"                                      
## [29] "q_data_value_50"                                      
## [30] "q_data_value_100"                                     
## [31] "q_data_value_any"                                     
## [32] "q_data_value_any_1_text"                              
## [33] "q_sell_your_data"                                     
## [34] "q_sell_consumer_data"                                 
## [35] "q_small_biz_use"                                      
## [36] "q_census_use"                                         
## [37] "q_research_society"                                   
## [38] "q_attn_check"                                         
## [39] "showdata"                                             
## [40] "incentive"                                            
## [41] "connect"                                              
## [42] "race_black_or_african_american"                       
## [43] "race_white_or_caucasian"                              
## [44] "race_asian"                                           
## [45] "race_other"                                           
## [46] "race_american_indian_native_american_or_alaska_native"
## [47] "race_native_hawaiian_or_other_pacific_islander"       
## [48] "life_lost_a_job"                                      
## [49] "life_moved_place_of_residence"                        
## [50] "life_divorce"                                         
## [51] "life_had_a_child"                                     
## [52] "life_became_pregnant"

Output esperado: Mensaje con dimensiones del objeto enriquecido (~5,027 filas, ~52 columnas) seguido de la lista completa de nombres de columna. Este output debe ser idéntico al que produce el code1.R original de Apple.


8 Bloque 8 — Gráficos individuales del survey

8.1 ¿Qué hace este bloque y por qué es necesario?

Este bloque genera los 20 gráficos de barras del survey llamando a bar_plot() una vez por variable. Cada llamada produce un objeto ggplot que se imprime directamente en el documento HTML, permitiendo una exploración visual completa de la distribución de la muestra.

La visualización del survey cumple una función metodológica importante: detectar variables con distribuciones muy desbalanceadas (>95% en una categoría) que podrían causar near-zero variance en el modelo, y verificar que la muestra tiene representación razonable en todos los grupos demográficos reportados en la Sección 2 del paper.

Se usan race_long y life_long (formato largo, generados en los Bloques 5 y 6) para los gráficos de “conteo individual”: esto permite ver la distribución de cada categoría por separado, sin el ruido de las combinaciones multi-respuesta que contiene q_demos_race y q_life_changes.

8.2 Términos nuevos o poco comunes

  • fill_pal = pal[c("azul_1", "naranja")]: Para variables binarias (solo dos categorías), se pasan dos colores contrastantes de la paleta. Produce gráficos más limpios que usar los 10 colores para dos barras.
  • text_size = 2.8: Tamaño reducido de las etiquetas para variables con muchas categorías (raza, educación, estado) donde el tamaño por defecto causaría solapamiento.
  • Near-zero variance: Situación donde casi todos los valores de una variable son iguales. Estas variables aportan casi ninguna información al modelo y pueden causar inestabilidad numérica en la estimación.
bar_plot(survey, q_prolific_mturk,
         "Participation in Amazon MTurk", "Response")

bar_plot(survey, q_demos_age,
         "Age group distribution", "Age group")

bar_plot(survey, q_demos_hispanic,
         "Hispanic/Latino origin", "Response",
         fill_pal = pal[c("azul_1", "naranja")])

bar_plot(survey, q_demos_race,
         "Race combinations (declared)", "Combination",
         text_size = 2.8)

bar_plot(race_long, race_raw,
         "Participants by race (individual count)", "Race")

bar_plot(survey, q_demos_education,
         "Education level", "Education", text_size = 2.8)

bar_plot(survey, q_demos_gender,
         "Declared gender identity", "Gender")

bar_plot(survey, q_sexual_orientation,
         "Declared sexual orientation", "Orientation")

bar_plot(survey, q_demos_state,
         "State distribution", "State", text_size = 2.4)

bar_plot(survey, q_amazon_use_howmany,
         "People sharing Amazon account", "Number of people")

bar_plot(survey, q_amazon_use_hh_size,
         "Household size", "People in household")

bar_plot(survey, q_amazon_use_how_oft,
         "Amazon order frequency", "Frequency")

bar_plot(survey, q_substance_use_1,
         "Household cigarette use", "Response")

bar_plot(survey, q_substance_use_2,
         "Household marijuana use", "Response")

bar_plot(survey, q_substance_use_3,
         "Household alcohol use", "Response")

bar_plot(survey, q_personal_1,
         "Diabetes in household", "Response")

bar_plot(survey, q_personal_2,
         "Wheelchair use in household", "Response")

bar_plot(survey, q_life_changes,
         "Life changes in 2021 (combinations)", "Combination",
         text_size = 2.8)

bar_plot(life_long, life_raw,
         "Life changes in 2021 (individual)", "Life change")

bar_plot(survey, q_demos_income,
         "Income range", "Response")

Output esperado: 20 gráficos de barras horizontales renderizados en el documento, uno por variable. Cada gráfico muestra barras ordenadas de mayor a menor con etiquetas de porcentaje. Este output es idéntico al que produce el code1.R original de Apple.


9 Bloque 9 — Exportación del PDF de gráficos

9.1 ¿Qué hace este bloque y por qué es necesario?

⚠️ MOD-01 — CAMBIO ADIDAS: El nombre del PDF cambia de "survey_barplots.pdf" a "survey_barplots_adidas.pdf". Es la única modificación en todo el Script 1. El contenido es idéntico porque describe el mismo survey demográfico.

Este bloque almacena los 20 gráficos en la lista plots_survey y los exporta a un único archivo PDF. La razón de este doble paso — imprimir en pantalla en el Bloque 8, luego construir la lista aquí — es que el Bloque 8 sirve para la visualización interactiva en el Rmd, mientras que esta lista sirve exclusivamente para la exportación al archivo PDF.

El flujo es: pdf() abre un dispositivo gráfico de archivo, el for loop imprime cada gráfico con print() explícito (necesario porque dentro de loops los objetos ggplot no se imprimen automáticamente), y dev.off() cierra el dispositivo y escribe el archivo en disco.

9.2 Términos nuevos o poco comunes

  • Dispositivo gráfico: Destino donde R envía los gráficos. pdf() redirige la salida gráfica a un archivo en lugar de a la pantalla.
  • grDevices::pdf(out_pdf, width = 10, height = 7): Abre dispositivo gráfico PDF con dimensiones en pulgadas. Las dimensiones 10×7 son adecuadas para barras horizontales.
  • for (p in plots_survey) print(p): Dentro de un loop, los objetos ggplot no se imprimen automáticamente. print() explícito es necesario para enviarlos al dispositivo activo.
  • grDevices::dev.off(): Cierra el dispositivo gráfico activo y finaliza la escritura del archivo. Sin este paso, el PDF puede quedar corrupto o incompleto.
plots_survey <- list(
  bar_plot(survey, q_prolific_mturk,
           "Participation in Amazon MTurk", "Response"),
  bar_plot(survey, q_demos_age,
           "Age group distribution", "Age group"),
  bar_plot(survey, q_demos_hispanic,
           "Hispanic/Latino origin", "Response",
           fill_pal = pal[c("azul_1", "naranja")]),
  bar_plot(survey, q_demos_race,
           "Race combinations (declared)", "Combination",
           text_size = 2.8),
  bar_plot(race_long, race_raw,
           "Participants by race (individual count)", "Race"),
  bar_plot(survey, q_demos_education,
           "Education level", "Education", text_size = 2.8),
  bar_plot(survey, q_demos_gender,
           "Declared gender identity", "Gender"),
  bar_plot(survey, q_sexual_orientation,
           "Declared sexual orientation", "Orientation"),
  bar_plot(survey, q_demos_state,
           "State distribution", "State", text_size = 2.4),
  bar_plot(survey, q_amazon_use_howmany,
           "People sharing Amazon account", "Number of people"),
  bar_plot(survey, q_amazon_use_hh_size,
           "Household size", "People in household"),
  bar_plot(survey, q_amazon_use_how_oft,
           "Amazon order frequency", "Frequency"),
  bar_plot(survey, q_substance_use_1,
           "Household cigarette use", "Response"),
  bar_plot(survey, q_substance_use_2,
           "Household marijuana use", "Response"),
  bar_plot(survey, q_substance_use_3,
           "Household alcohol use", "Response"),
  bar_plot(survey, q_personal_1,
           "Diabetes in household", "Response"),
  bar_plot(survey, q_personal_2,
           "Wheelchair use in household", "Response"),
  bar_plot(survey, q_life_changes,
           "Life changes in 2021 (combinations)", "Combination",
           text_size = 2.8),
  bar_plot(life_long, life_raw,
           "Life changes in 2021 (individual)", "Life change"),
  bar_plot(survey, q_demos_income,
           "Income range", "Response")
)

# [MOD-01] Nombre del PDF cambiado para Adidas
out_pdf <- "survey_barplots_adidas.pdf"
grDevices::pdf(out_pdf, width = 10, height = 7)
for (p in plots_survey) print(p)
grDevices::dev.off()
## png 
##   2
message("Saved: ", out_pdf)
## Saved: survey_barplots_adidas.pdf

Output esperado: El mensaje Saved: survey_barplots_adidas.pdf confirmando que el archivo fue escrito en el directorio de trabajo. El PDF tendrá 20 páginas, una por gráfico.


10 Resumen de modificaciones respecto a code1.R (caso Apple)

Bloque Estado Detalle
1 — Paquetes Sin cambios Mismos paquetes
2 — Config visual Sin cambios Misma paleta y locale
3 — Funciones (bar_plot, mode_safe, reorder_drop_id) Sin cambios Funciones idénticas al original
4 — Carga survey (survey.csv) Sin cambios Mismo archivo
5 — Race dummies Sin cambios Mismos 6 niveles, mismo pipeline
6 — Life dummies Sin cambios Mismos 5 niveles, mismo pipeline
7 — survey_enriched Sin cambios Mismo merge con left_join
8 — Gráficos individuales Sin cambios Mismas 20 variables
9 — Export PDF MOD-01 PDF renombrado a survey_barplots_adidas.pdf