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

viernes, 15 de mayo de 2026

DATOS. Tratamiento de datos

Limpieza de datos (II)

Normalización de datos

Cuando en la tabla-formulario de un documento de texto cabe la posibilidad de que los datos se expresen de diferentes maneras, existe una elevada probabilidad de que así sea. Aunque predomine una determinada forma, vamos a decir que canónica, es altamente probable que también se empleen otras formas que no lo son tanto. Un buen ejemplo de ello lo tienes en la tabla que sigue.

Como puedes ver en este ejemplo ficticio, que no lo es en este aspecto de la variabilidad de expresión de la fecha, junto a la forma canónica (círculo naranja), nos encontramos con otras forma diferentes (por ejemplo, pero no sólo, la marcada con el círculo verde). Aunque para un análisis visual de los datos esto no supone ningún problema (salvo en casos muy concretos), para el tratamiento automatizado de los datos se convierte en un serio problema: esas fecha no-canónicas no son reconocidas como tales fechas. Para evitar la pérdida de datos que esto implica, es necesario convertir estos datos al formato fecha. Podemos hacerlo manualmente, con ayuda de las funciones de formato de Calc (o Excel), aunque esto nos llevará tiempo. También contamos con la opción de automatizar el reajuste de formato mediante un script Python como el que sigue.



# 0. Bibliotecas y módulos ---

import pandas as pd
import re
from datetime import datetime

# 1. Funciones ---

# 1.a Función secundaria. Transforma string en fechas ---

def normalizar_fecha(texto):

    if pd.isna(texto) or str(texto).strip() == "":
        return None
    
    dato = str(texto).lower().strip()
    
    meses_map = {
        'enero': 1, 'febrero': 2, 'marzo': 3, 'abril': 4, 'mayo': 5, 'junio': 6,
        'julio': 7, 'agosto': 8, 'septiembre': 9, 'octubre': 10, 'noviembre': 11, 'diciembre': 12
    }

    try:
        return pd.to_datetime(dato, dayfirst=True).date()     # Intento 1: Formato numérico completo (ej. 2/02/2026)
    except:
        pass
    anio_detectado = re.search(r'(\d{4})', dato)               # Intento 2: Formato parcial (ej. enero 2014)
    if anio_detectado:
        anio = int(anio_detectado.group(1))
        for mes_nombre, mes_numero in meses_map.items():
            if mes_nombre in dato:
                return datetime(anio, mes_numero, 1).date()      # Retornamos siempre el día 1 del mes encontrado
    return None                                                  # Si llega aquí, no se pudo convertir y devolvemos None

# 1.b Función principal ---

def procesar_limpieza(ruta_entrada, columna_objetivo, ruta_salida):
    
    try:
        print(f"Cargando archivo: {ruta_entrada}...")
        df = pd.read_csv(ruta_entrada, sep=';', encoding='utf-8')
        df['fecha_corregida'] = df[columna_objetivo].apply(normalizar_fecha)                # Aplicamos la normalización (funcion 1a)
        df['fecha_corregida'] = pd.to_datetime(df['fecha_corregida'], errors='coerce')      # Convertimos a datetime Pandas (AAAA-MM-DD)
        df.to_csv(ruta_salida, sep=';', index=False, encoding='utf-8')                      # Exportamos el resultado (csv)
        print(f"Éxito: Archivo '{ruta_salida}' generado con fechas estandarizadas.")
        
    except Exception as e:
        print(f"Error durante el proceso de código: {e}")

# 2. Script principal (Llamada a la función principal) ---

if __name__ == "__main__":

    archivo_origen = r''            # Aquí la ruta del archivo csv de trabajo
    columna_fechas = 'fecha'        # Aquí el nombre de la columna a procesar
    archivo_destino = r''      		# Aquí el nombre del csv resultante
    
    procesar_limpieza(archivo_origen, columna_fechas, archivo_destino) # Llamada a la función


La estructura del script es relativamente simple: dos funciones y un script. El script llama a la función principal pasando parámetros (procesar_limpieza(archivo_origen, columna_fechas, archivo_destino)) que el profesional asigna a variables (vg. archivo_origen = r'') y la función principal hace uso de la secundaria para tratar el contenido textual del campo y devolver un formato fecha del módulo datetime en formato Pandas (AAA-MM-DD).

