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

jueves, 5 de marzo de 2026

DATOS. Acceso a datos

CSV. Datos estructurados (III)

Acceso y descarga de csv

Vamos a centrarnos en este script en el trabajo con un archivo .csv, pensando no en el acceso (tema ya tratado en esta entrada), sino en tener disponible su contendio desde el script. Eso sí, seguiremos usando el módulo propio CSV, si bien ahora en la opción de diccionario.

Para que se entienda mejor el proceso a seguir, empezaremos por el acceso al archivo .csv. La diferencia principal respecto al script parejo de la entrada anterior es que sustituímos lector = csv.reader(archivo, delimiter=',') por lector_dict = csv.DictReader(archivo). El resto derivan de ésta.



import csv

archivo_nombre = 'datos/datos.csv'										# 1. Definimos la ruta del archivo

try:
    with open(archivo_nombre, mode='r', encoding='utf-8') as archivo:  # 2. Abrimos el archivo en modo lectura ('r')
        lector_dict = csv.DictReader(archivo)                          # 3. Creamos el lector de diccionarios
        print(f"Leyendo datos de: {archivo_nombre}\n" + "-"*30)
        for fila in lector_dict:                                       # 4. Recorremos cada fila (cada fila es un diccionario)
            nombre = fila['Nombre']                                                         
            edad = fila['Edad']        
            print(f"Usuario: {nombre} | Edad: {edad}")                          

except FileNotFoundError:                                              #Control de errores
    print(f"Error: El archivo '{archivo_nombre}' no existe.")
except KeyError as e:
    print(f"Error: No se encontró la columna {e} en el archivo.")


A pesar de las diferencias, ambos archivo comparten la misma limitación: la base de datos sólo está disponible dentro del ámbito de with open(), pero fuera no podemos acceder a los datos, así que si nos interesa deberemos trasladarlos a una colección de datos del propio script, concretamente a una lista de diccionarios.



import csv

archivo_nombre = 'datos/datos.csv'                                                  # 1. Definimos la ruta del archivo

datos_internos = []                                                                 # Lista para almacenar la base de datos en memoria


try:
    with open(archivo_nombre, mode='r', encoding='utf-8') as archivo:  				# 2. Abrimos el archivo en modo lectura ('r')
        lector_dict = csv.DictReader(archivo)                                       # 3. Creamos el lector de diccionarios
        print(f"Leyendo datos de: {archivo_nombre}\n" + "-"*30)
        for fila in lector_dict:                                                    # 4. Recorremos cada fila (que es un diccionario)
            nombre = fila['Nombre']                                                         
            edad = int(fila['Edad'])
            datos_internos.append(fila)        
            print(f"Usuario: {nombre} | Edad: {edad}")                          
        print(f"--- Carga finalizada ---")
        print(f"Se han almacenado {len(datos_internos)} registros en el diccionario interno.")

    if datos_internos:																# Ejemplo: Acceder al primer registro almacenado
        primero = datos_internos[0]
        print(f"Primer registro en memoria: {primero['Nombre']} tiene {primero['Edad']} años.")
        
except FileNotFoundError:                                                           #Control de errores
    print(f"Error: El archivo '{archivo_nombre}' no existe.")
except KeyError as e:
    print(f"Error: No se encontró la columna {e} en el archivo.")

#Ejemplo de acceso a la estructura del diccionario ---------------------------------------------------------------------------------------

registro_ejemplo = datos_internos[0]                   # Tomamos el primer diccionario de nuestra lista para el ejemplo
print(f"Claves: {list(registro_ejemplo.keys())}")      # A. Identificar solo las CLAVES
print(f"Valores: {list(registro_ejemplo.values())}")   # B. Identificar solo los VALORES


Lo que nos permite importar al script el contenido del archivo .csv empieza aquí (datos_internos = []) y se concreta en estas dos líneas:
  • for fila in lector_dict: ----> Recorre el archivo .csv
  • ----datos_internos.append(fila) ----> Añade contenido a la lista (de diccionarios) datos_internos

Ellas hacen posible el correcto acceso a los datos mediante instrucciones como print(f"Claves: {list(registro_ejemplo.keys())}"), que muestra las claves del diccionario.

Cierto que la consecuencia es un incremento de la carga de memoria, pero la ventaja en rapidez de funcionamiento compensa cuando necesitamos realizar diferentes acciones sobre los datos desde un mismo script. Tal es el caso del que sigue y con el que finalizamos esta entrada: un script mediante el que accedemos a una base de datos, la cargamos en memoria y realizamso sobre ella un conjunto de acciones que aquí se concretan en seleccionar y filtrar registros.



import csv

# --- FASE 1: CARGA DE DATOS  ---------------------------------------------------

ruta_archivo = 'datos/datos.csv'
datos_internos = []

try:
    with open(ruta_archivo, mode='r', encoding='utf-8') as archivo:
        lector = csv.DictReader(archivo)
        for fila in lector:
            fila['Edad'] = int(fila['Edad']) # Convertimos el valor de [Edad] a entero para poder filtrar
            datos_internos.append(fila)
    print(f"Sistema listo. {len(datos_internos)} registros cargados.\n")

except FileNotFoundError:
    print("Error: No se encontró el archivo en D:/. Por favor, créalo primero.")
    exit() # Finaliza el script si no hay datos

# --- FASE 2: OPCIONES ------------------------------------------------------------

while True:
    print("\n===============================")
    print("   GESTOR DE CONSULTAS")
    print("===============================")
    print("1. Buscar por NOMBRE (Coincidencia parcial)")
    print("2. Filtrar por EDAD (Exacta o Rango)")
    print("3. Salir")
    
    opcion = input("\nSeleccione una opción: ").strip()

    if opcion == '3':
        print("Saliendo del sistema...")
        break

# --- OPCIÓN 1: BÚSQUEDA POR NOMBRE ---
    if opcion == '1':
        termino = input("Escriba el nombre o parte de él: ").strip().lower()
        encontrados = [r for r in datos_internos if termino in r['Nombre'].lower()]
        
        print(f"\nResultados para '{termino}':")
        if encontrados:
            for e in encontrados:
                print(f"ID: {e['Nombre']} | Edad: {e['Edad']}")
        else:
            print("No se encontraron coincidencias.")

# --- OPCIÓN 2: FILTRO POR EDAD ---
    elif opcion == '2':
        print("\n--- Filtro de Edad ---")
        print("A. Edad exacta")
        print("B. Rango (Mínimo y Máximo)")
        sub = input("Elija modalidad (A/B): ").strip().upper()

        resultados = []
        try:
            if sub == 'A':
                objetivo = int(input("Edad a buscar: "))
                resultados = [r for r in datos_internos if r['Edad'] == objetivo]  # Identificamos si el valor de la clave 'Edad' coincide
            
            elif sub == 'B':
                v_min = int(input("Edad mínima: "))
                v_max = int(input("Edad máxima: "))
                resultados = [r for r in datos_internos if v_min <= r['Edad'] <= v_max]  # Filtro por intervalo de valores          
         
            if resultados:
                print(f"\nSe hallaron {len(resultados)} personas:")
                for r in resultados:
                    print(f"• {r['Nombre']} - {r['Edad']} años")
            else:
                print("No hay registros en ese rango.")
                
        except ValueError:
            print("Error: Por favor, ingrese solo números enteros para la edad.")

    else:
        print("Opción no válida. Por favor, marque 1, 2 o 3.")

print("\nPrograma finalizado correctamente.")


NOTA: El archivo .csv de prueba es necesario para el correcto funcionamiento de estos tres script, aunque siempre los puedes adaptar para que funcionen con un .csv diferente. No obstante, si prefieren no hacerlo, aquí lo puedes descargar.

domingo, 1 de marzo de 2026

DATOS. Acceso a datos

CSV. Datos estructurados

Biblioteca Pandas. Acceso a .csv

Aunque el módulo nativo (CSV) es suficiente cuando la tabla de datos es sencilla y el volumen de datos que contiene moderado, para trabajar con bases de datos grandes y complejas es preferible usar la bibliteca Pandas. Por eso no vamos a dedicar al módulo nativo más tiempo del necesario, puesto que será Pandas la opción preferente: lo que sirve para lo mucho, sirve para lo poco. Ganamos tiempo.

Veamos cómo acceder a nuestra base de datos de libros ahora con Pandas.



import pandas as pd
import os

ruta_archivo = 'datos\libros3.csv'

