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.



miércoles, 2 de abril de 2025

Herramientas. PyGame

Presentaciones, demostraciones y juegos.

El motivo de incluir PyGame en este blog es facilitar un medio para el logro de los objetivos planteados en la sección Aprendizaje. Cierto que para ello ya disponemos del servicio Impress, de recursos OOo Basic (docap) e incluso, aunque con limitaciones, también de la biblioteca Python-pptx. Sin embargo PyGame no permite adentrarnos en un mundo, el de los videojuegos, con un gran potencial para el desarrollo de la intervención educativa  (incluyendo la llamada gamificación) y de los SEO; en este último caso como herramienta de evaluación.


Debo reconocer, no obstante, que si PyGame interesa como recurso es por las posibilidades de aprendizaje que genera, ya que también exige un proceso de aprendizaje no exento de complejidad. Por ello, lo que en este blog se trate sobre esta biblioteca será únicamente lo que responda a los objetivos que en él se plantea, motivo por el que señalo en el párrafo de inicio de esta entrada a los objetivos de la sección Aprendizaje.

Pero en la sección a la que pertenece esta entrada (y las que siguen sobre el mismo tema) (Herramientas) se plantearán los procedimientos básicos que se requiere saber para hacer uso de esta biblioteca, incluyendo los conocimientos necesarios que se requiere dominar sobre el lenguaje Python y sobre el paradigma de programación orientada a objetos, aunque ambos como extensión y/o derivación del aprendizaje del trabajo con PyGame.

Para poder iniciar este recorrido deberemos empezar por instalar la biblioteca, así que procederemos como sabemos y corresponde desde Símbolo del sistema escribiendo después del prompt pip install pygame; siempre en el supuesto de trabajar desde Windows.

Queda con esto situada la temática y el enfoque, incluyendo sus limitaciones. Para mayor conocimiento del tema iré añadiendo a esta entrada algunas referencias documentales. Para empezar...


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.

Herramientas. OpenPyXL

Biblioteca openpyxl (2). Acceso a datos.

La segunda parte, y a la vez la inversa del proceso descrito en la entrada anterior, es el acceso a un libro Excel y a los datos contenidos en cualquiera de sus hojas. Existen diferentes recursos Python para hacerlo, pero por ahora nos limitaremos a la biblioteca openpyxl.



Lo primero que tenemos que hacer es acceder al módulo que nos permite cargar un libro Excel mediante la instrucción correspondiente (from openpyxl import load_workbook), que, mediante su función load_workbook() permite acceder con facilidad a cualquier archivo identificado como .xlsx. Esta función recibe un parámetro asociado al atributo filename que asocia la dirección del archivo (load_workbook(filename = 'Libro2.xlsx') -> en este caso se supone que el archivo se encuentra en el mismo directorio que el script). Para facilitar el manejo del objeto, lo referenciamos en una variable, lo que facilita el trabajo posterior (vg. libro = load_workbook(filename = 'Libro2.xlsx'))

El segundo paso consiste en acceder a la hoja en la que se encuentran nuestros datos.  Esto podemos hacerlo de dos formas diferentes:
  • Accediendo a la hoja activa (la primera) (hoja = libro.active)
  • O identificando la hoja por su nombre (Vg. hoja =libro['Hoja0'])
Aunque cualquiera de los dos procedimientos es válido, posiblemente sea el segundo el de mayor utilidad, aunque requiere saber de antemano el nombre de cada una de las hojas a las que deseamos acceder; nada que deba suponer un problema para nosotros, teniendo en cuenta el uso que vamos a hacer de script de este tipo.

El tercer paso es en realidad un conjunto de opciones (de las que vamos a ver algunas de ellas), que tienen en común, como es de esperar, el acceso a los datos contenidos en celdas.

Y la forma más simple de hacerlo es identificando directamente la celda por su posición (A1). Así, por ejemplo, para celda1 = hoja['A1'].value, la expresión print('El dato de la celda A1 es: '+ celda1) nos muestra el contenido de dicha celda. Aunque si el contenido es un número nos dará error, siendo necesario convertir la variable a string (str(celda1)). Dado que no incurrimos en error en caso de convertir un string en string, parece conveniente emplear la función de conversión por defecto cuando el objetivo sea el manejo de cadenas de texto (concatenación), como es el caso de lo que pretendemos con la función print().