La función principal es sumamente importante: (1) Carga el csv de trabajo, (2) Llama a la función secundaria para normalizar el formato (3) convierte el dato devuelto por esa función al formato datetime de Pandas y (4) genera el scv de salida.

  1. df = pd.read_csv(ruta_entrada, sep=';', encoding='utf-8')
  2. df['fecha_corregida'] = df[columna_objetivo].apply(normalizar_fecha)
  3. df['fecha_corregida'] = pd.to_datetime(df['fecha_corregida'], errors='coerce')
  4. df.to_csv(ruta_salida, sep=';', index=False, encoding='utf-8')

Pero la función secundaria no es menos interesante, como veremos a continuación. Recordemos:



def normalizar_fecha(texto):

    if pd.isna(texto) or str(texto).strip() == "":
        return None
    
    dato = str(texto).lower().strip()
    
    meses_map = {
        'enero': 1, 'febrero': 2, 'marzo': 3, 'abril': 4, 'mayo': 5, 'junio': 6,
        'julio': 7, 'agosto': 8, 'septiembre': 9, 'octubre': 10, 'noviembre': 11, 'diciembre': 12
    }

    try:
        return pd.to_datetime(dato, dayfirst=True).date()     # Intento 1: Formato numérico completo (ej. 2/02/2026)
    except:
        pass
    anio_detectado = re.search(r'(\d{4})', dato)               # Intento 2: Formato parcial (ej. enero 2014)
    if anio_detectado:
        anio = int(anio_detectado.group(1))
        for mes_nombre, mes_numero in meses_map.items():
            if mes_nombre in dato:
                return datetime(anio, mes_numero, 1).date()      # Retornamos siempre el día 1 del mes encontrado
    return None                                                  # Si llega aquí, no se pudo convertir y devolvemos None



En esta función podemos diferenciar varias partes:
  • Primero un procedimiento de control para cuando la variable texto está vacía o no es válida
  • A continuación la variable texto se convierte a str y se normaliza a minúscula, pasando esta doble conversión a la varible dato
  • Después construímos una diccionario de meses:numerales meses_map para capturar el contenido del campo que procesamos y que a esta función hemos pasado como parámetro def normalizar_fecha(texto)

Lo que viene a continuación es el núcleo central de la función, que se desarrolla dentro de una estructura de control de excepciones try...except, en la que la parte try controla la presencia en dato de la formulación de la fecha según el formato dd/mm/aaaa y la transformar una entrada un objeto de fecha puro Año-Mes-Día de la librería Pandas. La parte except controla la posible comisión de errores

Si la primera solución no resolvió el procesamiento de la variable, la segunda utiliza la expresión regular re.search(r'(\d{4})', dato) para localizar y obtener el dato año (4 dígitos dentro del texto) y si lo encuentra recorre el listado de meses para ver si el término nombre del mes está escrito en dato. Si lo está, sobre el dato año y la conversión numérica del dato mes se construye una fecha, usando 1 como dato día.

Con este script hemos normalizado reformulandolo el campo fecha para que sea posible trabajar con esta variable en la fase de análisis de datos. El resultado es una nueva columna o campo (fecha_corregida). Cierto que que no todos los datos se han podido modificar según lo esperado debido a que el dato original no se ajusta a las formas esperadas. esto puede dar lugar a una nueva pérdida de datos, aunque al ser una circunstancia controlada y de relativa poca importancia, también podemos optar por realizar una última revisión "manual" a fin de evitar una pérdida de datos innecesaria y no deseada. Llegados a este punto, no es una mala solución.

sábado, 9 de mayo de 2026

DATOS. Tratamiento de datos

Limpieza de datos (I)

Datos faltantes

Es muy frecuente que en una colección de datos de cierta entidad se observen campos en blanco o con una marca sustituta (ver tabla abajo), a consecuencia de diversos motivos, incluídos los errores de recogida de datos y la simple ausencia de éstos por no producirse determinada respuesta. Lo que en realidad importa para su tratamiento posterior es que las ausencias sean razonablemente escasas; en caso contrario, la calidad del conjunto de datos se ve muy seriamente comprometida.

Entorno Elemento
Python puro None
Data Science (Pandas/NumPy) NaN
Base de datos (SQL) NULL
Lenguaje R NA

