Mostrando entradas con la etiqueta Evaluación. Mostrar todas las entradas
Mostrando entradas con la etiqueta Evaluación. Mostrar todas las entradas

lunes, 7 de abril de 2025

Evaluación. Python.

Del registro a la tabla

Quedó dicho en [esta entrada] que los soportes -hoja de datos como informatización (o como mera digitalización) de la evaluación era un recurso de cierta incidencia en la práctica profesional (menos de lo deseable, también quedó dicho), así que es de esperar que exista un número importante de documentos que contengan datos de este tipo en los expedientes SEO. Dentro de una buena práctica del Servicio como tal está el análisis de esta documentación y Python nos puede ayudar en ello.


De hecho [el script de la entrada] antes citada puede considerarse la base de ese procedimiento, si bien necesitamos complementarlo con el procedimiento que desarrollamos precisamente [en la entrada que ha precedido] a la actual. En realidad lo que ahora se trata es de aplicar ese mismo procedimiento a una segunda situación o necesidad: la conversión de registros individuales en una base de datos.

Resumiendo, se trata de lo siguiente:
  • Recopilamos los datos de interés de documentos-Excel que contienen registros de resultados de aplicación de una prueba de la que carecemos de una tabla de datos que sirva de síntesis acumulativa de datos. Para ello empleamos el procedimiento descrito en este script (o uno equivalente, convenientemente adaptado a la realidad del registro a estudiar). 
from openpyxl import load_workbook

lista_al = ['Jaime','Lorenzo','Maria','Juan','Antonio','Lucrecia','Marcos','Susana','Noemi','Javier']
nombres = []
item3 = []
pdt = []

for i in range(0,9):
    libro = load_workbook(filename = 'Reg_'+lista_al[i]+'.xlsx')
    hoja =libro['Sheet']
    
    celda_nom = hoja['B2'].value
    print (celda_nom)
    nombres.append(celda_nom)
    
    celda_it3 = hoja['G4'].value
    print(celda_it3)
    item3.append(celda_it3)
    
    celda_pd = hoja['J2'].value
    print(celda_pd)
    pdt.append(celda_pd)

  •  Sobre la lista de datos que hemos obtenido en el proceso anterior creamos un documento-Excel en formato hoja de datos o data frame. Para ello podemos recurrir al procedimiento desarrollado en este script, con los ajustes que resulten precisos, claro está.

#Importar el módulo de creación del libro excell
from openpyxl import Workbook

#Lista de encabezados
campos = [
            'Nombre','Apellidos','Curso',
            'r1','r2','r3','r4','r5','r6','r7',
            'p1','p2','p3','p4','p5','p6','p7',
            'PD','Porcentaje','P. típica'
    ]

#Crear libro
tabla = Workbook()

#Hoja activa
hoja = tabla.active

#Copiar encabezados en columnas

for i in range(0,20):
    hoja.cell(row = 1, column = i+1, value = campos[i])


#Guardar libro
tabla.save("Prueba_uno_tabla.xlsx")

Y finalizamos trasladando los datos recogidos en la fase 1 de este procedimiento a la tabla del documento-Excel qua acabamos de crear. Es en este punto donde debemos pensar en un procedimiento específico, ya que también lo es el objetivo que nos proponemos; no obstante, si lo que nos planteamos en estos momentos no tiene pretensiones de ser demasiado complejo, lo cierto es que el mismo procedimiento que empleamos para crear el encabezado de la tabla nos puede servir para completar la tabla, columna a columna, ya que así hemos recogido los datos de los registros individuales: en listas que se asimilan a las columnas de la tabla.

Con las modificaciones que corresponde, esta podría ser una primera solución. No es muy "elegante" que digamos, y puede ser mejora, pero es funcional, sencilla y replicable, así que de momento nos quedamos con ella. Aquí te muestro un ejemplo...

for i in range(0,10):
    hoja.cell(row = i+2, column = 1, value = nombres[i])

... que no te resultará desconocido después de haber estudiado el script de creación de la tabla: de hecho es el mismo, salvo la modificación del funcionamiento de filas y columnas y la llamada a una de las listas de los contenidos recuperados mediante el script de acceso a los registros.

Documento. Aquí tienes acceso al script comentado en la entrada. Con él finalizamos un recorrido por posibles opciones de trabajo con Python en el proceso de automatización de la evaluación. Pero hay más... más adelante.

Evaluación. Python.

Base de datos en Excel

Aunque nada impide que podamos trabajar con registros individuales según vimos en el [entrada anterior], realmente no parece que sea el modo más eficiente de tratar con múltiples entradas. De ahí que en el [docap modelo] desarrolláramos un procedimiento para crear una tabla de datos o data frame, si se prefiere. El objetivo mínimo con Python es alcanzar al menos el mismo nivel de complejidad en el procedimiento.



Para ello, y en principio, debemos crear un script que en genere un soporte con formato de tabla, escribiendo los encabezados y, sobre esta base, un segundo script va añadiendo al soporte-tabla de datos cada una de los registros a modo de filas. Al finalizar el proceso, deberemos disponer de un data frame con todos los registros en formato filas-columnas. Posteriormente precisaremos desarrollar procedimientos para acceder a ese conjunto de datos, pero para ello openpyxl posiblemente ya no sea la mejor opción, dado que, para ello, existe bibliotecas más potentes. Pero esta es ya otra cuestión.

De momento vamos a centrarnos en crear una tabla de datos (base de datos) sobre Excel recurriendo a openpyxl, empezando por crear el documento-base; si bien antes deberemos diseñar (sobre papel) la estructura de la tabla: cuantos campos (columnas) y cual será su identificador de columna (cabecera). Tomaré como referencia la estructura de contenidos ideada [en esta ocasión], considerando como datos de interés los de identificación, las respuestas del sujeto a los ítem, la puntuación que recibe en cada ítem y las puntuaciones finales (sumatorio de PD, porcentaje y puntuación típica). Todo ello hace un total de 20 campos-columnas, lo que resulta ya suficientemente complejo.

