Cómo crear exámenes aleatorizados con R/exams y R Markdown

En este artículo hago una primera aproximación a la creación de exámenes aleatorizados con el sistema R/exams y R Markdown. La motivación para escribirlo ha sido la de compartir la experiencia de cómo se pueden crear exámenes on line y poder usarlos en la plataforma Blackboard de la Universidad de Sevilla, pero también tiene una infinidad de aplicaciones adicionales: exámenes escritos, ejercicios de autoaprendizaje, preguntas dinámicas sobre dispositivos móviles, etc). Y lo mejor de todo es que todo lo necesario es software libre.
R Markdown
R
Autor

Pedro L. Luque

Fecha de Publicación

1 mayo 2020

Vídeos sobre “cómo crear exámenes aleatorizados”

Introducción a R/exams

El sitio web oficial de “R/exams” se localiza en la siguiente url:

http://www.R-exams.org/

El mantenedor de R/exams es Achim Zeileis.

Cómo explicó muy bien “Achim Zeileis” en una presentación en 2018 R/exams es una herramienta que se apoya en R para generar exámenes en todas las situaciones habituales:

  • exámenes escritos
  • pruebas on line y
  • cuestionarios interactivos.

Este documento es principalmente una traducción particular del documento anterior. Y además, es una primera versión, creado con ciertas prisas para que fuese posible utilizarlo a partir de los primeros días de mayo de 2020. Espero mejorarlo con mi propia experiencia y con las sugerencias que me propongan. Nota: Este documento en sus distintos formatos ha sido creado con R Markdown.

La motivación inicial de R/exams fue utilizarlo en cursos universitarios en Austria con miles de alumnos en asignaturas sobre Estadística, Probabilidad o Matemáticas, de titulaciones como Empresariales, Económicas, Ciencias Sociales, Psicología, etc. Además, en su desarrollo participaron un grupo de profesores.

La estrategia fue que en todos los cursos:

  • permitiera su uso en todas las fases de la formación: aprendizaje personalizado, retroalimentación (feedback) y evaluación.

  • Y que el mismo grupo de ejercicios creados sirviera durante todo el curso.

En la siguiente tabla se resume las formas de usarlo:

Modo Aprendizaje Retroalimentación Evaluación
Síncrono Lecturas Cuestionarios Interactivos Exámenes escritos
Síncrono Transmisión en vivo (+ Tutoriales) NA
Asíncrono Libros de texto Cuestiones de autocomprobación Test on line
Asíncrono vídeo en directo (+ Forum) NA

Es útil en el aprendizaje estándar de libros de texto más presentaciones o en el aprendizaje on line con videoconferencias o vídeos grabados.

La retroalimentación y la evaluación tendrían las siguientes características:

  • Escalabilidad: a través de ejercicios dinámicos aleatorizados.
  • Retroalimentación: al permitir obtener las soluciones correctas completas.
  • Flexibilidad: renderizado automático en diferentes formatos de evaluación.

La librería o paquete R “exams” permite:

  • “Ejercicios”:

    • Cada ejercicio en un único fichero (.Rmd para R Markdown o Rnw para Sweave+LaTeX).
    • Contiene el enunciado y opcionalmente la correspondiente solución.
    • Y de carárter dinámico si se usa código R para la aleatorización.
  • “Tipos de respuestas”:

    • De elección única (“schoice”) o elección múltiple (“mchoice”).
    • Valores numéricos (“num”).
    • Cadenas de texto cortas (“string”).
    • Combinación de las anteriores (“cloze”). Actualmente, no disponible para utilizar en Blackboard.
  • “Salidas”:

    • “PDF”: completamente personalizables con evaluación automática mediante escaneado de una página.

    • “HTML”: completamente personalizables.

    • “Moodle XML”.

    • “QTI XML standard (versión 1.2 o 2.1)”, por ejemplo para Blackboard, OLAT/OpenOLAT.

    • “ARSnova” (uso en dispositivos móviles), “TCExam”, “LOPS”,…

  • Solamente requiere software open-source.

Introducción a la creación de ejercicios dinámicos

Los ejercicios dinámicos se crean en ficheros de texto “plano”, los cuales tienen la siguiente estructura:

  1. Generación de datos aleatorios (opcional).

  2. Enunciado.

  3. Solución (opcional).

  4. Metainformación.

Se pueden crear en formato R Markdown (.Rmd) o formato Sweave+LaTeX (.Rnw).

Veamos algunos ejemplos básicos de ejercicios en R Markdown.

Ejercicios dinámicos: elección única

El fichero “pregunta.Rmd” tendría la siguiente estructura:

Question
========

¿Cuál de estas ciudades es la capital de España?

Answerlist
----------

* Madrid
* Sevilla
* Barcelona
* Valencia
* París
* Londres
* Bilbao
    
    

Meta-information
================
exname: Capital de España
extype: schoice
exsolution: 1000000
exshuffle: 5

Nota: Question (junto a los “========), y Meta-information son los elementos que diferencian las distintas partes de la pregunta. El parámetros extype: schoice está indicando el tipo de pregunta. El parámetro exsolution: 1000000 está indicando que la primera respuesta es la verdadera (”1”) y las restantes son falsas (“0”). El parámetro exshuffle: 5 le indica que seleccione 5 opciones al azar.

Visto con formato de salida pdf (se usó la función: exams2pdf()):

Ejercicios dinámicos: Preguntas de respuesta numérica

El fichero “pregunta.Rmd” tendría la siguiente estructura:

```{r, echo=FALSE, results="hide"}
## parametros
a <- sample(2:9, 1)
b <- sample(seq(2, 4, 0.1), 1)
c <- sample(seq(0.5, 0.8, 0.01), 1)
## solucion
res <- exp(b * c) * (a * c^(a-1) +
       b * c^a)
```


Question
========

¿Cuál es la derivada de 
$$f(x) = x^{`r a`} e^{`r b` x}$$
evaluada en $x = `r c`$?


Meta-information
================
extype: num
exsolution: `r fmt(res)`
exname: Derivadas exponencial
extol: 0.01

