Mostrando entradas con la etiqueta Colecciones de datos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Colecciones de datos. Mostrar todas las entradas

martes, 22 de septiembre de 2026

LENGUAJES. Python

DataFrame (Pandas) (II)

Colecciones de datos

En la entrada anterior creamos un DataFrame partiendo de un diccionario integrado en el propio script. Esta fórmula es adecuada desde el punto de vista didáctico, ya que ayuda a comprender la conexión entre las estructuras nativas de Python y las de Pandas, así como a experimentar con sus primeras manipulaciones. Sin embargo no refleja la dinámica de trabajo habitual.

En esta segunda entrada vamos a proceder del modo en que es más común trabajar, ya que en los proyectos reales, los datos residen en fuentes externas, como archivos CSV u hojas de cálculo. Mediante funciones de lectura se cargan los datos en memoria en forma de DataFrame, lo que permite separar el almacenamiento de los datos y su procesamiento. Esto mejora el flujo de trabajo y permite aprovechar al máximo la potencia analítica de Pandas.

Para el caso vamos a utilizar el archivo Avandono_escolar.csv, obtenido de la página web del MEFPD, sobre el cual realizaremos diferentes manipulaciones mediante Pandas, una vez convertido en un DataFrame de trabajo.

Empezaremos por acceder al archivo .cvs (1) para cargarlo en nuestro script, siguiendo lo que te propongo a continuación.


import pandas as pd

# 1. Definir la ruta del archivo CSV
ruta_archivo = r'directorio_de_archov.csv' #Sustituye por tu ruta

# 2. Leer el archivo CSV y cargarlo en un DataFrame
df = pd.read_csv(ruta_archivo, sep=';', decimal=',')


Lo primero que deberás hacer es sustituir directorio desde el que se cargará el archivo .csv. Después debes prestar atención a la instrucción mediante la que se carga ese archivo con DataFrame df = pd.read_csv(ruta_archivo, sep=';', decimal=','), ya que contiene dos especificaciones que son necesarias teniendo en cuenta las características de nuestro archivo de origen, dado que los campos están separados por ; y se utiliza la , como separador de la parte decimal del número. Normalmente separamos los campos por , y en los números empleamos . como separador de decimales, por ello la instrucción normal de carga es más simple df = pd.read_csv(ruta_archivo), pero es este caso fallaría (2)

Ahora podremos, por ejemplo, visualizar los primeros y los últimos registros de nuestro DataFrame, lo que nos sirve para confirmar que la carga ha resultado exitosa y ajustada a la estructura del DataFrame. Para ello añadiremos el siguiente código al script.



# 3. Mostrar las primeras cinco filas del DataFrame
print("Primeras 5 filas y últimas 5 filas del DataFrame:")
print(df)

print("\nTipos de datos asignados por Pandas:")
print(df.dtypes)


Si sólo deseamos ver las primeras filas usamos la instrucción print(df.head()) y si añadimos una cantidad dentro de la función print(df.head(10)) esteremos explicitando cuantas filas queremos ver (3)

Visualizar el DataFrame puede ser interesante para conocer su contenido y estructura, pero muy posiblemente ambos nos sean ya conocidos (4). Lo que realmente hace útil a Pandas son las posibilidades que nos ofrece de manipular el contenido del DataFrame, utilizando filtros para seleccionar parte del mismo, realizando cálculos sobre sus datos o generando nuevas columnas a partir de las existentes. Veamos en el código que sigue algunas de estas opciones.



#Ejemplos de trabajos sobre el DataFrame

# 1. Filtros. Aplicar un filtro con dos condiciones combinadas con '&'. Cada condición DEBE ir encerrada entre paréntesis (...)
sub_df = df[(df['Territorio'] == 'TOTAL') & (df['Sexo'] == 'Ambos')] #Establecemos los filtros
sub_df = sub_df.sort_values(by='periodo', ascending=True) # Reordenamos el DataFrame en orden ascendente
n = len(sub_df) # Obtenemos el número de registros (filas) del DataFrame para su posterior visualización

# 2. Cálculos. Calcular la diferencia interanual sobre la columna del valor ('Total %'). diff() resta a la fila actual el valor de la fila anterior
sub_df['dif'] = sub_df['Total %'].diff().round(2)

# 3. Hacer una copia explícita (Buena práctica recomendada en Pandas). Así evitamos avisos tipo 'SettingWithCopyWarning' si se modifica el sub_df
sub_df = sub_df.copy()

# 4. Visualizar el resultado
print(f"Filas resultantes en el sub-dataframe: {len(sub_df)}")
print(sub_df.head(n))


Finalmente, nos puede interesar guardar un archivo CSV con el trabajo realizado, creando un nuevo archivo (sin modificar el original) con los resultados de nuestro trabajo. Para ello añadiremos al script el código que sigue (4).



# Guardamos el sub-DataFrame como csv

# 1. Definir la ruta completa del nuevo archivo CSV (en la misma carpeta)
ruta_salida = r'C:\Mi_Ruta\Nuevo_Archivo.csv'

# 2. Guardar el sub-DataFrame
sub_df.to_csv(ruta_salida, sep=';', decimal=',', index=False, encoding='utf-8-sig')

print(f"El archivo se ha guardado correctamente en: {ruta_salida}")


Mediante Pandas es posible realizar manipulaciones de un DataFrame mucho más complejas que las explicadas en estos script, lo que permite realizar estudios diversos sobre los datos que contiene. También podemos construir nuevos DataFrames a partir de otros combinando sus datos, siempre que sus estructuras y contenidos lo permitan. Todo ello implica concretar un plan de trabajo, lo que es más propio de un proyecto de análisis que de una entrada como la actual, pensada para explicar la naturaleza de la colección de datos llamada DataFrame.

Otro campo de interés en el trabajo con estas colecciones de datos usando Pandas es la ejecutar funciones estadísticas sobre sus datos, pero esto lo dejaremos para tratarlo en otra sección. De momento esta la damos por cerrarda aquí.

NOTAS
1 Para lo que previamente habremos descargado dicho archivo desde el enlace que ofrezco en el párrafo anterior o, si se prefiere, desde el enlace a la web del MEFPD. En ambos casos deberemos haber guardado el archivo con .csv en un determinado directorio. Esa ubicación será la que sustituya a que empleamos en el script a modo de ejemplo.
2 Fallaría no en el momento de la carga, sino al trabajar con el DataFrame. Por eso doy toda esta explicación para evitarte tener que investigar sobre esta cuestión. En todo caso, es conveniente que conozcas de antemano cómo está construído tu archivo .csv para que ajuste la instrucción de carga.
3 En este caso veremos 10 filas o registros. Para ver sólo las últimas filas empleamos print(df.tail())
4 Ya sabes que puedes visualizar un archivo csv desde Bloc de notas o desde Calc, lo que te recomiendo como alternativa a visualizarlo en el CMD mediante Python.
4 Como supondrás, deberás modificar la instrucción ruta_salida = r'C:\Mi_Ruta\Nuevo_Archivo.csv' sustituyendo la ruta por la que corresponda en tu caso. Te sugiero que sea la misma que empleaste para guardar el archivo csv original, dando al nuevo un nombre que ilustre su contenido. En este, por ejemplo, caso algo así como Datos_Totales.csv.

sábado, 19 de septiembre de 2026

LENGUAJES. Python

DataFrame (Pandas) (I)

Colecciones de datos

Un DataFrame es una estructura de datos bidimensional (organizada en filas y columnas) similar a una tabla de base de datos, una hoja de cálculo o una matriz. Es el objeto fundamental para la manipulación y análisis de datos en programación, especialmente en bibliotecas como Pandas (Python).

Los datos de cada columna pueden ser de distinto tipo (pero los de la misma columna han de ser del mismo), lo que le otorga gran flexibilidad, y dispone de índices por filas y por columnas, lo que le proporciona un funcionamiento eficiente en cuanto al acceso, el filtrado de datos y su alineación. Además es posible realizar operaciones vectorizadas, lo que nos permite aplicar transformaciones, filtros y cálculos matemáticos a columnas completas de forma eficiente sin necesidad de recurrir a bucles explícitos. En todas estas características se hace notar la influencia de los array de NumPy, base sobre la que se diseñó la librería Pandas y el concepto de DataFrame en Python (1).

La creación de un DataFrame (2) puede partir de la transformación de colecciones de datos nativas de Python como, por ejemplo, a partir de un diccionario cuyos valores son listas de datos:



import pandas as pd

# Creacción del diccionario
datos = {
    'Nombre': ['Ana', 'Carlos', 'Elena', 'David'],
    'Edad': [4, 6, 8, 10],
    'Centro': ['CP San Tirso', 'Col. Donato Barato', 'CP Sevilla', 'CP Luna LLena'],
    'Madre': ['María', 'Rosana', 'Carlota', 'Venancia']
}

# Conversión de diccionario a DataFrame

df = pd.DataFrame(datos)


Primero importamos la biblioteca Pandas con el alias que es costumbre import pandas as pd y después de crear el diccionario datos lo convertimos en una DataFrame df = pd.DataFrame(datos). A partir de aquí podemos realizar diferentes operaciones con el DataFrame creado haciendo uso de instrucciones Python y de otras específicas de Pandas.