def acceder_con_pandas():
    # 1. Verificar si el archivo existe
    if not os.path.exists(ruta_archivo):
        print(f"❌ El archivo '{ruta_archivo}' no se encuentra.")
        return

    try:
        # 2. Cargar el CSV
        # Pandas detecta automáticamente la cabecera
        df = pd.read_csv(ruta_archivo, encoding='utf-8')

        print("--- 🐼 Acceso Exitoso con Pandas ---")
        
        # 3. Visualización rápida
        print("\n📊 Resumen del contenido (Primeras 5 filas):")
        print(df.head())

        # 4. Información técnica (Nº filas, columnas y tipos de datos)
        print("\nℹ️ Información técnica:")
        df.info()

        # 5. Ejemplos de acceso directo
        if not df.empty:
            # Acceder a una columna específica (ej: 'Título')
            if 'Título' in df.columns:
                print("\n📖 Lista de Títulos:")
                print(df['Título'].to_list())

            # Acceder a un registro específico por su índice (ej: el primero)
            print("\n📍 Primer registro completo:")
            print(df.iloc[0])

    except UnicodeDecodeError:
        print("❌ Error de codificación. Prueba con encoding='latin1' o 'ansi'.")
    except Exception as e:
        print(f"❌ Ocurrió un error inesperado: {e}")

if __name__ == "__main__":
    acceder_con_pandas()

Para ir avanzando puedes analizar este script y aplicarlo a un caso real (un archivo csv como éste) para ver su funcionamiento. Esto es especialmente interesante su lo comparas con el funcionamiento de script basados en el módulo nativo CSV.

NOTA: Acuérdate de guardar tu cvs como librso3.csv dentro de una carpeta que deberás llamar datos o modificar la ruta de acceso de la instrucción ruta_archivo = 'datos\libros3.csv'

DATOS. Acceso a datos

CSV. Datos estructurados (II)

Módulo CSV. Nuevo registro de datos

En la entrada precedente aprendimos a acceder a un archivo .csv usando el módulo CSV; en la actual aprenderemos a añadir registros usando el mismo módulo. Además iremos directos al grano, digo, al script...



import csv
import os
from datetime import datetime

ruta_archivo = 'datos\libros3.csv'
NOMBRE_CAMPO_FECHA = 'fecha_lectura'

def sesion_carga_masiva():
    if not os.path.exists(ruta_archivo):
        print(f"❌ El archivo '{ruta_archivo}' no existe.")
        return

    try:
        # 1. Carga inicial para conocer la estructura y el último ID
        with open(ruta_archivo, mode='r', encoding='utf-8', newline='') as archivo:
            lector = csv.reader(archivo)
            cabecera = next(lector)
            datos_existentes = list(lector)
            
        # El ID inicial se basa en lo que ya hay en el archivo
        siguiente_id = len(datos_existentes) + 1
        campos_a_pedir = cabecera[1:-2]

        print(f"--- 🚀 Sesión iniciada. Para terminar escribe 'salir'.\n")

        while True:
            print(f"📝 Preparando Registro #{siguiente_id}:")
            datos_usuario = {}
            cancelar = False

            # 2. Bucle para pedir cada campo de la cabecera
            for columna in campos_a_pedir:
                valor = input(f"   {columna}: ").strip()
                
                if valor.lower() == 'salir':
                    cancelar = True
                    break
                datos_usuario[columna] = valor

            if cancelar: 
                break

            # 3. Procesamiento de Fecha y Validación
            fecha_str = datos_usuario.get(NOMBRE_CAMPO_FECHA)
            try:
                fecha_obj = datetime.strptime(fecha_str, "%d/%m/%Y")
                mes_auto = fecha_obj.month
                anio_auto = fecha_obj.year

                # 4. Construcción de la fila
                nueva_fila = [siguiente_id]
                for columna in campos_a_pedir:
                    nueva_fila.append(datos_usuario[columna])
                nueva_fila.extend([anio_auto,mes_auto])

                # 5. Guardado físico en el CSV
                with open(ruta_archivo, mode='a', encoding='utf-8', newline='') as archivo_escritura:
                    escritor = csv.writer(archivo_escritura)
                    escritor.writerow(nueva_fila)

                print(f"✅ Registro #{siguiente_id} guardado con éxito.\n")
                print("Escribe 'salir' para finalizar registro.\n")
                
                # Incrementamos el ID para el próximo libro de esta misma sesión
                siguiente_id += 1

            except ValueError:
                print(f"\n❌ ERROR: '{fecha_str}' no es una fecha válida (DD/MM/AAAA).")
                print("⚠️ Este registro no se guardó. Por favor, reinténtalo.\n")
                # No incrementamos el ID porque el registro falló

        print(f"\n--- Sesión finalizada. Se han añadido nuevos libros. ---")

    except Exception as e:
        print(f"❌ Error crítico en la sesión: {e}")

if __name__ == "__main__":
    sesion_carga_masiva()


Tomo como referencia el mismo conjunto de datos (libros3.csv) que nos sirvió para aprender a acceder y visualizar registros, pero esta vez planteo un procedimiento simple de escritura de registros mediante la función csv.writer(). Además de esta función, writerow() también es básica para el correcto funcionamiento del script, ya que es la que se encarga de añadir un uevo registro a nuestro documento.

En este caso utilizo una función (sesion_carga_masiva()), a la que llamo mediante el procedimiento if __name__ == "__main__":, aun pendiente de explicación. Todo llegará.

Aunque el usuario debe introducir la mayoría de los campos, algunos se implementan automáticamente. Tal es el caso del id del libro (siguiente_id = len(datos_existentes) + 1) y de los datos correspondientes al año y al mes de lectura, dereivados ambos del campo fecha_lectura (nueva_fila.extend([anio_auto,mes_auto])).

Dado que el script debe permitir la entrada múltiple de registros, se implementa un bucle (while True:) que lo hace posible, requiriendo al usuario la expresión 'salir' para cerrar el bucle y finalizar el script.

viernes, 27 de febrero de 2026

DATOS. Acceso a datos

CSV. Datos estructurados (I)

Módulo CSV. Acceso al archivo

Antes de nada una aclaración que parece necesaria: cuando hablamos de acceso a datos debemos distinguir entre lo que es acceder al contenido de un archivo y lo que implica acceder a un determinado dato o conjunto de datos que éste contiene. Lo primero implica hacer uso de determinadas tecnologías y lo segundo, además, la aplicación de determinados procesimientos.

En lo que se refiere a esta entrada cabe diferenciar esos procedimientos en el marco de una distinción que resulta de gran interés: la diferencia que implica acceder a datos estructurados y a datos no estructurados. Cuando hablamos, como ahora, de acceder a datos csv estamos planteando acceder a un tipo de documento (extensión .csv) que es de naturaleza estructurada, como lo son también los datos de una base de datos o los datos contenidos en una hoja de cálculo.

El formato .csv es uno de los formatos básicos para el trabajo con Python. Otro es el formato .txt, pero entre ambos existe una diferencia muy importante: los datos .csv están estructurados, mientras que los datos .txt no lo están. El acceso simple a ambos es sumamente sencillo desde Python, pero mientras que sigue siendo sencillo acceder a contenidos concretos de un archivo .csv, no lo es si trabajamos con un archivo .txt. Es este el motivo por el que (despues de hacerlo sobre directorios y archivos) nos planteamos ahora tratar el acceso a los archivos .csv y sus contenidos.

Un archivo .csv (comma-separated values) es una archivo de texto plano que contiene datos tabulares estructurados en filas y columnas separados por comas u otros delimitadores (punto y coma, tabulaciones).

Cada línea del archivo corresponde a una fila en la tabla, y cada valor de esa fila se separa por un delimitador (coma). Dada su simplicidad pueden ser leídos por casi cualquier hoja de cálculo o software de gestión de datos.

Python cuenta con herramientas para trabajar con archivos .csv, empezando por el módulo nativo CSV (que no requiere instalación mediante pip al ser nativo, aunuse ser importado) y siguiendo por Pandas y NumPy. Empezaremos por CSV

El módulo CSV cuenta con clases para leer y escribir datos tabulares en formato CSV y permite escribir y leer este tipo de archivo.

Existen dos enfoques de trabajo de este módulo en el tratamiento que da a los datos del archivo .csv: como listas o como diccionarios.

  • csv.reader(), enfoque lista en la lectura del archivo csv
  • csv.writer(), enfoque lista en la escribir en un archivo csv
  • csv.DictReader(), enfoque diccionario de la lectura de un archivo csv
  • csv.DictReader(), enfoque diccionario para la escritura en archivos csv