Nota: en este ejemplo, aparece al inicio un “trozo” (o “chunk”) de código R en el que determinadas variables R toman valores aleatorios. El valor de estas variables será incluido en la pregunta con lo que se conseguirá que sea una pregunta aleatorizada. En este caso es una pregunta con respuesta numérica: extype: num. El parámetro exsolution que contiene la respuesta numérica verdadera tiene un valor obtenido con la ayuda del lenguaje R. El parámetro extol: 0.01 determina la tolerancia del error de redondeo permitida en la respuesta.

Visto con formato de salida pdf (se usó la función: exams2pdf()):

Creación de exámenes

Los pasos en R serían los siguientes:

  1. Crear una lista con los ficheros de ejercicios que se incluirán en el examen.

    miexamen = list(
      "deriv2.Rmd",
      "fruit2.Rmd",
      c("ttest.Rmd","boxplots.Rmd")
    )

    Generaría exámenes al azar:

    • Primero selecciona un ejercicio de cada elemento de la lista.
    • Genera números aleatorios/entradas para cada ejercicio seleccionado.
    • Combina todos los ejercicios en ficheros: PDF, HTML, DOCX, …
  2. Crear exámenes:

    • Exámenes escritos (para corrección automatizada al escanear la hoja de respuestas):

      exams2nops(miexamen, n = 3, dir = odir,
            language = "es", institution = "Curso Estadística 2020")
    • Pruebas on line:

      exams2blackboard(miexamen, n = 10, dir = odir)
    • Cuestionarios interactivos:

      exams2arsnova(miexamen, n = 1, dir = odir)
    • Otros: exams2pdf(), exams2html(), exams2pandoc() (para salidas: “docx”, “odt”, “markdown”, …), exams2qti12(), exams2qti21(), exams2moodle(), …

Recomendaciones para usar R/exams

Las recomendaciones que hace A. Zeileis si uno quiere probar R/exams son:

  • Comience con ejercicios simples antes de pasar a tareas más complejas.
  • Centrarse en el contenido de los ejercicios.
  • No se preocupe demasiado por el diseño/formato.
  • Intente formar un equipo (con profesores, asistentes, etc.).
  • Utilice los tipos de ejercicio de manera creativa.
  • No tenga miedo de probar cosas, especialmente en evaluaciones formativas.
  • Control de calidad exhaustivo para ejercicios dinámicos antes de evaluaciones importantes.

Los recursos y referencias a R/exams son:

Mi propuesta para usar R/exams y R Markdown

En este apartado, explico en primer lugar los pasos para instalar el software necesario (gratuito) en un ordenador personal. Y presento además una plantilla con la que facilitar el uso de R/exams y R Markdown para la creación de ejercicios dinámicos y exámenes utilizables en la plataforma Blackboard.

Instalación del sistema para crear exámenes con: R/exams + R Markdown

Se puede encontrar información específica sobre cómo instalar “R/exams”, R, RStudio y LaTeX en su página oficial “Installing R/exams”.

A continuación, se explica de forma breve el proceso de instalación:

  1. Instalar R y RStudio en nuestro ordenador.

    Puede encontrar información más detallada de la instalación de estos programas informáticos gratuitos en un artículo de mi página web: Instalación de R, RStudio y LaTeX.

  2. Instalar las librerías R necesarias para crear exámenes con R Markdown.

    Copie las siguientes instrucciones R para instalar las librerías R (o también llamados paquetes) necesarias para la construcción de exámenes:

    install.packages(c("knitr","exams","tinytex"), dependencies = TRUE)
    tinytex::install_tinytex()

    Inicie el programa “RStudio” y en la pestaña de la consola de R (“Console”) copie las líneas de código anteriores, péguelas y pulse la tecla de “Retorno” (o “Enter”) para que se inicie el proceso de instalación. Si aparece alguna pregunta de forma interactiva elija siempre la primera opción.

Organización recomendada de las carpetas del proyecto para mantener exámenes

En esta carpeta se mantendrán almacenados todos los documentos que se creen en relación con los exámenes que se realicen.

Inicialmente aparecen las siguientes subcarpetas:

  • “bancopreguntas”: en ella se guardarán todos los ficheros con preguntas o ejercicios que conformarán nuestro banco de preguntas para posteriormente utilizar en nuestros exámenes.

    • Dentro de esta subcarpeta hay otra subcarpeta llamada: “demosexams” que contiene una colección de preguntas (en inglés) que han sido creadas por los desarrolladores del paquete “exams”, y con la cual se podrán ver ejemplos de todos los tipos de preguntas que se pueden crear además de encontrar las posibilidades de configuración que se admiten al diseñar preguntas (cómo incluir imágenes, cómo incluir ficheros de datos, cómo presentar datos en forma de tabla, cómo escribir expresiones matemáticas, etc).
  • “coleccionexamenes”: en ella se guardarán los ficheros que se utilizarán para generar los exámenes que planifiquemos.

  • “modelos”: en ella están los ficheros que pueden usarse como modelos para crear los distintos tipos de preguntas o exámenes, o cualquier otro fichero útil, como pueden ser plantillas de diseño de exámenes.

  • “docs”: (opcional) se colocarán aquí ficheros con información útil: manuales, tutoriales o cualquier otra información que nos facilite/recuerde los pasos a seguir para realizar las tareas relacionadas con la creación de exámenes y su uso en plataformas de enseñanza virtual, como Blackboard.

  • “exagenerados”: se localizarán los ficheros generados al crear los exámenes.

Nota: las carpetas y ficheros no deben contener caracteres acentuados, espacios en blanco, “ñ”, etc. para evitar conflictos con estos programas ya que no están diseñados para su uso en español, y pueden ser motivo de errores en el momento de la creación de documentos.

Nota: La librería “exams” tiene una función que permite crear también una estructura de carpetas y ficheros ejemplo para trabajar: exams::exams_skeleton("examsdemo").

Generación de exámenes

Se supone que ya se tienen disponibles las preguntas o ejercicios en el banco de preguntas necesarias para el examen que se quiere generar.