Por ejemplo, podemos mostar el contenido del DataFrame en el CMD mediante print(df), obteniendo...

También podemos incrementar el contenido en ambas estructuras (diccionario y DataFrame), comprobando de paso las diferencias en el manejo de ambas estructuras.



# ==================================================
# PROCEDIMIENTO A: Añadir "registro" al DICCIONARIO
# ===================================================

# Al tratarse de un diccionario de listas, hay que añadir cada elemento clave a clave en su lista correspondiente.

datos['Nombre'].append('Lucas')
datos['Edad'].append(5)
datos['Centro'].append('CP San Tirso')
datos['Madre'].append('Laura')

print("Diccionario actualizado:")
print(datos)
print("--------------------------")


# =================================================
# PROCEDIMIENTO B: Añadir "registro" al DATAFRAME
# =================================================

# Los DataFrames de Pandas son estructuras inmutables en tamaño sobre la memoria de NumPy.
# A partir de Pandas 2.0+, la forma de añadir una fila es crear un mini-DataFrame y concatenarlo mediante pd.concat().

nuevo_df = pd.DataFrame([{
    'Nombre': 'Lucas',
    'Edad': 5,
    'Centro': 'CP San Tirso',
    'Madre': 'Laura'
}])

df = pd.concat([df, nuevo_df], ignore_index=True)

print("DataFrame actualizado:")
print(df)
print("--------------------------")

También podemos ejecutar diferentes modificaciones en el mismo, como, por ejemplo...



# 1. Selección de columnas

nombres = df['Nombre']
centros_y_madres = df[['Centro', 'Madre']]

# 2. Filtrado por condición (operación vectorizada)

# Alumnos mayores de 6 años (Elena y David)
mayores_de_6 = df[df['Edad'] > 6]

# Alumnos del colegio "CP San Tirso"
san_tirso = df[df['Centro'] == 'CP San Tirso']

# 3. Creación y modificación de columnas (operación vectorizada)

# Calcular el año de nacimiento estimado
df['Año_Nacimiento'] = 2026 - df['Edad']

# Indicador booleano para etapa infantil (menores o iguales a 5 años)
df['Es_Infantil'] = df['Edad'] <= 5

# 4. Agregación y resumen de datos

edad_promedio = df['Edad'].mean()


... que realizan las modificaciones y cálculos explicados en los comentarios, pero que no se muestran en pantalla al faltar las instrucciones print() que dejo de tu mano. Una vez realizadas estas modificaciones, lo lógico es guardar el DataFrame modificado para poder utilizarlo más adelante. Para ello es suficiente con implementar al final del script el código siguiente (3)



# Guardar el DataFrame en la ruta especificada

df.to_csv("D:/datos/mi_df.csv", index=False, encoding='utf-8')

print("DataFrame guardado correctamente en D:/datos/mi_df.csv")
print("--------------------------")


NOTAS
1 Pandas es una librería diseñada en 2008 por Wes McKinney, quien construyó la arquitectura del DataFrame para que usara las ventajas de bajo nivel de NumPy.
2 Lo frecuente es trabajar con archivos ya creados (tablas en hojas de datos o archivos CSV), pero en esta entrada nos limitaremos a la transformación de colecciones de datos insertas en el script.
3 La ruta donde se guardar realmente queda de tu mano, por lo que deberás modificar la instrucción "D:/datos/mi_df.csv" de df.to_csv("D:/datos/mi_df.csv", index=False, encoding='utf-8'). Observa que el DataFrame se guarda como archivo csv. Finalmente, mediante index=False evitamos que se guarde la columna del índice numérico (0, 1, 2, 3)

jueves, 17 de septiembre de 2026

LENGUAJES. Python

Array (NumPy)

Colecciones de datos

Tras estudiar las colecciones de datos nativas de Python (listas, tuplas, conjuntos y diccionarios), nos centraremos a continuación en las colecciones no nativas pertenecientes a bibliotecas externas. La primera de ellas será el array, estructura de datos fundamental de la biblioteca NumPy.

En términos generales, un array es una estructura de datos homogénea que almacena elementos del mismo tipo en un bloque de memoria contiguo, lo que permite un acceso rápido a sus elementos mediante índices.

Aunque Python incluye el módulo nativo array, en la práctica se recurre al array de NumPy. Esta estructura replica el modelo del lenguaje C: datos homogéneos, contiguos en memoria y de tamaño fijo. Esta disposición optimizada permite ejecutar operaciones vectorizadas directamente a nivel de hardware sin necesidad de iterar con bucles, ofreciendo un rendimiento muy superior en el cómputo masivo de datos. Esto último se entiende mejor considerando que, a diferencia de lo que sucede al usar únicamente Python —donde los datos se procesan de uno en uno mediante instrucciones repetitivas (bucles)—, con NumPy los datos se organizan en la memoria de forma tan eficiente que el procesador puede realizar el cálculo sobre millones de elementos de un solo golpe.

Si quieres comparar la diferencia de rendimiento de Python respecto a NumPy, puedes ejecutar este script en tu consola:



import time
import numpy as np

# Datos: 1 millón de enteros
N = 1_000_000

# ==========================================
# 1. MODO NATIVO (Python puro)
# ==========================================
lista_python = list(range(N))

inicio_python = time.time()
# Procesamiento de uno en uno usando un bucle/comprensión de listas
resultado_python = [x ** 2 for x in lista_python]
tiempo_python = time.time() - inicio_python


# ==========================================
# 2. MODO NUMPY (Operación vectorizada)
# ==========================================
array_numpy = np.arange(N)

inicio_numpy = time.time()
# Procesamiento "de un solo golpe" (vectorizado)
resultado_numpy = array_numpy ** 2
tiempo_numpy = time.time() - inicio_numpy


# ==========================================
# RESULTADOS
# ==========================================
print(f"Tiempo Python nativo: {tiempo_python:.4f} segundos")
print(f"Tiempo NumPy:         {tiempo_numpy:.4f} segundos")

velocidad = tiempo_python / tiempo_numpy
print(f"\nNumPy fue aproximadamente {velocidad:.1f} veces más rápido.")


Lo que este script demuestra es que NumPy procesa los arrays de forma mucho más eficiente que Python nativo, pero la verdad es que esa diferencia es de escasa relevancia para el uso que, como orientadores y usuarios de a pie, podemos hacer de esta tecnología. De hecho, el uso directo de NumPy nos aporta muy poco, ya que ni vamos a desarrollar modelos de Inteligencia Artificial ni necesitamos procesar millones de datos en el día a día.

Sin embargo, comprender la existencia del array de NumPy es crucial por una razón de peso: es el motor invisible de todo lo que sí vamos a usar. Aunque raramente escribamos import numpy en nuestros propios scripts, esta biblioteca es la piedra angular sobre la que se construyen las herramientas que sí resultan verdaderamente útiles en nuestro trabajo:

  • Es la base de Pandas, la biblioteca por excelencia para manejar tablas, hojas de cálculo y datos de alumnos, que está construida internamente sobre arrays de NumPy. Gracias a esta arquitectura, Pandas nos permite cruzar listas de asistencia con notas, detectar patrones de riesgo de abandono escolar o baremar baterías de tests psicopedagógicos en un solo clic.
  • Permite la estadística automatizada: cuando calculamos puntuaciones típicas (z, T), percentiles o desviaciones en pruebas diagnósticas, las herramientas de cálculo estadístico que usamos por debajo emplean la precisión y rapidez de NumPy.
  • Facilita la visualización y generación de gráficos para nuestros informes, los cuales se construyen leyendo los datos directamente a través de esta estructura.

En definitiva, el array de NumPy es una pieza invisible para el usuario de a pie: no necesitamos programarlo directamente para sacar provecho de Python, pero saber que existe nos permite entender cómo y por qué nuestro ordenador es capaz de procesar toda la información del centro educativo con tanta solidez, orden y precisión. Por esta razón, en posteriores entradas haremos uso de NumPy como herramienta subyacente en otras tecnologías; concretamente, en Pandas.

martes, 15 de septiembre de 2026

LENGUAJES. Python

Diccionarios complejos (III)

Colecciones de datos

En entradas anteriores analizamos cómo estructurar los datos básicos de un expediente educativo en formato JSON (nombre, curso, nee) y cómo gestionar colecciones sencillas como la lista de medidas_apoyo.

Sin embargo también necesitamos almacenar información más compleja de tipo evolutivo, como la que supone el campo intervenciones, un elemento complejo formado por una lista de diccionarios. En esta entrada nos centraremos en cómo trabajar con este tipo de datos mediante Python.


import json
import os
from collections import Counter

# 1. Definimos la ruta del archivo JSON (puedes ajustarla según tu estructura de carpetas)
RUTA_ARCHIVO = "D:/Tu_Ubicacion/archivo_JSON/exped_orienta.JSON"

def cargar_datos_expediente(ruta):
    """Carga y devuelve el contenido completo del archivo JSON."""
    if not os.path.exists(ruta):
        print(f" Error: El archivo no existe en la ruta: '{ruta}'")
        return None

    try:
        with open(ruta, "r", encoding="utf-8") as f:
            return json.load(f)
    except json.JSONDecodeError:
        print(f" Error: El archivo '{ruta}' no tiene un formato JSON válido.")
        return None
    except Exception as e:
        print(f" Error inesperado al leer el archivo: {e}")
        return None