Veamos cómo se concretan ambos enfoques en el manejo de un mismo archivo csv.


import csv

#Acceso al archivo csv
ruta = 'datos\libros2.csv'
with open(ruta, mode='r', encoding='utf-8', newline='') as archivo:

# Creamos el lector enfocado a lista (csv.reader())
    lector = csv.reader(archivo, delimiter=',')
    
# Visualizamos la cabecera de la tabla csv  (mediante next())
    cabecera = next(lector)
    print(f"Listado de nombres de columnas (cabecera): {cabecera}")
    
# Iteramos sobre las filas mostrando las tres primeras columnas: id, autor y título
    for fila in lector:
          print(f"Id: {fila[0]}, Autor: {fila[1]}, Título {fila[2]}")


Esta es la forma más sencilla de acceder a un archivo csv. Es útil cuando conocemos la estructura de la tabla y el archivo tiene un tamaño manejable.

Es importante utilizar al fórmula with open() para facilitar el manejo del archivo csv, ya que nos permite abrirlo y cerrarlo de forma automática.

El acceso al contenido del archivo viene dada por la función csv.reader(), que, como vimos antes, nos permite capturar dicho contenido en una lista; primero a la cabecera (next()) y después a los datos mediante un bucle (for fila in lector:).

Sin embargo, esta forma de acceso tiene una limitación importante para el posterior acceso a los datos: es de recorrido único, por lo que deberemos repetir el procedimiento de acceso tantas veces como maniobras necesitemos realizar con el contenido del archivo csv.

En el script anterior la maniobra consistió en listar los registros y mostrarlos por pantalla (print(f"Id: {fila[0]}, Autor: {fila[1]}, Título {fila[2]}")) y en el siguiente accedemos a un registro determinado y dentro de él a una serie de campos empleado también un bucle y un contador para identificar el número de registro de deseamos visualizar (conta = 0), en este caso el nº 5 (if conta == 5)


#Mostramos un registro determinado y dentro de él dos campos.
#Para ello empleamos la iteración y nuestro conocimiento de la estructura de la tabla 

    for fila in lector:
        conta += 1
        if conta == 5:
            print(f"Autor: {fila[1]}, Título {fila[2]}")
            break 

Basándonos en los anterior, podemos crear un buscador sencillo de registros que funcione a petición del usuario. Simplemente se trata aplicar las funciones vistas antes, csv.reader() y otras, y poco más. Te dejo el script resultante de este propósito para que lo puedas usar en tus proyectos.

import csv

ruta_archivo = 'datos\libros3.csv'

def buscador_interactivo():
    try:
        # 1. Abrimos el archivo
        with open(ruta_archivo, mode='r', encoding='utf-8', newline='') as archivo:
            # Cargamos el lector
            lector = csv.reader(archivo)
            cabecera = next(lector)
            
            # Convertimos el lector a una lista para poder buscar varias veces. De no hacerlo sólo podríamos buscar un registro.
            # La consecuencia es un incremento de la carga de trabajo para la memoria del sistema (el contenido de la lista, ahora en memoria)
            datos = list(lector)
            total_registros = len(datos)

            print(f"--- 🔎 Buscador de registros cargado. ({total_registros} registros disponibles) ---")

            while True:
                # 2. Solicitar número al usuario
                entrada = input("\nIntroduce el número de registro a buscar (o 'salir' para terminar): ")

                if entrada.lower() == 'salir':
                    print("Gracias por usar nuestro buscador de registros.")
                    break

                # 3. Validar que sea un número
                try:
                    objetivo = int(entrada)
                except ValueError:
                    print("❌ Por favor, introduce un número válido.")
                    continue
                encontrado = False
                
                # 4. Buscar usando la función enumerate() sobre la lista de datos
                # 4.1. 'datos' es nuestro conjunto de filas.
                # 4.2. 'start=1' indica el inicio del conteo desde 1 (y no desde 0).
                # 4.3. Cada vuelta, 'i' recibe el número de fila y 'fila' los datos. 'i' es el contador automático de enumerate()
                for i, fila in enumerate(datos, start=1):
                    if i == objetivo:
                        print(f"\n✅ Registro #{i} encontrado:")
                        # Mostramos cada dato con su nombre de columna
                        for col, valor in zip(cabecera, fila):
                            print(f"   {col}: {valor}")
                        encontrado = True
                        break
                
                if not encontrado:
                    print(f"⚠️ El registro {objetivo} no existe. El rango es de 1 a {total_registros}.")

    except FileNotFoundError:
        print(f"❌ Error: No se encontró el archivo '{ruta_archivo}'.")

# Ejecutar el buscador
buscador_interactivo()


Datos

Fases del proceso

De forma resumida y para empezar, podemos decir que en el procesamiento de datos se pueden diferenciar tres fases: acceso, limpieza y análisis. Esta diferenciación, en lo que al orden de sucesión se refiere, es, con frecuencia, más formal que real, ya que no siempre sus fases se presentan en la secuencia expuesta. No obstante, la diferenciación es conceptualmente válida y necesaria.

La fase primera, la de acceso, es aquella en la que se recopilan los datos de la o las fuentes. Se trata de una fase muy sensible por la complejidad técnica que conlleva y por la necesaria atención a cuestiones de tipo ético-legal.

Puede ser que para los SEO, por lo que nos interesa y por el campo en que nos movemos, muchas de esas complejidades se simplifiquen bastante, pero también se expresan de forma radical. Por ejemplo, debemos garantizar absolutamente la confidencialidad de los datos, de modo que en ningún caso sean expuestos en la red, al menos (y sólo en determinadas circunstancias) sin antes haber sido sometidos a un riguroso de proceso de anonimación.

Salvadas estas cuestiones, como en cualquier otro campo de la investigación y de la intervención, deberemos resolver satisfactoriamente los problemas técnicos que derivan de las fuentes, empezando por los relativos al acceso.

Compartimos también con quienes trabajan con datos, el interés por que éstos sean fiables y valiosos; también porque sean suficientes, pero en esto nuestro interés no conlleva necesidades que sí afectan a otros campos. Para nosotros es más importante la calidad que la cantidad... dentro de ciertos límites de suficiencia, por supuesto.

La limpieza y depuración (data cleaning) busca mejorar la calidad de los datos, eliminando todo aquello que dificulta el posterior análisis (el "ruido") y corrigiendo errores. Esta fase consiste en...

  • Eliminar los datos duplicados y/o irrelevantes,
  • Dar a los valores faltantes (missing Data) el tratamiento que resulte más adecuado, bien sea por eliminación, bien por imputación (mediante qué procedimiento)
  • Corregir los errores estructurales: homogeneizar formatos, corregir errores tipográficos, estandarizar categorías y unificar unidades de medida.
  • Decidir el tratamiento de los valores atípicos (outliers) que pueden llegar a sesgar el análisis.
En resumen, la limpieza de datos garantiza la fiabilidad de los datos y, en consecuencia, de los resultados del análisis (fase siguiente del proceso), reduce el sesgo y asegura la compatibilidad entre datos.

La tercera fase, el análisis de datos (data analysis), es el proceso de inspeccionar, modelar y transformar los datos para generar información útil que sirva para la comprensión de los fenómenos y/o la toma de decisiones.

Los tipos de análisis básicos son los siguientes:

  • El análisis descriptivo, que, como su nombre indica, describe los datos y, en su caso, los resume mediante diversos estadísticos (descriptivos).
  • El análisis exploratorio (EDA), usado para entender la estructura de los datos, encontrar patrones y detectar anomalías.
  • El análisis de diagnóstico, que consiste en investiga por qué ha pasado algo, buscando relaciones causa-efecto.
  • El análisis predictivo, que utiliza datos históricos para predecir que podría suceder en el futuro.
  • Y el análisis prescriptivo, que partiendo de las predicciones, sugiere acciones para aprovecharlas o para reducir su probabilidad.

viernes, 20 de febrero de 2026

DATOS. Acceso a datos

Transcripción de audios

También los audios no interesan como fuentes de datos, pero la transcripción "manual" de una grabación de audio para convertirla en texto editable, aun en el caso de grabaciones cortas, lleva una cantidad ingente de tiempo. Posiblemente esta sea una de las razones, si no la de mayor peso, por la cual no es frecuente que los SEO utilicen este soporte como sistema de recogida de datos. Por suerte, actualmente disponemos de recursos para automatizar la transcripción de audio a texto de forma funcional y fiable, on-line y en local.