Sobre esta estructura creamos en nuestro script una lista de encabezados (campos[]):

campos = [
            'Nombre','Apellidos','Curso',
            'r1','r2','r3','r4','r5','r6','r7',
            'p1','p2','p3','p4','p5','p6','p7',
            'PD','Porcentaje','P. típica'
    ]

El siguiente paso consiste en crear el libro Excel (tbl = Workbook()). No necesitamos crear ninguna hoja ya que es suficiente con la que se genera por defecto, con lo que simplemente la identificaremos como hoja activa (hoja = tabla.active).

La copia de los encabezados en la primera fila de la tabla se resuelve mediante un bucle for (for i in range(0,19):) utilizando el procedimiento que permite identificar individualmente fila (que será fija -> fila 1) y columna (que será variable -> columna i+1, y cambiará a cada vuelta del ciclo, al igual que el contenido a escribir)  -> (hoja.cell(row = 1, column = i+1, value = campos[i]))

Para finalizar, sólo resta guardar el archivo (tabla.save("Prueba_uno_tabla.xlsx"))

Hasta aquí podemos decir que ya sabemos crear un libro en formato de tabla simple, pero ahora nos falta aprender a llenarla de contenido. Para ello podemos situarnos en diferentes contextos, pero vamos a hacerlo en el que posiblemente sea el más común: el que resulta como alternativa a la creación de registros individuales, esto es, cada vez que introducimos los datos de un alumno, estos (además de servir para generar un informe individualizado, cuestión que ahora no nos interesa), se guardan en una tabla-base de datos o data frame, según se prefiera.

Para ello, tomando como base el script de referencia, deberemos elaborar uno distinto del que nos sirvió para crear la tabla, que se define, en primer lugar, como de acceso al archivo-tabla antes creado (tabla = load_workbook(filename = 'Prueba_uno_tabla.xlsx')), lo que requiere haber importado previamente el módulo load_workbook (from openpyxl import load_workbook) y posteriormente habernos situado en la hoja que contiene la tabla (hoja = tabla.active).

Obviamente necesitamos acceder a los datos mediante el o los procedimientos que correspondan; en este caso, según lo establecido en [el script] que nos sirve de base, y que ahora no hace falta explicar. 

Aunque no es obligatorio, sí es conveniente unificar en una única lista (lista_datos = []) todos los contenidos que deseamos guardar en nuestra base de datos. Para ello utilizaré de forma reiterada y acumulativa la función append() (vg. lista_datos.append(nombre)), aprovechando un ciclo for (for i in range(0,7):) para cargar en la lista-sumatorio cada uno de los registros que se presentan también en formato lista en el cuerpo del script de recogida de datos (vg. lista_datos.append(respuestas[i])).

Una vez creada la lista única nos queda trasladar su contenido a la base de datos, pero antes tenemos que identificar en qué registro o fila corresponde hacerlo. Para ello, una posible solución pasa por identificar cual es la primera celda de la columna A que no contiene datos y utilizar ese valor para posicionarnos en el proceso final de escritura del registro. 

El segmento de script que se encarga de identificar esa celda presenta cierta complejidad, por lo que es conveniente explicarlo con cierto detalle.
  • Identificamos una variable como fila con valor 0 para disponer de una referencia de base (fila = 0). Esto no es obligatorio hacerlo, pero clarifica el proceso.
  • Después establecemos un ciclo como recurso para recorrer la columna A (for i in range(1,100):). Aunque he puesto como límite la celda A100, se puede aumentar o reducir este valor si se prevé que el número de registros va a ser mayor o menor. En todo caso se trata de una referencia sujeta a modificación que no tendrá mayor relevancia en el funcionamiento real del script, como podremos entender más adelante.
  • Después establecemos el procedimiento de desarrollo del bucle (celda = hoja['A'+str(i)].value) que se puede resumir diciendo que la variable celda contiene sucesivamente el valor contenido en las celdas de la columna A de la hoja activa (hoja[]), siendo la expresión 'A'+str(i) la responsable de modificar la celda referenciada en función del desarrollo del bucle y el cambio de valor de la variable contador i.
  • El condicional incrustado en el ciclo que sigue a la instrucción anterior nos permite valorar el cumplimiento del criterio indicado antes: si la celda no contiene ningún valor (está vacía) (if celda == None:) entonces damos a la variable fila el valor de i (fila = i), lo que nos asegura que es coincidente con el identificador numérico de la fila.
  • En ese caso se produce el fin del bucle gracias a la instrucción break; pero en caso contrario (else:), continua el desarrollo del bucle (instrucción continue). En realidad, esta segunda parte del condicional no es necesaria por redundante, pero clarifica el funcionamiento de la estructura.
Con ese valor de fila establecido, procedemos finalmente a escribir en la tabla los valores contenidos en nuestra lista (lista_datos[]) usando el mismo procedimiento que ya usamos en el script con que generamos el encabezamiento de la tabla y que se explica en la primera parte de esta entrada.
  • Mediante un bucle for (for i in range(0,20):) con el que vamos a recorrer las posiciones de la tabla y de la lista de datos...
  • ... gracias a la función cell() y sus tres parámetros (hoja.cell(row = fila , column = i+1, value = lista_datos[i])):
    • El valor fila (row = fila), donde empleamos la variable fila calculada antes.
    • El valor de la columna (column = i+1), que se va incrementando en el transcurso del buble, recorriendo así todas las columnas de la tabla.
    • Y el dato a grabar en las celdas correspondientes, que se ubica en las diferentes posiciones de nuestra lista (value = lista_datos[i]), que también recorremos mediante los valores sucesivos de la variable contador i.