Se tendrían que realizar los siguientes pasos para conseguir las distintas versiones del examen para subir, por ejemplo, a la plataforma Blackboard.

  • Paso 1. De la carpeta “modelos” hacer una copia del fichero modelo existente para generar exámenes para la plataforma elegida. En este caso, se ha hecho una copia del fichero: “modeloexamen_Blackboard.R” en la carpeta “coleccionexamenes”. Se le cambiará el nombre para que sea reconocible a quien va dirigido. Por ejemplo, podría llamarse: “examen_Asignatura01-20200415.R”.

  • Paso 2. Se abre en RStudio el fichero R creado, al hacer click sobre ese fichero (navegar por el panel “Files” de RStudio). Se modifica el contenido del fichero para adaptarlo al examen que se quiere generar:

    • Se modifican los valores iniciales:

      • semilla = 1234. Fijar la semilla permitirá por ejemplo, que si se borran los exámenes generados por algún motivo, se puedan volver a generar los mismos. También es útil para que los distintos tipos de salida que se quieran generar: Blackboard, pdf, html, …, tengan los mismos enunciados y soluciones.

      • numcopias = 3. El número de exámenes distintos que se quieren generar. Se puede empezar generando 2 o 3 modelos para su revisión. Una vez que se vea que todo es correcto, se establece el número final de exámenes. Si generamos un número igual o mayor que el número de alumnos, se asegura que los alumnos harán exámenes diferentes.

      • numintentos = 1. Este valor indica el número de intentos que tendrá el alumno para realizar el examen. Generalmente será 1 para un examen, pero para la realización de actividades o simplemente para que ensayen los alumnos podría permitirse un número mayor.

      • nombreexamen = "Asignatura01-20200415-". Se establece el nombre de los ficheros que se van a generar.

      • VsalidaHTML = TRUE (o FALSE). Se crea (TRUE) o no (FALSE) la salida HTML del examen.

      • VsalidaPDF = TRUE (o FALSE). Se crea (TRUE) o no (FALSE) la salida PDF del examen.

      • Vfecha = "2020-04-15". Se indica la fecha que aparecería en la cabecera de los exámenes generados (en algunos formatos).

    Nota: en R el símbolo # indica que lo que le sigue es un comentario.

  • Paso 3. Hay que indicar cuáles son los ficheros del “banco de preguntas” que se van a utilizar en el examen. Para ello habrá que modificar los datos correspondientes a “miexamen”:

    ## ATENCIÓN: ACTUALMENTE exams NO SOPORTA EN LA SALIDA BLACKBOARD
    ##    LA CREACIÓN DE PREGUNTAS TIPO: cloze
    miexamen = list(
      # Pregunta 1
      "bancopreguntas/demosexams/boxplots.Rmd",
      # Pregunta 2
      "bancopreguntas/demosexams/confint.Rmd",
      # Pregunta 3  (elige una pregunta al azar de la lista que aparece)
       c("bancopreguntas/demosexams/tstat.Rmd",
         "bancopreguntas/demosexams/tstat2.Rmd"),
      # Pregunta 4
      "bancopreguntas/demosexams/deriv.Rmd",
      # Pregunta 5  (elige una pregunta al azar de la lista que aparece)
       c("bancopreguntas/demosexams/fruit.Rmd",
         "bancopreguntas/demosexams/fruit2.Rmd"),
      # Pregunta 6
      "bancopreguntas/demosexams/essayreg.Rmd",
      # Pregunta 7
      "bancopreguntas/demosexams/Rlogo.Rmd"    # EL ÚLTIMO NO LLEVA ","
    )

    Como puede observarse, hay que indicar el camino relativo que apunta hasta los ficheros de preguntas a utilizar.

    Se puede usar un único fichero para una pregunta (ejemplos de las preguntas: 1, 2, 4, 6 y 7), pero también pueden indicarse varios ficheros para que al azar elija uno para crear la pregunta (ejemplos de las preguntas: 3 y 5). Esto último genera aún más aleatoriedad a la generación de preguntas (habitualmente se suelen utilizar preguntas de una dificultad y tipología parecida). También existen otras posibilidades.

    Nota. Generalmente se recomienda generar la salida en otro formato, bien “html” (rápido de generar) o bien “pdf” (menos rápido y los requisitos de instalación suelen producir más inconvenientes). Con estas salidas será más fácil revisar los enunciados y soluciones si se necesita realizar una revisión del examen con el alumno, por ejemplo, si el alumno entregó su examen escrito escaneado en pdf no tendríamos que mirar en la plataforma de enseñanza virtual.

  • Paso 4. Una vez realizados los pasos anteriores solamente queda generar los exámenes al hacer click en RStudio sobre el botón “Source” de la parte superior derecha del fichero del examen (“examen_Asignatura01-20200415.R”).

    En este proceso, se pueden producir errores, R los indicaría en la consola resaltándolos en color rojo. Tendrían que resolverse siguiendo las indicaciones.

    Una vez terminado el proceso sin mensajes de error, se pueden encontrar los ficheros generados en la carpeta: “exagenerados”. El fichero generado para Blackboard es un fichero zip comprimido, en el ejemplo: “Asignatura01-20200415-BB.zip”. Este será el fichero que habrá que importar en Blackboard, al entrar en el apartado “Herramientas->Exámenes, Encuestas y Bancos de preguntas” (se explica en una sesión posterior).

    Nota. Todos los ficheros generados también se podrían guardar como copia de seguridad en una subcarpeta de “coleccionexamenes”, por ejemplo, con el mismo nombre del fichero del modelo: “examen_Asignatura01-20200415”.

Algunos comentarios adicionales sobre personalización

A continuación se muestran argumentos adicionales que pueden añadirse a la función que genera los exámenes:

  • points. Vector de enteros. El número de puntos que se asigna a cada uno de los ejercicios propuestos

  • nsamp. Vector de enteros. El número de ficheros de ejercicios seleccionados al azar de cada sublista en el listado de preguntas. Usa muestreo sin reemplazamiento si es posible (solamente si algún elemento nsamp es mayor que la longitud del elemento correspondiente del listado, se emplearía muestreo con reemplazamiento).

  • resolution, width, height. Valores numéricos que fijan las características de los gráficos png generados.

  • pdescription. De tipo carácter. Descripción del conjunto de copias del examen (“pool”).

  • pinstruction. De tipo carácter. Instrucciones del conjunto de copias de examen.

  • tdescription. De tipo carácter. Descripción del examen concreto.

  • tinstruction. De tipo carácter. Instrucciones del examen concreto.

  • base64. De tipo lógico o vector de caracteres. ¿Los ficheros suplementarios se deberían incluir en el interior de los ficheros generados? Si no se define nada (NULL) solamente incluiría los ficheros de imágenes. Si es un vector base64=c("png","RData","rda") se incluirían los ficheros de imágenes “png”, los ficheros binarios de R que contienen datos: “RData” y “rda”.