Hace tiempo que existen on-line recursos para realizar esta transformación, pero no nos garantizan satisfactoriamente la confidencialidad, y además pueden suponer costes económicos o limitaciones de algún tipo. La consecuencia es que recurrir a estos medios no siempre (pocas veces diría yo) pueden considerarse una opción real. Aquí es donde las opciones "en local" se vuelven herramientas de gran interés... Pero presentan ciertas complicaciones y algunas limitaciones.

La principal limitación es que algunas de ellas están pensadas precisamente para hacer posible el proceso inverso: convertir texto a audio, de modo que sea factible generar recuros de utilidad para implementar determinadas pautas DUA, pensadas para garantizar la accesibilidad de los contenidos, por ejemplo, a invidentes.

El uso de alternativas IA puede cubrir también ese tipo de necesidades. Un ejemplo de ello es la funcionalidad de conversoón a audio disponible en NotebookLM; pero en esta aplicación no contamos con herramientas de conversión audio-a-terxto y estamos hablando precisamente de un servicio-en-local.

Sí existen, no obstante, alternativas que corren en local y que son fiables en sus resultados, posibles en cuanto a exigencia de recursos de memoria y procesamiento y confiables en términos de garantía de confidencialidad. Esto último a consecuencia precisamente de correr-en-local.

No puedo afirmar que Whisper sea la única opción que cumpla estas condiciones, pero sí que las cumple y que funciona aceptablemente bien ante demandas reales, comprobadas personalmente. Cierto es que esta biblioteca necesita una instalación que no se resuelve únicamente con el consabido pip install, pero que tampoco conlleva una complejidad excesiva. En todo lo anterior me estoy refiriendo a Windows, donde se necesita, además, acceso a FFmpeg, lo que requeire la previa instalación de Chocolatey desde PowerShell.

No es que ese proceso resulte especialmente complicado ni arriesgado, pero no me responsabilizo de asesorate al respecto. Te recomiento que consultes el tema con quien consideres pertinente. Yo lo hice con la Web y con IA-Gemini; y he de decir que con éxito.

En esta entrada me limito a exponer el resultado en forma de script Python, de cuyo buen funcionamiento doy fe en un sistema no especialmente potente ni dispone de GPU. El que sigue es el script resultante de pelearme durante un rato con IA-Gemini 3.



import whisper
import os

def transcribir_a_unidad_d_y_pantalla(ruta_audio_input):
    # 1. Configuración de nombres y rutas
    nombre_archivo = os.path.basename(ruta_audio_input)
    nombre_sin_extension = os.path.splitext(nombre_archivo)[0]
    ruta_salida_txt = f"D:/{nombre_sin_extension}_transcripcion.txt"

    try:
        if not os.path.exists(ruta_audio_input):
            print(f"❌ Error: No se encontró el archivo en: {ruta_audio_input}")
            return

        # 2. Carga del modelo
        print("Cargando modelo Whisper...")
        modelo = whisper.load_model("base")

        # 3. Transcripción
        print(f"Procesando: {nombre_archivo}...")
        resultado = modelo.transcribe(ruta_audio_input, language="es", fp16=False)      
        
        texto_final = resultado["text"]

        # 4. Mostrar por pantalla (NUEVO)
        print("\n" + "="*50)
        print("TEXTO TRANSCRITO:")
        print("="*50)
        print(texto_final)
        print("="*50 + "\n")

        # 5. Guardar en Unidad D:
        if os.path.exists("D:/"): #Si no dispones de esta unidad o deseas utilizar otra dirección, debes especificarlo AQUÍ
            with open(ruta_salida_txt, "w", encoding="utf-8") as f:
                f.write(texto_final)
            print(f"✅ Archivo guardado con éxito en: {ruta_salida_txt}")
        else:
            print("❌ Error: La unidad D: no está disponible.")

    except Exception as e:
        print(f"❌ Ocurrió un error inesperado: {e}")

# --- VARIABLE DE RUTA ---
mi_variable_ruta = "AQUÍ LA RUTA DE TU AUDIO" #No te olvides concretar la ruta de tu audio

if __name__ == "__main__":
    transcribir_a_unidad_d_y_pantalla(mi_variable_ruta)

Este script bien merece un análisis detallado, cosa que no toca en esta entrada, pero no podía esperar a proporcionártelo por si te interesa para desarrollar un sistema de registro sistemático de actuaciones o para convertir a texto información sensible.

Como te dije, lo he probado personalmente con diferentes tipos de archivos de sonido (archivos wav creados con Audacity y archivos de audio generados con NotebookLM), incluso con archivos de vídeo (mp4 generado con NotebookLM) y el resultado es muy satisfactorio. Pueden observarse algunos errores, pero son mínimos y no comprometen la fidelidad del contenido.

Cierto que para poder usar este script necesitas resolver primero las cuestiones de adaptación de tu sistema (al menos si trabajas con Windows), según indiqué. POr propia experiencia supongo que no te resultará complicado.

La mayor limitación es, no obstante, que esta herramienta sólo resuelve el problema de la transcripción de voz_a_texto, no el acceso al contenido textual resultante, pero esta es una batalla que no podríamos librar sin antes pelear la presente.

Nota

El código mostrado en esta entrada ha sido desarrollado usando IA Gemini 3 y posteriormente revisado y adaptado por el autor.

DATOS. Acceso a datos

Recuento de directorios y archivos

Para completar el tratamiento de los directorios y de los archivos como datos, en esta entrada retomaremos su recuento pero esta vez haciendo uso de la biblioteca pathlib. De este modo dispondremos de un recurso con funcionalidad similar a la que ofrece os, pero más actualizado. Sólo nos interesa aquí como alternativa, por lo que me limitaré a unos mínimos en el tratamiento de esta cuestión.

Empezaré por presentar un script que resume los procedimientos vistos en las dos entradas anteriores de esta misma subsección: el recuento en profundidad de directorios y archivos de forma diferenciada y la cuantificación de los archivos en función de su extensión.


from pathlib import Path
from collections import Counter

#---- FUNCIÓN-------------------------------------------------------------

def analizar_directorio(ruta_entrada, archivo_salida):
    # Convertimos la cadena de texto en un objeto Path
    path_raiz = Path(ruta_entrada)

    if not path_raiz.exists() or not path_raiz.is_dir():
        print(f"Error: La ruta '{ruta_entrada}' no es un directorio válido.")
        return

    # Inicializamos contadores
    total_directorios = 0
    total_archivos = 0
    extensiones = Counter()

    # rglob('*') busca de forma recursiva en todos los niveles
    for elemento in path_raiz.rglob('*'):
        if elemento.is_dir():
            total_directorios += 1
        elif elemento.is_file():
            total_archivos += 1
            # .suffix devuelve la extensión con el punto (ej: .txt)
            ext = elemento.suffix.lower() if elemento.suffix else "Sin extensión"
            extensiones[ext] += 1

    # Construcción del informe de resultados
    informe = []
    informe.append("=" * 50)
    informe.append(f"INFORME DE ANÁLISIS RECURSIVO")
    informe.append(f"Directorio origen: {path_raiz.absolute()}")
    informe.append("=" * 50)
    informe.append(f"Total de directorios encontrados: {total_directorios}")
    informe.append(f"Total de archivos encontrados:    {total_archivos}")
    informe.append("-" * 50)
    informe.append(f"{'EXTENSIÓN':<20} | {'CANTIDAD':<10}")
    informe.append("-" * 50)

    # Ordenar extensiones por frecuencia (de mayor a menor)
    for ext, cuenta in extensiones.most_common():
        informe.append(f"{ext:<20} | {cuenta:<10}")
    
    informe.append("=" * 50)

    # Convertir la lista del informe en un solo string
    texto_final = "\n".join(informe)

    # Mostrar por pantalla
    print(texto_final)

    # Guardar en archivo TXT
    try:
        # Pathlib también permite escribir archivos directamente
        Path(archivo_salida).write_text(texto_final, encoding='utf-8')
        print(f"\n[ÉXITO] El informe se ha guardado en: {archivo_salida}")
    except Exception as e:
        print(f"\n[ERROR] No se pudo escribir el archivo: {e}")

# --- USO DE LA FUNCIÓN -------------------------------------------------------

