Código
pacman::p_load(dplyr, DT, here)
here::i_am("pre-pilot-analysis/ppilot-analysis.qmd")Este documento presenta los análisis del pre-piloto de la Encuesta JUSMER Justicia y Merecimiento, la cual se enmarca en el FONDECYT Regular N°1250518 “Justicia de mercado y Merecimiento del bienestar social” (jus-mer.com). El objetivo de este insumo es orientar la toma de decisiones para la construcción de la versión final de la encuesta.
Para ello, el documento se estructura a partir de los siguientes ejes: Primero se presentan los antecedentes principales del pre-piloto. Luego, se exponen los resultados del análisis de los comentarios abiertos, para posteriormente presentar los resultados del análisis de los tiempos de respuesta de la encuesta, detallando por pregunta y por módulo. Seguido de esto, se presentan propuestas para la versión final de la encuesta en base a los resultados del análisis. Por último, se presentan las conclusiones y recomendaciones finales.
El pre-piloto de la Encuesta JUSMER se realizó en dos etapas:
Primero se aplicó un pre-piloto con todas las preguntas que se decidieron incluir en la encuesta, además de integrar espacios de comentarios para recibir retroalimentación de los respondentes. Esta versión estuvo activa desde el 03 de julio hasta el 12 de julio del 2026 (solamente una respuesta válida se escapa de este plazo, la cual fue registrada el 20 de julio).
Posteriormente, se aplicó un segundo pre-piloto con las mismas preguntas la primera versión, pero la diferencia fue que se eliminaron las cajas de comentarios, ya que esta versión buscaba recabar información sobre los tiempos de respuesta de cada pregunta y batería temática. El segundo pre-piloto estuvo activo desde 15 de julio hasta el 20 de ese mismo mes.
Ambas versiones del pre-piloto fueron construidas con surveydown, una plataforma de código abierto para la generación de encuestas, la cual se basa mayoritariamente en las lógicas de Quarto y Shinyapp. Para el despliegue de la encuesta recurrimos a dos plataformas: El primer pre-piloto se alojó en HuggingFace, mientras que el segundo se alojó en Posit Cloud. Esto se debió al intento de explorar plataformas que fueran más eficientes para el despliegue de la encuesta, ya que shinyapp.io presentaba problemas de lentitud y límites de almacenamiento por parte del servidor. Por último, para el almacenamiento de datos se utilizó Supabase, una plataforma gratuita que permite conectar las encuestas con bases de datos alojadas en la nube, las cuales se actualizan automáticamente.
Cargamos los paquetes necesarios para el análisis y fijamos la ubicación del documento para facilitar la llamada de archivos externos
pacman::p_load(dplyr, DT, here)
here::i_am("pre-pilot-analysis/ppilot-analysis.qmd")resumen <- read.csv(
here::here("pre-pilot-analysis", "tbl/comentarios-prepiloto-resumen.csv"),
stringsAsFactors = FALSE,
check.names = FALSE
)Se unificaron los datos de ambas tablas de Supabase con información actualizada el 23/07, las cuales almacenaron información de las dos versiones de pilotaje implementadas; existen 20 casos válidos para el análisis.
Cálculos relevantes para los tiempos de respuesta:
time_end - time_start para cada caso (marca de tiempo de término de la encuesta - marca de tiempo del inicio de la encuesta).Primero se cargan las bases de datos extraídas de Supabase y se unifican en una sola base de datos los casos válidos para su posterior análisis.
pacman::p_load(dplyr, DT)
comentarios_csv <- here::here("input", "tbl/comentarios-prepiloto.csv")
tiempos_csv <- here::here("input", "tbl/tiempos-prepiloto.csv")
union_csv <- here::here("pre-pilot-analysis", "tbl/tiempos-prepiloto-union.csv")
ids_muestra <- c(1394, 1924, 501, 1785, 1722, 1968, 374, 1878) # casos validos del primer prepiloto
comentarios <- read.csv(comentarios_csv, stringsAsFactors = FALSE, check.names = FALSE,
colClasses = "character", na.strings = "", encoding = "UTF-8")
comentarios <- comentarios[comentarios$respID %in% ids_muestra, ]
tiempos <- read.csv(tiempos_csv, stringsAsFactors = FALSE, check.names = FALSE,
colClasses = "character", na.strings = "", encoding = "UTF-8")
# comentarios-prepiloto.csv (primer pre-piloto) trae columnas adicionales (bloque de comentarios) que no existen en tiempos-prepiloto.csv (segundo pre-piloto); se conservan solo las columnas en comun para poder unir ambas bases.
comentarios <- comentarios[, names(tiempos)]
df_union <- rbind(tiempos, comentarios)
dir.create(dirname(union_csv), showWarnings = FALSE, recursive = TRUE)
write.csv(df_union, union_csv, row.names = FALSE, na = "")Se filtran los casos válidos mediante la confirmación del consentimiento como medida de robustez y luego se calculan los parámetros de tiempo mediante el parseo de las marcas temporales
df <- df_union
df <- df[!is.na(df$consent_understand) & df$consent_understand == "yes", ]
parse_ts <- function(x) {
as.POSIXct(sub(" UTC$", "", x), format = "%Y-%m-%d %H:%M:%S", tz = "UTC")
}
df$time_start <- parse_ts(df$time_start)
df$time_end <- parse_ts(df$time_end)
verifica_1 <- df %>%
filter(respID == 817) # crear objeto para comprobar el for
qcols <- grep("^time_q_", names(df), value = TRUE)
for (col in qcols) df[[col]] <- parse_ts(df[[col]]) # este for convierte todas las columnas de "tiempo por pregunta" de texto a formato de fecha/hora utilizable (character a POSIXct)
for (q in paste0("cbc_q", 1:6)) {
df[[paste0("time_q_", q)]] <- parse_ts(df[[paste0("time_p_", q, "_page")]])
} # considera la marca temporal del cambio de página como tiempo por pregunta sólo para las preguntas del conjointPara las preguntas del conjoint (cbc_q1 a cbc_q6), time_q_ (tiempo en que responde una pregunta) tiene vacíos reales no atribuibles a preguntas no respondidas (la respuesta y time_p_..._page, que marca el tiempo del cambio de página sí están presentes), por lo que se reemplaza por el timestamp de cambio de página que es la medida confiable para estas preguntas (cada tarea del conjoint se aloja en una página por separado). Los items se registran sin problemas.
Comprobamos que el bucle que vuelve manipulable la hora (for) funcione correctamente
class(verifica_1$time_q_mj_1)[1] "character"
qcols <- grep("^time_q_", names(verifica_1), value = TRUE)
for (col in qcols) verifica_1[[col]] <- parse_ts(verifica_1[[col]])
for (q in paste0("cbc_q", 1:6)) {
verifica_1[[paste0("time_q_", q)]] <- parse_ts(verifica_1[[paste0("time_p_", q, "_page")]])
} # replica en verifica_1 el mismo ajuste aplicado a df para las preguntas del conjoint
class(verifica_1$time_q_mj_1)[1] "POSIXct" "POSIXt"
El cambio se aplica exitosamente
Estimamos el tiempo total de la encuesta por cada respondente
tiempo_caso <- data.frame(
session_id = df$session_id,
respID = df$respID,
assigned_module = df$assigned_module,
time_start = df$time_start,
time_end = df$time_end,
tiempo_total_seg = as.numeric(difftime(df$time_end, df$time_start, units = "secs")),
stringsAsFactors = FALSE
)
tiempo_caso$tiempo_total_min <- round(tiempo_caso$tiempo_total_seg / 60, 2)# Objetos que almacenan las preguntas de cada batería temática, para luego poder generar tablas de resumen por batería.
demo_1 <- c("respondent", "sexo", "year_of_birth", "education")
conjoint <- c("cbc_practice", "manip_check", "cbc_q1", "cbc_q2", "cbc_q3", "cbc_q4", "cbc_q5", "cbc_q6")
mjp <- c("mj_1", "mj_2", "mj_3")
mjp_new <- c("ref_1", "ref_2", "ref_3", "proy_1", "proy_2")
mjp_time <- c("pres_1", "pres_2", "pres_3", "pres_4", "pres_5", "pres_6")
comodifica <- c("valor_1", "valor_2", "valor_3", "valor_4", "valor_5")
exp_comodifica <- c("dec_1", "dec_2", "dec_3")
lucro <- c("lucro_1", "lucro_2", "lucro_3")
neoliberal <- c("neolib_1", "neolib_2", "neolib_3", "neolib_4")
nicera <- c("control_1", "control_2", "control_3", "actitud_1", "actitud_2", "reciproc_1", "reciproc_2", "reciproc_3", "actitud_4", "id_1", "id_2", "id_3", "nec_1", "nec_2", "nec_3", "esf_1", "esf_2", "esf_3", "esf_4", "esf_5")
aditional <- c("adc_qstn", "adc_5", "adc_6", "adc_7")
demo_2 <- c("gender", "income", "salud_prov", "educ_prov")Se calcula el tiempo de respuesta de cada pregunta
duracion_mat <- matrix(NA_real_, nrow = nrow(df), ncol = length(qcols),
dimnames = list(NULL, qcols))
for (i in seq_len(nrow(df))) {
last_time <- df$time_start[i]
for (col in qcols) {
cur <- df[[col]][i]
if (!is.na(cur)) {
duracion_mat[i, col] <- as.numeric(difftime(cur, last_time, units = "secs")) # se calcula la diferencia entre un checkpoint (marca temporal de la pregunta) y el checkpoint anterior
last_time <- cur
}
}
} # este for calcula cuánto tiempo tardó cada respondente en cada pregunta
detalle_list <- lapply(qcols, function(col) {
data.frame(
session_id = df$session_id,
respID = df$respID,
assigned_module = df$assigned_module,
pregunta = sub("^time_q_", "", col),
tiempo_seg = as.numeric(difftime(df[[col]], df$time_start, units = "secs")),
duracion_seg = duracion_mat[, col],
stringsAsFactors = FALSE
)
})
tiempo_pregunta_detalle <- do.call(rbind, detalle_list)
tiempo_pregunta_detalle <- tiempo_pregunta_detalle[!is.na(tiempo_pregunta_detalle$tiempo_seg), ]
# Desviacion estandar, por caso, de la duracion (duracion_seg) entre todas
# las preguntas que respondio: mide que tan variable fue su ritmo de
# respuesta pregunta a pregunta.
sd_duracion_caso <- stats::aggregate(
duracion_seg ~ session_id,
data = tiempo_pregunta_detalle,
FUN = function(x) sd(x, na.rm = TRUE)
)
names(sd_duracion_caso)[2] <- "sd_duracion_pregunta_seg"
tiempo_caso <- merge(tiempo_caso, sd_duracion_caso, by = "session_id", all.x = TRUE)
tiempo_caso$sd_duracion_pregunta_seg <- round(tiempo_caso$sd_duracion_pregunta_seg, 2)Verificamos la aplicación correcta del bucle recalculando manualmente las duraciones para un solo caso (verifica_1 contiene sólo el caso 817) y comparando el resultado contra la fila generada por el bucle
verifica_1[qcols] <- lapply(verifica_1[qcols], parse_ts)
last_time <- verifica_1$time_start
verif_tbl <- data.frame(
pregunta = character(),
checkpoint_anterior = as.POSIXct(character()),
tiempo_pregunta = as.POSIXct(character()),
duracion_seg = numeric(),
stringsAsFactors = FALSE
)
for (col in qcols) {
cur <- verifica_1[[col]][1]
if (!is.na(cur)) {
verif_tbl <- rbind(verif_tbl, data.frame(
pregunta = sub("^time_q_", "", col),
checkpoint_anterior = last_time,
tiempo_pregunta = cur,
duracion_seg = as.numeric(difftime(cur, last_time, units = "secs"))
))
last_time <- cur
}
} # se aplica el for a la base verifica_1
i_verifica <- which(df$respID == verifica_1$respID[1])
verif_tbl$duracion_mat_original <- unname(duracion_mat[i_verifica, paste0("time_q_", verif_tbl$pregunta)])
verif_tbl| pregunta | checkpoint_anterior | tiempo_pregunta | duracion_seg | duracion_mat_original |
|---|---|---|---|---|
| consent_understand | 2026-07-20 14:19:14 | 2026-07-20 14:19:37 | 23 | 23 |
| respondent | 2026-07-20 14:19:37 | 2026-07-20 14:19:44 | 7 | 7 |
| sexo | 2026-07-20 14:19:44 | 2026-07-20 14:19:48 | 4 | 4 |
| year_of_birth | 2026-07-20 14:19:48 | 2026-07-20 14:19:53 | 5 | 5 |
| education | 2026-07-20 14:19:53 | 2026-07-20 14:19:58 | 5 | 5 |
| cbc_practice | 2026-07-20 14:19:58 | 2026-07-20 14:20:55 | 57 | 57 |
| manip_check | 2026-07-20 14:20:55 | 2026-07-20 14:21:17 | 22 | 22 |
| cbc_q1 | 2026-07-20 14:21:17 | 2026-07-20 14:21:18 | 1 | 1 |
| cbc_q2 | 2026-07-20 14:21:18 | 2026-07-20 14:22:40 | 82 | 82 |
| cbc_q3 | 2026-07-20 14:22:40 | 2026-07-20 14:22:56 | 16 | 16 |
| cbc_q4 | 2026-07-20 14:22:56 | 2026-07-20 14:23:21 | 25 | 25 |
| cbc_q5 | 2026-07-20 14:23:21 | 2026-07-20 14:37:18 | 837 | 837 |
| cbc_q6 | 2026-07-20 14:37:18 | 2026-07-20 14:38:05 | 47 | 47 |
| mj_1 | 2026-07-20 14:38:05 | 2026-07-20 14:38:49 | 44 | 44 |
| mj_2 | 2026-07-20 14:38:49 | 2026-07-20 14:39:01 | 12 | 12 |
| mj_3 | 2026-07-20 14:39:01 | 2026-07-20 14:39:08 | 7 | 7 |
| ref_1 | 2026-07-20 14:39:08 | 2026-07-20 14:39:29 | 21 | 21 |
| ref_2 | 2026-07-20 14:39:29 | 2026-07-20 14:39:33 | 4 | 4 |
| ref_3 | 2026-07-20 14:39:33 | 2026-07-20 14:39:40 | 7 | 7 |
| proy_1 | 2026-07-20 14:39:40 | 2026-07-20 14:40:00 | 20 | 20 |
| proy_2 | 2026-07-20 14:40:00 | 2026-07-20 14:40:05 | 5 | 5 |
| ranking_politicas_educacion | 2026-07-20 14:40:05 | 2026-07-20 14:41:37 | 92 | 92 |
| ranking_politicas_salud | 2026-07-20 14:41:37 | 2026-07-20 14:41:48 | 11 | 11 |
| ranking_politicas_pensiones | 2026-07-20 14:41:48 | 2026-07-20 14:41:50 | 2 | 2 |
| ranking_politicas_cuidado_ninos | 2026-07-20 14:41:50 | 2026-07-20 14:41:43 | -7 | -7 |
| rank_1 | 2026-07-20 14:41:43 | 2026-07-20 14:42:18 | 35 | 35 |
| rank_2 | 2026-07-20 14:42:18 | 2026-07-20 14:42:23 | 5 | 5 |
| rank_3 | 2026-07-20 14:42:23 | 2026-07-20 14:42:31 | 8 | 8 |
| rank_4 | 2026-07-20 14:42:31 | 2026-07-20 14:42:34 | 3 | 3 |
| rank_5 | 2026-07-20 14:42:34 | 2026-07-20 14:42:38 | 4 | 4 |
| pres_1 | 2026-07-20 14:42:38 | 2026-07-20 14:42:53 | 15 | 15 |
| pres_2 | 2026-07-20 14:42:53 | 2026-07-20 14:42:55 | 2 | 2 |
| pres_3 | 2026-07-20 14:42:55 | 2026-07-20 14:42:59 | 4 | 4 |
| pres_4 | 2026-07-20 14:42:59 | 2026-07-20 14:43:11 | 12 | 12 |
| pres_5 | 2026-07-20 14:43:11 | 2026-07-20 14:43:18 | 7 | 7 |
| pres_6 | 2026-07-20 14:43:18 | 2026-07-20 14:43:27 | 9 | 9 |
| valor_1 | 2026-07-20 14:43:27 | 2026-07-20 14:43:42 | 15 | 15 |
| valor_2 | 2026-07-20 14:43:42 | 2026-07-20 14:43:57 | 15 | 15 |
| valor_3 | 2026-07-20 14:43:57 | 2026-07-20 14:44:10 | 13 | 13 |
| valor_4 | 2026-07-20 14:44:10 | 2026-07-20 14:44:21 | 11 | 11 |
| valor_5 | 2026-07-20 14:44:21 | 2026-07-20 14:44:47 | 26 | 26 |
| gender | 2026-07-20 14:44:47 | 2026-07-20 14:44:58 | 11 | 11 |
| income | 2026-07-20 14:44:58 | 2026-07-20 14:45:07 | 9 | 9 |
| salud_prov | 2026-07-20 14:45:07 | 2026-07-20 14:45:19 | 12 | 12 |
| educ_prov | 2026-07-20 14:45:19 | 2026-07-20 14:45:22 | 3 | 3 |
A partir de la tabla, podemos observar que el bucle logra contabilizar exitosamente los segundos entre una pregunta y su sucesora.
Se construye un resumen de descriptivos por pregunta (n, media, mediana, sd, min, max)
resumen_list <- lapply(qcols, function(col) {
pregunta <- sub("^time_q_", "", col)
modulos_presentes <- sort(unique(df$assigned_module[!is.na(df[[col]])]))
modulo_txt <- if (length(modulos_presentes) == 0) {
NA_character_
} else if (length(modulos_presentes) == length(unique(na.omit(df$assigned_module)))) {
"Todos"
} else {
paste(modulos_presentes, collapse = "/")
}
x <- tiempo_pregunta_detalle$tiempo_seg[tiempo_pregunta_detalle$pregunta == pregunta]
d <- tiempo_pregunta_detalle$duracion_seg[tiempo_pregunta_detalle$pregunta == pregunta]
data.frame(
pregunta = pregunta,
modulo = modulo_txt,
n = length(x),
media_seg = round(mean(x), 2),
mediana_seg = round(median(x), 2),
sd_seg = round(sd(x), 2),
min_seg = round(min(x, Inf), 2),
max_seg = round(max(x, -Inf), 2),
duracion_media_seg = round(mean(d), 2),
duracion_mediana_seg = round(median(d), 2),
stringsAsFactors = FALSE
)
})
tiempo_pregunta_resumen <- do.call(rbind, resumen_list)
tiempo_pregunta_resumen <- tiempo_pregunta_resumen[tiempo_pregunta_resumen$n > 0, ]Se calcula una estimación del tiempo total de la encuesta si un mismo respondente tuviera que responder la seccion común + los tres módulos (A + B + C), en vez de solo el que le tocó por aleatorización.
Para cada caso se suman las duraciones (duracion_seg) de sus preguntas comúnes y, por separado, las de sus módulos asignados; luego se promedia cada módulo por separado y se suman los 4 promedios.
tiempo_pregunta_detalle <- merge(
tiempo_pregunta_detalle,
tiempo_pregunta_resumen[, c("pregunta", "modulo")],
by = "pregunta", all.x = TRUE
)
# El caso 374 se excluye en la Tabla Tiempo de respuesta por respondente (tabla 2) para no alterar los tiempos promedio de respuesta; sigue presente en el resto del reporte.
detalle_estimado <- tiempo_pregunta_detalle[tiempo_pregunta_detalle$respID != "374", ]
suma_por_caso <- function(seccion) {
sub <- detalle_estimado[detalle_estimado$modulo == seccion, ]
stats::aggregate(duracion_seg ~ session_id, data = sub, FUN = sum, na.rm = TRUE)
}
comun_caso <- suma_por_caso("Todos")
moduloA_caso <- suma_por_caso("A")
moduloB_caso <- suma_por_caso("B")
moduloC_caso <- suma_por_caso("C")
tiempo_estimado_total <- data.frame(
seccion = c("Común (todos)", "Modulo A", "Modulo B", "Modulo C"),
n_casos = c(nrow(comun_caso), nrow(moduloA_caso), nrow(moduloB_caso), nrow(moduloC_caso)),
promedio_seg = round(c(
mean(comun_caso$duracion_seg),
mean(moduloA_caso$duracion_seg),
mean(moduloB_caso$duracion_seg),
mean(moduloC_caso$duracion_seg)
), 2),
stringsAsFactors = FALSE
)
tiempo_estimado_total$promedio_min <- round(tiempo_estimado_total$promedio_seg / 60, 2)
tiempo_estimado_total <- rbind(
tiempo_estimado_total,
data.frame(
seccion = "Total estimado (común + A + B + C)",
n_casos = NA_integer_,
promedio_seg = round(sum(tiempo_estimado_total$promedio_seg), 2),
promedio_min = round(sum(tiempo_estimado_total$promedio_seg) / 60, 2)
)
)Guardamos las salidas para luego llamar a cada tabla en la sección correspondiente.
out_dir <- here::here("output/tiempos-prepiloto")
dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)
write.csv(tiempo_caso, file.path(out_dir, "tiempos-caso.csv"), row.names = FALSE, na = "")
write.csv(tiempo_pregunta_detalle, file.path(out_dir, "tiempos-pregunta-detalle.csv"), row.names = FALSE, na = "")
write.csv(tiempo_pregunta_resumen, file.path(out_dir, "tiempos-pregunta-resumen.csv"), row.names = FALSE, na = "")
write.csv(tiempo_estimado_total, file.path(out_dir, "tiempos-estimado-total.csv"), row.names = FALSE, na = "")Aquí se cargan las tablas para el análisis de la sección 3.3, las cuales fueron construidas manualmente para visualizar de mejor manera los tiempos de respuesta de las secciones de Ranking de políticas y Participación Estado/Mercado.
tiempos_part <- read.csv(
here::here("input", "tbl/tiempos-part.csv"),
stringsAsFactors = FALSE, check.names = FALSE, na.strings = ""
)
names(tiempos_part)[1] <- "respID"
tiempo_ranking <- read.csv(
here::here("input", "tbl/tiempo-ranking.csv"),
stringsAsFactors = FALSE, check.names = FALSE, na.strings = ""
)
names(tiempo_ranking)[1] <- "respID"La Tabla 1 presenta la recopilación de los comentarios realizados por los respondentes en la primera versión del pre-piloto. Estos comentarios se pueden entender en dos grupos: (1) comentarios relacionados a items y (2) comentarios sobre la tarea de asignación.
Respecto a los comentarios sobre items, se presentan dudas respecto a la comprensión de las preguntas y sugerencias para aclarar lo que buscan medir. Hay un comentario sobre cómo responder la pregunta de nivel educativo en caso de que el respondente fuera universitario, por lo que queda en una posición intermedia entre “educación media completa” y “educación universitaria incompleta”. En la misma línea, la pregunta de ingresos también generó confusión debido a que no le quedó claro a un encuestado si se preguntaba por el ingreso total del hogar o a nivel individual. Además, se realizó una sugerencia para consultar de manera más precisa el género de las personas, incluyendo categorías que aluden a disidencias sexogenéricas.
Los comentarios de la tarea de asignación apuntan principalmente al peso de los atributos que presenta el conjoint, donde se señaló que sólo un atributo llegaba a determinar la decisión de los respondentes, entendiéndose que las demás características eran prescindibles. En este marco, para un encuestado el atributo que orientó toda su decisión fue el género, mientras que para otro fue la necesidad, expresada en términos económicos. Además, se sugirió que para el atributo de necesidad se modificara el nivel de “holgura” ya que inducía a confusión. Si bien, estos comentarios sobre la tarea de asignación pueden ser fructíferos para replantear su modo de implementación (cantidad de atributos y niveles, expresión de los niveles, entre otros), el hecho de que un atributo tenga más peso que otros en la toma de decisiones es justamente lo que busca comprender el conjoint, por lo que son sugerencias que se deben considerar pero con la cautela suficiente.
Para el análisis de comentarios se omitieron todos aquellos que provienen del Equipo JUSMER, los cuales se realizaron para comprobar el despliegue exitoso de la encuesta en las plataformas correspondientes
tiempo_caso |>
filter(respID != "749") |>
select(
respID, assigned_module, time_start, time_end, tiempo_total_min,
sd_duracion_pregunta_seg
) |>
arrange(desc(tiempo_total_min)) |>
datatable(
filter = "top",
rownames = FALSE,
options = list(pageLength = 15, scrollX = TRUE),
colnames = c(
"respID", "Módulo", "Inicio", "Fin", "Tiempo total (min)",
"DE duración por pregunta (s)"
)
)La Tabla 2 expone los tiempos de demora en finalizar la encuesta para cada respondente válido. Las últimas dos columnas son las más importantes: Tiempo total (min) muestra los minutos que utilizó el encuestado para completar la encuesta, y DE duración por pregunta (s) representa la dispersión en los segundos de respuesta de las personas por pregunta.
El caso respID 374 tiene un tiempo total de 16.062 minutos, muy por sobre el resto. Esto se debe a que abrió la encuesta en un inicio, marcando el time_start, pero la completó con más de una semana de dilación. Sin embargo, al observar sus respuestas, estas se mantienen en los tiempos normales, comprobando el tiempo en el que respondió la primera y última pregunta (se demoró aproximadamente 15 minutos).
A partir de los resultados presentados en la tabla se observa que solamente hay 3 casos de los 20 totales que lograron responder la encuesta en el tiempo establecido inicialmente (15 minutos como máximo). Al flexibilizar el umbral de respuesta podemos considerar al caso 374, junto con los 3 que se demoraron ~16 minutos. De esta manera, quienes estuvieron en un margen de tiempo de respuesta aceptable se eleva a 7 casos. Aun así, no se alcanza un 50% de éxito en los tiempos de respuesta, incluso considerando que se aplicó una aleatorización de módulos para que la encuesta no fuese demasiado extensa.
En esta sección se presenta la duración estimada de cada pregunta del cuestionario agrupada temáticamente, para así realizar un análisis detallado de cada batería y cada pregunta. El tiempo por pregunta se calcula mediante la diferencia entre la hora registrada y la hora del checkpoint anterior, es decir, la pregunta que la precede. A partir de esto, se presentan el tiempo promedio de respuesta y la mediana del tiempo de cada pregunta.
Para establecer una comparación entre las preguntas más rápidas de responder y aquellas más demorosas, se fijó como umbral un máximo de 30 segundos, es decir, las preguntas que demoran (tanto en su promedio como en la mediana) > 30 segundos en ser respondidas, se encuentran destacadas con rojo.
Generamos la lista de baterías temáticas recurriendo a los objetos creados en el chunk batterie-division, y luego construimmos una función que permite visualizar los tiempos promedios y la mediana de cada pregunta de la batería respectiva.
baterias <- list(
"Demográficos inicio" = demo_1,
"Conjoint" = conjoint,
"Market Justice Preferences" = mjp,
"Market Justice Preferences Nuevos" = mjp_new,
"MJP Tiempo" = mjp_time,
"Comodificación" = comodifica,
"Escenario comodificación" = exp_comodifica,
"Lucro" = lucro,
"Neoliberalismo" = neoliberal,
"NICERA" = nicera,
"Adicionales" = aditional,
"Demográficos (bloque 2)" = demo_2
)
tabla_bateria <- function(nm) {
ids <- baterias[[nm]]
tab <- tiempo_pregunta_resumen[tiempo_pregunta_resumen$pregunta %in% ids, ]
tab <- tab[order(match(tab$pregunta, ids)), ]
tab$label <- resumen$label[match(tab$pregunta, resumen$variable)]
tab |>
select(pregunta, label, modulo, n, duracion_media_seg, duracion_mediana_seg) |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tab), scrollX = TRUE),
colnames = c(
"Pregunta", "Label", "Módulo", "N",
"Duración media (s)", "Duración mediana (s)"
)
) |>
formatStyle(
c("duracion_media_seg", "duracion_mediana_seg"),
backgroundColor = styleInterval(30, c("white", "#f79b9b"))
)
}tabla_bateria("Conjoint")tabla_bateria("Market Justice Preferences")tabla_bateria("Market Justice Preferences Nuevos")pres_1, pres_2 y pres_3 tienen el siguiente preámbulo: ¿En qué medida está de acuerdo o en desacuerdo con las siguientes afirmaciones?
En cambio, pres_4, pres_5 y pres_6 anuncian: Y pensando hace 10 años atrás, ¿usted cree que el acceso a los siguientes servicios entre quienes tienes más y menores ingresos se ha vuelto…?
tabla_bateria("MJP Tiempo")tabla_bateria("Comodificación")tabla_bateria("Escenario comodificación")El preámbulo de la batería de Lucro es: Qué tan justo o injusto le parece que empresas privadas obtengan ganancias monetarias administrando…
tabla_bateria("Lucro")tabla_bateria("Neoliberalismo")tabla_bateria("NICERA")tabla_bateria("Adicionales")tabla_bateria("Demográficos inicio")tabla_bateria("Demográficos (bloque 2)")Los resultados demuestran, en primer lugar, que todas la tareas de asignación rondan los 40 segundos de demora, a excepción de dos: el ejemplo, que se demora en promedio más de 2 minutos, y la tarea 1 que promedia 16 segundos. Para la primera excepción, esto encuentra sentido en que los respondentes deben entender a cabalidad la tarea de asignación, lo cual requiere un esfuerzo de comprensión lectora y de atención. Para la segunda excepción esto puede deberse a la rápida asociación del ejercicio con su ejemplo. Por lo demás, las otras tareas del conjoint se encuentran dentro de rangos temporales similares, con una mediana de 20-30 segundos de demora por tarea (ver Tabla 3).
Para la batería de Market Justice Preferences (MJP), se observa que las preguntas 1 y 2 son las que más tiempo demandan, mientras que la pregunta 3 es la más rápida de responder (ver Tabla 4). Con la idea de avanzar en la medición de MJP, se implementaron dos nuevas baterías: MJP Nuevos (Tabla 5) y MJP Tiempo (Tabla 6). Para ambas baterías se observa un tiempo de demora debajo del umbral establecido, donde la batería que busca medir los cambios en el tiempo de MJP es la que contiene preguntas menos demorosas, donde cuatro de ellas son respondidas, en promedio, en menos de 5 segundos.
La batería de Comodificación se divide en dos sub-baterías: la primera presenta preguntas en el formato habitual de encuestas, utilizando una escala Likert de 5 categorías de respuesta, mientras que la segunda son preguntas que rozan el carácter experimental, ya que se presentan escenarios ficticios donde el respondente debe tomar decisiones de acuerdo a la subvención de salud y educación para personas que no son capaces de costear estos servicios. Estas dos baterías fueron pensadas por el equipo para ampliar la medición en el campo de MJP. Ahora, en la batería de items (Tabla 7) solamente la primera pregunta tiene una demora promedio > 30 segundos, mientras que las demás se mantienen dentro de los márgenes. Por otro lado, las preguntas de escenarios hipotéticos (Tabla 8) tienden a demorar más debido a que requieren un mayor esfuerzo cognitivo. Cabe señalar que dec_3aparece primero que las otras dos preguntas, por lo que tiene sentido que sea la más demorosa, mientras que después se presenta dec_1, la cual se mantiene apenas debajo del umbral de los 30 segundos.
La batería de Lucro (Tabla 9) es una implementación nueva de preguntas que surgió por parte del equipo JUSMER, mientras que la batería de Neoliberalismo (Tabla 10) es una parte de la Escala de Neoliberalismo propuesta por Girerd, Jost, y Bonnot (2023), donde se seleccionó solamente una dimensión debido a su afinidad temática con esta encuesta. Ambas baterías comparten un patrón de tiempos de demora, donde la primera pregunta es la más demorosa y las demás se mantienen muy por debajo del umbral de 30 segundos.
En Tabla 11 se presenta la batería NICERA, la cual apunta a aplicar la escala CARIN junto con la escala NICER, agregando las preguntas de esfuerzo. Esta escala está compuesta por 20 preguntas, donde la totalidad de ellas son preguntas con una escala Likert de 5 categorías de respuesta. A modo general, se puede observar que, a excepción de control_3 y esf_5, todas las demás preguntas se demoran al menos 15 segundos en responderse. Ahora, un cuarto (25%) del total de la batería supera el umbral de 30 segundos, lo que puede deberse a la exigencia que implica pensar activamente en los criterios de merecimiento que invocan las personas, lo que finalmente puede requerir un mayor esfuerzo cognitivo.
La Tabla 12 muestra un par de preguntas adicionales que fueron agregadas por el equipo de investigación, donde la primera pregunta es la más demorosa, mientras que las demás se mantienen por debajo del umbral, donde adc_3 es la que más rápido se respondió, con un tiempo de demora aproximado de sólo 4 segundos. Por último, en la Tabla 13 y Tabla 14 se presentan las preguntas de caracterización general de la encuesta, donde solamente la pregunta relacionada a los ingresos (income) sobrepasa el umbral de 30 segundos.
Para cerrar esta sección es importante aclarar que no se contabilizaron las secciones del Ranking de políticas ni Participación Estado/Mercado, debido a que la manera en que se registraron sus tiempos funciona de manera distinta al resto de las preguntas, principalmente porque son preguntas que no exigen un orden de respuesta secuencial, lo que causa que algunos respondentes se salten preguntas, y posteriormente vuelven a las que dejaron en blanco. Por ello, en la siguiente sección se detallan los tiempos estimados para estos casos.
tiempo_ranking |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tiempo_ranking), scrollX = TRUE),
colnames = c(
"respID", "Precedente", "Educación", "Salud", "Pensiones",
"Cuidado niños", "Carreteras", "Tiempo estimado (s)"
)
)En la Tabla 15 se presentan los tiempos de respuesta registrados en la base de datos original para cada política considerada en el ranking. Al no poder estimar el tiempo de respuesta con el método aplicado en las demás preguntas, el tiempo aproximado fue calculado a partir de la diferencia entre el último y el primer registro temporal.
En esta situación, los respondentes podían partir seleccionando cualquiera de las 5 alternativas. Como se explica en la metodología, los tiempos estimados de demora por pregunta fueron calculados a partir de la diferencia de tiempo entre la pregunta actual y su predecesora. Debido a la no-restricción de orden es que en algunos casos se presentan valores negativos, reflejando que la pregunta de valor negativo fue respondida antes que su predecesora.
Para calcular el tiempo estimado de respuesta del ranking, se calculó la diferencia entre la última alternativa en ser respondida y la primera para cada respondente. Al promediar los tiempos de respuesta de cada encuestado, la media de tiempo para el ranking fue de 93 segundos.
tiempos_part |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tiempos_part), scrollX = TRUE),
colnames = c(
"respID", "Precedente", "Primera pregunta part", "Última pregunta part",
"Siguiente", "Tiempo aprox. batería (s)"
)
)En la Tabla 16 se presentan los tiempos aproximados de respuesta en la batería de participación Estado/Mercado. Cabe considerar que esta batería está construida a partir de sliders, los cuales estaban fijados en la categoría media (4). Puede ser que debido a esto, algunos respondentes no interactuaron en el slider, por lo que el valor de su respuesta se registró pero no así el tiempo en responder. En consecuencia, para estimar el tiempo de respuesta se recurrió a las preguntas que precedían y proseguían la batería.
Considerando los 4 casos que respondieron esta batería, el tiempo promedio es de 39 segundos. Si se excluye el caso con el tiempo registrado más alto (74 segundos), el tiempo promedio de la batería disminuye a 28 segundos.
tiempo_estimado_total |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = 5),
colnames = c("Sección", "N casos", "Promedio (s)", "Promedio (min)"),
caption = htmltools::tags$caption(
style = "caption-side: bottom; text-align: left;",
"En la suma total de los módulos falta un caso en comparación al total ",
"debido a que solamente respondió el consentimiento"
)
)En la Tabla 17 se observan los tiempos de demora en responder las secciones comúnes de la encuesta, es decir, aquellas que todos los respondentes pudieron atender, junto con los tres módulos aleatorizados. Para estimar cuánto se demoraría alguien en responder la encuesta completa si los tres módulos estuvieran disponibles (en vez de uno solo asignado al azar), se realiza lo siguiente: La suma del tiempo promedio dedicado a la sección común y de la media temporal dedicada a cada módulo (A, B y C) — calculado solo con los casos que efectivamente respondieron ese módulo.
Con esto, se estima que responder la sección común más los tres módulos completos tomaría en promedio 28 minutos.
Primero generamos una tabla general que contiene los descriptivos de todas las preguntas de la encuesta, para luego desagregarla por baterías temáticas.
Construimos una función para generar tabla de descriptivos por batería temática, usando la tabla general ya construida.
tabla_desc_bateria <- function(nm) {
ids <- baterias[[nm]]
tab <- tabla_desc[tabla_desc$variable %in% ids, ]
tab <- tab[order(match(tab$variable, ids)), ]
tab |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tab), scrollX = TRUE),
colnames = c(
"Variable", "Label", "Tipo", "N válido", "NA", "Sin responder",
"Media", "Mediana", "DE", "Mín", "Máx", "N categorías", "Moda"
)
) |>
formatStyle(
"sd",
backgroundColor = styleInterval(1.0, c("white", "#f79b9b"))
)
}tabla_desc_bateria("Conjoint")tabla_desc_bateria("Market Justice Preferences")tabla_desc_bateria("Market Justice Preferences Nuevos")tabla_desc_bateria("MJP Tiempo")tabla_desc_bateria("Comodificación")tabla_desc_bateria("Escenario comodificación")tabla_desc_bateria("Lucro")tabla_desc_bateria("Neoliberalismo")tabla_desc_bateria("NICERA")tabla_desc_bateria("Adicionales")tabla_desc_bateria("Demográficos inicio")tabla_desc_bateria("Demográficos (bloque 2)")Para observar qué preguntas obtuvieron respuestas más homogéneas, se calculó la desviación estándar (DE) de cada pregunta, y se destacaron con rojo aquellas que tienen una DE > 1.0.
Si bien, se reflejan distinciones entre todas las preguntas, hay algunas preguntas que resultan destacables en la variabilidad de sus respuestas. Por ejemplo, todas las preguntas de la batería de comodificación presentan una desviación estándar superior a 1.0, lo que supera el umbral establecido. Esto es un antecedente a considerar para su implementación, ya que podría resultar fructífero para los eventuales análisis.
Por otro lado, la batería NICERA también presenta varias preguntas con una desviación estándar superior a 1.0, específicamente 12, lo que refleja que las respuestas de los encuestados en esta sección son las más heterogéneas. Con esto, se puede aseverar que la batería NICERA es una de las más relevantes para la versión final de la encuesta, ya que permite capturar una mayor diversidad de opiniones y percepciones entre los respondentes.
En esta sección se presentan distintas propuestas para la versión final de la encuesta, considerando los resultados del análisis de tiempos de respuesta y los objetivos del instrumento.
Como parte fundamental de la encuesta, se propone integrar la batería de meritocracia, la cual no fue incluida en los pre-pilotos. Sin embargo, estimamos su duración promedio en base a los datos de la encuesta EDUMERCO, instrumento donde sí se aplicó la escala de meritocracia.
Ahora bien, EDUMERCO no registra timestamp por pregunta individual, solo el rango created/ended de la sesion completa, la cual incluye ademas de meritocracia otras baterias presentes en la base (caracterizacion y deservingness).
Para aproximar el tiempo de respuesta por pregunta se calculó, para cada caso, una tasa segundos/pregunta usando el total de preguntas efectivamente respondidas por cada caso (entre las tres baterias), y luego se aplica esa tasa a las 8 preguntas del bloque de meritocracia, estimando un promedio general de tiempo de respuesta por pregunta.
load(here::here("pre-pilot-analysis", "tbl/meritocracia.RData"))
merit_vars <- paste0("merit_0", 1:8)
carac_vars <- grep("^carac_a", names(meritocracia), value = TRUE)
des_vars <- grep("^des_", names(meritocracia), value = TRUE)
todas_vars <- c(carac_vars, merit_vars, des_vars)
merit_df <- meritocracia[, c("session_id", "created", "ended", todas_vars)]Se conservan solo los casos que efectivamente respondieron el bloque de meritocracia completo (created/ended validos y sin NA en las 8 preguntas)
merit_df <- merit_df[
!is.na(merit_df$created) & !is.na(merit_df$ended) &
stats::complete.cases(merit_df[, merit_vars]),
]
merit_df$created <- as.POSIXct(merit_df$created, format = "%Y-%m-%d %H:%M:%S", tz = "UTC") # transformación de string a fecha/hora
merit_df$ended <- as.POSIXct(merit_df$ended, format = "%Y-%m-%d %H:%M:%S", tz = "UTC")
merit_df$duracion_total_seg <- as.numeric(difftime(merit_df$ended, merit_df$created, units = "secs"))
merit_df$n_respondidas <- rowSums(!is.na(merit_df[, todas_vars]))
merit_df$duracion_pregunta_seg <- merit_df$duracion_total_seg / merit_df$n_respondidasSe excluyen los casos con una tasa > 300 s/pregunta (27 casos), los cuales corresponden a sesiones abandonadas y retomadas horas o dias despues (algunos casos tenían sesiones de hasta ~358 horas), que no reflejan tiempo de respuesta real y distorsionan la media.
merit_df <- merit_df[merit_df$duracion_pregunta_seg <= 300, ]
tiempo_merit_resumen <- data.frame(
pregunta = merit_vars,
n = nrow(merit_df),
duracion_media_seg = round(mean(merit_df$duracion_pregunta_seg), 2),
duracion_mediana_seg = round(median(merit_df$duracion_pregunta_seg), 2),
stringsAsFactors = FALSE
)Por último estimamos visualizamos el tiempo estimado de respuesta de cada pregunta de la batería de meritocracia a partir de las decisiones tomadas, las cuales buscan reflejar la aproximación más fidedigna posible a la realidad de los tiempos de respuesta de esta batería.
tiempo_merit_resumen |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tiempo_merit_resumen), scrollX = TRUE),
colnames = c("Pregunta", "N", "Duración media (s)", "Duración mediana (s)")
) |>
formatStyle(
c("duracion_media_seg", "duracion_mediana_seg"),
backgroundColor = styleInterval(30, c("white", "#f79b9b"))
)Esta versión adquiere un carácter minimalista, ya que solamente considera mantener en la versión final de la encuesta las siguientes secciones: (1) caracterización general, (2) la tarea de asignación, (3) batería clásica de market justice, (4) batería NICERA y (5) la batería de meritocracia.
Para estimar cuánto se demoraría alguien en responder esta versión, se selecciona un subconjunto de baterías (demográficos inicio, conjoint, Market Justice Preferences, NICERA, meritocracia y demográficos de cierre) y se suma, dentro de cada una, el tiempo promedio de respuesta (duracion_media_seg) de sus preguntas — obteniendo así el tiempo promedio estimado para responder esa batería completa. De la misma manera se calculan los tiempos estimados de las alternativas posteriores
tiempo_bateria_seg <- function(nm) {
ids <- baterias[[nm]]
sum(tiempo_pregunta_resumen$duracion_media_seg[tiempo_pregunta_resumen$pregunta %in% ids])
} # función para calcular el tiempo promedio de cada batería temática
tiempo_demo1 <- tiempo_bateria_seg("Demográficos inicio")
tiempo_conjoint <- tiempo_bateria_seg("Conjoint")
tiempo_mjp <- tiempo_bateria_seg("Market Justice Preferences")
tiempo_nicera <- tiempo_bateria_seg("NICERA")
tiempo_demo2 <- tiempo_bateria_seg("Demográficos (bloque 2)")
tiempo_merit <- sum(tiempo_merit_resumen$duracion_media_seg)
tiempo_estimado_minimalista <- data.frame( # dataframe para organizar las baterías y sus tiempos promedio
bateria = c(
"Demográficos inicio", "Conjoint", "Market Justice Preferences",
"NICERA", "Meritocracia", "Demográficos (bloque 2)"
),
tiempo_promedio_seg = c(
tiempo_demo1, tiempo_conjoint, tiempo_mjp, tiempo_nicera, tiempo_merit, tiempo_demo2
),
stringsAsFactors = FALSE
)
tiempo_estimado_minimalista$tiempo_promedio_min <- round(tiempo_estimado_minimalista$tiempo_promedio_seg / 60, 2)
tiempo_estimado_minimalista <- rbind(
tiempo_estimado_minimalista,
data.frame(
bateria = "Total estimado",
tiempo_promedio_seg = sum(tiempo_estimado_minimalista$tiempo_promedio_seg),
tiempo_promedio_min = round(sum(tiempo_estimado_minimalista$tiempo_promedio_seg) / 60, 2)
)
)tiempo_estimado_minimalista |>
datatable(
rownames = FALSE,
options = list(dom = "t", pageLength = nrow(tiempo_estimado_minimalista)),
colnames = c("Batería", "Tiempo promedio (s)", "Tiempo promedio (min)")
)Esta segunda alternativa se basa en la idea de implementar la primera ola dividida en dos partes. Así, la primera parte de la encuesta contiene: (1) caracterización general, (2) la tarea de asignación, (3) batería clásica de market justice y (4) la escala de meritocracia. La segunda parte contiene: (1) caracterización general, (2) la tarea de asignación y (3) batería NICERA. De esta manera, se busca reducir el tiempo de respuesta de la encuesta, manteniendo las secciones esenciales para los análisis posteriores.
A partir de los resultados del análisis del pre-piloto, se pueden extraer las siguientes conclusiones y sugerencias para la versión final de la encuesta:
La retroalimentación entregada por los respondentes en la primera versión del pre-piloto (la cual se visualiza en la sección 2.2) se puede sintetizar en dos conjuntos: comentarios puntuales de items y consideraciones para la tarea de asignación. En el primer caso, los respondentes entregaron comentarios sobre la adición de elementos a las preguntas, como la integración de los impuestos a la pregunta de ingresos. También se expresaron confusiones respecto a lo que apuntaban ciertas preguntas, como el nivel educacional o el mismo ingreso, entre otras. En el segundo caso, se recibieron sugerencias sobre la implementación de la tarea de asignación, especialmente haciendo alusión a que, el peso de sólo un atributo determinaba la decisión del respondente. A partir de esto, se puede concluir que la encuesta requiere, por un lado, una revisión de los ítems para mejorar la comprensión de la encuesta, y por otro, una discusión en torno a la tarea de asignación, donde si bien los comentarios son valiosos, apuntan justamente a lo que busca comprender el conjoint.
En segundo lugar, se pudo observar que los tiempos de respuesta de la encuesta en general son demasiado altos, tanto a nivel de módulos como a nivel de batería temática. Esto se refleja en que la encuesta completa, considerando los tres módulos, tiene un tiempo estimado de respuesta de 28 minutos. En este contexto, la escala NICERA es la más problemática, puesto que es la más extensa con 20 preguntas, la cual tiene un tiempo de respuesta promedio de 8 minutos.
Debido al diagnóstico de alta demora de respuesta, se propusieron dos alternativas para la versión final de la encuesta: una versión minimalista con las baterías esenciales y otra dividida en dos partes. La primera propuesta de encuesta final sirve más como un diagnóstico, ya que queda claro su inviabilidad a causa de la demora estimada (22 minutos aproximadamente). Por ello, la segunda propuesta de encuesta final es la más realista, ya que permite mantener las secciones esenciales para los análisis posteriores, reduciendo el tiempo de respuesta de la encuesta, aunque sería pertinente buscar maneras de reducir el tiempo para mantenerse en el margen de los 15 minutos, por ejemplo, acortando la batería NICERA. Esto se podría realizar excluyendo los ítems de esfuerzo, donde la escala pasaría a ser precisamente CARIN, pero con estos cambios, la encuesta basada en Conjoint + Merecimiento quedaría en un tiempo estimado de respuesta de 17.24 minutos, lo cual se acerca más al estándar de 15 minutos, pero sigue sobrepasando el tiempo recomendado.
Por último, cabe destacar que los resultados descriptivos de las baterías temáticas muestran que la batería de comodificación y la batería NICERA son las que presentan mayor heterogeneidad en las respuestas, lo que sugiere que estas baterías son relevantes para la versión final de la encuesta. Ahora bien, considerando que la batería de coomdificación no es esencial para la primera ola debido a las limitaciones de tiempo, no se debe desechar, sino reconsiderar para la segunda ola del estudio.