Este script finaliza necesariamente guardando de nuevo el archivo original, para que sea efectivo el incremento de datos de la tabla (tabla.save('Prueba_uno_tabla.xlsx')). En ocasiones nos puede interesar guardar el archivo con un nombre diferente cada vez que actualizamos nuestra base de datos, pero generalmente no va a ser el caso.

Documentos. Mediante este script puede crear la tabla y mediante este otro pasar datos a la misma. El primero queda explicado al inicio de esta entrada y el segundo en su segunda parte.

sábado, 5 de abril de 2025

Evaluación. Python.

Acceso a registros

La facilidad con la que se crean soportes para registro de datos en el servicio Hoja de cálculo (Calc o Excel, que también en esto son iguales), hace que sea frecuente encontrar este sistema como recurso aplicado a diferentes pruebas. En realidad se usa menos de lo que cabría esperar y sería de desear, pero aun así su uso es relativamente frecuente. Por lo visto en la [entrada anterior] también es sencillo crear estos registros mediante Python (OpenPyXL).


Y, claro está, no tiene mucho sentido tomarse el tiempo necesario para generar este sistema de registro si después no vamos a hacer uso de él. En tiempos en los que se trataba únicamente de digitalizar el registro de los resultados de la evaluación, la consulta del mismo era directa y sobre el soporte y los cálculos que se pudieran implementar en él mediante fórmulas propias, servían para construir el informe de resultados. 

Pero si implementamos procedimientos de automatización del informe, ese almacenamiento de registros puede resultar innecesario, salvo que pensemos en procesos posteriores de análisis de datos. Y es en esto en lo que actualmente estamos, a caballo entre la sección Evaluación y la sección (análisis de) Datos, en lo que a la estructura de este blog se refiere.

El objetivo de esta entrada es plantear en Python un sistema que permita acceder de forma automática a un determinado conjunto de registros de datos soportados sobre Excel (con independencia de que originalmente sean documentos Calc o Excel, ya que, como sabemos, la conversión de unos en otros es factible mediante LibreOffice). Es, como se puede deducir, un procedimiento complementario al desarrollado en la propuesta formulada en la [entrada anterior], motivo por el que se puede afirmar que dicha propuesta forma parte (y es la parte primera) de un procedimiento de doble acceso, por más que, en cuanto a script, resulte más apropiado diferenciar uno de otro, y en consecuencia, también en cuanto a entrada del blog.

El uso de este procedimiento para acceder a un registro individual puede estar justificado en ciertos casos, pero en la mayoría se trata de acceder a un conjunto de registros iguales para analizar determinado subconjunto de datos de los mismos. En sentido estricto, por tanto, se trata más de una cuestión que afecta a la sección Datos, que a Evaluación, pero por continuidad con la temática en que se enmarca la mantendré en ésta.

Partimos pues de la necesidad de trabajar con una lista de nombres de archivo, lo que nos obliga a modificar el planteamiento de trabajo expuesto [en la entrada] en la que se expuso el modo de acceder a hojas de cálculo mediante OpenPyXL, aunque podremos aprovechar estos procedimientos para resolver nuestra tarea actual.

Otro requisito previos es tener bien definido el objetivo de trabajo y conocer el documento de referencia para establecer correctamente el conjunto de hojas y celdas a las que debemos acceder.

Para ejemplificar este propuesta, crearé un conjunto de 10 registros con el script de la entrada precedente. Lo que me propongo es acceder a esos registros (libros Excel) y, en ellos, al nombre del alumno, a la puntuación que obtiene en uno de los ítem y al sumatorio PD. Este es el scritp necesario.

from openpyxl import load_workbook

lista_al = ['Jaime','Lorenzo','Maria','Juan','Antonio','Lucrecia','Marcos','Susana','Noemi','Javier']
nombres = []
item3 = []
pdt = []

for i in range(0,9):
    libro = load_workbook(filename = 'Reg_'+lista_al[i]+'.xlsx')
    hoja =libro['Sheet']
    
    celda_nom = hoja['B2'].value
    print (celda_nom)
    nombres.append(celda_nom)
    
    celda_it3 = hoja['G4'].value
    print(celda_it3)
    item3.append(celda_it3)
    
    celda_pd = hoja['J2'].value
    print(celda_pd)
    pdt.append(celda_pd)

  • Primero accedo al contenido de la librería que contiene las funciones de acceso a libros Excel (from openpyxl import load_workbook) 
  • Después creo una lista con los identificadores de los archivos a los que deseo acceder...
(lista_al = ['Jaime','Lorenzo','Maria','Juan','Antonio','Lucrecia','Marcos','Susana','Noemi','Javier'])
  • ... y tres listas vacías más, una para cada colección de datos (vg. nombres = [])
  • Mediante una estructura de bucle (for i in range(0,9):) genero un proceso cíclico que recorriendo la lista de libros (lista_al libro = load_workbook(filename = 'Reg_'+lista_al[i]+'.xlsx')) y estableciendo la hoja Sheet como hoja activa (hoja =libro['Sheet']), carga en una variable el contenido de las celdas seleccionadas (vg. celda_nom = hoja['B2'].value) y lo añade mediante la función append() al final de la lista creada previamente (vg. nombres.append(celda_nom))
(La función print() (print (celda_nom)) sirve únicamente para verificar el funcionamiento, por lo que se puede comentar en el script definitivo).

Con esto únicamente hemos accedido al contenidos predefinido de múltiples libros Excel, y resta tratar esos datos del modo que se desee, pero esa es una cuestión que ahora no tiene mayor importancia, por lo que, una vez alcanzado el objetivo propuesto, podemos dar por finalizado el script, si bien deberemos retomarlo (y modificarlo) para un objetivo concreto.