mi_directorio = "D:/BasesDatosTest" 
nombre_txt = "analisis_pathlib.txt"

analizar_directorio(mi_directorio, nombre_txt)

Además de la diferencia esperada en cuanto importación de librerías (pathlib frente a os, y Counter de la biblioteca collections), las diferencias claves respecto a los script basados en os radica en el uso de determindas funciones específicas de pathlib:

  • la función Path.rglob('*') simplifica la búsqueda recursiva ("r" viene de "recursive"). Al usar el comodín *, pedimos que busque todo lo que hay dentro del directorio, con independencia de los niveles de anidamiento que pueda presentar su estructura.
  • Counter es un diccionario especializado que automatiza el conteo de las exteniones de los archivos (extensiones = Counter()).
  • El atributo.suffix devuelve directamente la extensión del archivo (elemento.suffix.lower()). Mediante .lower unificamos el formato de la extensión a minúsculas evitando así posibles duplicidades.

Aunque es una cuestión secundaria en este script, interesa destacar también la forma en que se genera aquí el archivo .txt, ya que supone una mejora respecto al modo básico de proceder: Path.write_text() es una forma abreviada de escribir archivos de texto sin necesidad de abrir y cerrar flujos manualmente.

Nota

El código mostrado en esta entrada ha sido desarrollado usando IA Gemini 3 y posteriormente adaptado por el autor.

jueves, 19 de febrero de 2026

DATOS. Acceso a datos

Recuento de archivos

Si en la entrada temáticamente anterior tratamos al directoiro como dato, en esta haremos lo mismo con el archivo: también el recuento de archivos puede ser objeto de análisis, para lo cual, es de gran utilidad saber utilizar bibliotecas-Python como os o pathlib.

De momento, en esta entrada trataremos el tema de un modo similar a cómo lo hicimos en la anterior (ya citada): aprenderemos a identificar específicamente los archivos en dos niveles de profundidad respecto a la estructura de directorios y a diferenciar los archivos en función de su extensión. Esto último constituye el paso previo a acceder a los archivos propiamente dichos en busca de información contenida en ellos; tema que nos lleva a la segunda fase del proceso de acceso a datos.

El script que sigue es la formulación más sencilla de nuestro actual objetivos: identificar los archivos existentes en un directorio dado, mostrarlos por pantalla y cuantificarlos. Los comentario incluídos en el script facilitan la comprensión de su funcionamiento.



import os

#0. Establecemos la ruta (directorio) sobre el que trabajar 
ruta_archivos = "C:/BasesDatosTest"

if not os.path.exists(ruta_archivos): #Comprobamos que la ruta existe
    print("La ruta especificada no existe.")

contador = 0 #Creamos el contador de archivos

# 1. Iteramos sobre cada elemento dentro del directorio buscando los archivos
for elemento in os.listdir(ruta_archivos): # Construimos la ruta completa del elemento
    ruta_completa = os.path.join(ruta_archivos, elemento)
    if os.path.isfile(ruta_completa): # Comprobamos que el elemento es un archivo (no directorio)
        contador += 1 # incrementamos el contador de archivos
        print(elemento) # e imprimimos por pantalla el nombre de cada archivo encontrado

#2. Informamos del resultado (número de archivos encontrados)
print(f"Número total de archivos en el directorio: ", contador)


Lo que hace que este script identifique los archivos (no los directorios) es la función os.path.isfile(), que permite especificar que estamos buscando archivos. Recuerda que la búsqueda de directorios requería la función os.path.isdir().

Dentro de la sencillez y limitación de este script está que únicamente nos da acceso a la raiz del directorio, por lo que el resultado sólo es fiable si el directorio no contiene subdirectorios (directorios secundarios o anidados). En ese caso es de esperar que existan archivos dentro de ellos, con lo que este recuento no sería fiable. Para resolver este problema usaremos un segundo script.


import os

#0. Directorio a estudiar
ruta_raiz = "D:/BasesDatosTest" 

if not os.path.exists(ruta_raiz): #Control simple de error
    print ("Error: La ruta proporcionada no existe.")

total_archivos = 0 #Contador de archivos

# 1. Recuento recursivo de archivos mediante os.walk()).
for ruta_actual, subcarpetas, archivos in os.walk(ruta_raiz): # os.walk() recorre TODOS los (sub)directorios
    print(archivos) #Mostramos la lista de archivos que contiene cada directorio
    cantidad_en_esta_carpeta = len(archivos)
    total_archivos += cantidad_en_esta_carpeta
    print(ruta_actual, "  || ",cantidad_en_esta_carpeta," archivos") #Imprime el recuento de archivos en el (sub)directorio
        
#2. Informe final de resultados
print("*" * 150)
print(f"CONTEO FINALIZADO.")
print("-" * 25)
print(f"Total de archivos (incluyendo subdirectorios): ",  total_archivos )
print("*" * 150)

Este segundo script recorre cada uno de los subdirectorios del principal mediante la función os.walk() obteniendo los archivos existentes en la lista archivos. Hemos creado instrucciones print() que facilitan información del listado de archivos de cada (sub)directorio en print(archivos)) y del recuento de los archivos que contienen en print(ruta_actual, " || ",cantidad_en_esta_carpeta," archivos"), pero esta información no es estrictamente necesaria, pudiendo ser comentadas ambas líneas. Lo realmente importante en cuanto a resultados lo encontramos a partir del comentario #2. Informe final de resultados.

Para finalizar esta entrada vamos a consisderar una tercera cuestión en relación a los archivos como datos: la tipología de archivos, identificada con su extensión. Se trata de un tipo de dato que puede resultar muy relevante según en qué tipo de investigación, ya que se relaciona con cuestiones como la importancia del tipo de herramienta informática (servicio, si se prefiere) que predomina en determinadas actuaciones. En estos contextos, analizar el peso del tipo de archivo es una forma de operativizar esas variables de forma sencilla. También contamos con recursos en Python para obtener este tipo de información.



import os

#Función. INICIO -----------------------------------------------------

def analizar_y_guardar(ruta_raiz, archivo_salida):
    
#A. Control básico de error
    if not os.path.exists(ruta_raiz):
        print("Error: La ruta no existe.")
        return

#B. Para almacenar contenido
    total_general = 0
    conteo_extensiones = {}
    informe_texto = "" # Aquí acumularemos todo lo que irá al TXT

#C. Informe. Encabezado del informe
    linea_div = "-" * 70 + "\n"
    encabezado = f"INFORME DE DIRECTORIO: {ruta_raiz}\n"
    columnas = f"{'DIRECTORIO':<55} | {'ARCHIVOS':<10}\n"
    
    informe_texto += encabezado + linea_div + columnas + linea_div
    print(encabezado + linea_div + columnas + linea_div, end="")

#D. Recorrido del directorio-base en profundidad (os.walk()) para capturar el total de archivos 
    for ruta_actual, _, archivos in os.walk(ruta_raiz):
        cantidad = len(archivos)
        total_general += cantidad
        
#E. Registro (sub)directorio por (sub)directorio
        linea_carpeta = f"{ruta_actual:<55} | {cantidad:<10}\n"
        informe_texto += linea_carpeta
        print(linea_carpeta, end="")

#F. Procesamiento de las extensiones de los archivos (no de su nombre o raiz)
        for nombre in archivos:
            _, ext = os.path.splitext(nombre)
            ext = ext.lower() if ext else "Sin extensión"
            conteo_extensiones[ext] = conteo_extensiones.get(ext, 0) + 1

#G. Resumen Final de extensiones (categorías + frecuencias)
    resumen_global = "\n" + "="*40 + "\n"
    resumen_global += "RESUMEN GLOBAL DE EXTENSIONES\n"
    resumen_global += "="*40 + "\n"
    resumen_global += f"Total de archivos analizados: {total_general}\n"
    resumen_global += "-" * 40 + "\n"
    resumen_global += f"{'EXTENSIÓN':<15} | {'CANTIDAD':<10}\n"
    resumen_global += "-" * 40 + "\n"

    for ext, cant in sorted(conteo_extensiones.items(), key=lambda x: x[1], reverse=True):
        resumen_global += f"{ext:<15} | {cant:<10}\n"
    
    resumen_global += "="*40 + "\n"
    
#H. Informe. Unimos todo el contenido
    informe_texto += resumen_global
    print(resumen_global)