def mostrar_datos (datos_totales):
    """
    Itera sobre todos los registros, muestra las intervenciones,
    el total de actuaciones por alumno y un resumen estadístico global.
    """
    if not datos_totales:
        print("No hay datos para mostrar.")
        return

    total_registros = len(datos_totales)
    sumatorio_actuaciones = 0
    conteo_actuaciones_por_alumno = []

    print("=" * 65)
    print(f" REPORTES DE INTERVENCIONES - TOTAL REGISTROS: {total_registros}")
    print("=" * 65)

    # Iteramos sobre cada alumno en el diccionario principal
    for id_alumno, expediente in datos_totales.items():
        intervenciones = expediente.get("intervenciones", [])
        num_actuaciones = len(intervenciones)

        # Actualizamos métricas globales
        sumatorio_actuaciones += num_actuaciones
        conteo_actuaciones_por_alumno.append(num_actuaciones)

        print(f"\n>>> EXPEDIENTE: {id_alumno}")
        print("-" * 45)

        if not intervenciones:
            print("  (Sin intervenciones registradas)")
        else:
            for idx, sesion in enumerate(intervenciones, 1):
                fecha = sesion.get("fecha", "Sin fecha")
                motivo = sesion.get("motivo", "Sin especificar")
                acuerdos = sesion.get("acuerdos", "Sin acuerdos registrados")

                print(f"  [{idx}] Fecha:   {fecha}")
                print(f"      Motivo:  {motivo}")
                print(f"      Acuerdos: {acuerdos}\n")

        # Muestra del total al final de cada expediente
        print(f"  --> Total de actuaciones en {id_alumno}: {num_actuaciones}")

    # ---------------------------------------------------------
    # RESUMEN ESTADÍSTICO FINAL
    # ---------------------------------------------------------
    print("\n" + "=" * 65)
    print(" RESUMEN ESTADÍSTICO DE ACTUACIONES")
    print("=" * 65)
    print(f" • Total de alumnos/registros analizados: {total_registros}")
    print(f" • Sumatorio total de actuaciones:        {sumatorio_actuaciones}")
    print("\n Distribución de registros según número de actuaciones:")

    # Contamos la frecuencia de cada cantidad de actuaciones
    distribucion = Counter(conteo_actuaciones_por_alumno)

    # Ordenamos por número de actuaciones de menor a mayor
    for num_act, cant_alumnos in sorted(distribucion.items()):
        porcentaje = (cant_alumnos / total_registros) * 100
        print(
            f"   - Registros con {num_act} actuaciones: "
            f"{cant_alumnos} alumno(s) ({porcentaje:.1f}%)"
        )

    print("=" * 65)


# --- EJECUCIÓN DEL SCRIPT ---
if __name__ == "__main__":
    datos_json = cargar_datos_expediente(RUTA_ARCHIVO)
    mostrar_datos (datos_json)


Con este primer script cargamos un archivo JSON que contiene los datos desde una ubicación que será distintan en cada caso y que en este script de identifica como RUTA_ARCHIVO = "D:/Tu_Ubicacion/archivo_JSON/exped_orienta.JSON" siendo exped_orienta.JSON el nombre del archivo (datos inventados) que puedes descargar desde este enlace. Así sólo tienes que ajustar la ruta en el script.

La primera función del script def cargar_datos_expediente(ruta): se encarga de cargar el archivo, controlando los posibles errores que se pudieran cometer, por lo que se puede simplificar una vez que, como programadores de nuestro script y usuarios, ya tengamos asegurada la carga del documento JSON. En ese caso la función podría quedar simplificada como sigue:



def cargar_datos_expediente(ruta):
    """Carga y devuelve el contenido completo del archivo JSON."""
    with open(ruta, "r", encoding="utf-8") as f:
        return json.load(f)


El siguiente paso consiste en acceder a los datos y mostrarlos por pantalla. Para ello implementamos en el script la segunda función def mostrar_datos (datos_totales):, que además de mostrar el listado de registros con las actuaciones de cada uno de ellos, también realiza un recuento de cuantas actuaciones tiene cada registro y finaliza con una pequeña estadística que se muestra al final del listado. Por este motivo esta función se vuelve especialmente extensa y compleja, requiendo una explicación de cada una de sus partes.

Además de controlar la existencia de datos en el archivo JSON, en el inicio de la función se establecen unas variables que nos servirán para realizar los recuentos solicitados...



if not datos_totales:
        print("No hay datos para mostrar.")
        return

    total_registros = len(datos_totales)
    sumatorio_actuaciones = 0
    conteo_actuaciones_por_alumno = []


... y después se define el formato de la cabecera del informe que se imprimirá en su momento en el CMD



print("=" * 65)
    print(f" REPORTES DE INTERVENCIONES - TOTAL REGISTROS: {total_registros}")
    print("=" * 65)


Pero es a partir de este punto que empezamos a construir la estructura del contenido de ese informe, recorriendo el contenido del archivo...



# Iteramos sobre cada alumno en el diccionario principal
    for id_alumno, expediente in datos_totales.items():
        intervenciones = expediente.get("intervenciones", [])
        num_actuaciones = len(intervenciones)

        # Actualizamos métricas globales
        sumatorio_actuaciones += num_actuaciones
        conteo_actuaciones_por_alumno.append(num_actuaciones)


... y accediendo a los datos del diccionario anidado en el campo...



print(f"\n>>> EXPEDIENTE: {id_alumno}")
        print("-" * 45)

        if not intervenciones:
            print("  (Sin intervenciones registradas)")
        else:
            for idx, sesion in enumerate(intervenciones, 1):
                fecha = sesion.get("fecha", "Sin fecha")
                motivo = sesion.get("motivo", "Sin especificar")
                acuerdos = sesion.get("acuerdos", "Sin acuerdos registrados")

                print(f"  [{idx}] Fecha:   {fecha}")
                print(f"      Motivo:  {motivo}")
                print(f"      Acuerdos: {acuerdos}\n")

        # Muestra del total al final de cada expediente
        print(f"  --> Total de actuaciones en {id_alumno}: {num_actuaciones}")


El cuarto bloque de esta función está dedicada a la composición del conteo de datos con el que finaliza el informe que se muestra por pantalla, por lo que se puede omitir si no se desea esta información.



  # ---------------------------------------------------------
    # RESUMEN ESTADÍSTICO FINAL
    # ---------------------------------------------------------
    print("\n" + "=" * 65)
    print(" RESUMEN ESTADÍSTICO DE ACTUACIONES")
    print("=" * 65)
    print(f" • Total de alumnos/registros analizados: {total_registros}")
    print(f" • Sumatorio total de actuaciones:        {sumatorio_actuaciones}")
    print("\n Distribución de registros según número de actuaciones:")

    # Contamos la frecuencia de cada cantidad de actuaciones
    distribucion = Counter(conteo_actuaciones_por_alumno)

    # Ordenamos por número de actuaciones de menor a mayor
    for num_act, cant_alumnos in sorted(distribucion.items()):
        porcentaje = (cant_alumnos / total_registros) * 100
        print(
            f"   - Registros con {num_act} actuaciones: "
            f"{cant_alumnos} alumno(s) ({porcentaje:.1f}%)"
        )

    print("=" * 65)


Además de mostrar los registros (las actuaciones) por pantalla, también podemos realizar modificaciones sobre esos contenidos. Una de ellas (posiblemente la de mayor interés en la práctica) es la de añadir una nueva actuación dentro del registro de un determinado alumno. Para ello añadiremos este script al anterior.



# =============================================================================
# FASE 2: EDICIÓN E INCREMENTO DE INTERVENCIONES
# =============================================================================

def seleccionar_alumno(datos_totales):
    """
    Presenta los alumnos con un índice numérico para seleccionar uno.
    """
    print("\n" + "=" * 60)
    print(" SELECCIÓN DE ALUMNO / REGISTRO PARA AÑADIR INTERVENCIÓN")
    print("=" * 60)

    lista_ids = list(datos_totales.keys())

    for idx, id_alumno in enumerate(lista_ids, 1):
        nombre = datos_totales[id_alumno].get("nombre", "Sin nombre")
        num_intervenciones = len(datos_totales[id_alumno].get("intervenciones", []))
        print(f"  [{idx}] ID: {id_alumno} | Nombre: {nombre} ({num_intervenciones} intervenciones)")

    print("-" * 60)

    while True:
        opcion = input(f"Seleccione un alumno (1-{len(lista_ids)}): ").strip()
        if opcion.isdigit() and 1 <= int(opcion) <= len(lista_ids):
            return lista_ids[int(opcion) - 1]
        print(" Opción no válida. Introduzca un número de la lista.")


def capturar_nueva_intervencion():
    """
    Captura por consola los campos del nuevo diccionario anidado.
    """
    print("\n--- NUEVA ACTUACIÓN ---")
    fecha = input("• Fecha (AAAA-MM-DD): ").strip()
    motivo = input("• Motivo de la intervención: ").strip()
    acuerdos = input("• Acuerdos alcanzados: ").strip()

    return {
        "fecha": fecha if fecha else "Sin fecha",
        "motivo": motivo if motivo else "Sin especificar",
        "acuerdos": acuerdos if acuerdos else "Sin acuerdos registrados"
    }