Documento. Aunque repetitivo con el contenido de la entrada, este enlace te da acceso al script que explico en ella. Su correcto funcionamiento presupone que tiene creados los libros que resultan del desarrollo de la función load_workbook(), los cuales se generan mediante el [este otro script]. Los script y los libros Excel están ubicados en el mismo directorio. Para probar el funcionamiento del script debes hacerlo desde el IDE de Python, no desde el cmd (símbolo de sistema).

viernes, 4 de abril de 2025

Evaluación. Python.

Modelo de registro de datos

Frente al modelo básico en el que no he considerado necesario registrar los resultados, partimos ahora de que esto sí es necesario. Para ello crearé un sistema de registro basado en una hoja de cálculo.



Obsérvese que hablo de registro (individual) de datos, no de base de datos, diferencia esta muy importante. de hecho, este segundo modelo se diferencia del [anterior], pero también deja pendiente un tercer nivel de desarrollo para alcanzar lo ya conseguido mediante OOo Basic y el docap [que ya explicamos].

Ahora me planteo usar una hoja de cálculo, aunque también podría ser una alternativa válida utilizar un documento de texto: en sentido estricto, ambas ofrecen el mismo servicio. Pero en previsión de desarrollos posteriores es evidente que una hoja de cálculo presenta ventajas de acceso que no se dan en un documento de texto.

En cuanto al código, este segundo modelo aprovecha el desarrollado en el anterior script, introduciendo la creación de un libro Excel y la escritura de la información que se considera relevante para crear el registro individual de resultados. Para ello implemento procedimientos de trabajo de la biblioteca [OpenPyXL].

Y dado que el el código de base ya fue explicado en la [entrada precedente], me centraré en ésta en explicar el código que se añade y que se sitúa (salvo la instrucción from openpyxl import Workbook que se ubica al inicio del script) tras los cálculos de las puntuaciones del sujeto y la creación del informe en soporte documento.

#Hoja de datos
hc_reg = Workbook()

#Acceder a la hoja activa
hoja_act= hc_reg.active

#Escritura en celdas de los datos de identificación

id_datos = ['Registro de resultados','Nombre','Apellidos','Curso']
datos_per = [nombre, apellidos, curso ]

for i in range(0,4):
    hoja_act['A'+str(i+1)] = id_datos[i]

for i in range(0,3):
    hoja_act['B'+str(i+2)] = datos_per[i]

#Guardar datos de los resultados de la prueba

hoja_act['E1'] = 'ítem'
hoja_act['F1'] = 'Respuestas'
hoja_act['G1'] = 'Puntos'

for i in range (1,8):
    hoja_act['E'+str(i+1)] = 'ítem'+str(i)
    hoja_act['F'+str(i+1)] = respuestas[i-1]
    hoja_act['G'+str(i+1)] = pd[i-1]
    
hoja_act['I2'] = 'PD'
hoja_act['J2'] = pdt

hoja_act['I3'] = 'Porcentaje'
hoja_act['J3'] = pc

hoja_act['I4'] = 'Punt. típica'
hoja_act['J4'] = pz

#Guardar hoja
hc_reg.save('C:/PON_AQUÍ_TUS_DIRECTORIOS/Reg_' + nombre + '.xlsx')

Si has seguido este blog te darás cuenta de que lo fundamental de este código se corresponde con lo explicado en [esta entrada]. En todo caso te remito a ella para mayor detalle y aquí pasaré por alto las cuestiones básicas vistas en ella, como la creación del libro Excel (hc_reg = Workbook()) o el posicionamiento en la hoja (hoja_act= hc_reg.active).

La escritura de datos en una celda (vg. hoja_act['E1'] = 'ítem') no presenta ninguna dificultad y a esta instrucción he recurrido con frecuencia, como puedes observar. esto hace que el código no sea muy elegante, pero sí muy sencillo de entender y replicar.

Tampoco es demasiado complejo escribir una colección de datos en un grupo de celdas (esto es, crear una tabla, aunque con las limitaciones que esto tiene en este momento). Veamos como ejemplo el procedimiento más complejo de los que he usado en esta oportunidad: la creación de la tabla de respuestas y puntuaciones.

  • Primero creo la cabecera de la tabla (vg. hoja_act['E1'] = 'ítem')
  • Después utilizo un for (for i in range (1,8):)
  • ... y recorro las celdas colocando los datos generados directamente mediante el propio bucle (hoja_act['E'+str(i+1)] = 'ítem'+str(i)) o tomando los resultados de una lista (vg. hoja_act['F'+str(i+1)] = respuestas[i-1])
Observa que, para que todo funcione como se espera, es fundamental controlar qué valores se asignan al contador i, ya que unas veces se usa como referente para identificar la celda (['E'+str(i+1)]), otras para crear una cadena de texto ('ítem'+str(i)) y otra para referenciar el índice de una lista (respuestas[i-1])

Documento. Desde este enlace puedes acceder (y descargar) al script que desarrolla este segundo modelo.


jueves, 3 de abril de 2025

Evaluación. Python.

Modelo básico

Dentro de las posibles situaciones en que se puede requerir automatizar la evaluación, voy a considerar en esta entrada la más básica en términos de desarrollo del algoritmo, de ahí su título.



Supongamos las siguientes condiciones como punto de partida: se trata de recoger los datos de una prueba individual aplicada a un alumno de la que no se requiere almacenar más información que la que deriva de un análisis simple de los datos. En esta situación, que es más simple que la que planteamos en el [docap-modelo],  vamos a necesitar un script (python) para la recogida de datos, la corrección de la prueba, el análisis de resultados y su posterior traslado a un documento de texto a modo de informe. Para crear el informe voy a usar la biblioteca python-docx, pero, en función del carácter básico del modelo, me limitaré a sus componentes más básicos. Resumiendo, que a fuerza de ser todo básico, esto es lo mínimo que se puede hacer.