Si conocemos (y así será la mayoría de la veces) la estructura de los datos a los que deseamos acceder, podemos crear un procedimiento basado en la repetición del modelo anterior, lo cual es funcional y suficiente cuando deseemos acceder a hojas de cálculo diseñadas como registro de datos, pero puede no ser lo más adecuado cuando trabajamos con hojas de cálculo que contienen tablas, que son la mayoría de las que nos podemos encontrar. En estos casos nos van a ser de utilidad conocer otras formas de acceder al contenido.

Disponemos, en primer lugar, de un procedimiento para conocer cuantas filas y columnas hay creadas en nuestra hoja, punto de partida interesante para el trabajo posterior:
  • col_n = hoja.max_column -> print('Número total de columnas: '+str(col_n))
  • fil_n = hoja.max_row -> print('Número total de filas : '+str(fil_n))
 ... nos devuelven respectivamente sendos integer con el número de columnas y de filas. Estos datos son de interés cuando la hoja está organizada de forma simple y compacta, pero puede generar confusión cuando cuenta con tablas diferentes y de escasa identidad en cuanto a contenido. De ahí también el interés de construir tablas con sentido y unidad de criterio, aunque sea a costa de aumentar el número de hojas del libro. 

Pero bueno, esta es otra cuestión. Volvamos a lo que nos interesa, que ahora es acceder al contenido de una fila de datos, para lo cual recurrimos a una estructura compleja basada en la función cell().value y una estructura for...
  • valores_fila = [hoja.cell(row=2, column=i).value for i in range(1,hoja.max_column+1)]
... que trataré de explicar a continuación:
  • los valores obtenidos mediante la función cell() toma como referencia (en esta caso) la segunda fila (row =2) de la tabla (colección de celdas) de la hoja referenciada en la variable hoja (hoja.cell().value). 
  • Sobre las columnas iterará el bucle for que desarrolla el atributo value de la estructura (.value for i...)
  • Es este ciclo es que se encarga de recoger los valores de las columnas (de la fila indicada antes), desde la primer hasta la última de las columnas (hoja.max.column+1)
El resultado que obtenemos (print(valores_fila)) es una lista con los datos de la fila en cada una de las columnas. Si variamos el parámetro row accedemos a otra fila y si modificamos el primer valor de la función range() accedemos a la columna que indiquemos y a las sucesivas hasta finalizar la colección de columnas. De este modo podremos acceder a los datos de un determinado registro (fila) de una tabla de datos, si bien se requiere conocer el contenido de dicha tabla para que resulte realmente útil. En otro caso, serán necesarios 8por ejemplo) procedimientos de búsqueda basados en condicionales.

De modo similar podemos acceder al contenido de una columna de datos mediante una estructura simular a la anterior...
  • valores_col=[hoja.cell(row=i,column=2).value for i in range(2,7)]
... en la que...
  • usamos la misma función cell().value asociada a un ciclo for (cell().value for i...) en la que ahora la itinerancia de ciclo se realiza sobre el identificador de la fila (row=i), mientras que se establece el valor fijo de la columna (column=2 -> en este caso nos interesa al segunda columna de la tabla)
  • ...lo que repercute en el contenido de range(), que tiene como primer parámetro la segunda celda (de la segunda columna) empezando desde B1 (ergo B2) y recorre hasta la posición 7
El resultado (print(valores_col)) es la colección de nombres (columna 2) de cada uno de los registros (desde José hasta Sonia)

Para finalizar esta entrada, vamos a ver cómo podemos acceder a un conjunto de registros (filas y columnas), lo que equivale decir que a una base de datos. Este procedimiento viene a ser el resumen de los dos anteriores, pero se desarrolla de forma diferentes.
  • Primero creamos un rango de celdas (las que nos interesan para nuestro análisis) mediante un procedimiento de acotado sobre la hoja (rango_celdas = hoja['A2':'C6'])
  • Y después lo recorremos mediante for indicando explícitamente cada uno de los elementos de su estructura (for cell1, cell2,cell3 in rango_celdas:)
  • ... para (en este ejemplo) mostrarlo por pantalla (print(cell1.value, cell2.value,cell3.value))
El rango de celdas establecido identifica la fila (en este caso A2, para no acceder al encabezado de la tabla) y la columna final (en este caso C6, para recoger todos los registros de datos). La enumeración de celdas en la estructura for y en el función print() es fundamental para que efectivamente podamos acceder a ellas mediante el bucle y (en este caso) mostrar su contenido (vg. cell1.value).

Documento. Accede desde este enlace al script en el que se recoge el código analizado en esta entrada.