Los siguientes argumentos son más específicos para plataformas de enseñanza virtual (exams2blackboard(), exams2qti12(), …):

  • solutionswitch = FALSE. De tipo lógico. Si es FALSE, solamente se muestra el resultado numérico correcto al finalizar el examen o prueba.

  • cutvalue=1000. Las pruebas on line para las cuales no tengan sentido aprobarla (pasarla) o suspenderla, este argumento fija el nivel de puntos con el que se aprobaría. En este caso, se está fijando a un valor muy alto: cutvalue=1000 para que nunca se alcance el aprobado.

La sintaxis R de la función “exams2blacboard” que crea exámenes para la plataforma virtual “Blackboard” es la siguiente:

 exams2blackboard(file, n = 1L, nsamp = NULL, dir = ".",
    name = NULL, quiet = TRUE, edir = NULL,
    tdir = NULL, sdir = NULL, verbose = FALSE,
    resolution = 100, width = 4, height = 4, encoding = "",
    num = NULL, mchoice = NULL,
    schoice = mchoice, string = NULL, cloze = NULL,
    template = "blackboard",
    pdescription = "This is an item from an item pool.",
    tdescription = "This is today's test.",
    pinstruction = "Please answer the following question.",
    tinstruction = "Give an answer to each question.",
    maxattempts = 1, zip = TRUE, points = NULL,
    eval = list(partial = TRUE, negative = FALSE),
    base64 = FALSE, converter = NULL, seed = NULL,
    ...)

La sintaxis R de la función “exams2qti21” que crean exámenes para el estándar IMS QTI 2.1 para plataformas virtuales es la siguiente:

  exams2qti21(file, n = 1L, nsamp = NULL, dir = ".",
    name = NULL, quiet = TRUE, edir = NULL,
    tdir = NULL, sdir = NULL, verbose = FALSE,
    resolution = 100, width = 4, height = 4, svg = FALSE, encoding  = "",
    num = NULL, mchoice = NULL,
    schoice = mchoice, string = NULL, cloze = NULL,
    template = "qti21", duration = NULL,
    stitle = "Exercise", ititle = "Question",
    adescription = "Please solve the following exercises.",
    sdescription = "Please answer the following question.",
    maxattempts = 1, cutvalue = 0, solutionswitch = TRUE,
    zip = TRUE, points = NULL,
    eval = list(partial = TRUE, negative = FALSE),
    converter = NULL, base64 = TRUE, mode = "hex", ...)

Para salidas html:

  exams2html(file, n = 1L, nsamp = NULL, dir = ".", template = NULL,
    name = NULL, quiet = TRUE, edir = NULL, tdir = NULL, sdir = NULL, verbose = FALSE,
    question = "<h4>Question</h4>", solution = "<h4>Solution</h4>",
    mathjax = NULL, resolution = 100, width = 4, height = 4, svg = FALSE,
    encoding = "", envir = NULL, converter = NULL, seed = NULL, ...)

Nota: en la salida html se pueden emplear los siguientes argumentos:

  • question. De tipo carácter o lógico. ¿Debe incluirse el enunciado en la salida HTML? Si el argumento es de tipo carácter se usará como cabecera para las cuestiones resultantes. El argumento question también puede ser un vector que controla la salida para el plantilla (“template”).

  • solution. De tipo carácter o lógico. Mirar el argumento question.

Para salidas pdf:

  exams2pdf(file, n = 1L, nsamp = NULL, dir = ".", template = NULL, 
    inputs = NULL, header = list(Date = Sys.Date()), name = NULL, 
    control = NULL, encoding = "", quiet = TRUE, transform = NULL,
    edir = NULL, tdir = NULL, sdir = NULL, texdir = NULL,
    verbose = FALSE, points = NULL, seed = NULL, ...)

Notas sobre Blackboard:

  • Blackboard soporta la importación de banco de preguntas QTI 2.1, aunque se recomienda usar la función exams2blackboard.

  • Pero las preguntas tipo “cloze” (varios apartados) actualmente “R/exams” no las importa correctamente.

Existen funciones en “R/exams” que generan exámenes para otras plataformas virtuales como:

  • exams2moodle(), para Moodle.
  • exams2openolat, para OpenOlat.
  • etc (seguirá creciendo).

Mi plantilla para usar R/exams y R Markdown

He creado un proyecto RStudio al que he llamado “examsconRmd” (descargar).

Es un fichero comprimido zip que contiene una carpeta con un proyecto RStudio que trae la estructura de ficheros mencionada anteriormente y que contiene:

  • En la carpeta “bancopreguntas”: ficheros con ejemplos de ejercicios/preguntas R/exams en formato R Markdown.

  • En la carpeta “modelos”: ficheros con modelos propuestos

    • Para crear los distintos tipos de ejercicios R/exams. Si se quiere hacer algún tipo de ejercicio se cogería de esta carpeta el fichero del modelo deseado y se haría una copia en la carpeta “bancopreguntas”, donde se personalizaría para que contenga la redacción de nuestro ejercicio.

    • Y también para crear los exámenes/pruebas según el tipo de salida: para Blackboard, para pdf y para html. Se seguiría un proceso parecido al anterior, es decir, se haría un copia en la carpeta “coleccionexamenes”, donde se cambiaría de nombre y se personalizaría indicando los ejercicios que debe contener. Desde estos ficheros en RStudio se podrían generar los ficheros con los exámenes al pulsar el botón “Source” (esquina superior derecha del fichero). Los ficheros generados se colocarán en la carpeta: “exagenerados”. Si se quiere usar en la plataforma Blackboard se tendría que realizar el proceso de importación (se ilustra visualmente en un apartado posterior).