def agregar_intervencion_interactiva(datos_totales):
    """
    Permite agregar de forma iterativa intervenciones en el mismo registro
    o cambiar a otros expedientes.
    """
    while True:
        id_alumno = seleccionar_alumno(datos_totales)
        expediente = datos_totales[id_alumno]

        if "intervenciones" not in expediente:
            expediente["intervenciones"] = []

        # Bucle para añadir varias intervenciones al mismo alumno
        while True:
            print(f"\nAñadiendo actuación para: {id_alumno} ({expediente.get('nombre', '')})")
            
            nueva_actuacion = capturar_nueva_intervencion()
            expediente["intervenciones"].append(nueva_actuacion)
            
            print(f"\n [OK] Actuación añadida. Total actual en {id_alumno}: {len(expediente['intervenciones'])}")

            mas_mismo = input("\n¿Desea añadir OTRA intervención a ESTE MISMO alumno? (s/n): ").strip().lower()
            if mas_mismo != "s":
                break

        mas_otros = input("\n¿Desea seleccionar OTRO alumno para añadir intervenciones? (s/n): ").strip().lower()
        if mas_otros != "s":
            print("\n Finalizada la sesión de altas.")
            break


Con él creamos un listado de alumnos para identificar aquel en el que queremos añadir una nueva actuación def seleccionar_alumno(datos_totales): y después añadimos los datos de esa actuación def agregar_intervencion_interactiva(datos_totales):, previa identificación de sus campos def capturar_nueva_intervencion():

Además podemos añadir más de una actuación respecto al mismo alumno, o añadir actuaciones realizadas con otro alumno def agregar_intervencion_interactiva(datos_totales):

Hasta el momento sólo hemos consultado los registros de actuaciones, identificado a un alumno en concreto e incrementado las actuaciones que se han realizado respecto a ese alumno, pero si dejamos así el script perderemos los cambios introducidos. Para que esto no suceda deberemos implementar otro segmento de código a nuestro script que se encargue de guardar en la dirección que corresponda un nuevo archivo JSON.



# =============================================================================
# FASE 3: PERSISTENCIA Y ACTUALIZACIÓN EN DISCO
# =============================================================================

def guardar_datos_actualizados(datos_totales, ruta=RUTA_ARCHIVO):
    """
    Sobrescribe el archivo JSON original con la estructura de datos
    actualizada en memoria, aplicando formato indentado.
    """
    print("\n" + "=" * 60)
    print(" GUARDANDO CAMBIOS EN EL ARCHIVO JSON")
    print("=" * 60)

    # Sobrescribimos el archivo con la información acumulada en memoria
    with open(ruta, "w", encoding="utf-8") as f:
        json.dump(datos_totales, f, ensure_ascii=False, indent=4)

    print(f" [OK] El archivo '{ruta}' se ha actualizado correctamente.")
    

En este segmento sólo se identifica una función def guardar_datos_actualizados(datos_totales, ruta=RUTA_ARCHIVO): cuyo nombre es suficientemente expresivo y que no requiere más explicación.

Lo que sí es necesario (ahora) es modificar el segmento inicial de la función if __name__ == "__main__": para incluir las nuevas funcinalidades. El resultado es el que sigue. Corre de tu cuenta actualizarla en el script final que construyas con los segmentos que te he ido proporcionando.



# =============================================================================
# BLOQUE PRINCIPAL DE EJECUCIÓN
# =============================================================================

if __name__ == "__main__":

    # 1. Cargar archivo JSON
    datos_json = cargar_datos_expediente(RUTA_ARCHIVO)

    # 2. Mostrar datos iniciales y resumen estadístico
    mostrar_datos(datos_json)

    # 3. Iniciar proceso interactivo de alta de intervenciones
    agregar_intervencion_interactiva(datos_json)

    # 4. Guardar los cambios acumulados en el archivo JSON
    guardar_datos_actualizados(datos_json, RUTA_ARCHIVO)


Podríamos implementar más utilidades dentro de este script, pero para el objetivo de la entrada del blog creo que tenemos más que suficiente. Puede que lo qua haga falta sea crear un procedimiento de trabajo que incluya la creación, actualización, manejo y archivo de un documentoJSON a modo de base de datos. En ese caso habría que identificar el objetivo y la estructura de contenidos. Un sistema de seguimiento de actuaciones como el que hemos simulado podría ser un buen tema de trabajo.

viernes, 7 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (II)

Colecciones de datos

En un diccionario complejo el value (par clave:valor) de un posible "campo" puede estar formado por una lista de datos ([]), de modo que el tratamiento de esos datos haga encesario implementar estrategias específicas. Sin pretender ser exhaustivos, ese tratamiento incluye operaciones como conteo, visualización y consulta (de toda la lista o de un elemento), añadido, supresión o modificación... En esta entrada estudiaremos cómo realizar estas operaciones.

Ya que por algún punto hay que empezar, nos situaremos en el siguiente contexto: hemos definido la estructura de diccionario...



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


... ya sabemos cómo añadir elementos a los "campos" simples nombre - curso - nee...



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    

... pero todavía no sabemos cómo introducir datos en el "campo" "medidas_apoyo": [] cuyo value es una lista, como podemos identificar por el uso corchetes [] como delimitadores. El script que sigue cubre esta necesidad, aunque tiene una funcionalidad limitada.



import json
import os


def solicitar_booleano(mensaje):
    """Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n."""
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ["s", "si", "sí"]:
            return True
        elif respuesta in ["n", "no"]:
            return False
        print(
            " [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No."
        )


# --- FUNCIÓN Cumplimentar datos de medidas_apoyo ---------------------------------------

def capturar_medidas_apoyo():
    """Captura de forma interactiva la lista de medidas de apoyo."""
    medidas = []
    quiere_anadir = solicitar_booleano(
        "¿Desea registrar alguna medida de apoyo?"
    )

    contador = 1
    while quiere_anadir:
        medida = input(f" -> Ingrese la medida de apoyo #{contador}: ").strip()
        if medida:
            medidas.append(medida)
            contador += 1
        quiere_anadir = solicitar_booleano("¿Desea añadir otra medida de apoyo?")

    return medidas


# --- FUNCIÓN PRINCIPAL de ejecución. -----------------------------------------------

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")

    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print(
        "  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)"
    )
    print(
        "  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)"
    )
    print("-------------------------------------\n")

    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano(
        "¿Presenta Necesidades Educativas Especiales (NEE)?"
    )

    # 1. Llamamos a la nueva utilidad para generar la lista
    medidas_apoyo = capturar_medidas_apoyo()

    # 2. Retornamos el diccionario completo con el nuevo campo
    registro = {
        "codigo_alumno": cod_alumno,
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": medidas_apoyo,
    }

    return registro

# ==============================================================================
# BLOQUE DE EJECUCIÓN PRINCIPAL
# ==============================================================================
if __name__ == "__main__":
    # Solicitamos o asignamos el número identificador para el código de alumno
    num_expediente = 1

    # Llamada a la función principal para capturar los datos interactivos
    expediente_alumno = capturar_ficha(num_expediente)

    # Mostramos el resultado por pantalla con formato JSON legible
    print("\n==========================================")
    print("      REGISTRO GENERADO CON ÉXITO")
    print("==========================================")
    print(json.dumps(expediente_alumno, indent=4, ensure_ascii=False))


Este script complementa el de creación de registro añadiendo, a partir de def capturar_medidas_apoyo(): le procedimiento para obtener los primeros posibles datos de medidas_apoyo. Esta función es llamada desde la función principal def capturar_ficha(codigo_num): y se basa, en lo fundamental, en el bucle while quiere_anadir: que permite al usuario introducir cuantas medidas de apoyo desee.

Resuelto el procedimiento de creación de datos, nos situamos ahora en el segundo contexto: ya disponemos de un archivo JSON que contiene datos suficientes como para que sea pertinente realizar diferentes operaciones con la información de medidas_apoyo de los distintos registros-diccionarios.

En este contexto empezamos por acceder al archivo JSON para trabajar con él desde el script (en memoria) como diccionario complejo. Este procedimiento ya nos es conocido.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    

De las entradas anteriores sabemos crear el listado las claves principales, acceder al contenido de un archivo concreto o a todos ellos. También sabemos manipular el contenido de deteminados campos. Ahora nos detendremos en el acceso y las posibles modificaciones del contenido del campo cuyo value es una lista. Empezaremos por obtener el listado completo de registros mostrando cuántas y cuales son las medidas educativas en cada uno de ellos.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

try:
    # 1. Cargar el contenido del archivo JSON en memoria
    with open(ruta_archivo, "r", encoding="utf-8") as archivo:
        expedientes = json.load(archivo)

    print("\n==================================================")
    print("    LISTADO COMPLETO DE EXPEDIENTES Y MEDIDAS     ")
    print("==================================================\n")

    # Adaptación dinámica según si el JSON es una lista [...] o un diccionario {...}
    registros = (
        expedientes.items()
        if isinstance(expedientes, dict)
        else enumerate(expedientes, 1)
    )

    for id_registro, datos in registros:
        # Obtenemos la clave principal (clave del dict o campo 'codigo_alumno')
        if isinstance(datos, dict):
            clave_principal = datos.get("codigo_alumno", f"Registro #{id_registro}")
            medidas = datos.get("medidas_apoyo", [])
        else:
            clave_principal = str(id_registro)
            medidas = datos if isinstance(datos, list) else []

        # Contamos la cantidad total de elementos en la lista
        total_medidas = len(medidas)

        # Imprimimos la salida formateada en CMD
        print(f"📌 Clave Principal: {clave_principal}")
        print(f"   Total de medidas educativas aplicadas: {total_medidas}")

        if total_medidas > 0:
            print("   Listado de medidas:")
            for i, medida in enumerate(medidas, 1):
                print(f"      {i}. {medida}")
        else:
            print("   Listado de medidas: (Sin medidas registradas)")

        print("-" * 50)