Después de importar la librería python-docx (from docx import Document) adelantándonos a lo que será la fase final del proceso, empezamos por diferenciar las distintas partes de que se compone este proyecto, esquema que nos puede ser de mucha utilidad para otras ocasiones:
  • Procedimientos para la recogida de datos en base a variables y listas de datos. Diferencio el procedimiento de recogida de datos personales del de recogida de respuestas a los ítem. Veremos posteriormente esta diferencia con más detalle.
  • Procedimientos de corrección de la prueba y de análisis (simple) de los resultados.
  • Y procedimientos para la elaboración del informe (soporte documento Word)
Debo explicar que, para facilitar la comparación entre los recursos y también para simplificar la elaboración de estas entradas, he tomado como referencia y ejemplo el docap modelo explicado en [esta entrada], incluyendo el formulario empleado en esa ocasión y su estructura.

Sobre esa base, en la recogida de datos, como dije antes se diferencian dos procedimientos: el que se requiere para los datos de identificación y el necesario para las respuestas a los ítem. Esta diferencia es doble, ya que afecta tanto al sistema de referenciación (variables vs. colecciones de datos) como al procedimiento de recogida (input() vs. ciclo for). Me explico y concreto
  • Para recoger los datos de identificación utilizo variables y hago uso de la función input() (vg. nombre = input('Nombre: '))
  • Pero para recoger las respuestas a los ítem, primero creo una lista vacía (respuestas =[]) que después doto de contenido mediante un bucle (for i in range(0,7):) y la función append() (respuestas.append(item)) que se aplica tras solicitar la entrada de datos, eso sí, también mediante input() (item =input('Ítem ' + str(i+1) + ":  "))
La segunda base (corrección y análisis) también se puede dividir en dos subfases, siendo la primera de ellas la corrección de la prueba y la segunda el cálculo de resultados en función de las referencias usadas (que son las mismas que en el docap). veamos con cierto detalle cada una de ellas, empezando por la primera ya que presenta cierta complejidad.
  • En primer lugar debemos contar con una lista que contenga las respuestas correctas (soluciones =['A','B','C','B','A','C','D']) así declarar una tercera lista donde guardar las puntuaciones que resulten de la corrección de los ítem (pd =[])
  • La puntuación del ítem implica la comparación de la respuesta obtenida anteriormente con la esperada y referenciada en la lista soluciones[].
  • Para realizar esta comparación deberemos formular un segundo bucle (for i in range(0,7):) que contenga un condicional en el cual se comparan ambas listas (if respuestas[i] == soluciones[i]:) y se genera el valor de la puntuación  y su alternativa (p = 1) (else: -> p = 0)
  • Concluimos el proceso con la función append() (pd.append(p)) para dotar de contenido a la lista pd[]
La segunda es mucho más simple y reproduce los mismos cálculos que vimos en OOo Basic: (recuérdese que se trata de un simple ejercicio, por lo que se simplifican en extremo los cálculos y el análisis)
  • Mediante un bucle (for i in range(0,7):) establecemos el sumatorio de la puntuación directa (pdt = pdt + pd[i])
  • Y partiendo de ésta y de los valores media (med = 5.38) y desviación típica (dt = 0.47), calculamos el porcentaje de aciertos (pc = (pdt/7)*100) y la puntuación típica (pz = (pdt - med) / dt)