Las opciones disponibles para hacer frente a esta realidad son varias, desde la eliminación del registro hasta el relleno del campo con determinado valor, resultante de algún tipo de cálculo. Para conocer cuales son esas alternativas, además de consultar a la IA, que lo que hace es resumir el conjunto de posibilidades disponibles, sustituyendo así a una simple búsqueda web, también podemos consultar alguna página específicamente pensada para tratar este tema. Yo aquí te ofrezco un ejemplo de la segunda opción; la primera corre de tu cuenta.

No es por comodidad, pero en este caso voy a optar por la forma más simple de resolver el problema: eliminando el registro. No es precisamente la mejor solución, especialmente cuando no disponemos de muchos registros, pero sí la más adecuada para un caso como el que nos ocupa en el que no hay la pretensión de representatividad estadística ni interés por crear un procedimiento de (aprendizaje automático (AA). Además, en este caso, en la mayoría de los registros faltan todos los datos que pueden faltar, dado que son resultado de una opción de tratamiento del documento que ahora no me intersa comentar; sirva con decir que las tablas simplemente no están cumplimentadas, por lo que es hasta coherente eliminar estos registros. Puede que no lo sea tanto en los casos en que sólo falta la fecha, pero son muy escasos y el riesgo real de pérdida de información es mínimo.



import pandas as pd
import os
import numpy as np

# --- Función ---

def limpiar_tabla_especifica(ruta_absoluta):
    if not os.path.exists(ruta_absoluta):
        print(f"Error: No se localiza el archivo en {ruta_absoluta}")
        return

    try:
        df = pd.read_csv(ruta_absoluta, sep=';', engine='python')   # 1. Carga los datos con el separador identificado (sep=';')
        total_inicial = len(df)                                     # Conteo inicial para calcular los registros afectados

        df = df.replace(r'^\s*$', np.nan, regex=True)               # 2. Convertir espacios vacíos (" ") en nulos reales (NaN)

        columnas_a_validar = ['fecha', 'SEO', 'OE']                 # 3. Eliminar registros si falta dato en fecha-SEO-OE
        df_limpio = df.dropna(subset=columnas_a_validar)

        total_final = len(df_limpio)                                # 4. Cálcular los registros eliminados
        eliminados = total_inicial - total_final

        ruta_directorio = os.path.dirname(ruta_absoluta)            # 5. Exportar el resultado a csv
        nombre_salida = "lista_t1_sin_nulos.csv"
        ruta_salida = os.path.join(ruta_directorio, nombre_salida)

        df_limpio.to_csv(ruta_salida, sep=';', index=False)

        print("-" * 30)                                              # 6. Informe a mostrar por consola
        print("INFORME DE PROCESAMIENTO")
        print("-" * 30)
        print(f"Registros analizados: {total_inicial}")
        print(f"Registros eliminados (faltaba fecha, SEO o OE): {eliminados}")
        print(f"Registros válidos restantes: {total_final}")
        print(f"Archivo generado: {ruta_salida}")

    except Exception as e:
        print(f"Se produjo un error durante la ejecución: {e}")

# --- Llamada a la función ---

ruta_csv = r"" 														# Aqui la ruta de tu archivo csv
limpiar_tabla_especifica(ruta_csv)                                  # Llamada a la función


Mediante este script eliminamos los registros que carecen de datos en las columnas (variables) de interés columnas_a_validar = ['fecha', 'SEO', 'OE'], cosa que sucede gracias al uso de la función df.dropna() de Pandas (df_limpio = df.dropna(subset=columnas_a_validar)). Al especificar el identificador de las columnas hacemos que el script sea dependiente de la tabla, a la vez que nos permite identificar lo que deberemos cambiar si cambiamos de tabla.

Y hablando de especificidades, debo decir que para que el script funcione ha sido necesario especificar el separador de campos de la tabla (df = pd.read_csv(ruta_absoluta, sep=';', engine='python')), ya que el csv-base utiliza ; en vez del esperado (,) por defecto por Pandas.

El resultado tiene una doble expresión: archivo como csv (comentario 5 del script) se muestra una síntesis por consola (comentario 6). No proporciono ninguna prueba por innecesaria; es suficiente con que pienses en la última tabla de esta entrada sin el registro INF_003.dot.

martes, 21 de octubre de 2025

MAV. Python.

Pillow

Matriz a partir de imagen


En la [entrada anterior] creamos una imagen a partir de los datos de una matriz. Ahora haremos el proceso inverso: obtener los datos numéricos que definen una imagen dada, lo que equivale a generar una matriz a partir de esos datos.

 

Lo interesante de lo que estamos haciendo es que podemos trabajar con datos numéricos para crear imágenes y viceversa: obtener los datos numéricos de los que se compone una imagen digitalizada. A partir de esto podemos realizar manipulaciones generales o específicas del color y, a partir de éste, también del dibujo y de las formas que conforman la imagen.

En el script que sigue obtenemos la matriz de datos de una imagen dada y, partiendo de ésta, volvemos a construir la imagen, en este caso, sin modificación alguna respecto a la original.

from PIL import Image
import numpy as np

#Cargar imagen

img_dir = 'img/Paisaje.jpg'
img = Image.open(img_dir)

#Visualizamos la imagen cargada

img.show()

#Convertimos la imagen en matriz

array_img = np.array(img)

#Visualizamos la matriz

print(array_img)

#Convertimos de nuevo la matriz en imagen

img_array = Image.fromarray(array_img)

#Visualizamos la imagen creada

img_array.show()

Para ello ha sido suficiente con importar ambas bibliotecas (Pillow y NumPy) y usar dos funciones específicas, una asociada a Numpy (array_img = np.array(img)) y otra a Pillow (img_array = Image.fromarray(array_img)). La primera para convertir la imagen en una matriz y la segunda para invertir el procedimiento.

A partir de aquí se nos abren todas las opciones que derivan del trabajo con matrices numéricas. Todo un mundo.


MAV. Python.


Pilow

Imagen a partir de una matriz


Cuando creamos por primera vez una imagen mediante Pilow lo primero que pensé fue que poco interés tenía la cosa; cuando sobre esa base unimos dos imágenes para componer una tercera, ya me pareció más interesante y a la vista del interés que tiene para la descomposición de la imagen en colores parece que el tema va ganando en interés. Añado ahora una razón más para valorar positivamente la creación de imágenes en Pilow.


Me refiero a la construcción de una imagen (sencilla, eso sí) no mediante la función ya conocida (img_base = Image.new("L",(d_x,d_y),"white")), sino mediante una nueva función que ilustra la relación entre Pilow y NumPy: la función  fromarray(array_base). Pero no adelantemos acontecimientos.

from PIL import Image
import numpy as np

#Crear array  de base
array_base = np.zeros([550,750,3], dtype = np.uint8)

#Agregar colores
array_base[:,0:300] = [255,128,0]
array_base[:,300:] = [0,0,255]

#Crear la imagen a partir del arreglo
img_array = Image.fromarray(array_base)

#Mostrar la imagen creada
img_array.show()

Mediante este script he creado una sencilla imagen a partir de datos numéricos organizados en una matriz tridimensional (array_base = np.zeros([550,750,3], dtype = np.uint8)) mediante la función np.zeros(), que recibe como primer parámetro una tupla con tres valores: ancho, alto y 3 canales de color como tercer dimensión.

Los dos primeros, además de definir el tamaño de la matriz, también nos sirven como referencias necesarias para concretar las posiciones de las diferentes configuraciones de color que compondrán la imagen final...

vg. array_base[:,0:300] = [255,128,0]

... atribuye al primer elemento la posición 0-0 a 0:300 (ejes x-y) y como color asignado la tupla [R.G.B] [255,128,0].

Posteriormente se construye la imagen a partir de esos datos mediante el método fromarray() que recibe como parámetro la variable imagen (img_array = Image.fromarray(array_base)).

Si en lugar de una imagen RGB quisiéramos crear una imagen en escala de grises deberíamos usar un script como el siguiente:

from PIL import Image
import numpy as np

#Crear array  de base
array_base = np.zeros([550,750], dtype = np.uint8)

#Agregar grises
array_base[:,0:300] = [50]
array_base[:,300:] = [235]

#Crear la imagen a partir del arreglo
img_array = Image.fromarray(array_base)

#Mostrar la imagen
img_array.show()

Puedes apreciar el cambio en la construcción del array (ahora array_base = np.zeros([550,750], dtype = np.uint8)) y en el procedimiento para agregar el color (vg array_base[:,0:300] = [50]). El resto es igual.

MAV. Python.


Pillow

Filtros a partir de la separación de colores


Hablando de filtros, toda imagen se puede entender como una mas o menos compleja combinación de los tres colores básicos (rojo-verde-azul), cuando de una imagen en color se trata, y de la menor o mayor intensidad de la presencia de cada uno de ellos, siendo su valor mínimo 0 y máximo 255. El blanco resulta de la máxima intensidad de los tres (255,255,255) y el negro de la mínima (0,0,0). A partir de aquí, podemos trabajar con una imagen como con una matriz de datos numéricos, lo que abre interesantes posibilidades de trabajo. La primera de ellas generar filtros basados en la manipulación de las intensidades de los colores básicos de la imagen.


La consecuencia de lo anterior, lo que realmente nos interesa, es el trabajo con la imagen como matriz de datos, siendo la cuestión de su uso como filtro más bien secundaria. Se trata, en realidad, de estudiar la interesante combinación del trabajo con Pillow y con NumPy, como recurso para facilitar el manejo de imágenes mediante código. Ahora nos centraremos en uno de los procedimientos básicos y seguiremos trabajando en esta dirección en las entradas que siguen.

Dicho esto empezaremos por importar las dos bibliotecas

from PIL import Image
import numpy as np

... y cargando una imagen con la que trabajar

img_dir = 'img/formas.jpg'
img = Image.open(img_dir)

Obtenemos sus dimensiones (ancho y alto)...

d_x, d_y = img.size

... para poder crear otra imagen de esas mismas dimensiones, la cual nos servirá de base para generar los filtros de color (observa que creamos la imagen de tipo escala de grises ("L") y le damos un fondo, en este caso blanco ("white")

img_base = Image.new("L",(d_x,d_y),"white")

Lo más interesante del procedimiento es la obtención de los datos de los tres canales de color de la imagen y su conversión en una matriz mediante el método splitz(), así de sencillo:

col_R, col_G , col_B = img.split()

A partir de aquí, podremos aplicar el filtro de color (R, G, B) sobre la imagen original combinándola con la imagen de base que creamos en blanco (también podría ser en negro), siendo suficiente con introducir el canal de color deseado y mantener el original de la imagen de base ocupando el lugar de los otros dos, v.g. para el rojo...

img_R = Image.merge('RGB',(col_R, img_base, img_base))

Si utilizamos los tres canales, el resultado es la imagen original

img_R = Image.merge('RGB',(col_R,col_G,col_B))



lunes, 12 de mayo de 2025

Datos. Python

Bibliotecas para el análisis de datos (II). Numpy


Mucho más compleja pero también mucho más potente que Stadistics, Numpy es una biblioteca fundamental para el trabajo en el ámbito científico y computacional. En lo tocante a los SEO, Numpy sobrepasa con mucho nuestras necesidades, pero dada su importancia en el ámbito del tratamiento de datos y por ser base para el manejo de otros paquetes de interés, es necesario un mínimo conocimiento de Numpy.


NumPy (Numeric Python) es el paquete que genera un objeto de matriz multidimensional, objetos derivados y una gran variedad de funciones para operaciones matemáticas, lógicas, ordenamiento, selección, álgebra lineal básica, operaciones estadísticas básicas, simulación aleatoria y otras.

Este trabajo con matrices sustituye al que realizamos en Python con listas. La flexibilidad de las listas Python tiene como contrapartida una pérdida de eficiencia en el uso de la memoria, siendo este es el punto fuerte de Numpy.

Conforme vayamos necesitando incorporar funciones de esta biblioteca, las iremos viendo en las secciones que corresponda, fundamentalmente en [Datos]. De momento dejo aquí [enlace a la página oficial del proyecto] para cualquier consulta que pueda ser necesaria. También dispones de una [guía actualizada] aunque por desgracia únicamente en inglés.

Otro material de interés es la guía de [DataScientest], que no es mucho, pero a falta de una guía en español, al menos sirve para hacernos una idea de esta biblioteca. De todas formas, aunque no llegue al nivel de la guía oficial, ni trate exclusivamente sobre NumPy, para empezar [Hidalgo Romero] es un documento imprescindible.

Para temas relacionados con la estadística me parece interesante [esta página].