except FileNotFoundError:
    print(f" [!] Error: No se encontró el archivo en la ruta '{ruta_archivo}'.")
except json.JSONDecodeError:
    print(
        " [!] Error: El archivo JSON no tiene un formato válido o está corrupto."
    )


Podríamos dirigir nuestra atención al análisis del contenido de este "campo", pero dejaremos estos procedimientos para una tercera fase del estudio de los diccionarios y nos atendremos a explicar los procedimientos de manejo y modificación del value lista, si bien no es muy diferente del del que ya estudiamos en el tratamiento de estas colecciones de datos.

Al tratarse de un diccionario ya desarrollado en cuanto a su contenido, lo primero que deberemos hacer es identificar y acceder al registro de nuestro interés. Para ello debemos usar la clave principal /(vg. "ALU-2024-01") y, a partir de ahí a la clave "medidas_apoyo".

Disponemos de dos procedimientos para acceder a los datos de la lista. Bien directamente identificando al alumno por su clave:



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Identificamos directamente al alumno por su clave

id_alumno = "ALU-2024-01"

# 3. Verificar existencia y mostrar las medidas elemento por elemento
if id_alumno in expedientes:
    print(f"Medidas de apoyo para {id_alumno}:")
    for medida in expedientes[id_alumno]["medidas_apoyo"]:
        print(f"- {medida}")



... o bien hacerlo de forma interactiva introducciendo por input() el identificador de su clave, suponiendo que tenemos acceso a un listado de claves y nombres que nos facilitan la identificación del sujeto deseado.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    
# 2. Listado de claves-sujetos

listado = [f"{clave} - {datos['nombre']}" for clave, datos in expedientes.items()]
for al in listado:
    print(al)

# 3. Solicitar la clave directamente al usuario
clave = input("Introduce la clave del expediente: ")

# 4. Acceder al valor del campo 'medidas_apoyo' del registro indicado
medidas = expedientes[clave]["medidas_apoyo"]

# 5. Mostrar el contenido actual
print("Medidas de apoyo actuales:", medidas)


Una vez que accedemos al listado de medidas, podemos limitarnos a mostrarlo, pero también podemos actuar sobre él. Veamos algunas acciones posibles. Se escriben a continuación del script anterior).



print("\n--- ACCIONES SOBRE LAS MEDIDAS ---")

# (1) Conocer el número de medidas
print(f"\n4. Número total de medidas registradas: {len(medidas)}")

# (2) Añadir una nueva medida
nueva = input("1. Introduce la nueva medida a añadir: ").strip()
medidas.append(nueva)
print("   Lista actualizada:", medidas)

# (3) Borrar una medida
a_borrar = input("\n2. Introduce la medida a eliminar: ").strip()
medidas.remove(a_borrar)
print("   Lista tras borrar:", medidas)

# (4) Ordenar alfabéticamente las medidas
medidas.sort()
print("\n3. Medidas ordenadas alfabéticamente:", medidas)

# (5) Saber si existe una determinada medida
buscar = input("\n5. Consulta si existe una medida concreta: ").strip()
print(f"   -> ¿Existe '{buscar}' en la lista?: {buscar in medidas}")


Lo suyo sería actualizar el archivo JSON, pero por ahora no es necesario ya que sólo estamos aprendiendo a trabajar con los componentes del diccionario.

martes, 4 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (I)

Colecciones de datos

En las dos entradas previas en las que hemos trabajado con diccionarios hemos empleado de forma intencional formulaciones simples de esta colección de datos. Se trataba de comprender en qué consistía y cómo se podía trabajar con ella, así que la simplicidad se imponía como necesidad. Pero la realidad es que un diccionario "normal" no suele ser tan sencillo ni su virtud o interés requiere de que los sea.

En esta entrada idearemos una estructura compleja (y más realista, incluso) de un diccionario y trabajaremos con ella. Se trata de comprobar la realidad y la utilidad real de un diccionario. Para ello vamos a plantearlo como colección de datos potencialmente aplicable a un proyecto real de trabajo (como SEO) aunque ajustado a las necesidades didácticas del momento.

Imaginemos que sobre una estructura de diccionario queremos construir un recurso de seguimiento del alumnado. Se trata de una forma de expediente, muy parecido a lo que se podría dar soporte con una base de datos, pero también registrar y mantener como un documento JSON. Algo como esto:



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


Muestro aquí la estructura de un elemento del documento CSV sin contenidos ni reales ni supuestos (eso viene después) para que podemos observar en qué consiste la complejidad de la que estamos hablando cuando lo trasladamos a diccionario (ya sabemos que estructura y contenido se mantienen): ahora cada elemento del diccionario - aquí "ALU-cod-n": - es la clave de un value que es a su vez un diccionario, de ahí que el conjunto se pueda considerar como un diccionario de diccionarios.