Finalizamos este script implementado el código que nos permite generar el documento-informe, haciendo uso, en este caso, de la biblioteca python-docx, concretamente las funciones de crear (informe = Document()) y grabar el documento (informe.save('C:/PON_AQUÍ_TUS_DIRECTORIOS/Info'+ nombre +'.docx')), incorporando como contenido un título (informe.add_heading('Prueba de conocimientos básicos', level = 0)), una breve identificación del sujeto (vg. informe.add_paragraph('Datos personales del alumno: ' + nombre + ' ' + apellidos)) y la síntesis de los resultados obtenidos, ésta mediante un listado (vg. informe.add_paragraph('Puntuación directa: ' + str(pdt), style='List Bullet')

Obsérvese que se ha personalizado el identificador del informe empleando el contenido de la variable nombre ('Info'+ nombre +'.docx'). Esto permite generar diferentes informes siempre que no coincidan los nombres de los alumnos, lo que no es suficiente, siendo necesario idear sistemas más complejos para evitar las consecuencias no deseadas que derivan de esa coincidencia. Pero para nuestro objetivo actual me ha parecido que es suficiente.

Documento. Descarga desde este enlace el código de este scritp.



martes, 1 de abril de 2025

Evaluación. Python.

Modelo Python ofimático

Sirva este proyecto (que aquí se inicia) como explicitación de que existen diversas alternativas para alcanzar el mismo (o similar) objetivo: en este caso, automatizar el proceso de evaluación.


En realidad el objetivo que me planteo es menos ambicioso y ligeramente diferente, ya que no aspiro a mayor nivel de automatización que el conseguido hasta este momento con OOo Basic [ver modelo docap], pero sí a mostrar diferentes desarrollos que son posibles con Python y sus módulos o bibliotecas orientadas al uso de servicios ofimáticos (esta vez del paquete MSO-A), lo que permite desarrollar enfoques no previstos en OOo Basic, incluyendo la creación automática de un presentación como recurso de devolución de información a colectivos.

Pero como el camino tiene cierto recorrido, no es conveniente abordarlo en una única entrada, por lo que esta servirá únicamente como presentación del pequeño conjunto que sigue y para identificar los tres procedimientos que me planteo desarrollar aquí:
  • Solución pensada como recogida de datos de un registro único y en un único momento.
  • Solución pensada como creación de un registro individual de datos a concretar en dos procedimientos complementarios.
  • Y solución orientada a la creación de una base de datos simple.
Explicaré cada una de estas opciones en sus respectivas entradas.

martes, 11 de marzo de 2025

Aprendizaje. Item.

Actividades de aprendizaje (y evaluación)

En la entrada anterior mostré un material que incluye  una presentación Impress y un soporte Calc para la realización de actividades (no distingo aquí entre ejercicios y actividades). Este segundo material nos introducen en el campo de las actividades de aprendizaje y evaluación.



En la práctica no voy a distinguir entre ambas actividades, ya que la diferencia es más de uso y fin que de forma, e incluso en esas distinciones pueden no serlo. Lo que sí me ha parecido necesario es tratar las diferentes formas en que se pueden presentar los ítem de las actividades.

Estas modalidades de ítem incluyen también diferentes soportes LibreOffice, aunque lo más frecuente que se presenten directamente sobre controles de formulario y en soporte Calc.

En las entradas que siguen dentro de este subapartado explico diferentes formas de expresión del ítem, delimitándolas a formatos compatibles con LibreOffice. Empezaré por reflexionar sobre el papel del ítem como elemento básico de una actividad para seguir con la explicación de cada uno de los tipos de ítem, aunque sin pretender agotarlas todas.

viernes, 28 de febrero de 2025

Evaluación.

Justificación del tratamiento diferencial de la evaluación

Queda dicho en una entrada de similar objetivo que esta, que desde la perspectiva del blog está justificada la diferenciación de dos secciones para el tratamiento de las tareas como docap. En esta entrada me interesa clarificar cómo afecta esta diferenciación al contenido de la sección que aquí se justifica: Evaluación.


Gracias a la diferenciación del contenido en dos secciones, podemos entrarnos en ésta en el desarrollo de propuestas específicas de recogida y análisis de datos y en la formalización de procedimientos de automatización de informes de resultados. Nos olvidamos aquí de las diferentes problemáticas que conlleva la elaboración de recursos diseñados para ser utilizados directamente por el alumnado.

Esta omisión, que lo es relativamente dado que es objeto de atención en la sección del blog que complementa la presente, nos permite atender específicamente a lo que resulta nuclear en la evaluación, así como enlazar con las problemáticas tratadas respectivamente en la sección Textos (la automatización de informes) y Datos (el análisis de datos), a la vez que provee a la sección Aprendizaje de los necesarios módulos complementarios para el desarrollo integral de instrumentos de intervención.

Aunque al menos inicialmente el soporte-base será LO-Calc y OOo Basic como lenguaje de programación, es de esperar que seamos capaces de implementar también el lenguaje Python, bien sea manteniendo el uso del soporte anterior (Calc, es decir, una hoja de cálculo), bien empleando otros alternativos. En todo caso, lo que realmente importa aquí es que generemos modelos de trabajo que permitan automatizar integralmente, hasta donde esto es posible, la evaluación de pruebas, dadas las ventajas que conlleva dicha automatización para el trabajo docente y más aun de los SEO.

miércoles, 18 de diciembre de 2024

Evaluación. Automatización.

Modelo básico de docap

Calificar de básico este modelo equivale a decir que es concreción del docap tipo 2 visto en [esta entrada]; decir docap incide en el uso de OOo Basic como lenguaje de referencia. Al estar en la sección Evaluación concretamos que este docap es un intento de automatización de los procedimientos de evaluación, con las especificaciones que realizamos en la [entrada que precede] a la actual.


Aclaro en primer lugar que me limito al docap-modelo el de tipo 2 por ser el de uso más frecuente también en el ámbito de la evaluación, pero también por similitud con el soporte básico de recogida de datos que veremos la sección Análisis.

Entrando ya en materia y completando lo indicado en la entrada citada al inicio de ésta, el docap-modelo que presento ahora no se diferencia demasiado del presentado [en esta entrada] respecto al trabajo con documentos. Salvando algunas diferencias (que puede haberlas), la mayor y más significativa es que el docap actual añade una base de datos a los componentes del docap; el resto de no tiene por qué ser diferente, aunque sí lo será el procedimiento de análisis. No obstante, como este análisis no será abordado detenidamente en esta entrada por los mismos motivos (más otros añadidos) (1) que en aquel caso, las diferencias reales entre ambos soportes, en términos de código, se reducen significativamente, por lo que no resultará demasiado complicado explicar y entender lo que a continuación voy a exponer: en gran medida será suficiente con repetir lo ya visto.

Para concretar un docap-modelo de evaluación primero se debe especificar en que nivel se recoge los datos, ya que es posible diferenciar tres: respuestas del alumno, puntuaciones obtenidas por ítem y puntuación total de la prueba. Considero que para que resulte realmente operativo (en términos de automatización) se debe entender como nivel básico el segundo, aunque en función de los objetivos de esta entrada yo trabajaré desde el primer nivel: recogida de las respuestas, que implica que la puntuación de las mismas se debe resolver en el docap.

Una segunda cuestión, aunque se refiere al análisis de datos y dije antes que en esto no iba a ser abordado en esta entrada, es con qué referentes se van a calificar los resultados. Esta cuestión requiere aclarar previamente qué tipo de evaluación se propone: normativa, criterial o mixta. Estas cuestiones no corresponden a esta entrada, ya que es necesario clarificar el significado concreto de esos conceptos y sus implicaciones, por eso opto aquí por una solución de compromiso simplificada (2).

La primera fase del proyecto es, como en los modelos documentales, el trabajo con la hoja de cálculo, empezando por la creación del formulario (hoja Form) (3). Este podría ser el resultado:


No he pretendido hacer nada complicado, por lo que es mejorable (también a nivel estético), pero sirve para nuestro objetivo actual: recoger la información de forma eficiente sin sobrepasar el nivel de complejidad técnica básico, que por ahora consiste en el uso de funcionalidades de formulario de Calc. No obstante presenta algunas diferencias respecto al nivel más elemental de trabajo con documentos, y lo explico a continuación:
  • En el formulario se diferencian tres partes, frente a las dos de los modelos anteriores: a parte de los botones de comando, los controles de entrada se diferencian por su función: los tres primeros son para datos de identificación y los restantes para la recogida de las respuestas de los ítem.
  • Esta diferencia se reproduce en la asignación de esos controles a celdas: los controles de datos de identificación se asocian a tres celdas de la columna I, y los de los controles de ítem a siete celdas de la columna J.
  • Esta diferenciación por columnas puede no ser necesaria, pero sí la diferenciación en el acceso a cada uno de los dos tipos de datos, como veremos en el código correspondiente. Después explicaré el motivo.
Además, y esa es la principal diferencia con los docap-modelo documentales, deberemos crear una segunda hoja (Datos) donde crearé la tabla de datos, concretamente los encabezamientos de las columnas que sirven para identificar sus campos.

Una vez preparado el documento Calc, corresponde desarrollar el código que nos permite acceder a los datos introducidos por el usuario. Para ello declaro las variables (4) y accedo al documento y a la hoja Form, siguiendo el procedimiento ya conocido...

Dim oHoja As Object, oCelda As Object
Dim mDatosper() As String, mCeldasi() As String
Dim mResp() As String, mCeldasj () As String, mAcierto() As String
Dim mPtos() As Integer, PD As Integer
Dim n1 As Integer, nj As Integer
Dim i As Integer
Dim Md As Single, Dt As Single, Pz As Single, Pc As Single
Dim mDatos() As Variant

oHoja = ThisComponent.getSheets().getByName("Form")

Observa que he declarado dos matrices para contener los datos (mDatosper() y mResp()), ya que me interesa trabajar de forma independiente con cada uno de los dos bloques. Como dije antes, esta diferenciación obedece al diferente tratamiento que tendrán los datos de identificación y los de respuestas a los ítem: los primeros no presentan novedad respecto a otras ocasiones, pero los segundos sí: sobre ellos realizaremos operaciones específicas, como tendremos ocasión de ver en su momento. Repito: con los datos personales nos limitamos acceder a ellos, pero los datos de respuesta a los ítem serán usados para generar los elementos de la tercera matriz (mPtos()), que contendrá la puntuación de los ítem, que resulta de la corrección de la prueba; los referentes para la corrección de los ítem están contenidas en una cuarta matriz (mAcierto()).

Por ello, el acceso a las respuestas del alumno, con la que finaliza la primera fase del script principal Main se resuelve de forma similar a cómo se resolvió el acceso a los datos personales, mediante un bucle For (For i = 0 To UBound(mResp()))

ni = 2
ReDim mDatosper(ni)
mCeldasi= Array("I1","I2","I3")

For i = 0 To UBound(mDatosper())
oCelda = oHoja.getCellRangeByName(mCeldasi(i))
mDatosper(i) = oCelda.getString()
Next

A continuación entramos en la segunda fase de Main, en la que corregimos las respuestas dadas por el alumno. En este caso el sistema es muy sencillo, pero puede ser mucho más laborioso de no trabajar con una matriz específica de respuestas, de ahí que sea recomendable diferenciarla de la de datos de identificación, como indiqué en su momento.

Para realizar esta corrección damos contenido a la matriz que contiene las respuestas correctas (mAcierto() = Array("A","B","C","D","E","F","G")) y mediante un nuevo bucle (For i = 0 To  UBound(mResp())) y un condicional (If mResp(i) = mAcierto(i) Then) recorremos la matriz mResp() y comparamos su contenido con el esperado (mResp(i) = mAcierto(i)), asignando la puntuación que corresponde a cada ítem según se cumpla o no dicha condición. Aprovecho este mismo bucle  para calcular el sumatorio de las puntuaciones del alumno (PD = PD + mPtos(i)), lo que supone entrar en la tercera fase del script.

mAcierto() = Array("A","B","C","D","E","F","G")

For i = 0 To  UBound(mResp())
If mResp(i) = mAcierto(i) Then
mPtos(i) = 1
Else
mPtos(i) = 0
End If
PD = PD + mPtos(i)
Next

Como dije, este sumatorio constituye el inicio de la tercera fase, la cual está dedicada a realizar los cálculos que nos permitirán calificar los resultados que obtiene el alumno: el porcentaje de aciertos, en función del tamaño del test y su puntuación directa (Pc = (PD/(nj+1))*100), y la puntuación típica (Pz = (PD - Md)/Dt), en función de supuestos valores de media (Md = 5.38) y la desviación típica (Dt = 0.47) (5).

A continuación se desarrolla la cuarta fase del script principal (Main) que tiene como objetivo construir con los datos parciales anteriores una matriz única de datos con la que trabajaremos en las subrutinas a las que se llama al final de Main (PasarDatos(mDatos()) y Info(mDatos())) (quinta fase). 

Aunque podríamos ahorrarnos esta cuarta fase, por los motivos que dije en su momento puede no ser la mejor opción; pero, en todo caso, sí es conveniente crear una matriz única con todos los datos necesarios, ya que será muy útil para el correcto funcionamiento de las subrutinas que veremos más adelante. 

Para crear esta matriz-síntesis recurriré de nuevo a los bucles como medio para automatizar la asignación de contenidos, aunque es necesario asignar de forma directa algunos de ellos, como puedes comprobar en el código que sigue:

ReDim mDatos(19)

For i = 0 To UBound (mDatosper())
mDatos(i) = mDatosper(i)
Next

For i = 0 To UBound( mResp())
mDatos(i+3) = mResp(i)
Next

For i = 0 To UBound(mPtos())
mDatos(i+10) = mPtos(i)
Next

mDatos(17) = PD
mDatos(18) = Pc
mDatos(19) = Pz

Primero redimensionamos la matriz-resumen (ReDim mDatos(19)) y después vamos asignando los datos, empezando por los personales (mDatos(i) = mDatosper(i)), siguiendo por las respuestas del alumno (mDatos(i+3) = mResp(i)), después los resultantes de la corrección de la prueba (mDatos(i+10) = mPtos(i)), finalizando con los datos-resumen, resultantes de los cálculos (vg. mDatos(17) = PD) (6).

Paso ahora a explicar los dos procedimientos que restan y que se ejecutan de forma independiente del script principal, mediante dos subrutinas: 
  • La primera (Sub PasarDatos(Datos() As Variant)) está pensada para construir la base de datos, trasladando los datos de la matriz-resumen (mDatos()) a la tabla creada en la hoja Datos.
  • La segunda (Sub Info(Datos() As Variant)), ya conocida de [esta entrada], sirve para crear el informe (sobre Writer).
Estas subrutinas constituyen una especie de "externalización de procedimientos" del algoritmo, pero esto, además de ejemplificar el uso del principio de la programación modular, resulta necesario explicarlos con detalle, dada la relativa complejidad de ambos subprocesos y su independencia respecto al objetivo principal del script Main

Dado que la segunda subrutina ya es conocida desde la publicación de [esta entrada], me limitaré a explicar la primera. Con ella automatizamos la creación de la tabla o base de datos, dotando a nuestro docap de un potencial muy interesante: recopilar los resultados de las aplicaciones individuales de una prueba, lo que facilita el posterior análisis de dichos datos (7).

Sub PasarDatos(Datos() As Variant)

Dim oHojaBD As Object, oCeldaInicio As Object, oCeldaNuevoRegistro As Object, oCeldaId As Object
Dim i As Integer, a As Integer, b As Integer, c As Integer, d As Integer

oHojaBD = ThisComponent.getSheets().getByName("Datos")

c = 1000

For i = 0 To c
oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))
If oCeldaInicio.getValue() = 0 Then
a = i + 2
d = i + 1
Exit For
End If
Next