Nota: Puede probar un sistema en la nube listo para trabajar, en RStudio Cloud, accediendo directamente al proyecto en el siguiente enlace: Mi R/exams en RStudio Cloud. La primera vez que se acceda habrá que registrarse (el registro es gratuito).

La primera vez, aparecerá un mensaje en rojo: “TEMPORARY PROJECT” y a su derecha aparece un botón “Save a Permanent Copy” que nos permite, al pulsar sobre él, crear en nuestra cuenta una copia independiente del proyecto a la que solamente usted tendrá acceso.

Para instalar LaTeX de forma local en nuestra cuenta de RStudio Cloud y poder construir ficheros pdf, se deben ejecutar las siguientes líneas en la consola de RStudio:

library(tinytex)
install_tinytex()

Este paso se haría una vez, aunque es un proceso que puede durar alrededor de un minuto.

Creación de preguntas con R Markdown y R/exams

Ya se han visto dos ejemplos de ejercicios dinámicos en la introducción:

  • Respuesta de elección única (“schoice”).
  • Respuesta de tipo numérica (“num”).

Pero antes de seguir con otros ejemplos se presentará el uso de la aleatorización y el manejo de cadenas de texto en el lenguaje R de forma muy básica.

Funciones útiles del lenguaje R para aleatorización y manejo de cadenas

En este apartado se presentarán algunas de las funciones del lenguaje R que más se usan en la creación de ejercicios aleatorizados: funciones para obtener números aleatorios y funciones para manejar cadenas.

Habría que tener algunas nociones sobre el lenguaje R para entender este apartado mejor, pero simplemente como referencia básica al uso de funciones en R.

La función sample()

La función permite extraer elementos aleatoriamente sin reemplazamiento y con reemplazamiento. Se describirá su funcionamiento con sencillos ejemplos de uso.

En el siguiente ejemplo se extrae un número al azar de entre los números que van del 1 al 6:

sample(x=6,size=1)   # o también: sample(6,1)
[1] 1

En el siguiente ejemplo se extraen al azar 15 valores con reemplazamiento entre los números que van del 1 al 10:

sample(x=10,size=15,replace=TRUE)
 [1]  4  7  1  2  7  2  3  1  5  5 10  6 10  7  9

Si volvemos a repetir la misma llamada se obtendrían valores diferentes

sample(x=10,size=15,replace=TRUE)
 [1]  5  5  9  9  5  5  2 10  9  1  4  3  6 10 10

Se puede definir la semilla del generador de números aleatorios de R con ayuda de la función set.seed():

set.seed(10)

De forma que si repetimos la llamada:

sample(x=10,size=15,replace=TRUE)
 [1]  9 10  7  8  6  7  3  8 10  7 10  2  8  8  7

Y volvemos a definir la semilla en el mismo valor:

set.seed(10)

La misma llamada producirá los mismos valores aleatorios:

sample(x=10,size=15,replace=TRUE)
 [1]  9 10  7  8  6  7  3  8 10  7 10  2  8  8  7

Esto permite que la generación de números aleatorios sea reproducible, una cuestión muy importante en investigación.

Otros ejemplos de uso de la función sample() son:

sample(x = c("mayor", "menor", "igual"),
        size = 5,replace=TRUE)
[1] "menor" "igual" "igual" "menor" "menor"
sample(x = c(1,10,20,30,40,50,60),size = 3)
[1] 40 60 50

Existen otras funciones R para generar números aleatorios como: rnorm(n, mean = 0, sd = 1), runif(n, min = 0, max = 1), etc.

La función paste() y paste0() para pegar cadenas

Estas funciones concadenan vectores y después los convierten a tipo cadena o “character”.

paste("El valor absoluto del test estadístico es igual a ", 
  round(abs(0.123827), digits = 3), 
  ".", 
  sep = "")
[1] "El valor absoluto del test estadístico es igual a 0.124."

Resaltar dos librerías R que contienen muchas funciones para trabajar con caracteres: “stringr” y “glue”.

Preguntas de respuesta múltiple

El fichero “pregunta.Rmd” tendría la siguiente estructura:

```{r data generation, echo = FALSE, results = "hide"}
## datos
n <- 40 + sample(1:12, 2) * 3
Waiting <- rnorm(sum(n), sd = sample(30:40, 1)/10) + rep(sample(30:80, 2)/10, n)
Waiting[Waiting < 0] <- 0
dat <- data.frame(
  TEspera = Waiting,
  Supermercado = factor(rep(1:2, c(n[1], n[2])), levels = 1:2, labels = c("SPARAG", "CONSUMO"))
)

## cuestiones/respuestas (questions/answer)
questions <- character(5)
solutions <- logical(5)
explanations <- character(5)

tt <- t.test(TEspera ~ Supermercado, data = dat, var.equal = TRUE,
  alternative = sample(c("less", "greater", "two.sided"), 1))

questions[1] <- "El valor absoluto del test estadístico es mayor que 1.96."
solutions[1] <- abs(tt$statistic) > 1.96
explanations[1] <- paste("El valor absoluto del test estadístico es igual a ", 
  round(abs(tt$statistic), digits = 3), ".", sep = "")

questions[2] <- "Se realizó un test de una cola."
solutions[2] <- tt$alternative != "two.sided"
explanations[2] <- paste("El objetivo de la prueba es demostrar que la diferencia de medias es ",
  switch(tt$alternative, "two.sided" = "distinto a", "less" = "menor que", 
  "greater" = "mayor que"), "0.")

questions[3] <- "El p-valor es mayor que 0.05."
solutions[3] <- tt$p.value > 0.05
explanations[3] <- paste("El p-valor es igual a ", format.pval(tt$p.value, digits = 3),
  ".", sep = "")

questions[4] <- paste("El test muestra que el tiempo de espera es mayor en Sparag ",
  "que en Consumo.")
solutions[4] <- tt$p.value < 0.05 & tt$alternative != "less" & diff(tt$estimate) < 0
explanations[4] <- if (solutions[4]) paste("El resultado del test es significativo ($p < 0.05$)",
  "y de aquí que la alternativa muestra que la diferencia de medias es",
  ifelse(tt$alternative == "two.sided", "distinto a ", "mayor que"), "0.") else 
  paste(ifelse(tt$alternative != "less", "", paste("La prueba tiene como objetivo mostrar",
  "que la alternativa de que el tiempo de espera sea más corto en Sparag que en Consumo.")),
  ifelse(tt$p.value < 0.05, "", "El resultado del test no es significativo ($p \\ge 0.05$)."))

questions[5] <- paste("La prueba muestra que el tiempo de espera es más corto en Sparag que en Consumo.")
solutions[5] <- tt$p.value < 0.05 & tt$alternative != "greater" & diff(tt$estimate) > 0
explanations[5] <- if (solutions[5]) paste("El resultado del test es significativo ($p < 0.05$)",
  "y por lo tanto se verifica la alternativa, que la diferencia de medias es",
  ifelse(tt$alternative == "two.sided", "distinto a", "menor que"),
  "0.") else paste(ifelse(tt$alternative != "greater", "", 
  paste("El objetivo de la prueba es demostrar que el tiempo de espera en Sparag es mayor que en Consumo.")),
  ifelse(tt$p.value < 0.05, "", "El resultado del test no es significativo ($p \\ge 0.05$)."))
```

