En este documento, vamos a analizar las evaluaciones del Nodo de Innovación de la Universidad de los Andes. El objetivo de este análisis es combinar las evaluaciones de los cursos de la Maestría y el Doctorado en Gestión de la Innovación Tecnológica en un solo archivo, para poder analizar las tendencias y el desempeño de los cursos y profesores.
Las evaluaciones consisten en dos tipos de datos: frecuencias y comentarios. Las frecuencias contienen respuestas cuantitativas de los estudiantes sobre diversos aspectos de los cursos, mientras que los comentarios son respuestas cualitativas en las que los estudiantes pueden expresar sus opiniones más detalladas.
Primero, necesitamos cargar las librerías necesarias para trabajar
con los datos. Utilizaremos readxl para leer archivos
Excel, dplyr para manipular los datos, y
writexl para exportar los datos.
library(readxl)
## Warning: package 'readxl' was built under R version 4.2.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.2.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(writexl)
En esta sección, definimos las rutas a los archivos Excel que contienen las evaluaciones de los estudiantes. Existen cuatro directorios principales: dos para las evaluaciones de frecuencias y dos para los comentarios, diferenciando entre la Maestría y el Doctorado.
rutas_frecuencias <- list(
Maestria = "C:/Users/tomas/OneDrive/Documents/UniAndes/Evaluaciones/Frecuencias/Maestría",
Doctorado = "C:/Users/tomas/OneDrive/Documents/UniAndes/Evaluaciones/Frecuencias/Doctorado"
)
rutas_comentarios <- list(
Maestria = "C:/Users/tomas/OneDrive/Documents/UniAndes/Evaluaciones/Comentarios/Maestría",
Doctorado = "C:/Users/tomas/OneDrive/Documents/UniAndes/Evaluaciones/Comentarios/Doctorado"
)
La función leer_archivos se utiliza para leer todos los
archivos Excel de una ruta específica y añadir información adicional.
Esta función realiza las siguientes tareas:
read_excel para leer cada archivo.leer_archivos toma tres parámetros: la
ruta de los archivos, el tipo (Frecuencia o
Comentarios), y el programa (Maestría o Doctorado).~$ ya que son generados por Excel cuando se abre un archivo
y no son útiles.bind_rows() para que los datos de todos los
archivos queden unificados y listos para ser analizados.leer_archivos <- function(ruta, tipo, programa) {
archivos <- list.files(ruta, full.names = TRUE, pattern = "\\.xlsx$")
# Filtrar archivos temporales que comienzan con ~$
archivos <- archivos[!grepl("^~\\$", basename(archivos))]
data <- lapply(archivos, function(archivo) {
df <- read_excel(archivo)
# Añadir columnas con tipo (Frecuencia o Comentarios) y semestre
df <- df %>%
mutate(
Tipo = tipo,
Programa = programa,
Semestre = sub(".*_(20[0-9]{3}0[1|2])_.*", "\\1", basename(archivo))
)
return(df)
})
# Unir todos los data frames de la lista en uno solo
data_combinada <- bind_rows(data)
return(data_combinada)
}
En esta sección, usamos la función leer_archivos para
leer todos los archivos de frecuencias y comentarios de la Maestría y el
Doctorado. De esta forma, obtenemos cuatro data frames: dos para
frecuencias y dos para comentarios.
frecuencias_maestria <- leer_archivos(rutas_frecuencias$Maestria, "Frecuencia", "Maestría")
frecuencias_doctorado <- leer_archivos(rutas_frecuencias$Doctorado, "Frecuencia", "Doctorado")
comentarios_maestria <- leer_archivos(rutas_comentarios$Maestria, "Comentarios", "Maestría")
comentarios_doctorado <- leer_archivos(rutas_comentarios$Doctorado, "Comentarios", "Doctorado")
Ahora vamos a unir los data frames de frecuencias y comentarios en un
solo data frame llamado Evaluaciones. Para evitar problemas
con columnas duplicadas, primero identificamos las columnas comunes
entre los data frames y luego seleccionamos solo esas columnas para
combinarlos.
# Unir todos los data frames sin eliminar columnas
Evaluaciones <- bind_rows(frecuencias_maestria, frecuencias_doctorado, comentarios_maestria, comentarios_doctorado, .id = "Fuente")
Finalmente, exportamos el data frame Evaluaciones a un
archivo Excel llamado mega_evaluaciones.xlsx para tener
todos los datos en un solo archivo y poder analizarlo fácilmente en el
futuro.
Este proceso nos permite consolidar toda la información de las evaluaciones de los estudiantes en un solo archivo, facilitando el análisis de las tendencias a lo largo de los semestres y los programas. Cada paso está diseñado para asegurarnos de que los datos se manejen de manera consistente y completa.
La base de datos de Evaluaciones o mega_evaluaciones.xlsx contiene observaciones por cada opción de respuesta, lo cual dificulta su análisis para el desempeño de cada pregunta. Por lo que se deben clasificar estas opciones de respuesta por categorías de positivo, neutral o negativo para luego hacer un indicador de desempeño que permita tener una sola observación por pregunta con todas las frecuencias en sus opciones de respuesta. El primer paso es identificar un diccionario o tabla de mapeo con todos los tipos de pregunta, preguntas y opciones de respuesta para clasificar:
preguntas_opciones <- Evaluaciones %>%
filter(Tipo == "Frecuencia") %>%
select(idPregunta, pregunta, respuesta, opcionRespuestaPregunta) %>%
distinct()
Ahora filtramos aún más, pues cada tipo de pregunta en
opcionRespuestaPregunta tiene las mismas opciones:
preguntas_opciones <- preguntas_opciones %>%
select(opcionRespuestaPregunta, respuesta) %>%
distinct()
Con estas opciones en cuenta, creamos un tibble que contenga el diccionario de clasificación:
library(tibble)
## Warning: package 'tibble' was built under R version 4.2.3
respuestas_dict <- tribble(
# tiempo
~opcion, ~clasificacion,
"Igual (IG)", "Neutral",
"Mayor (MA)", "Negativa",
# acuerdo
"Ni en acuerdo ni en desacuerdo (N)", "Neutral",
"De acuerdo (A)", "Positiva",
"Totalmente de acuerdo (TA)", "Positiva",
"En desacuerdo (D)", "Negativa",
"Totalmente en desacuerdo (TD)", "Negativa",
"No aplica (NA)", "Neutral",
# cantidad
"Algunos (AL)", "Neutral",
"Todos (TO)", "Positiva",
"Ninguno (NI)", "Negativa",
# sino
"SÍ (SÍ)", "Positiva",
"NO (NO)", "Negativa",
# carga
"Nada (ND)", "Positiva",
"Mucho (MU)", "Negativa",
"Demasiado (DE)", "Negativa",
"Poco (PO)", "Neutral",
# satisfaccion
"Satisfactoria (SA)", "Positiva",
"Muy satisfactoria (MS)", "Positiva",
"Insatisfactoria (IN)", "Negativa",
"Muy insatisfactoria (MI)", "Negativa",
# relevancia
"Ni relevante ni irrelevante (NR)", "Neutral",
"Relevante (RE)", "Positiva",
"Irrelevante (IR)", "Negativa",
"Poco relevante (PR)", "Negativa",
"Muy relevante (MR)", "Positiva",
# aprendi
"No, no afecto mi proceso de aprendizaje. (NP)", "Positiva",
"Sí, afecto bastante mi proceso de aprendizaje. (AB)", "Negativa",
"Sí, afecto un poco mi proceso de aprendizaje. (AP)", "Negativa",
# sinoP1 & sinoP2
"Sí, a algunas (AL)", "Neutral",
"Sí, a todas (TO)", "Positiva",
"No, a ninguna (NI)", "Negativa",
# sinoFrec
"No, nunca (N)", "Positiva",
"Sí, algunas veces (PV)", "Negativa",
"Sí, una única vez (UV)", "Negativa",
"Prefiero no responder (NA)", "Neutral",
"Sí, frecuentemente (FR)", "Negativa"
# NOTA: opciones "multiple" no se listan aquí,
# pues les daremos clasificación NA (o "NoClas") en el join.
)
Ahora se clasifican todas las respuestas en la tabla original según la tabla de mapeo. Se filtran solo las respuestas cuantitativas para evitar confuciones con las respuestas abiertas.
Evaluaciones_clasif <- Evaluaciones %>%
filter(Tipo == "Frecuencia") %>%
left_join(respuestas_dict, by = c("respuesta" = "opcion"))
A continuación, según las clasificaciones de las distintas opciones
de pregunta, podemos resumir la base de datos para que en cada
observación solo tengamos las respuestas positivas, negativas y
neutrales de cada curso en cada semestre y para cada profesor. Excluimos
las opciones que tienen NA en la clasificación para omitir
las preguntas de opción múltiple.
indicadores_cursos <- Evaluaciones_clasif %>%
filter(Tipo == "Frecuencia", !is.na(clasificacion)) %>%
group_by(periodo, Programa, nombreCurso, nombreProfesor, idPregunta) %>%
summarize(
total_respuestas = sum(nRespuestasOpcion, na.rm = TRUE),
positivas = sum(nRespuestasOpcion[clasificacion == "Positiva"], na.rm = TRUE),
neutrales = sum(nRespuestasOpcion[clasificacion == "Neutral"], na.rm = TRUE),
negativas = sum(nRespuestasOpcion[clasificacion == "Negativa"], na.rm = TRUE)
) %>%
mutate(
pct_positivas = positivas / total_respuestas,
pct_neutrales = neutrales / total_respuestas,
pct_negativas = negativas / total_respuestas
)
## `summarise()` has grouped output by 'periodo', 'Programa', 'nombreCurso',
## 'nombreProfesor'. You can override using the `.groups` argument.
Las preguntas marcadas con opcionRespuestaPregunta == “multiple” significan que cada observación representa una opción seleccionada para una pregunta de tipo “marque todo lo que aplique”. Para resumir y hacer más fácil la visualización, primero agrupamos por semestre, curso y profesor, y luego cambiamos la estructura de los datos a un formato para que las observaciones representen cada pregunta, al igual que los indicadores de los cursos.
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.2.3
Evaluaciones_multiple <- Evaluaciones %>%
filter(Tipo == "Frecuencia", opcionRespuestaPregunta == "multiple")
# Ahora hacemos un pivot_wider para que cada "respuesta" sea una columna
Evaluaciones_multiple <- Evaluaciones_multiple %>%
# Agrupas por las columnas de interés (ajusta según tu estructura).
group_by(periodo, Programa, nombreCurso, nombreProfesor, idPregunta, pregunta, respuesta) %>%
# Si tienes una columna con el conteo (p.ej. nRespuestasOpcion), resumirla.
summarise(
n = sum(nRespuestasOpcion, na.rm = TRUE),
.groups = "drop"
) %>%
# Pivotear
pivot_wider(
names_from = respuesta, # cada valor único en 'respuesta' se vuelve una columna
values_from = n,
values_fill = 0 # rellena 0 donde no haya datos
)
Ahora se crea el diccionario de preguntas y respuestas con opción múltiple al seleccionar las variables clave y dejar una sola de cada una.
diccionarioMultiple <- Evaluaciones %>%
# Filtras solo respuestas de opción múltiple
filter(opcionRespuestaPregunta == "multiple") %>%
# Extraes las columnas relevantes
select(idPregunta, pregunta, respuesta) %>%
distinct()
Finalmente, exportamos el diccionario para importarlo al tablero de Power BI y hacer las relaciones con la base de mega_evaluaciones.
Los comentarios de los estudiantes en formato texto contienen muchas
palabras como artículos o conectores que no permiten una buena
visualización en la nube de palabras. Estas palabras se conocen como
stopwords y deben ser eliminadas de los comentarios para la
visualización.
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.2.3
library(stopwords)
## Warning: package 'stopwords' was built under R version 4.2.3
# Filtrar únicamente los comentarios abiertos
Evaluaciones_comentarios <- Evaluaciones %>%
filter(Tipo == "Comentarios") %>%
# Creamos un ID por fila/registro de comentario
mutate(idComentario = row_number()) %>%
# Seleccionamos columnas de interés, ajusta según necesites
select(idComentario, periodo, nombreCurso, nombreProfesor,
Programa, idPregunta ,pregunta, respuesta)
Ahora debemos Tokenizar, es decir, separar cada comentario en
“palabras” (tokens). Eliminar stopwords (artículos, conectores,
preposiciones, etc.). Tidytext trae una lista de stopwords para inglés
por defecto. Para español, podemos usar el paquete
stopwords y construir un tibble con ellas.
# Creamos un tibble con las stopwords en español
stopwords_es <- tibble(word = stopwords("es"))
stopwords_personalizadas <- c("curso", "profesor", "profesora", "clase")
# Unir a la lista general en español
stopwords_es <- tibble(word = c(stopwords("es"), stopwords_personalizadas)) %>%
distinct()
Ahora se tokenizan las respuestas.
Evaluaciones_comentarios_limpio <- Evaluaciones_comentarios %>%
# Con unnest_tokens, convertimos la columna 'respuesta' en tokens (palabras).
# 'word' será el nuevo nombre de la columna con cada token.
unnest_tokens(word, respuesta) %>%
# Eliminamos stopwords haciendo un anti_join con nuestra tabla de stopwords.
anti_join(stopwords_es, by = "word")
En este punto, cada fila de
Evaluaciones_comentarios_limpio representa una palabra
(token). Ya no se ve la frase completa, sino cada token que no sea
stopword. Ahora se deben reagrupar las respuestas para que quede
nuevamente una respuesta por fila.
library(stringr)
Evaluaciones_comentarios_limpio <- Evaluaciones_comentarios_limpio %>%
group_by(idComentario, periodo, nombreCurso, nombreProfesor, Programa, idPregunta, pregunta) %>%
summarise(
respuesta_limpia = str_c(word, collapse = " ")
) %>%
ungroup()
## `summarise()` has grouped output by 'idComentario', 'periodo', 'nombreCurso',
## 'nombreProfesor', 'Programa', 'idPregunta'. You can override using the
## `.groups` argument.
Finalmente, se exporta como una base de datos los comentarios sin stopwords para construir las nubes de palabras en Power BI.
write_xlsx(Evaluaciones_comentarios_limpio,
"C:/Users/tomas/OneDrive/Documents/UniAndes/Evaluaciones/ComentariosLimpios.xlsx")