MsgBox "Id de la nueva entrada: " & d

'Escritura de los datos en el registro vacío

oCeldaId = oHojaBD.getCellRangeByName("A" & CStr(a))
oCeldaId.setValue(d)

For b = 0 To UBound(Datos())
oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b+1,a-1)
oCeldaNuevoRegistro.setString(Datos(b))
Next

End Sub

Esta subrutina cuenta con un argumento que remite a la matriz-resumen (PasarDatos(Datos() As Variant)de los datos del script principal Main, resultando ahora evidente la necesidad de su creación: gracias a ellos se simplifica la escritura de esos datos en la tabla.

Pero antes de dar ese paso debemos acceder a la hoja Datos (oHojaBD = ThisComponent.getSheets().getByName("Datos")) y posicionarnos adecuadamente en la primera fila no escrita (8) mediante un bucle condicionado

For i = 0 To c
oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))
If oCeldaInicio.getValue() = 0 Then
a = i + 2
d = i + 1
Exit For
End If
Next

Cuando encontramos la celda de la columna A (oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))) que cumple la condición (oCeldaInicio.getValue() = 0) asignamos el valor de inicio a la variable-contador que nos van a permitir recorrer las diferentes columnas de la tabla (a = i + 2) y de captura el valor Id de la fila A que precede a la que no cumple el criterio del condicional (d = i + 1). Después interrumpimos el bucle.