#I. Informe. Guardado del archivo de informe
    try:
        with open(archivo_salida, "w", encoding="utf-8") as f:
            f.write(informe_texto)
        print(f"\n[ÉXITO] Informe guardado en: {archivo_salida}")
    except Exception as e:
        print(f"\n[ERROR] No se pudo guardar el archivo: {e}")

# Función. FINAL--------------------------------------------------------

#Llamada a o ejecución de la función ---------------------------------

mi_ruta = "D:/BasesDatosTest"
nombre_informe = "informe_del_analisis.txt"

analizar_y_guardar(mi_ruta, nombre_informe)


Este script contiene al anterior y añade la funcionalidad de identificar el tipo de archivo por su extensión. Pero es de mayor complejidad que el anterior no sólo por ese motivo: implica el uso de una función y la creación de un informe permanente mediante la creación de un archivo .txt.

Vayamos por partes, y la primera y más importante se refiere al recuento de las extensiones de los archivos, que podemos ver tras el comentario F (Procesamiento de las extensiones...). En este conjunto de instrucciones destaca la siguiente: _, ext = os.path.splitext(nombre), en la que la función os.path.splitext() divide la ruta en dos partes: la raíz y la extensión: como lo que nos interesa es la extensión, la raiz se sustitye por _. Si lo que nos interesara fuera la raiz o nombre del archivo, la instrucción se presentaría de este modo: nombre_raiz, _ = os.path.splitext().

La segunda tiene que ver con el uso de la función analizar_y_guardar() que, como vemos tiene dos parámetros, los mismos que encontramos en la llamada a la función la final del script (analizar_y_guardar(mi_ruta, nombre_informe)). Sobre el uso de funciones en Python tenderemos que volver en otro momento.

Finalmente, a diferencia de los dos script anteriores, en este hemos optado por generar un archivo que guarde tota la información en un formato básico (.txt), lo que nos servirá como elemento de relación con el posterior desarrollo de esta sección del blog; cosa que queda para próximas entradas en las que trabajaremos con el acceso al contenido de losa archivos, emepezando precisamente por los más accesibles: precisamente los archivos de texto plano.

Nota

El código mostrado en esta entrada ha sido desarrollado usando IA Gemini 3.
Cuando consideré necesario fui moldeando la respuesta de la IA hasta obtener el script deseado y finalmente modiqué el contenido del script para adaptarlo a mis objetivos, por ejemplo modificando el código inicial para eliminar una función cuando el uso de funciones no me pareción pertinente.

miércoles, 18 de febrero de 2026

DATOS. Acceso a datos.

El directorio como dato

Podemos trabajar con directorios como para de la gestión documental, pero también podemos considerar el directorio como un tipo más de dato, sujeto, en consecuencia, a procesos de análisis. En ambos casos es necesario disponer de recursos para el manejo de directorios. Bibliotecas como os y pathlib de Python nos los proporcionan.

En esta entrada nos plantearemos el tratamiento de los directorios como datos, por lo que enfocaremos los procedimientos de uso de las funciones que ofrecen esas librerías al logro de este objetivo, con independencia de otros tratamientos posibles que se abordan en otras publicaciones de este blog. No se puede descartar que parte de lo que se diga aquí no haya sido tratado en ellas; y aunque el enfoque sea diferente (pensado más en la creación y manipulación de archivos), las instrucciones y los procedimientos de trabajo no tienen por qué serlo en lo fundamental.

Centrándonos ahora en lo que aquí nos interesa, nos situaremos ante circunstancias en las que, por diferentes razones (suponemos ahora que en el marco de un proyecto de análisis de datos) necesitamos conocer la estructura de (sub)directorios de un conjunto documental (en su lugar y momento trabajaremos sobre un proyecto real que materializa esta hipótesis). En este momento, ese conocimiento (referido a los directorios) se convierte en nuestro objetivo. Herramientas como las contenidas en el módulo os nos sirven para identificar las rutas (relativas o absolutas) y de generar el listado (base de la cuantificación) de los (sub)directorios implicados.

A modo de ejemplo, veamos una concreción de lo anterior como script:



import os

# Define la ruta del directorio
directorio = "D:/BasesDatosTest"

# Obtiene una lista con los nombres de los archivos y carpetas
contenido = os.listdir(directorio)

#Lee el listado de componentes del directorio (serviría como ruta relativa)

print(f'Colección de elementos del directorio \n {contenido} \n')

#Recorre el directorio y muestra las rutas absolutas

print('Rutas absolutas de archivo \n')

for elemento in contenido:
    print(directorio + '/' + elemento + '\n')

#Recorre el directorio, cuenta los elementos y muestra los componentes y cuántos son

n_elem = 0

print('Listado de documentos y subdirectorios componentes \n')
 
for elemento in contenido:
    n_elem = n_elem +1
    print (f'Elemento número {str(n_elem)} -> {elemento}')

print(f'\n TOTAL elementos del directorio {str(n_elem)}')

Analizo acontinuación el contexto, el código y el resultado que obtenemos con él.

Empezando por el contexto, se presupone que nuestro objetivo es identificar la estructura de elementos de un determinado directorio ubicado en la unidad D: (directorio = "D:/BasesDatosTest"), a fin de conocer su composición (en principio a nivel cuantitativo).