Question
========

Se quiere comparar el tiempo de espera (en minutos) en las cajas de dos cadenas de supermercados
con sistemas diferentes. Se realizó el siguiente test estadístico:

```{r test output, echo = FALSE, comment = "## "}
print(tt)
```

¿Cuáles de los siguientes enunciados son correctos? (Nivel de confianza: 5%)

```{r questionlist, echo = FALSE, results = "asis"}
answerlist(questions, markup = "markdown")
```

Solution
========

```{r solutionlist, echo = FALSE, results = "asis"}
answerlist(ifelse(solutions, "Verdadero", "Falso"), 
        explanations, markup = "markdown")
```

Meta-information
================
extype: mchoice
exsolution: `r mchoice2string(solutions)`
exname: t-test de 2-muestras

Preguntas de respuesta de texto breve

El fichero “pregunta.Rmd” tendría la siguiente estructura:

```{r data generation, echo = FALSE, results = "hide"}
dat <- matrix(c(
  "lm",           "regresión de mínimos cuadrados",
  "glm",          "regresión de Poisson",
  "glm",          "regresión logística",
  "glm.nb",       "regresión binomial negativa",
  "model.matrix", "extraer la matrix regresora de un objeto R: modelo lineal
  (generalizado)",
  "coef",         "extraer los coeficientes estimados de un objeto R: modelo
  lineal (generalizado) ajustado",
  "vcov",         "extraer la matriz de covarianza estimada de un objeto R:
  modelo lineal (generalizado) ajustado",
  "logLik",       "extraer la log-verosimilitud ajustada de un objeto R: modelo
  lineal (generalizado) ajustado"),
  nrow = 2
)
i <- sample(1:ncol(dat), 1)
fun <- dat[1, i]
descr <- dat[2, i]
```

Question
========

¿Cuál es el nombre de la función R para `r descr`?

Solution
========

``r fun`` es la función R para `r descr`.

Ejecutar: `?`r fun`` para consultar su correspondiente página en el
manual de ayuda de R.


Meta-information
================
extype: string
exsolution: `r fun`
exname: Funciones R

Preguntas de respuesta numérica

El fichero “pregunta.Rmd” tendría la siguiente estructura:

```{r, echo=FALSE, results="hide"}
## parametros
a <- sample(2:9, 1)
b <- sample(seq(2, 4, 0.1), 1)
c <- sample(seq(0.5, 0.8, 0.01), 1)
## solucion
res <- exp(b * c) * (a * c^(a-1) +
       b * c^a)
```


Question
========

¿Cuál es la derivada de 
$$f(x) = x^{`r a`} e^{`r b` x}$$
evaluada en $x = `r c`$?


Meta-information
================
extype: num
exsolution: `r fmt(res)`
exname: Derivadas exponencial
extol: 0.01

Preguntas de respuesta numérica con ficheros de datos y gráficos incluidos

En el siguiente ejemplo se hacen varias cosas avanzadas:

  • Los cálculos se hacen en un fichero R aparte el cual se carga al inicio con la función source(). Este fichero puede a su vez cargar librerías R.

  • Se incluye un gráfico estáticos en el enunciado (en este ejemplo se ha incluido con el único fin de ver cómo se incluye). Si el gráfico se hubiera generado con R no habría que hacer nada de especial.

  • Se incluye un fichero de datos csv para que el estudiante pueda descargarlo y usarlo en sus cálculos. Además se ilustra cómo se pueden generar ficheros que luego queden almacenados junto con la salida generada (en este ejemplo, se crea un fichero RData que tiene toda la información relacionada con el enunciado y la solución del problema).

El fichero “pregunta.Rmd” tendría la siguiente estructura:


```{r datageneration, echo = FALSE, results = "hide",message=FALSE,warnings=FALSE}
## GENERACION DE DATOS
source("modelo_ejercicio_numerica_ale02_func.R")

pbmuj = round(runif(1,5,20)*(10^6),0)
ej1 = fun_gen_Datos_Prob_Fecundidad(Pob.Total.Mujeres = pbmuj,
                        semilla = NULL)

#Gano = sample(2000:2018,1)
#Gregion = sample(LETTERS,1)

GPobt = ej1$Pob_Total_t # produce error si ej1$Pob_Total_t en una expresion r ...
GPobt1 = ej1$Pob_Total_t1

solej1 = func_temaFec_ejerc01(ej1)

#####

Gpregs = character(8)
Gpregs[1] = "la edad media a la maternidad"
Gpregs[2] = sample(c("el ISF (método 1)",
                     "el índice sintético de fecundidad (método 1)",
                     "el número medio de hijos en la vida fertil de una mujer
                     (método 1)",
                     "el índice coyuntural de fecundidad (método 1)"
                     ),1)
Gpregs[3] = sample(c("el ISF (método 2)",
                     "el índice sintético de fecundidad (método 2)",
                     "el número medio de hijos en la vida fertil de una mujer
                     (método 2)",
                     "el índice coyuntural de fecundidad (método 2)"
                     ),1)
Gpregs[4] = sample(c("la TBR (método 1)",
                     "la tasa bruta de reemplazamiento (método 1)",
                     "la tasa bruta de reproducción (método 1)"
                     ),1)
Gpregs[5] = sample(c("la TBR (método 2)",
                     "la tasa bruta de reemplazamiento (método 2)",
                     "la tasa bruta de reproducción (método 2)"
                     ),1)
Gpregs[6] = sample(c("la TNR",
                     "la tasa neta de reproducción"
                     ),1)
Gpregs[7] = "la edad media de las madres que han tenido un hijo"
Gpregs[8] = "la edad media a la maternidad considerando únicamente las tasas
específicas de nacimientos femenimos"


Gcual = as.integer(sample(length(Gpregs),1))
Gpreg = Gpregs[Gcual]

## GENERACION DE RESPUESTAS/CUESTIONES
sol = switch (Gcual,
  round2(solej1$xMedia, 5),
  round2(solej1$ISFm1, 5),
  round2(solej1$ISFm2, 5),
  round2(solej1$TBRm1, 5),
  round2(solej1$TBRm2, 5),
  round2(solej1$TNR, 5),
  round2(solej1$xMedia1hijo, 5),
  round2(solej1$xMediaNfem, 5)
)
```



Question
========

En el año $t$ las mujeres residentes en una región, de 15 a 49 años de edad,
tuvieron los hijos que se indican en la tabla siguiente:

```{r echo=FALSE}
knitr::kable(ej1$tb_Nacim %>% select(-GEdad.iz,-GEdad.de),
             col.names = c("Grupos Edad", "Nac. Total","Nac. Hombre", "Nac. Mujer"))
```

La población femenina residente en la región era:

```{r echo=FALSE}
knitr::kable(ej1$tb_Pob_Muj,
             col.names = c("Grupos Edad","Población t","Población t+1"))
```


La población total a 1 de enero para $t$ era: `r GPobt` y para
"$t+1$" era: `r GPobt1`.

Sabemos también que según las Tablas de Vida para mujeres del año "$t-1$" los
valores de la población estacionaria eran:

```{r echo=FALSE}
knitr::kable(ej1$tb_nLx_Muj,
             col.names=c("Grupos Edad","nLx"))  #,booktabs=TRUE
```

A partir de la información facilitada, calcular para la región en el año $t$:
**`r Gpreg`**.


```{r, echo = FALSE, results = "hide"}
include_supplement("Rlogo.png",
  dir = find.package("exams"), recursive = TRUE)
```

```{r out.width="40%",echo=FALSE}
knitr::include_graphics("Rlogo.png")
```

Acceda al fichero: [fichero.csv](demogfecundidad_mod01.csv)



Solution
========

Usando el formulario de Demografía, se obtienen las tasas específicas de
fecundidad por edad (TEFx) y por edad y sexo nacimiento femenino (TEFfx):

\small
```{r echo=FALSE}
knitr::kable(solej1$tb_TEF)
```

\normalsize

La solución para **`r Gpreg`** es: 

$$
`r exams::fmt(sol, 3)`  \qquad (\text{más precisión: } `r fmt(sol,5)`)
$$


El resto de medidas son:


- TBN
```{r echo=FALSE}
solej1$TBNx1000
```
```{r}


- TGF
```{r echo=FALSE}
solej1$TGFx1000
```

- ISF método 1
```{r echo=FALSE}
solej1$ISFm1
```



- ISF método 2 (más preciso)
```{r echo=FALSE}
solej1$ISFm2
```



- $\bar{x}$Media (la edad media a la maternidad):
```{r echo=FALSE}
solej1$xMedia
```



- la edad media de las madres que han tenido un hijo:
```{r echo=FALSE}
solej1$xMedia1hijo
```

- la edad media a la maternidad considerando únicamente las tasas específicas 
de nacimientos femenimos
```{r echo=FALSE}
solej1$xMediaNfem
```

- sex.ratio
```{r echo=FALSE}
solej1$sex.ratio
```

- TBR método 1
```{r echo=FALSE}
solej1$TBRm1
```

- TBR método 2
```{r echo=FALSE}
solej1$TBRm2
```

- TNR
```{r echo=FALSE}
solej1$TNR
```

```


```{r include=FALSE}
Modelo = "demogfecundidad_mod01.Rmd"
save(ej1,solej1,Gpreg,Gcual,sol,Modelo,file = "demogfecundidad_mod01.RData")
write.csv(ej1,file = "demogfecundidad_mod01.csv")
exams::include_supplement("demogfecundidad_mod01.csv")
exams::include_supplement("demogfecundidad_mod01.RData")
```


Meta-information
================
extype: num
exsolution: `r exams::fmt(sol, 3)`
exname: Demografía Fecundidad 1 var (num)
extol: 0.01

Funciones útiles de la librería exams

  • La función round2() es una mejora de la función R: round(), ya que realiza el redondeo habitual.
round2(x, digits = 0) 
  • La función fmt() redondea y añade ceros al final (por defecto si digits es menor que 4).
fmt(x, digits = 2L, zeros = digits < 4L, ...)
  • La función char_with_braces agrega paréntesis a elementos negativos (para facilitar su visualización en ecuaciones).
char_with_braces(x)
  • La función num_to_tol (o equivalentemente num2tol) calcula la tolerancia absoluta basada en una solución numérica x y una tolerancia relativa reltol.
num_to_tol(x, reltol = 0.0002, min = 0.01, digits = 2)
  • El método toLatex configura un objeto R: “matrix” o “data.frame”, como una matriz LaTeX con paréntesis.
## S3 method for class 'matrix'
toLatex(object, skip = FALSE, fix = getOption("olat_fix"),
  escape = TRUE, ...)

## S3 method for class 'data.frame'
toLatex(object, rotate = FALSE, pad = " ~ ", align = NULL, row.names = FALSE, ...)

Por ejemplo:

> toLatex(iris[1:4,1:4])
[1] "\\begin{tabular}{rrrr}"
[2] "\\hline"   
[3] " ~ Sepal.Length ~  &  ~ Sepal.Width ~  &  ~ Petal.Length ~  &
                     ~ Petal.Width ~  \\\\ \\hline"
[4] " ~ 5.1 ~  &  ~ 3.5 ~  &  ~ 1.4 ~  &  ~ 0.2 ~  \\\\"
[5] " ~ 4.9 ~  &  ~ 3.0 ~  &  ~ 1.4 ~  &  ~ 0.2 ~  \\\\"
[6] " ~ 4.7 ~  &  ~ 3.2 ~  &  ~ 1.3 ~  &  ~ 0.2 ~  \\\\"
[7] " ~ 4.6 ~  &  ~ 3.1 ~  &  ~ 1.5 ~  &  ~ 0.2 ~  \\\\"
[8] "\\hline"                                                                   
[9] "\\end{tabular}"    
  • La función include_supplement() copia ficheros (estáticos, por ejemplo, gráficos, ficheros de datos, etc.) para su inclusión como suplemento en un ejercicio.

    include_supplement(file, dir = NULL, recursive = FALSE, target = NULL)

    Argumentos:

    • file: character. Un (vector de) nombre(s) de fichero.

    • dir: character. El directorio donde se puede encontrar el archivo. Si se usa dentro de “chunks” de código de los ejercicios R Markdown, el valor predeterminado es usar el directorio en el que se almacenan los ejercicios.

    • recursive: logical. ¿Deberían buscarse también los subdirectorios de dir para el archivo?

    • target: character. Un (vector de) nombre (s) de archivo de destino, por defecto se considera que es el mismo que el archivo.

    Por lo general, los archivos suplementarios se crean dinámicamente dentro de un ejercicio, por ejemplo, los datos se simulan y luego se representan o almacenan en un archivo, etc. Sin embargo, a veces un ejercicio desea incluir un archivo suplementario estático que esté disponible en algún directorio del sistema. Luego, la función include_supplement() es muy útil ya que copia dicho archivo de su directorio en los suplementos de un ejercicio. Luego se puede incluir/referenciar de la forma habitual en R Markdown en el texto de la pregunta/solución.

    Ver su uso en los modelos: “modelo_ejercicio_num_ale02.Rmd” y “modelo_ejercicio_num_ale03.Rmd”.

Guía visual para subir los exámenes generados a Blackboard

En los siguientes apartados se va a presentar una guía visual sobre el proceso de importación en Blackboard de los exámenes generados con R/exams y la configuración de las opciones disponibles en esta plataforma de enseñanza virtual para publicar un examen que los alumnos puedan realizar.

Cómo importar el examen generado en BlackBoard

En la plataforma de enseñanza virtual BlackBoard se dispone de dos vías para importar el examen generado como:

  • Examen.

  • Banco de Preguntas.

Apartado: Exámenes, Encuestas y Bancos de preguntas

Importar como Examen

Para importarlo como “Examen” habría que seguir los siguientes pasos una vez situados en el curso o asignatura en el que se quiere utilizar:

  1. Seleccionar: “Herramientas->Exámenes, Encuestas y Bancos de preguntas” (en el menú izquierdo).

  2. Hacer click sobre: “Exámenes” (ver “1” sobre la imagen anterior).

  3. Hacer click sobre: “Importar examen” (ver “1” sobre la siguiente imagen).

  1. Se adjuntará el fichero zip generado. Al hacer click sobre el botón de “Examinar mi equipo” (ver “1” sobre la siguiente imagen) se podrá seleccionar el fichero correspondiente.

  1. Se pulsará sobre el botón “Enviar” (ver “2” en la imagen anterior) para realizar el proceso de importación.

    Si todo ha ido bien, se verá en el listado de exámenes disponibles.

Importar como Banco de preguntas

Para importarlo como “Bamco de preguntas” habría que seguir los siguientes pasos una vez situados en el curso o asignatura en el que se quiere utilizar:

  1. Seleccionar: “Herramientas->Exámenes, Encuestas y Bancos de preguntas” (en el menú izquierdo).

  2. Hacer click sobre: “Banco de preguntas” (ver “2” sobre la imagen “Apartado: Exámenes, Encuestas y Bancos de preguntas” anterior).

  3. Hacer click sobre: “Importar banco de preguntas” (ver “1” sobre la siguiente imagen).

  1. Se adjuntará el fichero zip generado. Al hacer click sobre el botón de “Examinar mi equipo” (ver “1” sobre la siguiente imagen) se podrá seleccionar el fichero correspondiente (puede verse en “2”).

  1. Se pulsará sobre el botón “Enviar” (ver “3” en la imagen anterior) para realizar el proceso de importación.

    Si todo ha ido bien, se verá en el listado de exámenes disponibles (ver “3” en la imagen anterior). En la siguiente imagen puede verse el mensaje que el proceso de importación ha sido correcto.

Publicación del examen en Blackboard

A continuación se muestran una serie de capturas de pantalla de los pasos seguidos en la publicación de un examen que pueda ser realizado por los alumnos.

  • Creación de un elemento de contenido tipo examen.

  • Selección del examen y de preguntas.

  • Configuración de las opciones disponibles para que el alumno realice el examen.

  • Realización de un examen.

  • Consulta de las calificaciones obtenidas.

Creación de un elemento de contenido tipo examen.

Selección del examen y de preguntas.

Configuración de las opciones disponibles para que el alumno realice el examen.

Realización de un examen.

Consulta de las calificaciones obtenidas.

Referencias útiles

Cómo citar este trabajo

Para citar este trabajo en publicaciones, utilizar el siguiente formato:

Luque-Calvo, P.L. 2020. Cómo crear exámenes aleatorizados con R/exams y R Markdown. Disponible en http://destio.us.es/calvo

Para insertar esta referencia en un fichero bibliográfico BibTeX, añadir el siguiente código:

@Manual{Luque2020,
  title = {Cómo crear exámenes aleatorizados con R/exams y R Markdown},
  author = {Pedro L. Luque-Calvo},
  year = "2017",
  howpublished = {Disponible en \url{http://destio.us.es/calvo}}
}