Mostrando entradas con la etiqueta Excel. Mostrar todas las entradas
Mostrando entradas con la etiqueta Excel. Mostrar todas las entradas

miércoles, 24 de septiembre de 2025

Datos. Python.

Biblioteca Camelot (III)

Conversión de tablas a Excel




Para finalizar esta serie de entradas sobre Camelot, expongo en esta entrada la transcripción del script del autor de [ExcelMasPro] cuyo vídeo cité como fuente [en esta entrada]. Sirva de agradecimiento a su autor.


El objetivo de esta entrada es triple: reconocer lo que ExcelMasPro nos aporta con su trabajo, mostrar otro modo de trabajar con Python y retomar la referencia a la relación entre este lenguaje y las hojas de cálculo. Y todo en el mismo paquete.

Paso sin más a presentar el script que he escrito copiando literalmente el que se expone en el vídeo, aunque el que aquí presento omite la formulación de la función y la llamada a la misma. Esto no altera el propio funcionamiento del código, aunque lo simplifica adaptándolo al modo de trabajar que se ajusta a mi nivel actual de conocimiento:

import camelot
import os
import pandas as pd
from tkinter import Tk
from tkinter.filedialog import askopenfilename, askdirectory
from datetime import datetime

#Ocultar ventana principal de TKinter
Tk().withdraw()

#Seleccionamos el archivo pdf
print("Seleccione el archivo PDF:")
pdf_file = askopenfilename(
    title = "Seleccionar el archivo PDF",
    filetypes=[("Archivos PDF","*.pdf")]
    )
if not pdf_file:
    print("No se seleccionó ningún archivo. Saliendo...")

#Seleccionar la carpeta de destino
print("Seleccione la carpeta donde desea guardar el archivo Excel:")
output_folder = askdirectory(title="Seleccionar carpeta de destino")
if not output_folder:
    print("No se seleccionó ninguna carpeta. Saliendo...")
    
#Nombrar el archivo Excel
now = datetime.now().strftime("%m-%d-%y-%H-%M")
output_excel = os.path.join(output_folder, f'Import_PDF_a_Excel_{now}.xlsx')

#Extracción de las tablas del pdf mediante Camelot
print("Extrayendo tablas del PDF...")
tables = camelot.read_pdf(pdf_file,pages="all", flavor="stream")

#Guardar las tablas en Excel
if not tables:
    print("No se encontraron tablas en el archivo PDF:")

#Guardando las tablas
with pd.ExcelWriter(output_excel, engine="openpyxl") as write:
    for i, table in enumerate(tables):
        table.df.to_excel(write, sheet_name = f'Tabla_{i+1}', index=False)

En resumen, lo que hace este script es acceder a un archivo pdf, capturar las tablas que incluye (lo que Camelot identifica como tales, que no siempre lo son) y copiarlas en una hoja de cálculo en la que crea tantas hojas como tablas encuentra.

El uso de TKinter permite al script facilitar una entrada de datos personalizada y en formato gráfico; cosa que raramente he empleado yo por considerarlo innecesario en este nivel de aprendizaje, pero en este caso me ha parecido interesante mantenerlo por mostrar otras formas de trabajar con Python (más cercanas al desarrollo de aplicaciones o DocAp).

Me ha parecido especialmente interesante el modo de construir el nombre del documento Excel creado, aunque he realizado un pequeño cambio en el formato (uniendo todos los elementos mediante guion bajo).

Realmente este script es más que una forma de mostrar el uso de Camelot (o de TKinter), incluyendo el procedimiento de crear y guardar datos en una hoja de cálculo de Excel. Supone poner a nuestra disposición una pequeña aplicación que nos resuelve el problema de acceder a cualquier documento pdf y extraer las tablas que contenga dejándonoslas disponibles como documento Excel. No siempre funciona perfectamente (sobre todo cuando los documentos son complejos) y no siempre extrae todos los datos de las tablas (interesa comprobar la calidad del resultado), pero al menos para pdf sencillo y tablas "normales" el éxito está garantizado.


lunes, 7 de abril de 2025

Evaluación. Propuestas.

Python y 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.

viernes, 4 de abril de 2025

Evaluación. Propuestas.

Modelo de registro de datos en Python.


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 al script que desarrolla este modelo.


martes, 1 de abril de 2025

Python. Recursos.

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.