Con una instrucción (contenido = os.listdir(directorio), este script nos permite obtener el contenido del directorio solicitado, incluyendo los (sub)directorios y los archivos, lo que excede lo que necesitamos en este momento, pero que nos sirve como referencia para afinar el logro de nuestro objetivo. Lo que sigue no es otra cosa que diferentes formas de visualizar ese contenido.

No me detengo en explicar esas formas, ya que lo que realmente nos interesa es identificar de forma diferenciada los (sub)directorios existentes, sin atender (en estos momentos) a los archivos que también contiene el directorio principal.

Además también debemos tener en cuenta que este script sólo nos muestra el contenido inmediato de ese directorio principal, no el contendio de los subdirectorios, dato que también nos puede interesar en nuestro actual análisis de los directorios como contenido.

Vayamos por partes. Primero nos centraremos en aislar los subdirectorios para no mezclarlos con los archivos:


import os

# 0. Identificamos la ruta del directorio a estudiar
directorio = "D:/BasesDatosTest"

# 1. Guardamos el elemento sólo si es un directorio
directorios = [] #Creamos la lista para contener los directorios

for elemento in os.listdir(directorio): #Recorremos el iterable resultante de la función listdir()
    ruta_completa = os.path.join(directorio, elemento)  # Unimos la ruta base con el nombre del elemento
    if os.path.isdir(ruta_completa): # Añadimos cada subdirectorio encontrado (isdir()) a la lista de directorios append())
        directorios.append(elemento)

# 2. Identificamos el número de elementos de la lista directorios con la función len()
total_directorios = len(directorios)

# 3. Mostramos los resultados
print(f' Los subdirectorios encontrados son los siguientes: {directorios} \n') #Nombres de los (sub)directorios

print(f'El número total de directorios en "{directorio}" es: {total_directorios}') #Número de (sub)directorios (principales)

La función principal, la que nos resuelve el problema de identificar sólo los directorios (excluyendo los archivos) es isdir(), cuya función es evidente: identificar en el contenido del directorio obtenido por la función listdir() aquellos elementos que son directorios, excluyendo los elementos que no lo son. Otra función (len()), aplicada sobre la lista directorios (len(directorios)) nos permite contar el número de elementos (directorios).

El script anterior nos ha permitido identificar los directorios existente en la raiz de nuestro directorio de referencia, pero no sabemos si esos (sub)directorios cuentan, a su vez con subdivisiones (subdirectorios de segundo nivel), lo cual en determinados estudios puede ser un dato de interés. Si queremos profundizar en detalle, deberemos aplicar otro procedimiento, como el propuesto en este último script.


import os

# 0. Identificamos la ruta del directorio a estudiar
ruta_base = "D:/BasesDatosTest"

# Establecemos los contadores para el conteo de elementos
carpetas_principales = []
conteo_por_principal = {} # Diccionario para guardar {NombrePrincipal: TotalSubdirs}
total_subdirectorios_global = 0

print(f"--- DESGLOSE DE DIRECTORIOS EN: {ruta_base} ---\n")

# 1. Obtenemos los directorios de primer nivel
elementos_raiz = [f for f in os.listdir(ruta_base) if os.path.isdir(os.path.join(ruta_base, f))] #Obsérvese el uso de las funciones listdir() e isdir()

for principal in elementos_raiz:    #Este ciclo permite dar contenido a la lista mediante append()
    ruta_principal = os.path.join(ruta_base, principal)
    carpetas_principales.append(principal)
    
# 2. Para cada principal, recorremos recursivamente sus subdirectorios
    conteo_subdirs_rama = 0
    subdirectorios_lista = []
    
    for root, dirs, files in os.walk(ruta_principal):
        for d in dirs:
            conteo_subdirs_rama += 1
            ruta_relativa = os.path.relpath(os.path.join(root, d), ruta_base) # Guardamos la ruta relativa para que el listado sea legible
            subdirectorios_lista.append(ruta_relativa)
    
    conteo_por_principal[principal] = conteo_subdirs_rama # Guardamos el conteo y mostramos el listado de esta rama
    total_subdirectorios_global += conteo_subdirs_rama
    
    print(f"Directorio Principal: [{principal}]")
    if subdirectorios_lista:
        for s in subdirectorios_lista:
            print(f"  └── {s}")
    else:
        print("  └── (Sin subdirectorios)")
    print(f"  > Total subdirectorios en esta rama: {conteo_subdirs_rama}\n")

# 3. Resumen final del recuento de contenidos 
print("="*50)
print("RESUMEN DEL RECUENTO")
print("="*50)
print(f"Total de Directorios Principales: {len(carpetas_principales)}")
print(f"Total Global de Subdirectorios:   {total_subdirectorios_global}")
print(f"Total de carpetas (Suma total):    {len(carpetas_principales) + total_subdirectorios_global}")

Lo que este script añade al anterior es el acceso a los (sub)directorios y la visualización de su contenido (sólo de los subdirectorios de segundo, tercer... nivel (ver punto 2 del script). Para esto es fundamental el uso de la función os.walk() en (os.walk(ruta_principal)).

A fin de facilitar la obtención de datos, hemos incorporado al script un sistema de recuento que resumen la información obtenidas (punto 3 del script)

Nota

El código mostrado en esta entrada ha sido desarrollado usando Gemini-IA a modo de auxiliar de programación.
La respuesta de la IA fue moldeada mediante prompt sucesivos hasta obtener el funcionamiento deseado.
Finalmente, el contenido del script (instrucciones y comentarios) y su funcionamiento en local fue objeto de revisión, comprobación y modificación por parte del autor de esta entrada.

martes, 17 de febrero de 2026

DATOS

Objetivos del tratamiento de datos

Los datos son la base de todo programa informático, de los clásicos y de los basados en soluciones IA; para estos últimos aun más que para los primeros, puesto que los datos constituyen la base misma sobre la que se sustenta toda su arquitectura.

Tres son las áreas de trabajo que planteamos en esta sección: el acceso a los datos, su limpieza o tratamiento preparatorio y el análisis de datos.

El acceso a datos conlleva mucha más complejidad de lo que en principio se podría pensar, por lo que deberemos dar respuesta a sus diferentes condicionantes, entre los que se incluye la variedad de fuentes y sus implicaciones, el tipos de datos y el modo en que se presentan (no sólo en cuanto al soporte documental, que también), además de la diversidad de objetivos y de planteamientos de trabajo que nos propongamos desarrollar. De todo este conjunto de factores deriva la extensión y el peso de esta temática dentro de la sección.

La limpieza de datos es necesaria para disponer de datos de calidad, lo cual va a permitir el posterior desarrollo de nuestro análisis. En el caso de la automatización de procesos y/o del desarrollo de soluciones basadas en la IA esta calidad de los datos es fundamental, de ahí la importancia del correcto tratamiento preparatorio de nuestros datos.

Finalmente podemos considerar el análisis de datos junto con la automatización de textos, como razón de ser de este blog. En esta tercera subsección trataremos sobre las herramientas y las estrategias que facilitan este análisis. En otra sección plantearé líneas de concreción de estas prácticas adaptadas al trabajo de los SEO.

jueves, 11 de diciembre de 2025

Datos. Estadística.

Estadística del ítem

Índice de Dificultad del ítem (II)



Ya sabemos en qué consiste el IDi y cómo se calcula. Sobre este índice y sus características puedes leer algo en [esta entrada], pero lo que trataré en la actual son algunos datos que podemos obtener a partir del IDi.


Una buena información sobre los resultados de una prueba consiste, básicamente, en conocer los resultados de cada sujeto y el IDi de cada elemento o ítem de la prueba. Algo así como esto:
Por sencilla que parezca (y que es), esta tabla nos informa de los resultados de cada sujeto, dato a partir del cual podemos realizar cálculo descriptivos sobre la distribución (estadística descriptiva), y también de los IDi de cada uno de los ítem, lo que nos permite calificar cada uno de ellos según criterios de nivel de dificultad. Además, si la muestra de sujetos fuera más amplia y tuviéramos interés, podríamos establecer relaciones entre ambas puntuaciones.

Lo malo es que, con frecuencia es posible disponer de un tipo de puntuación, pero no del otro. Por ejemplo, en PLON-R. Fonología, contamos con datos del IDi de cada elemento de la prueba en función de la edad-base, lo cual no es lo ideal, pero puede ser suficiente para determinados análisis. 

A partir de estos datos tan limitados nos resulta difícil comprender el funcionamiento del test, pero podemos obtener alguna información relevante realizando inferencias a partir de los datos que sí poseemos.

Sabemos que el cálculo del IDi requiere saber el número de sujetos de la muestra (N) y el sumatorio de puntuaciones del ítem (una vez reducido a valores dicotómicos 0 - 1), de modo que IDi = Si/N. En consecuencia, sabido el IDi y N, el cálculo del número de aciertos en el ítem se reduce a aplicar Si = IDi * N

Conocido el valor Si de cada ítem, aplicando la fórmula anterior y realizando la suma de los resultados podemos obtener el total de aciertos y calcular su promedio. Siguiendo el ejemplo anterior...


... obtenemos 3 como promedio de aciertos por sujeto, el mismo que obtendríamos aplicando la fórmula  PROMEDIO() a lo datos Total de la primera tabla (PROMEDIO(H4:H13))

No podemos calcular la Dt, dato que sería de mucho interés para estudiar la distribución de los datos, pero ya tenemos un estadístico que nos puede ser de ayuda para el análisis de los resultados grupales e individuales. En [esta entrada] veremos un ejemplo de ese análisis.

martes, 18 de noviembre de 2025

Datos. Estadística.

Estadística del ítem

Índice de dificultad del ítem



Empezamos esta subsección analizando el que posiblemente sea el índice relacionado con el análisis del ítem más conocido y de más uso... pero también, como anécdota, el de nombre más impreciso.


Es ciertamente anecdótico y no genera mayor dificultad por lo conocido que resulta, pero el propio nombre de índice de dificultad (ID) se opone radicalmente a su naturaleza, ya que refleja de todo lo contrario: el acierto, que no el fallo. Es por eso que también se puede denominar (ahora sí, con toda la razón) índice de facilidad (IF).

Esto es así porque lo que se calcula es el nivel de acierto de un grupo de sujetos respecto a un ítem en concreto.

Así, si 20 alumnos responde a un ítem de una prueba y 15 de ellos lo hacen correctamente (1 * 15 = 15), el IF del ítem es 0,75, o también del 75%, que de las formas se puede expresar. Curiosamente el ID no es 1-IF = 0,25, como correspondería, sino que se asimilan IF = ID y también se usa ID = 15/20.

Los valores del ID van de 0 a 1, existiendo categorizaciones del nivel de dificultad, como es el caso de [la siguiente], adaptada a partir de Cortada (1999):


Clasificación del ítemÍndice de dificultad del ítem
Muy fácilDe 0,81 a 1,00
Relativamente fácilDe 0,66 a 0,80
Dificultad adecuadaDe 0,51 a 0,65
Relativamente difícilDe 0,31 a 0,50
DifícilDe 0,11 a 0,30
Muy difícilDe 0,00 a 0,10

Los ID son sensibles a las personas que responder al ítem, y a su número. Cuanto mayor sea la muestra de participantes en la baremación, más fiables son los resultados de los ID, mientras que muestras pequeñas pueden incidir en resultados de escasa fiabilidad.

Cierta es también la relación entre el nivel de dificultad de los ítem y resultados que puede obtener una muestra de sujetos: a mayores índices de dificultad, resultados más modestos, mientras que si los ítem presentan ID bajos o muy bajos, los resultados serán extraordinariamente altos. Es por ellos que los ID de los ítem deben corresponderse con el tipo de prueba que se quiere crear. Por ejemplo, para un prueba de screening es de esperar que los ítems tengan altos ID y resulten, en su conjunto, relativamente fáciles. En pruebas de selección, lo esperable es lo contrario: la mayoría de los ítem deben ser difíciles a muy difíciles. 

Pero también los sujetos empleados en la baremación de la prueba influyen en los ID de los ítem que la forman. Los sujetos competentes aciertan los ítem de alto nivel de dificultad, mientras que los de bajo nivel son acertados por todos o la mayoría de los sujetos. De ahí que sea necesario controlar también el nivel de conocimiento, aptitud o competencia de los sujetos que participan en la baremación de la prueba para evitar la sobreabundancia de altos o bajos niveles competenciales que puedan afectar a los ID de los ítem realmente observados, distorsionando el resultado del uso de la prueba con sus potenciales usuarios.

Conocer los ID de los ítem, además de permitir mejorar la calidad de las pruebas, también nos permite realizar análisis más ricos de los resultados obtenidos por un sujeto o por un grupo, siempre en función del tipo de prueba empleada y de cómo esté conformada en términos de ID de sus ítem. No es lo mismo que el niño falle (o acierte) los ítem fáciles que los difíciles. El significado de esos errores (o aciertos) puede ser interpretado de forma diferente que si desconocemos su ID.

Otro uso posible de los ID, en este caso los empíricos derivados de la aplicación de la prueba a un grupo, es su referencia respecto a un criterio establecido a priori dentro de un planteamiento de evaluación criterial. Si establecemos como nivel de logro (grupal) un determinado porcentaje y el ID de un subconjunto de ítem cumple/incumple las expectativas, esto nos está informando de lo cerca/lejos que está el grupo de alcanzar la meta de aprendizaje. De aquí podemos, además, derivar análisis en términos de los resultados individuales de determinados sujetos.

En resumen, el análisis del ID/IF del ítem, a pesar de su sencillez, puede ser más complejo de lo que aparenta; pero también servir para más objetivos de los que pudiéramos pensar desde una perspectiva psicométrica estricta.

Como vimos, su cálculo es extremadamente sencillo, por lo que no existen funciones built-in en los servicios de cálculo (Excel o Calc). Para su cálculo es necesario realizar la suma de las puntuaciones del grupo en el ítem y dividir después entre el N del grupo. En consecuencia, siendo B3:B22 la lista de puntuaciones del ítem y A3:A22 el listado de alumnos de un grupo...
  • E3 =SUMA(B3:B22)
  • E4 =CONTARA(A3:A22)
  • E5 =E3/E4
... podría ser el procedimiento se cálculo del ID en Calc.


Datos. Estadística.


Estadística del ítem



El análisis estadístico del ítem hace referencia al análisis de las preguntas de una prueba mediante procedimientos estadísticos para evaluar su calidad, dificultad, poder de discriminación y validez. 

Se basa en el análisis cuantitativo de las respuestas que dan los sujetos a los ítem de una prueba para, a partir de ellas, determinar si un ítem funciona correctamente y si está midiendo lo que se pretende.

El análisis de ítem permite, pues, mejorar la calidad de una prueba y ayuda a resolver los problemas que pueden presentar los propios ítem.

Algunos de los procedimientos empleados  para ello son los siguientes:

En esta subsección de Datos estudiaremos estos procedimientos manteniendo el mismo enfoque práctico empleado en el resto de las entradas de la sección, con aplicaciones en la práctica profesional. Procuraremos también ilustrar el uso de recursos informáticos, desde las utilidades de las hojas de cálculo y sus funciones hasta el desarrollo de script o el uso de bibliotecas especializadas.

Sirva esta sencilla entrada como presentación.

viernes, 7 de noviembre de 2025

Datos. Análisis.

Fases del análisis de datos

Acceso a datos (IV)



Siguiendo con esta temática, hemos visto [en la entrada anterior] como se podría concretar un procedimiento de acceso al contenido de un directorio y a sus subdirectorios con una finalidad concreta: identificar los documentos que contienen e identificar de que tipo son, en función de su extensión. Una de las cuestiones que mayor dificultad presentó en ese momento fue la de listar y cuantificar los documentos con independencia de la estructura del directorio. Ofrezco en esta un intento de solución partiendo de lo que sabemos sobre como [acceder a él].


Esta solución al problema ni es la única ni la mejor, pero sí suficientemente sencilla como para poder ser empleada incluso como herramienta o función; al menos así la he formulado, incluyendo la entrada interactiva de la dirección o ruta absoluta del directorio sobre el que deseemos trabajar. Primero te presento el script y después lo comento.

'''
Acceso a los archivos (documentos) que contiene un directorio, incluyendo los que se encuentran dentro de sus subdirectorios.
'''
import os

#Directorio principal sobre el que trabajar

directorio = input('Escriba o copie la ruta del directorio a analizar: ')

'''
Ejemplo de ruta (puedes usar la que desees): D:/ESTUDIOS/Estudio_Desarrollo
'''

#Implementación de la función os.walk()

archivos = []
list_archiv = []
i = 0

for carpeta,subcarpeta, archivo in os.walk(directorio):
    archivos.append(archivo)

#Número de grupos de archivos en función de la estructura del directorio principal

n_archiv = len(archivos)    

print (f'Número de grupos de archivos {str(n_archiv)}')

#Trasnformamos los agrupamientos en una lista única de documentos 

for i in range(n_archiv):
    for ar in archivos[i]:
        list_archiv.append(ar)

# Número total de documentos de la lista anterior

n_ar = len(list_archiv)

print(f'Total archivos {str(n_ar)}')

# Lista de los archivos identificados

print(f'Listado íntegro de los archivos del directorio {directorio}\n')

for a in list_archiv:
    print(a)

Y digo que lo comento (muy brevemente) porque la explicación es innecesaria, dados los comentarios que acompañan al código. Me limitaré a decir que se trata de uno de los posibles desarrollo del procedimiento de acceso al contenido de un directorio basado en la función os.walk() que aquí se ofrece como herramienta interactiva: es necesario que le proporciones al script la dirección (absoluta) de ese directorio del que quieres saber cuántos documentos contiene y cuáles son. 

Esto es posible mediante el uso de esa función dentro de un bucle for que nos permite recorrer la estructura del directorio (for carpeta,subcarpeta, archivo in os.walk(directorio):) y que pasa los archivos identificados a una lista (archivos.append(archivo)), pero también gracias al doble bucle (anidado) que nos permite acceder primero a cada una de las agrupaciones de archivos que devuelve el uso de la función (for i in range(n_archiv):) y después a su contenido (for ar in archivos[i]:) para entregarlos a una segunda lista (list_archiv.append(ar)), que es la que después mostramos como resultado final de nuestra "función".

Sobre el directorio con el que yo he probado el script, lo que obtengo es lo siguiente:
  • Primero el número de bloques de archivos (2) (Número de grupos de archivos 2);  el primero corresponde al propio directorio principal (D:/ESTUDIOS/Estudio_Desarrollo), que contiene un subdirectorio y una serie de documentos (5 hojas de cálculo),  y el segundo al citado subdirectorio (AnalisisTotal).
  • Segundo el número de documentos totales, esto es, los 5 ubicados en el directorio principal y los 7 que contiene el subdirectorio (Total archivos 12)
  • Finalmente se muestran todos los archivos (12) en forma de lista, dado que fueron solicitados (print(a)) dentro de un bucle que recorre el iterable (for a in list_archiv:) que se generó (list_archiv.append(ar)) mediante el bucle interior (anidado) que usamos para acceder a los archivos.
Desarrollo_AreaLenguaje.ods
Desarrollo_AreaMG.ods
Desarrollo_AreaMotricidad.ods
Desarrollo_AreasMF.ods
Desarrollo_AreasTotal.ods
Desarrollo_Precocidad.ods
Desarrollo_Promedio.ods
Desarrollo_Resultados.ods
Desarrollo_Resumen.ods
Desarrollo_Retraso.ods
Desarrollo_Validez.ods
Desarrollo_ValoresTotales.ods
 
A partir de aquí puedes pensar en diferentes formas de manejar la información obtenida. Puedes, por ejemplo, desarrollar procedimientos para almacenar esta la información; pero también puedes mejorar el script en su fase inicial, automatizando el acceso a una colección de directorios que deseas analizar. Las posibilidades son muchas.