Ese value además de ser un diccionario (de ahí que todo él se encuentre delimitado por {}, es un diccionario complejo ya que sus diferentes elementos-campos lo son de diferente tipología:

  • Tenemos contenidos-campos clave:valor simples (y de distinto tipo de datos), como "nombre": "al_nombre"
  • Pero también un contenido-campo cuyo value es una lista "medidas_apoyo": ["mad_1"],
  • Y más aun, un contenido-campo cuyo value es una lista de diccionarios (de la que ahora sólo muestro su primer componente clave:valor por simplificar: "intervenciones": [{"fecha": "int_aa_mm_dd",}]

Sobra decir que debermos conocer cómo tratar a cada subestructura o tipo de componente, tanto como con el conjunto. Empezaremos con ello a continuación.



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    
    # Se mantienen las estructuras de listas para su posterior implementación
    return cod_alumno, {
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": [],
        "intervenciones": []
    }

def guardar_en_json(nuevos_expedientes, ruta_salida):
    # Limpieza de comillas iniciales o finales introducidas por el usuario
    ruta_salida = ruta_salida.strip('\"\'')
    ruta_salida = os.path.normpath(ruta_salida)
    
    # 1. Comprobar si el directorio existe y crearlo si no
    directorio = os.path.dirname(ruta_salida)
    if directorio and not os.path.exists(directorio):
        os.makedirs(directorio, exist_ok=True)
        print(f" [+] Directorio creado: {os.path.abspath(directorio)}")
        
    datos_acumulados = {}
    
    # 2. Comprobar si el archivo JSON existe para cargar su contenido previo
    if os.path.exists(ruta_salida):
        try:
            with open(ruta_salida, mode="r", encoding="utf-8") as archivo:
                contenido = archivo.read().strip()
                if contenido:
                    datos_acumulados = json.loads(contenido)
            print(f" [+] Archivo existente detectado. Se añadirán los nuevos registros a los {len(datos_acumulados)} existentes.")
        except Exception as e:
            print(f" [!] Advertencia: No se pudo leer el archivo existente ({e}). Se iniciará un JSON nuevo.")
            datos_acumulados = {}
    else:
        print(" [+] El archivo no existe. Se creará un nuevo archivo JSON.")

    # 3. Anexar/actualizar los registros nuevos sobre el objeto cargado
    datos_acumulados.update(nuevos_expedientes)
    
    # 4. Sobrescribir el archivo con el diccionario actualizado en formato JSON legible
    with open(ruta_salida, mode="w", encoding="utf-8") as archivo:
        json.dump(datos_acumulados, archivo, ensure_ascii=False, indent=4)
        
    return os.path.abspath(ruta_salida)

def main():
    expedientes_sesion = {}
    contador = 1
    
    print("----------------------------------------------------------------")
    print(" SISTEMA DE REGISTRO DE EXPEDIENTES ACADÉMICOS")
    print("----------------------------------------------------------------")
    
    # Bucle de captura continua de fichas
    while True:
        codigo, ficha = capturar_ficha(contador)
        expedientes_sesion[codigo] = ficha
        contador += 1
        
        if not solicitar_booleano("\n¿Desea incorporar la ficha de otro alumno?"):
            break
            
    # Guardado de datos en JSON
    if expedientes_sesion and solicitar_booleano("\n¿Desea guardar los expedientes recopilados en un archivo JSON local?"):
        print("\n=========================================================================")
        print(" INSTRUCCIONES PARA LA RUTA DEL ARCHIVO JSON:")
        print(" 1. NO use comillas al principio ni al final (ejemplo: NO use \"C:\\datos\\alumnos.json\")")
        print(" 2. Puede usar barras inclinadas normales ( / ) o barras invertidas ( \\ ).")
        print(" 3. Asegúrese de incluir la extensión .json al final del nombre.")
        print(" 4. Ejemplos de rutas válidas:")
        print("    - Windows (ruta absoluta): C:/MisDatos/expedientes.json  ó  C:\\MisDatos\\expedientes.json")
        print("    - Carpeta actual (ruta relativa): ./expedientes.json  ó  expedientes.json")
        print("=========================================================================\n")
        
        ruta_archivo = input("Introduzca la ruta y nombre del archivo JSON: ").strip()
        try:
            ruta_final = guardar_en_json(expedientes_sesion, ruta_archivo)
            print(f"\n[OK] Registros guardados con éxito en:\n     {ruta_final}")
        except Exception as e:
            print(f"\n[ERROR] No se pudo guardar el archivo JSON: {e}")
            
    print("\nCierre de sesión del sistema.")

if __name__ == "__main__":
    main()



Aunque el script resultante sea mejorable, ya resulta suficientemente complejo (además de funcional) como para que nos hagamos una idea de qué estamos planteando cuando implementamos un diccionario de este nivel de complejidad. Lo que hemos pretendido es ejemplificar lo que sería la primera y básica implementación de una utilidad de este tipo: obtener del usuario los datos básicos (y simples, se podría añadir) que componen la estructura del diccionario y archivar esa información partiendo desde cero y en formato JSON. Cualquier proyecto de este tipo debe contener estas funcionalidades y ajustarse a la estructura del JSON deseado.

Pero si el contexto primero es correcto (y realista), el que planteo a continuación también lo es. Dando por supuesto el exito del anterior, creamos ahora una utilidad de acceso a los datos. Partimos de que ya tenemos algunos creados y deseamos acceder a ellos, entendiendo que ya hemos recopilado cierto volumen de información, disponible como archivo JSON. Evidentemente los datos que incluímos son inventados (sintéticos, que se dice) y no nos vamos a detener en valorar su ajuste a la realidad ni su interés pedagógico real. Para que lo que aquí te presento te sea de ayuda, proporcionaré acceso al documento "inventado" al final de esta entrada.

Dicho lo anterior, lo que ahora necesitamos es un script que nos permita acceder al documento JSON para trabajar con él en memoria como diccionario.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Obtener la clave y los datos del primer registro
primer_id = list(expedientes.keys())[0]
primer_registro = expedientes[primer_id]

# 3. Mostrar el resultado por pantalla (CMD)
print(f"--- PRIMER REGISTRO: {primer_id} ---")
print(json.dumps(primer_registro, ensure_ascii=False, indent=4))


Dado que estamos trabajando con archivos JSON, lo primero que haremos será importar la biblioteca correspondiente import json para cargar en memoria el archivo expedientes = json.load(archivo) desde su ubicación local ruta_archivo y accedemos a él with open() as archivo. Después obtenemos las claves de todos los archivos en formato lista list(expedientes.keys()) y el contenido del primer registro primer_registro = expedientes[primer_id] y lo mostramos por CMD print(json.dumps(primer_registro, ensure_ascii=False, indent=4)).

Mediante este par de script abordamos las dos maniobras básicas que deberemos realizar con diccionarios, simples o complejos. Pero como hay tarea para rato, parece conveniente finalizar ahora esta entrada.

Archivo de datos (formato JSON). Acceso para descarga desde este enlace. Deberás ajustar la dirección ruta_archivo a la que tú elijas como directorio del archivo que has descargado.

jueves, 30 de julio de 2026

LENGUAJES. Python

Diccionarios (II)

Colecciones de datos

Damos continuidad en esta entrada a la temática iniciada en la anterior sobre los diccionarios Python y sus usos. Ahora trataremos cuestiones básicas de manejo de los diccionarios y su contenido.



#A. Procedimientos directos de creación de diccionarios simples ========================

# 1. Creación estándar con llaves

usuario_ana = {
    "nombre": "Ana",
    "edad": 28,
    "lenguajes": ["Python", "JavaScript"],
    "activo": True
}

# 2. Creación con la función dict()

usuario_carlos= dict(nombre="Carlos", edad=35,lenguajes=["OOo Basic","Python"],activo=True)

#B. Acceso a datos =================================================

# 1. Acceso directo con llaves

print(usuario_ana["nombre"])  # Salida: Ana
print(usuario_carlos["edad"])  #Salida: 35


# 2. Acceso seguro con .get() (evita que el programa falle si la clave no existe)

print(usuario_ana.get("edad"))  # Salida: True
print(usuario_carlos.get("salario", "No especificado"))  # Salida: No especificada

#3. Acceso cuando el valor es una lista 

# Cuando el valor es una lista, sólo con la clave obtenemos como valor la propia lista

print(usuario_ana.get("lenguajes"))  # Salida: ["Python", "JavaScript"]

# 3.1. Usando corchetes. Obtener el primer elemento de la lista (índice 0)

primer_lenguaje = usuario_ana["lenguajes"][0]
print(primer_lenguaje)  # Salida: Python

# 3.2. Usando get(). Obtener el primer elemento con .get()

primer_lenguaje = usuario_carlos.get("lenguajes")[0]
print(primer_lenguaje)  # Salida: OOo Basic

# C. Modificar datos: cambiar, borrar y añadir datos ===============================

# 1. Modificar un valor existente
usuario_ana["edad"] = 29

# 2. Agregar una nueva pareja clave-valor
usuario_ana["ciudad"] = "Madrid"

# 3. Agregar elemento a un valor-lista (ej. lenguajes:[lista_lenguajes]

# 3.1. Agregar un nuevo elemento a la lista 'lenguajes' con append()
usuario_ana["lenguajes"].append("C++")

# (3.1.b) Si no estás seguro de que exista la clave 'lenguajes' usa setdefault() para crearlo y luego añade el elemento con append()
usuario_ana.setdefault("lenguajes", []).append("LUA")

# 3.2. Agregar un nuevo elemento a la lista 'lenguajes' usando .extend() (varios elementos)
usuario_carlos["lenguajes"].extend(["JavaScript", "C++", "PHP"])

# (3.2.b) ...o usando +=
usuario_ana["lenguajes"] += ["OOo Basic"]

print(usuario_carlos["lenguajes"])
print(usuario_ana["lenguajes"])

# 4. Borrar

# 4.1 Eliminar una clave específica con 'del'
del usuario_ana["activo"] #Elimina el par activo:True

# Extraer y eliminar un valor con .pop()
edad = usuario_ana.pop("edad") # Elimina el valor (29) del par "edad":29

# Limpiar todo el diccionario
usuario_carlos.clear() # Elimina todo el diccionario (usuario_carlos)

print(usuario_ana.get("activo", "No especificado")) #con get() evitamos mensaje de error. Devuelve mensaje ("No especificado")
print(edad) #Devuelve el valor del par 'edad':29
print(usuario_ana.get('edad')) #Devuelve None por defecto (ya que no se espifica mensaje)
print (usuario_carlos) # Devuelve un diccionario vacío ({})


Reproduzco en este script la creación de dos diccionarios simples para desarrollar la lógica de posibles manipulaciones sobre un diccionario y sus contenidos. La división del script en cuatro partes y la explicitación como comentarios de cada una de las posibles acciones facilita la comprensión del contenido y agiliza esta explicación complementaria.

Podemos diferenciar tres tipos de actuaciones sobre un diccionario:

  • Los procedimientos de acceso a los datos (sección B)
    • mediante el uso de llaves print(usuario_ana["nombre"]) - Salida: Ana
    • mediante la función get() para controlar el posible error de acceso
    • y el acceso en el caso específico de valores-lista, bien para visualizar la lista completa de valores print(usuario_ana.get("lenguajes")), bien para acceder a uno de ellos en concreto, por ejemplo, print(primer_lenguaje = usuario_ana["lenguajes"][0]), mediante el uso de corchetes
  • La modificación de datos (cambiar o añadir) (sección C del script)
    • mediante procedimientos simples usuario_ana["edad"] = 29 de cambio
    • o usuario_ana["ciudad"] = "Madrid" para añadir clave:valor, ambos basados en el uso de corchetes
    • o más complejos como es el caso de la modificación de valores-listas, que requieren el uso de funciones como append(), (sóla en combinación con setdefault()) para añadir un elemento-valor o la función extend() para añadir varios.
  • Finalmente, en la sección D tratamos el borrado de datos: clave: valor del usuario_ana["activo"], sólo valor edad = usuario_ana.pop("edad") y del diccionario completo usuario_carlos.clear()

Finalizo aquí esta segunda entrada sobre los diccionarios, pero aun hay materia para más...

miércoles, 29 de julio de 2026

LENGUAJES. Python

Diccionarios (I)

Colecciones de datos

Aunque ya sabemos algo sobre diccionarios en Python, lo cierto es que en esta subsección de LENGUAJES hemos hablado mucho de listas (y de tuplas) y muy poco de diccionarios, al menos mucho menos de lo que se merece.

Cuando uno comienza a programar en OOo Basic deberá trabajar con listas o arrays. Si después programa en Python lo "lógico" es que traslade su interés al uso las listas. Además las listas en Python son sencillas e intuitivas de usar y muy útiles.

Pero Python es mucho más rico y complejo que OOo Basic también es esto de las colecciones de datos y los diccionarios son también expresión de esa superioridad: un diccionario mejora la organización de los datos y también la velocidad y precisión del acceso, que está en función del significado y no de la posición como en las listas.

Un diccionario es una colección de elementos organizados mediante pares clave → valor (key:value) en la que cada dato-valor (que sí se puede modificar y duplicar) se almacena asociado a una clave única (por lo que es inmutable y única).



persona = {
    "nombre": "Ana",
    "edad": 34,
    "profesion": "Ingeniera"
}


En este caso el diccionario persona consta de tres claves y cada clave contiene un dato o valor. v.g. la clave "nombre" contiene el valor "Ana"

Si estuviéramos trabajando con listas persona = ["Ana", 34, "Ingeniera"] (1), para obtener la profesión deberíamos expresarlo como persona[2], mientras que como diccionario diremos persona["profesion"]. En el caso de las listas, cualquier modificación del contenido implica un cambio en el procedimiento de acceso que deberemos conocer de antemano; en el caso de los diccionarios es suficiente con conocer el nombre de la clave.

Si lo anterior no fuera suficiente motivo para entender las ventajas de los diccionarios sobre las listas, piensa en la cantidad de "objetos" del mundo real que se pueden expresar como diccionarios:

  • ejemplo: alumno - identificador - nombre - curso - etapa - centro - resultados académicos - MAD - ...
  • Pero también archivo - ruta - tamaño - fecha...

Estos "objetos" y otros muchos se pueden expresar en estructuras clave - valor. Además, Python implementa internamente los diccionarios mediante tablas hash (2), lo que hace que la búsqueda de un elemento por su clave sea, en condiciones normales, extremadamente rápida. En la práctica, acceder a un valor suele realizarse en tiempo constante, independientemente del tamaño del diccionario.

El método más habitual para crear un diccionario consiste en utilizar llaves, como en provincias = {}, instrucción con la que creamos un diccionario vacío que después podemos dotar de contenido, aunque también es posible (y muy frecuente) crear directamente el diccionario definiendo su estructura y contenido, como en este caso...



provincias = {
    "Asturias": "Oviedo",
    "León": "Leon",
    "Cantabria": "Santander"
}


... aunque también se puede crear un diccionario mediante la función dict() - provincias=dict().



# Bien...

provincias = dict(
    Asturias="Oviedo",
    Leon="León",
    Cantabria="Santander"
)

# O también...

provincias = dict([
    ("Asturias", "Oviedo"),
    ("León", "León"),
    ("Cantabria", "Santander")
])


Aunque esta es la forma simple de expresar un diccionario, lo normal es que su contenido sea más complejo. Pensemos, por ejemplo, que creamos el diccionario provincias pero queremos que contenga una colección de datos (capital - población_2025 - peso_sector_primario , peso_sector_secundario, peso_sector_terciario. Ahora ya no hay un ínico dato asociado a la clave, por lo que debemos expresar esta complejidad de otro modo. Y caben dos posibilidades:

  • Como lista provincia:[capital - población_2025 - peso_sector_primario , peso_sector_secundario]
  • o Como diccionarios anidados

Veamos el desarrollo de ambas posibilidades en un script.



# Valor como lista de valores

provincias = {
    "Asturias": ["Oviedo", 1004326, 4.1, 22.7, 73.2],
    "León": ["León", 442000, 8.5, 24.3, 67.2],
    "Cantabria": ["Santander", 593000, 3.8, 20.6, 75.6]
}


# Valor como diccionario anidado lista:valor

provincias = {
    "Asturias": {
        "capital": "Oviedo",
        "poblacion_2025": 1004326,
        "sector_primario": 4.1,
        "sector_secundario": 22.7,
        "sector_terciario": 73.2
    },

    "León": {
        "capital": "León",
        "poblacion_2025": 442000,
        "sector_primario": 8.5,
        "sector_secundario": 24.3,
        "sector_terciario": 67.2
    },

    "Cantabria": {
        "capital": "Santander",
        "poblacion_2025": 593000,
        "sector_primario": 3.8,
        "sector_secundario": 20.6,
        "sector_terciario": 75.6
    }
}


Aunque la primera formulación parece más sencilla de crear, en realidad su manejo resulta más complicado, por lo que es preferible recurrir a los diccionarios anidados por varias razones que no vamos a analizar aquí. Además, los diccionarios anidados constituyen la representación natural en Python de la estructura de un documento JSON. Esta correspondencia casi directa explica que los diccionarios anidados sean la estructura de datos preferida para trabajar con información procedente de diferentes fuentes, espcialmente con las que se expresan como archivos JSON.

Entre otras ventajas, esto hace posible que...



import json

with open("provincias.json", "w", encoding="utf-8") as archivo:
    json.dump(provincias, archivo, ensure_ascii=False, indent=4)


... nos permita almacenar los datos en un documento en formato JSON, el cual podremos visualizar desde bloc de notas.

{ "Asturias": { "capital": "Oviedo", "poblacion_2025": 1004326, "sector_primario": 4.1, "sector_secundario": 22.7, "sector_terciario": 73.2 }, "León": { "capital": "León", "poblacion_2025": 442000, "sector_primario": 8.5, "sector_secundario": 24.3, "sector_terciario": 67.2 }, "Cantabria": { "capital": "Santander", "poblacion_2025": 593000, "sector_primario": 3.8, "sector_secundario": 20.6, "sector_terciario": 75.6 } }

Como puedes comprobar, el archivo JSON reproduce prácticamente la misma estructura jerárquica que nuestro diccionario. Esta correspondencia es precisamente una de las razones por las que los diccionarios tienen un papel tan importante en Python cuando se trabaja con datos estructurados.

Para realizar el proceso inverso (cargar en Python un archivo JSON) deberemos ejecutar este script:



import json

with open("provincias.json", "r", encoding="utf-8") as archivo:
    provincias = json.load(archivo)


Llegados a este punto parece evidente que los diccionarios dan para algo más que una única entrada. Este es un buen momento para reconocerlo y dar por finalizada la actual. Continuará...

NOTAS
1 Esta sería una de las formas en que se podría expresar lo mismo como lista. Pero no es la única. Personalmente, para casos como ese, yo previero usar un procedimiento multilista (una por "campo") y trabajar después con procedimientos de acceso basados precisamente en las posiciones de los elementos de cada una de ellas.
2 Sin entrar en explicaciones que poco nos aportan aquí, una tabla hash es una estructura de datos que utiliza una función hash para transformar una clave arbitraria en un índice numérico que determina dónde se almacenará o buscará el dato asociado dentro de una tabla. Esto hace que se puedan localizar los datos igual de rápido con independencia del tamaño del diccionario (tiempo constante o O(1)), ya que evita la búsqueda secuencial.

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.

miércoles, 11 de febrero de 2026

LENGUAJES. Python

Usos prácticos de listas (I)

Colecciones de datos

Sabemos qué es una lista en Python y cómo podemos usarla, pero nos interesa conocer xómo y para qué podemos usarlas en la práctica. Una de estas utilidades será el tema de esta entrada, concretamente su uso como soporte para la recopilación de datos.

Dado que las listas permiten incluir diferentes tipos de datos, que los datos estén repetidos y que sea posoble modificarlos, estas colecciones son muy útiles para situaciones en las que tenemos que recopilar datos de distintos tipos y además desconocemos cuantos.

Un ejemplo de ello es cuando introducimos por teclado los resultados de la aplicación de una prueba grupal. En este caso tenemos datos de distinto tipo (string para nombres, datos en formato fecha, valores numéricos que se pueden repetir...) y no sabemos a priori de cuantos datos estamos hablando, ya que unos alumnos pueden haber resulto n ejercicios y otros n+/x.

En estas condiciones, podemos empezar declarando una lista vacía y después darle contenido desde teclado mediante un bucle. Una vez cumplimentado el "registro" procedemos a almacenarlo en un archivo a modo de base de datos.

Vamos a pedirle a Gemini que genere un script que cumpla estos objetivos y a analizar el modo en que plantea el uso de la lista.



import os

def registrar_evaluacion_continua():
    nombre_archivo = "base_datos_alumnos.txt"

    # Si el archivo no existe, creamos la cabecera para identificar columnas
    if not os.path.exists(nombre_archivo):
        with open(nombre_archivo, "w", encoding="utf-8") as f:
            headers = ["Nombre", "Apellidos", "Edad", "Curso"]
            items_headers = [f"Item_{i:02d}" for i in range(1, 31)]
            f.write(",".join(headers + items_headers + ["PD"]) + "\n")

    while True:
        print("\n" + "="*40)
        # 1. Recogida de identificación
        nombre = input("Nombre: ")
        apellidos = input("Apellidos: ")
        edad = input("Edad: ")
        curso = input("Curso (1º a 6º EP): ")

        # Iniciamos lista: [Nombre, Apellidos, Edad, Curso]
        sujeto_datos = [nombre, apellidos, edad, curso]

        aciertos = 0
        finalizado_por_tiempo = False

        print(f"\nIntroduzca resultados para {nombre} (1:Acierto, 0:Fallo, Enter:Fin tiempo)")

        # 2. Bucle de 30 ítems
        for i in range(1, 31):
            if not finalizado_por_tiempo:
                res = input(f"Ítem {i:02d}: ").strip()
                if res == "":
                    finalizado_por_tiempo = True
                    sujeto_datos.append("") # Celda vacía para el resto
                else:
                    sujeto_datos.append(res)
                    if res == "1":
                        aciertos += 1
            else:
                sujeto_datos.append("") # Relleno por no finalizado

        # 3. Añadir Puntuación Directa (PD) al final
        sujeto_datos.append(str(aciertos))

        # Feedback inmediato al usuario
        print(f"\n>>> REGISTRO FINALIZADO: {nombre} {apellidos}")
        print(f">>> Puntuación Directa obtenida: {aciertos}")

        # 4. Guardado persistente (Modo 'a' para no borrar sesiones previas)
        with open(nombre_archivo, "a", encoding="utf-8") as f:
            f.write(",".join(sujeto_datos) + "\n")

        # Opción de cerrar el script tras este alumno
        if input("\n¿Desea registrar a otro alumno ahora? (s/n): ").lower() != 's':
            break

    print(f"\n[SISTEMA] Datos sincronizados en '{nombre_archivo}'.")

if __name__ == "__main__":
    registrar_evaluacion_continua()


Este script funciona correctamente en local (trasladándolo al IDE) y se ajusta a lo solicitado al bot, aunque fue necesario ir puliendo la demanda hasta conseguirlo.

El problema, no obstante, es que no nos ayuda a entender y a aprender. Resuelve la tarea según unos parámetros que no quedan explicitados y que son corregidos cuando se le requiere, aunque tampoco se explica porqué no hizo antes lo que ahora es necesario, pero reincide en una propuesta que se aleja del modo "didáctico" en que queremos que lo haga para, ajustándose al nuestro actual nivel de complejidad en la demanda, facilite nuestro proceso de aprendizaje: su meta es fundamentalmente la eficiencia, resolver el problema.

Para lograr esto es necesario:

  • Subdivir la tarea paso a paso.
  • Pedir al chatbot que ejecute esa construcción secuencial del script.
  • Realizar sobre esas propuestas las modificaciones que consideremos pertinente.

De este modo es posible alcanzar nuestro objetivo, que es comprender el script antes que conseguir que funcione.Y todo ello en relación con nuestro objetivo inicial: el uso práctico de una lista como recurso para recopilar datos.

Desarrollaremos este procedimiento en entradas próximas. Dada su complejidad y el tiempo que requiere para su desarrollo, por el momento será suficiente con presentarlo.

martes, 3 de febrero de 2026

LENGUAJES. Python.

Listas. Manejo básico

Colecciones de datos

Las listas son las colecciones nativas de datos más simples y flexibles con las que cuenta Python lo que hace que sean usadas con mucha frecuencia.

Como dije en su presentación, una lista es una colección ordenada y mutables de datos. Además los datos pueden ser de diferentes tipos (caracteres, cadenas, enteros, reales, booleanos) y pueden estar repetidos. Esto, junto con las operaciones permitidas sobre ella, la convierten en un recurso muy flexible.

En este cuaderno te explico cómo trabajar con listas.

lunes, 2 de febrero de 2026

LENGUAJES. Python

Colecciones nativas

Colecciones de datos

Además de las variables, Python también permite trabajar con colecciones de datos. En el lenguaje-base están disponibles cuatro colecciones de datos, pero mediante bibliotecas complementarias podemos ampliar esta tipología básica. De momento, en esta entrada, nos limitaremos a presentar las colecciones nativas.

Empecemos por un breve resumen que nos ofrece Gemini a modo de síntesis.

Colecciones Ordenada Mutable Datos duplicados
Lista Sí Sí Sí
Tupla Sí No Sí
Conjunto (Set) No Sí No
Diccionario Sí Sí Claves - N
Valores - Sí

En esta entrada, que hace de presentación, vamos a explicar brevemente cada una de ellas. Desde este enlace puedes acceder a la información que ofrece la página oficial de Python y en entradas posteriores trataremos cada una de ellas de forma más detallada y por separado.

Listas (list). Son colecciones ordenadas y mutables que permiten incluir datos duplicados. Constituyen la mejor opción cuando necesitas trabajar con una secuencia de elementos ordenados y que se van a modificar frecuentemente. A nivel de sintaxis se caracterizan por estar delimitadas por corchetes mi_lista = [1, "hola", 3.14]

Tuplas (tuple) Son colecciones ordenadas e inmutables, lo que implica que no se pueden cambiar una vez creadas. Esto hace que sean rápidas en el manejo y más seguras para datos que no deben variar. A nivel de sintáxis se presentan delimitadas por paréntesis mi_tupla = (10, 20, 30)

Conjuntos (set) Son colecciones desordenadas y no indexadas. Su característica principal es que no permiten duplicados. Sí admiten el cambio de datos, pero sólo en el sentido de que es posible añadir o quitar elementos siempre que estos, a su vez, sean inmutable. Esto implica que podemos añadir números, cadenas o tuplas (que son inmutables), pero no listas (que son mutables). Son muy eficientes para operaciones matemáticas como uniones o intersecciones de conjuntos. Si convertimos una lista en un conjunto se eliminarán los elementos repetidos que podía contener la lista, lo que permite plantear procedimientos interesantes. Uno de ellos es la verificación de la pertenencia de un dato a un conjunto. Su sintaxis mi_set = {1, 2, 3, 3} es el uso de las llaves como delimitador.

Diccionarios (dict) Son colecciones de tipo par [clave-valor], extremadamente rápidos para buscar información conociendo la clave. Su uso es interesante para representar objetos (POO) y para trabajar con bases de datos en memoria o en mapeos. Se identifican sintácticamente por estar delimitados por llaves mi_dict = {"nombre": "Ana", "edad": 25} junto con la estructura clave-valor

viernes, 16 de mayo de 2025

LENGUAJES . Python

Listas y tuplas. Operadores

Colecciones de datos

Los operadores son, como ya sabemos, componentes de todo lenguaje de programación que permiten manipular los datos referenciados en las variables de diferentes maneras, según el tipo de dato y de operador de que se trate. También las colecciones de datos admiten la acción de operadores. Las listas y las tuplas comparten los mismos.

Los operadores disponibles en listas y tuplas son los siguientes:

  • : Permite obtener segmentos del contenido de ambas colecciones.
  • + y * Permiten realizar las operaciones sobre listas y tuplas. Veremos cuáles.
  • in / not in Permiten verificar la presencia / no presencia de determinado dato dentro del conjunto.
  • Los operadores relacionales <, <=, >, >= y !=, cuyas funciones describiremos en su momento.

Aunque figure en primer lugar, no trataré el operador : en esta entrada, dada la relación que mantiene con el acceso a los datos. Por este motivo desplazo su análisis a la entrada correspondiente.

Inicio este análisis con los operadores + y *, los cuales cumplen funciones específicas con listas y tuplas, diferentes de las que permiten ejecutar en variables.

  • El operador + permite la unión o concatenación de los elementos de varias listas. Como cabe suponer por lo que sabemos de los operaciones aritméticos, nos permiten realizar operaciones de suma y multiplicación con listas y tuplas.

lista1 = list([1,2,3,4])
lista2 = list([5,6,7,8])
print(lista1 + lista2) 

-> devuelve [1,2,3,4,5,6,7,8]

  • Este mismo operador, aplicado a tuplas funciona exactamente igual, aunque lógicamente devuelve una tupla (1,2,3,4,5,6,7,8)
  • La operación * tiene como efecto la repetición de la lista o la tupla sujeta a tal operación, siendo indiferente que el operador se sitúe antes o después de la lista o tupla. Así que print(3 * lista1) devuelve [1,2,3,4,1,2,3,4,1,2,3,4] y print(tupla1 * 2) devuelve (1,2,3,4,1,2,3,4). En ambos casos el número debe ser entero (ni 3.1 ni 2.4 se admiten como "multiplicadores")
  • Ambos operadores tienen el mismo efecto con independencia de la naturaleza de los elementos de la lista o de la tupla.
  • En ambos casos no está permitido el trabajo con listas y tuplas simultáneamente. No es posible ni lista + tupla, ni tupla * lista, ni ninguna otra combinación o permutación de elementos.
  • Los operadores in / not in permiten identificar si un dato forma parte o no de una lista o de una tupla. Ambos devuelven True o False según el resultado de la operación. Ejemplos para lista1 y tupla1 anteriores:


print(5 in lista1) #devuelve False
print(1 in lista1) #devuelve False

print(5 not in lista1) #devuelve True
print(1 not in lista1) #devuelve True

print(5 in tupla1) #devuelve False
print(1 in tupla1) #devuelve False

print(5 not in tupla1) #devuelve True
print(1 not in tupla1) #devuelve True


Los operadores relacionales permiten comparar dos listas entre sí, elemento a elemento y según criterios específicos en función del tipo de elemento. También devuelven True o False, según el resultado de la comparación. Veamos algunas comparaciones, tomando como referencia las listas y las tuplas creadas antes. Dada la multiplicidad de opciones me limitaré a unos pocos ejemplos, aunque los resultados se pueden hacer extensibles al resto de los operadores:



#Operadores relacionales con listas

print(lista1 == lista1) #Devuelve True

print(lista1 != lista2) #Devuelve True

#Operadores relacionales con tuplas

print(tupla1 == tupla1) #Devuelve True

print(tupla1 != tupla2) #Devuelve True

#Operadores relacionales con listas y tuplas

print('Operadores relacionales con listas y tuplas')

print(lista1 == tupla1) #Devuelve False por ser una lista y la otra tupla, aunque el contenido es el mismo