Después volvemos a posicionarnos en la celda de la columna A localizada mediante el bucle anterior (oCeldaId = oHojaBD.getCellRangeByName("A" & CStr(a))) y escribimos en ella el valor Id que corresponde (oCeldaId.setValue(d)) y de nuevo, mediante un bucle dimensionado en base a la matriz-resumen (For b = 0 To UBound(Datos())), recorremos ahora las columnas (campos) que se corresponden con la fila (oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b+1,a-1)) y trasladamos a cada celda los datos de la matriz (oCeldaNuevoRegistro.setString(Datos(b))).

Documento. Desde este enlace puedes acceder al docap explicado en esta entrada.

NOTAS

(1) Si en el caso de los docap-documento el tema del procesamiento remitía a las dificultades para tratar de forma simplificada el problema del procesamiento, en el caso de los docap-evaluación la problemática se acentúa, ya que de una prueba de evaluación se derivan muchos más datos cuantitativos y de categorización que requieren procesos de análisis de la bifurcación de mayor complejidad. No obstante el tratamiento de la opcionalidad en el caso de los contenidos textuales es realmente más complejo, ya que se ajustan en menor medida que los datos de un test a un planteamiento de tipo algorítmico.
(2) Por lo que no resulta satisfactorio desde ninguna de las perspectiva de análisis, pero tiene la ventaja de incluir referencias a ambos modelos (y al mixto). Además, y es lo que importa, evidencia los mecanismos básicos del análisis desde ambas perspectivas (la normativa y la criterial), aunque de forma muy simplificada.
(3) Dado que el informe se crea sobre un documento en blanco, no es necesario crearlo. También es posible utilizar un documento previamente creado y formateado, con lo que implica de trabajo previo y de modificación del código de acceso. Ver al respecto el modelo básico 2 de docap de documento en [esta entrada]
(4) Incluyendo las que serán necesarias para el procesamiento posterior y que explicaré en su momento.
(5) Ambos valores sirve únicamente a efectos del funcionamiento del docap, pero no reflejan valores reales de ninguna prueba en concreto.
(6) Puedes apreciar que los tres primeros bloques se ejecutan mediante bucles (reajustando los valores de los índices de la matriz-resumen) y en los últimos la asignación se realiza de forma directa.
(7) Este análisis es anticipatorio pero diferentes del que nos planteamos en la sección Análisis del Blog. También lo es este docap del que podemos crear dentro de ese campo de trabajo.
(8) Recuerda que una fila de una tabla equivale a un registro en una base de datos. Las columnas de la tabla equivalen a los campos de la base de datos y serán tratadas como variables en el análisis de datos.