Saltar navegación

Machine Learning con Scikit-learn

Librería Scikit-learn
Copilot. Scikit-learn (CC BY-SA)

Scikit-learn é unha biblioteca de Python utilizada principalmente para a aprendizaxe automática (machine learning). É unha ferramenta poderosa que permite construír modelos de clasificación, regresión, agrupamento (clustering), entre outras tarefas.

Ademais, proporciona ferramentas para preprocesar datos e avaliar os modelos, o que a converte nunha elección ideal para aprender  algoritmos de IA de forma sinxela e eficiente.

Actividades de aprendizaxe

Actividade 1: Clasificación

Obxectivo: Aprender como funciona un modelo de clasificación de Machine Learning. 
Instrución: Usar Scikit-learn para clasificar mazás e laranxas.

Unha instancia do modelo de árbore de decisión é simplemente un obxecto que creamos a partir da clase DecisionTreeClassifier en Scikit-learn.

É coma se tomásemos o "molde"  que representa como funciona unha árbore de decisión (que está definido en Scikit-learn) e construísemos nosa propia versión personalizada para resolver o noso problema específico.

Estamos a crear unha copia da árbore de decisión que podemos adestrar, usar para predicións e adaptar aos nosos datos.

Esta copia ten todos os métodos e propiedades necesarios para levar a cabo tarefas como clasificación ou predición.

A instancia adéstrase con datos (como pesos de froitas) para aprender un patrón. Despois, usamos esa mesma instancia para facer predicións baseadas nos novos datos que lle deamos.

Código Python: Clasificando e incorporando datos

# Importar as bibliotecas necesarias
import os
from PIL import Image
import numpy as np
import zipfile
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from google.colab import files



# Subir o arquivo comprimido (zip)
print("Por favor, carga o arquivo ZIP coas imaxes:")
uploaded = files.upload()  # Subir un arquivo ZIP

# Descomprimir o arquivo
for filename in uploaded.keys():
    with zipfile.ZipFile(filename, 'r') as zip_ref:
        zip_ref.extractall("dataset")  # Extraer todo nunha carpeta chamada 'dataset'
print("Carpeta descomprimida. Os datos están listos para procesar.")


# Función para cargar imaxes e extraer características
def cargar_e_extraer_caracteristicas(ruta_dataset):
    caracteristicas = []
    etiquetas = []
    for etiqueta, clase in enumerate(["apple", "orange"]):  # 0=apple, 1=orange
        ruta_clase = os.path.join(ruta_dataset, clase)
        print(f"Procesando a carpeta: {ruta_clase}")
        for nome_imaxe in os.listdir(ruta_clase):
            ruta_imaxe = os.path.join(ruta_clase, nome_imaxe)
            #print(f"Procesando a imaxe: {ruta_imaxe}")
            # Abrir a imaxe e redimensionala a 64x64 píxeles
            imaxe = Image.open(ruta_imaxe).resize((64, 64))
            imaxe_array = np.array(imaxe).flatten()  # Convertir a array plano (1D)
            caracteristicas.append(imaxe_array)
            etiquetas.append(etiqueta)  # Engadir etiqueta (0 ou 1)
    return np.array(caracteristicas), np.array(etiquetas)

# Ruta ao dataset descomprimido
ruta_dataset = "dataset/fruit-dataset"

# Cargar e procesar as imaxes
X, y = cargar_e_extraer_caracteristicas(ruta_dataset)

# Dividir os datos en conxuntos de adestramento e probas
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Crear e adestrar o modelo
modelo = RandomForestClassifier(random_state=42)
modelo.fit(X_train, y_train)

# Avaliar o modelo
predicións = modelo.predict(X_test)
precisión = accuracy_score(y_test, predicións)
print(f"Precisión do modelo: {precisión:.2f}")

# Bucle para subir e clasificar imaxes ata que o usuario decida rematar
def clasificar_imaxes_interactivas():
    while True:
        print("Por favor, carga unha imaxe desde o teu ordenador para clasificar:")
        arquivo_imaxe = files.upload()  # Subir imaxe
        for nome_imaxe in arquivo_imaxe.keys():
            imaxe = Image.open(nome_imaxe).resize((64, 64))
            imaxe_array = np.array(imaxe).flatten().reshape(1, -1)
            resultado = modelo.predict(imaxe_array)
            if resultado == 0:
                print("A imaxe cargada clasifícase como: Mazá (Apple)")
            else:
                print("A imaxe cargada clasifícase como: Laranxa (Orange)")
        # Preguntar ao usuario se quere continuar
        continuar = input("¿Queres clasificar outra imaxe? (si/non): ").strip().lower()
        if continuar != "si":
            print("Programa finalizado. Ata logo!")
            break

# Iniciar o proceso de clasificación interactiva
clasificar_imaxes_interactivas()
  

Código Python: Clasificando imaxes de laranxas e mazás

# Importar as bibliotecas necesarias
import os
from PIL import Image
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from google.colab import files

# Función para cargar imaxes e extraer características
def cargar_e_extraer_caracteristicas(ruta_dataset):
    caracteristicas = []
    etiquetas = []
    for etiqueta, clase in enumerate(["apple", "orange"]):  # 0=apple, 1=orange
        ruta_clase = os.path.join(ruta_dataset, clase)
        print(f"Procesando a carpeta: {ruta_clase}")
        for nome_imaxe in os.listdir(ruta_clase):
            ruta_imaxe = os.path.join(ruta_clase, nome_imaxe)
            #print(f"Procesando a imaxe: {ruta_imaxe}")
            # Abrir a imaxe e redimensionala a 64x64 píxeles
            imaxe = Image.open(ruta_imaxe).resize((64, 64))
            imaxe_array = np.array(imaxe).flatten()  # Convertir a array plano (1D)
            caracteristicas.append(imaxe_array)
            etiquetas.append(etiqueta)  # Engadir etiqueta (0 ou 1)
    return np.array(caracteristicas), np.array(etiquetas)

# Ruta ao dataset descomprimido
ruta_dataset = "dataset/fruit-dataset"

# Cargar e procesar as imaxes
X, y = cargar_e_extraer_caracteristicas(ruta_dataset)

# Dividir os datos en conxuntos de adestramento e probas
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Crear e adestrar o modelo
modelo = RandomForestClassifier(random_state=42)
modelo.fit(X_train, y_train)

# Avaliar o modelo
predicións = modelo.predict(X_test)
precisión = accuracy_score(y_test, predicións)
print(f"Precisión do modelo: {precisión:.2f}")

# Bucle para subir e clasificar imaxes ata que o usuario decida rematar
def clasificar_imaxes_interactivas():
    while True:
        print("Por favor, carga unha imaxe desde o teu ordenador para clasificar:")
        arquivo_imaxe = files.upload()  # Subir imaxe
        for nome_imaxe in arquivo_imaxe.keys():
            imaxe = Image.open(nome_imaxe).resize((64, 64))
            imaxe_array = np.array(imaxe).flatten().reshape(1, -1)
            resultado = modelo.predict(imaxe_array)
            if resultado == 0:
                print("A imaxe cargada clasifícase como: Mazá (Apple)")
            else:
                print("A imaxe cargada clasifícase como: Laranxa (Orange)")
        # Preguntar ao usuario se quere continuar
        continuar = input("¿Queres clasificar outra imaxe? (si/non): ").strip().lower()
        if continuar != "si":
            print("Programa finalizado. Ata logo!")
            break

# Iniciar o proceso de clasificación interactiva
clasificar_imaxes_interactivas()    

Actividade 2: Regresión

◦ Obxectivo: Entender como funciona a regresión lineal usando Scikit-learn.
◦ Instrución: Usar Scikit-learn para prever valores numéricos baseándose en datos de exemplo.

Estes valores son os resultados do modelo de regresión lineal, e cada un representa aspectos clave da relación entre as variables (tamaño da casa e número de habitacións) e o prezo predito.

Coeficientes do modelo: [1719.04761905, 14190.47619048]

Estes coeficientes indican canto cambia o prezo da casa por cada unidade adicional de cada variable:

1719.04761905: Se o tamaño da casa aumenta en 1 m², o prezo da casa aumenta aproximadamente en 1719,05 €.

14190.47619048: Se o número de habitacións aumenta en 1, o prezo da casa aumenta aproximadamente en 14190,48 €.

Son valores que o modelo aprende a partir dos datos proporcionados para captar como cada factor inflúe no prezo.

Intersección (bias): -809.5238095236709

Este é o punto onde a liña de regresión "corta" o eixo vertical (prezos) cando todas as variables son igual a 0.

En termos simples: Se unha casa tivese tamaño igual a 0 m² e número de habitacións igual a 0 (un caso teórico), o modelo prediría un prezo inicial de -809,52 €.

Este valor é unha constante base que o modelo usa para axustar os cálculos e reflicte a súa calibración inicial.

Actividade 3: Segmentación e manexo de arquivos de datos

Obxectivo: Comprender o concepto de clustering ou agrupamento co algoritmo Kmeans e aprender a usar dataset  libres da web en proxectos.
Instrución: Predicir os hábitos de compra a partir dun clustering de datos cun arquivo .csv. e aplicalos a unha ficticia multinacional de roupa e complementos.

    Que é un cluster?

    Un cluster é un grupo de elementos semellantes que se agrupan xuntos segundo as súas características.

    O algoritmo busca patróns nos datos para formar os grupos, sen necesidade de coñecer previamente que etiquetas teñen (aprendizaxe non supervisada).

    O clustering é unha técnica que nos axuda a agrupar elementos semellantes sen coñecer previamente as etiquetas ou categorías.

    Esta técnica de aprendizaxe non supervisada, significa que non lle dicimos ao ordenador que tipo de elemento está a ver( como fixemos cos laranxas/mazás ou can/gato), pero pedímoslle que os agrupe por características comúns.

    Código Python: Subir o arquivo e visualizar os primeiros datos

    # Importar as librerías
    import pandas as pd
    import matplotlib.pyplot as plt
    from google.colab import files  # Biblioteca para subir arquivos
    
    # Subir o arquivo desde o ordenador
    uploaded = files.upload()
    
    # Cargar os datos do arquivo CSV (o nome do arquivo será a chave do dicionario 'uploaded')
    datos = pd.read_csv(list(uploaded.keys())[0])
    
    # Mostrar as primeiras filas para entender os datos
    print(datos.head())  
        

    Código Python: Preprocesado de datos

    # Importar librerías necesarias
    import pandas as pd
    
    # Función para analizar, limpar, preprocesar e modificar datos de forma educativa
    def analizar_limpar_e_modificar_datos():
        print("Benvido/a! Este programa axudarache a analizar, limpar e preparar os teus datos para proxectos de IA.")
        print("Explicaremos cada paso para que comprendas por que é necesario realizar este proceso.")
    
        # Paso 1: Subir o arquivo CSV
        from google.colab import files
        print("\nPor favor, sube o arquivo CSV cos datos que desexas analizar e limpar:")
        uploaded = files.upload()
        nome_arquivo = list(uploaded.keys())[0]
        datos = pd.read_csv(nome_arquivo)
        print(f"\nArquivo '{nome_arquivo}' cargado correctamente!")
        print("\nVisualizando as primeiras filas dos datos:")
        print(datos.head())
    
        # Paso 2: Seleccionar columnas para analizar e limpar
        print("\nColumnas dispoñibles no arquivo:")
        print(list(datos.columns))
        print("\nSelecciona as columnas que queres analizar e limpar. Introduce os nomes separados por comas.")
        columnas_seleccionadas = input("Introduce os nomes das columnas (exemplo: 'Column1,Column2'): ").split(',')
    
        # Limpar espazos ao redor dos nomes das columnas
        columnas_seleccionadas = [col.strip() for col in columnas_seleccionadas]
        print(f"\nSeleccionaches estas columnas: {columnas_seleccionadas}")
    
        # Comprobar que as columnas existen
        for columna in columnas_seleccionadas:
            if columna not in datos.columns:
                print(f"\nErro: A columna '{columna}' non existe no arquivo. Revise os nomes.")
                return
    
        # Paso 3: Cambiar nomes das columnas seleccionadas
        print("\nQueres cambiar o nome dalgunha das columnas seleccionadas?")
        print("1 - Si, quero cambiar os nomes.")
        print("2 - Non, quero manter os nomes actuais.")
        cambiar_nomes = input("Introduce o número correspondente á túa elección: ")
    
        if cambiar_nomes == "1":
            novos_nomes = {}
            for columna in columnas_seleccionadas:
                novo_nome = input(f"Introduce o novo nome para a columna '{columna}': ")
                novos_nomes[columna] = novo_nome
            datos.rename(columns=novos_nomes, inplace=True)
            columnas_seleccionadas = [novos_nomes[col] for col in columnas_seleccionadas]
            print(f"\nOs nomes das columnas foron actualizados: {novos_nomes}")
        else:
            print("\nManteranse os nomes orixinais das columnas seleccionadas.")
    
        # Crear un DataFrame para traballar con só as columnas seleccionadas
        datos_seleccionados = datos[columnas_seleccionadas].copy()
    
        # Paso 4: Analizar e modificar valores en cada columna
        print("\nAgora imos analizar os valores nas columnas seleccionadas.")
        filas_a_eliminar = []
    
        for columna in columnas_seleccionadas:
            print(f"\nAnalizando a columna: '{columna}'")
            tipos_detectados = datos_seleccionados[columna].apply(type).unique()
            print(f"- Tipos de datos detectados: {[tipo.__name__ for tipo in tipos_detectados]}")
    
            # Preguntar ao usuario que tipo de dato debería ter a columna
            print("\nQue tipo de dato debería ter esta columna?")
            print("1 - bool (True/False)")
            print("2 - int (Números enteiros)")
            print("3 - float (Números decimais)")
            print("4 - str (Texto)")
            tipo_dato_esperado = input("Introduce o número correspondente ao tipo de dato esperado: ")
    
            tipo_actual = None
            if pd.api.types.is_bool_dtype(datos_seleccionados[columna]):
                tipo_actual = "1"
            elif pd.api.types.is_integer_dtype(datos_seleccionados[columna]):
                tipo_actual = "2"
            elif pd.api.types.is_float_dtype(datos_seleccionados[columna]):
                tipo_actual = "3"
            elif pd.api.types.is_string_dtype(datos_seleccionados[columna]):
                tipo_actual = "4"
    
            if tipo_dato_esperado == tipo_actual:
                print(f"- A columna '{columna}' xa está no formato esperado. Non require cambios.")
            else:
                # Convertir a columna ao tipo especificado
                try:
                    if tipo_dato_esperado == "1":
                        datos_seleccionados[columna] = datos_seleccionados[columna].astype(bool)
                    elif tipo_dato_esperado == "2":
                        datos_seleccionados[columna] = datos_seleccionados[columna].astype(int)
                    elif tipo_dato_esperado == "3":
                        datos_seleccionados[columna] = datos_seleccionados[columna].astype(float)
                    elif tipo_dato_esperado == "4":
                        datos_seleccionados[columna] = datos_seleccionados[columna].astype(str)
                    print(f"- A columna '{columna}' foi convertida ao formato esperado.")
                except ValueError as e:
                    print(f"\nErro ao converter os datos: {e}. Revisando os valores...")
    
            # Preguntar ao usuario se quere cambiar os valores das categorías detectadas
            categorias = datos_seleccionados[columna].unique()
            print(f"- A columna '{columna}' ten {len(categorias)} categorías: {categorias}")
    
            print("\nQueres cambiar os valores das categorías?")
            print("1 - Si, quero cambiar os nomes/valores das categorías.")
            print("2 - Non, quero manter os valores actuais.")
            cambio_categoria = input("Introduce o número da túa elección: ")
    
            if cambio_categoria == "1":
                conversion = {}
                for categoria in categorias:
                    print(f"Para a categoría '{categoria}', introduce o valor co que a queres substituír:")
                    novo_valor = input("Novo valor: ")
                    conversion[categoria] = novo_valor
                print(f"Mapa de conversión para '{columna}': {conversion}")
                datos_seleccionados[columna] = datos_seleccionados[columna].map(conversion)
    
            # Validación de valores inexistentes ou problemáticos
            print("- Comprobando valores inexistentes ou problemáticos na columna...")
            for i, valor in enumerate(datos_seleccionados[columna]):
                if pd.isnull(valor):  # Valor inexistente ou NaN
                    print(f"Fila {i+1}: O valor está baleiro ou inexistente.")
                    decision = input("Queres eliminar esta fila? (Si/Non): ").strip().lower()
                    if decision == "si":
                        filas_a_eliminar.append(i)
    
        # Eliminar filas erróneas segundo decisións do usuario
        print("\nEliminando filas problemáticas segundo as túas decisións...")
        datos_limpados = datos_seleccionados.drop(filas_a_eliminar, axis=0)
        print(f"Filas eliminadas: {len(filas_a_eliminar)}")
        
        # Paso 5: Gardar o arquivo limpo
        print("\nGardando o arquivo limpo e completamente preprocesado...")
        nome_saida = input("Introduce o nome do arquivo de saída (por exemplo, 'datos_limpados.csv'): ")
        datos_limpados.to_csv(nome_saida, index=False)
        print(f"\nArquivo limpo gardado como '{nome_saida}'. Contén os datos seleccionados e correctamente limpos.")
    
        # Ofrecer ao usuario descargar o arquivo
        print("\nDescargando o arquivo...")
        files.download(nome_saida)
    
    # Executar o programa educativo
    analizar_limpar_e_modificar_datos()
        

    Código Python: Creación de clústeres interactiva e con graficado de criterio de clasificación dun dato nun cluster

    # Importar librerías necesarias
    import pandas as pd
    import numpy as np
    from sklearn.preprocessing import StandardScaler
    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt
    
    # Para a visualización 3D (se se seleccionan 3 columnas)
    from mpl_toolkits.mplot3d import Axes3D
    
    # Función interactiva para clustering con explicaciones detalladas
    def clustering_interactivo():
        print("Benvido/a ao programa interactivo de clustering con K-Means.")
        print("Neste programa aprenderás paso a paso os conceptos de clustering, como a selección de columnas, escalado, elección do número de clusters,")
        print("o cálculo dos centroides e a análise das distancias entre puntos e centroides.\n")
        
        # Paso 1: Subida do arquivo CSV
        print("[Paso 1] Por favor, sube o arquivo CSV cos datos que desexas usar.")
        from google.colab import files
        uploaded = files.upload()
        nome_arquivo = list(uploaded.keys())[0]
        datos = pd.read_csv(nome_arquivo)
        
        print(f"\nO arquivo '{nome_arquivo}' foi cargado correctamente!")
        print("Visualización das primeiras 5 filas para que veas os teus datos:")
        print(datos.head())
        
        # Paso 2: Mostrar columnas dispoñibles e os seus tipos
        print("\n[Paso 2] Estas son as columnas dispoñibles no arquivo e os seus tipos de datos:")
        print(datos.dtypes)
        columnas_dispoñibles = list(datos.columns)
        print("\nRecorda: Os métodos de clustering utilizan valores numéricos para representar as dimensións dos datos.")
        input("\nPreme ENTER para continuar...")
        
        # Paso 3: Selección interactiva de columnas (2 ou 3 columnas)
        while True:
            print("\n[Paso 3] Selecciona 2 ou 3 columnas numéricas para realizar o clustering.")
            print("Por exemplo, se queres 2 columnas, escribe: col1, col2")
            print("E se prefires 3 columnas, escribe: col1, col2, col3")
            columnas_seleccionadas = input("Columnas seleccionadas: ").split(',')
            columnas_seleccionadas = [col.strip() for col in columnas_seleccionadas]
            
            # Verificar se as columnas existen
            columnas_non_existentes = [col for col in columnas_seleccionadas if col not in columnas_dispoñibles]
            if columnas_non_existentes:
                print(f"\nErro: As seguintes columnas non existen: {columnas_non_existentes}. Revisa os nomes e tenta de novo.")
                continue
            
            # Comprobar que se elixin 2 ou 3 columnas
            if len(columnas_seleccionadas) < 2 or len(columnas_seleccionadas) > 3:
                print("\nErro: Debes seleccionar exactamente 2 ou 3 columnas para este programa educativo.")
                continue
            
            # Validar que as columnas sexan numéricas
            columnas_non_numericas = [col for col in columnas_seleccionadas if not pd.api.types.is_numeric_dtype(datos[col])]
            if columnas_non_numericas:
                print(f"\nAs seguintes columnas non son numéricas: {columnas_non_numericas}.")
                print("Opcions:")
                print("1 - Escoller novas columnas numéricas.")
                print("2 - Usar só as columnas numéricas dos que seleccionaches.")
                eleccion = input("Que desexas facer? (1 ou 2): ").strip()
                if eleccion == "1":
                    continue
                elif eleccion == "2":
                    columnas_seleccionadas = [col for col in columnas_seleccionadas if col not in columnas_non_numericas]
                    if len(columnas_seleccionadas) < 2:
                        print("\nErro: Despois de filtrar, quedan menos de 2 columnas numéricas. Tenta de novo con outras columnas.")
                        continue
                    print(f"\nAs columnas seleccionadas modifícanse agora a: {columnas_seleccionadas}")
                    break
                else:
                    print("\nOpción non válida. Inténtase de novo.")
            else:
                break
        
        input("\nPreme ENTER para continuar á preparación dos datos...")
        
        # Paso 4: Escalado dos datos
        print("\n[Paso 4] Procesando e escalando os datos seleccionados...")
        print("Explicación: As columnas poden ter escalas diferentes, polo que escalamos os datos para que cada dimensión teña a mesma influéncia no clustering.")
        datos_num = datos[columnas_seleccionadas].copy()
        scaler = StandardScaler()
        datos_escalados = pd.DataFrame(scaler.fit_transform(datos_num), columns=columnas_seleccionadas)
        print("\nOs datos seleccionados foron escalados correctamente.")
        input("\nPreme ENTER para continuar á selección do número de clusters...")
        
        # Paso 5: Selección do número de clusters
        print("\n[Paso 5] Selección do número de clusters para o análisis.")
        print("Explicación: O número de clusters determina como se agruparán os puntos.\n"
              "- Un número baixo pode xerar grupos moi amplos,")
        print("  mentres que un número elevado pode crear grupos moi pequenos e específicos.")
        while True:
            try:
                num_clusters = int(input("Introduce o número de clusters (entre 2 e 10): ").strip())
                if 2 <= num_clusters <= 10:
                    break
                else:
                    print("Por favor, introduce un valor entre 2 e 10.")
            except ValueError:
                print("Erro: Debes introducir un número enteiro válido.")
        input("\nPreme ENTER para aplicar o algoritmo K-Means...")
        
        # Paso 6: Aplicación do algoritmo K-Means
        print(f"\n[Paso 6] Aplicando K-Means con {num_clusters} clusters.")
        print("Explicación: K-Means asigna cada punto a un cluster segundo a súa proximidade co centroide do grupo,")
        print("que é a posición media dos puntos dentro do cluster.")
        modelo = KMeans(n_clusters=num_clusters, random_state=42)
        datos_escalados['Cluster'] = modelo.fit_predict(datos_escalados)
        print("\nOs clusters foron asignados. Cada punto recibiu un identificador do cluster ao que pertence.")
        input("\nPreme ENTER para continuar coa análise dos puntos e os centroides...")
        
        # Paso 7: Selección e análise de 3 puntos aleatorios
        print("\n[Paso 7] Selección e análise de 3 puntos aleatorios do conxunto de datos.")
        print("Explicación: Este paso pretende que observes como se calculan as distancias dos puntos aos centroides de cada cluster.")
        if len(datos_escalados) < 3:
            print("Non hai suficientes datos para seleccionar 3 puntos. Usaremos todos os puntos dispoñibles.")
            puntos_analise = datos_escalados.copy()
            puntos_analise_originais = datos.copy()
        else:
            puntos_analise = datos_escalados.sample(n=3, random_state=42)
            puntos_analise_originais = datos.loc[puntos_analise.index]
        
        centroides = pd.DataFrame(modelo.cluster_centers_, columns=columnas_seleccionadas)
        print("\nCentroides obtidos polo algoritmo (valores escalados):")
        print(centroides)
        
        print("\nA continuación, analízase cada un dos puntos seleccionados separadamente.")
        for i, fila in puntos_analise.iterrows():
            print(f"\n--- Análise do punto (índice {i}) ---")
            print("Valores escalados do punto:")
            print(fila[columnas_seleccionadas])
            
            # Calcular distancias do punto a cada centroide
            distancias = np.linalg.norm(centroides - fila[columnas_seleccionadas], axis=1)
            for idx, distancia in enumerate(distancias):
                print(f"Distancia ao centroide do Cluster {idx}: {distancia:.2f}")
            cluster_asignado = np.argmin(distancias)
            print(f"Conclusión: Este punto pertence ao Cluster {cluster_asignado}, xa que presenta a menor distancia ({distancias[cluster_asignado]:.2f}).")
            
            # Xeración dunha gráfica individual para o punto analizado
            if len(columnas_seleccionadas) == 2:
                print("\nVisualización 2D para o punto analizado:")
                fig, ax = plt.subplots(figsize=(8,6))
                # Plot dos centroides
                for idx, centroide in centroides.iterrows():
                    ax.scatter(centroide[columnas_seleccionadas[0]], centroide[columnas_seleccionadas[1]],
                               s=200, label=f"Centroide {idx}", alpha=0.8, marker='X')
                # Plot do punto seleccionado
                ax.scatter(fila[columnas_seleccionadas[0]], fila[columnas_seleccionadas[1]],
                            color='red', s=100, label=f"Punto índice {i}", marker='o')
                # Trazo de liñas entre o punto e cada centroide
                for idx, centroide in centroides.iterrows():
                    ax.plot([fila[columnas_seleccionadas[0]], centroide[columnas_seleccionadas[0]]],
                            [fila[columnas_seleccionadas[1]], centroide[columnas_seleccionadas[1]]],
                            linestyle='--', color='gray')
                    # Calcula o punto medio para mostrar a distancia
                    mid_x = (fila[columnas_seleccionadas[0]] + centroide[columnas_seleccionadas[0]]) / 2
                    mid_y = (fila[columnas_seleccionadas[1]] + centroide[columnas_seleccionadas[1]]) / 2
                    ax.text(mid_x, mid_y, f"{distancias[idx]:.2f}", fontsize=9, color="blue")
                ax.set_xlabel(columnas_seleccionadas[0])
                ax.set_ylabel(columnas_seleccionadas[1])
                ax.set_title(f"Análise do Punto índice {i} (2D)")
                ax.legend()
                plt.show()
            elif len(columnas_seleccionadas) == 3:
                print("\nVisualización 3D para o punto analizado:")
                fig = plt.figure(figsize=(10,8))
                ax = fig.add_subplot(111, projection='3d')
                # Plot dos centroides
                for idx, centroide in centroides.iterrows():
                    ax.scatter(centroide[columnas_seleccionadas[0]], centroide[columnas_seleccionadas[1]], 
                               centroide[columnas_seleccionadas[2]], s=200, label=f"Centroide {idx}", alpha=0.8, marker='X')
                # Plot do punto seleccionado
                ax.scatter(fila[columnas_seleccionadas[0]], fila[columnas_seleccionadas[1]], fila[columnas_seleccionadas[2]],
                           color='red', s=100, label=f"Punto índice {i}", marker='o')
                # Trazo de liñas entre o punto e cada centroide
                for idx, centroide in centroides.iterrows():
                    xs = [fila[columnas_seleccionadas[0]], centroide[columnas_seleccionadas[0]]]
                    ys = [fila[columnas_seleccionadas[1]], centroide[columnas_seleccionadas[1]]]
                    zs = [fila[columnas_seleccionadas[2]], centroide[columnas_seleccionadas[2]]]
                    ax.plot(xs, ys, zs, linestyle='--', color='gray')
                    mid_x = (fila[columnas_seleccionadas[0]] + centroide[columnas_seleccionadas[0]]) / 2
                    mid_y = (fila[columnas_seleccionadas[1]] + centroide[columnas_seleccionadas[1]]) / 2
                    mid_z = (fila[columnas_seleccionadas[2]] + centroide[columnas_seleccionadas[2]]) / 2
                    ax.text(mid_x, mid_y, mid_z, f"{distancias[idx]:.2f}", fontsize=9, color="blue")
                ax.set_xlabel(columnas_seleccionadas[0])
                ax.set_ylabel(columnas_seleccionadas[1])
                ax.set_zlabel(columnas_seleccionadas[2])
                ax.set_title(f"Análise do Punto índice {i} (3D)")
                ax.legend()
                plt.show()
            else:
                print("Visualización non soportada para o número de columnas seleccionadas.")
            
            input("\nPreme ENTER para continuar co seguinte punto (se hai máis)...")
        
        print("\nAnálise completada. Agrazadecemos a túa participación e esperamos que este proceso te axude a comprender mellor os conceptos de clustering.")
        
    # Execución do programa
    clustering_interactivo()
        

    Os centroides en K-Means calcúlanse e actualízanse a medida que o algoritmo iterativamente refina os clusters.

    Inicialización : O primeiro centroide seleccionase aleatoriamente a partir dos datos.Para os centroides seguintes, utilizase o algoritmo k-means , que escolle cada novo centroide a base da distancia dos puntos aos centroides xa seleccionados.Con este método, cada punto ten unha probabilidade de ser escollido proporcional ao cadrado da súa distancia mínima a calquera centroide xa definido, o que garante unha boa dispersión inicial dos centroides.

    Asignación dos puntos: Unha vez establecidos os centroides iniciais, cada punto do dataset é asignado ao centroide máis próximo (medindo a distancia Euclídea, por exemplo).

    Recalcular os centroides: Despois de asignar os puntos, cada centroide récalcúlase como a media aritmética de todos os puntos que lle foron asignados. Este novo cálculo reflicte a posición central do cluster formado polos puntos que se achegaron máis a ese centroide.

    Iteración ata a converxencia: O proceso de asignación de puntos e recálculo de centroides repítese reiteradamente. O algoritmo detén as iteracións cando os centroides se estabilizan (ou sexa, cando os cambios de posición son prácticamente insignificantes) ou se alcanza un número máximo de iteracións.

    Este proceso garante que, a medida que o algoritmo avanza, os clusters se definen de xeito que os puntos dentro de cada grupo son os máis similares posibles, segundo a medida de distancia utilizada. Esta metodoloxía enfróntase sempre a algún punto de aleatoriedade (especialmente na elección inicial dos centroides), polo que, en diferentes execucións, os clusters poderían variar lixeiramente se non se establece unha semilla fixa para a aleatoriedade (por exemplo, random_state=42 no noso código).

    Código Python: Clustering completo dun arquivo de datos

    # Importar librerías necesarias
    import pandas as pd
    import numpy as np
    from sklearn.preprocessing import StandardScaler
    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt
    
    # Para a visualización 3D (se se seleccionan 3 columnas)
    from mpl_toolkits.mplot3d import Axes3D
    
    def clustering_interactivo():
        print("-----------------------------------------------------")
        print("Benvido/a ao programa interactivo de clustering con K-Means.")
        print("Neste proxecto veremos como unha máquina pode descubrir relacións entre datos\n"
              "que os humanos, a man ou a simple análise visual, podemos non detectar. Isto representa\n"
              "unha ferramenta moi poderosa en Big Data, abrindo un universo de posibilidades.")
        print("-----------------------------------------------------\n")
        
        # Paso 1: Subida do arquivo CSV
        print("[Paso 1] Subida do arquivo CSV:")
        print("Por favor, sube o arquivo CSV cos datos que desexas usar.")
        from google.colab import files
        uploaded = files.upload()
        nome_arquivo = list(uploaded.keys())[0]
        datos = pd.read_csv(nome_arquivo)
        
        print(f"\nO arquivo '{nome_arquivo}' foi cargado correctamente!")
        print("Visualización das primeiras 5 filas para que comprobes os teus datos:")
        print(datos.head())
        
        # Paso 2: Mostrar columnas dispoñibles e os seus tipos
        print("\n[Paso 2] Visualización das columnas dispoñibles e os seus tipos:")
        print(datos.dtypes)
        columnas_dispoñibles = list(datos.columns)
        print("\nRecorda: Para realizar clustering necesitamos valores numéricos para definir as dimensións.")
        input("\nPreme ENTER para continuar...")
        
        # Paso 3: Selección interactiva de columnas (2 ou 3 columnas)
        while True:
            print("\n[Paso 3] Selecciona 2 ou 3 columnas numéricas para o clustering.")
            print("Exemplo: para 2 columnas, escribe: col1, col2")
            print("         para 3 columnas, escribe: col1, col2, col3")
            columnas_seleccionadas = input("Columnas seleccionadas: ").split(',')
            columnas_seleccionadas = [col.strip() for col in columnas_seleccionadas]
            
            # Verificar se as columnas existen
            columnas_non_existentes = [col for col in columnas_seleccionadas if col not in columnas_dispoñibles]
            if columnas_non_existentes:
                print(f"\nErro: As seguintes columnas non existen: {columnas_non_existentes}. Revisa os nomes e tenta de novo.")
                continue
            
            # Comprobar que se seleccionen 2 ou 3 columnas
            if len(columnas_seleccionadas) < 2 or len(columnas_seleccionadas) > 3:
                print("\nErro: Debes seleccionar exactamente 2 ou 3 columnas para este programa educativo.")
                continue
            
            # Validar que as columnas sexan numéricas
            columnas_non_numericas = [col for col in columnas_seleccionadas if not pd.api.types.is_numeric_dtype(datos[col])]
            if columnas_non_numericas:
                print(f"\nAs seguintes columnas non son numéricas: {columnas_non_numericas}.")
                print("Opcións:")
                print("1 - Escoller novas columnas numéricas.")
                print("2 - Usar só as columnas numéricas dentre das que escolliche.")
                eleccion = input("Que desexas facer? (1 ou 2): ").strip()
                if eleccion == "1":
                    continue
                elif eleccion == "2":
                    columnas_seleccionadas = [col for col in columnas_seleccionadas if col not in columnas_non_numericas]
                    if len(columnas_seleccionadas) < 2:
                        print("\nErro: Despois de filtrar, quedan menos de 2 columnas numéricas. Tenta de novo con outras columnas.")
                        continue
                    print(f"\nAs columnas seleccionadas modifícanse a: {columnas_seleccionadas}")
                    break
                else:
                    print("\nOpción non válida. Inténtase de novo.")
            else:
                break
    
        input("\nPreme ENTER para continuar á preparación dos datos...")
        
        # Paso 4: Escalado dos datos
        print("\n[Paso 4] Escalado dos datos:")
        print("Explicación: As columnas poden ter escalas diferentes. Escalamos os datos para que cada dimensión teña o mesmo peso\n"
              "no proceso de clustering, evitando que unha variable domine a análise.")
        datos_num = datos[columnas_seleccionadas].copy()
        scaler = StandardScaler()
        datos_escalados = pd.DataFrame(scaler.fit_transform(datos_num), columns=columnas_seleccionadas)
        print("\nOs datos foron escalados correctamente.")
        input("\nPreme ENTER para continuar á selección do número de clusters...")
        
        # Paso 5: Selección do número de clusters
        print("\n[Paso 5] Selección do número de clusters:")
        print("Explicación: O número de clusters determina como se agruparán os puntos.\n"
              "Un número baixo pode xerar grupos moi amplos e un número elevado pode crear grupos máis pequenos.\n"
              "Elixe un número entre 2 e 10.")
        while True:
            try:
                num_clusters = int(input("Introduce o número de clusters (entre 2 e 10): ").strip())
                if 2 <= num_clusters <= 10:
                    break
                else:
                    print("Por favor, introduce un valor entre 2 e 10.")
            except ValueError:
                print("Erro: Debes introducir un número enteiro válido.")
        input("\nPreme ENTER para aplicar o algoritmo K-Means...")
        
        # Paso 6: Aplicación do algoritmo K-Means
        print(f"\n[Paso 6] Aplicando K-Means con {num_clusters} clusters.")
        print("Explicación: O algoritmo K-Means asigna cada punto ó cluster cuxo centroide (a media dos puntos) se achega máis a el.\n"
              "Este proceso repítese iterativamente ata que os clusters se estabilizan.")
        modelo = KMeans(n_clusters=num_clusters, random_state=42)
        datos_escalados['Cluster'] = modelo.fit_predict(datos_escalados)
        print("\nOs clusters foron asignados a cada punto.")
        input("\nPreme ENTER para ver a visualización global dos clusters...")
        
        # Novo Paso 7: Visualización global dos clusters
        print("\n-----------------------------------------------------")
        print("[Paso 7] Visualización global dos clusters:")
        print("Explicación: A máquina detectou relacións entre os datos e agrupounos en clusters.\n"
              "Na gráfica que verás a continuación, cada cor representa un cluster. Ademais,\n"
              "para cada cluster, mostramos un exemplo representativo (o punto máis próximo ó centroide).\n"
              "Isto ilustra como se poden detectar patróns nun universo de datos que un ser humano podería non notar.")
        print("-----------------------------------------------------\n")
        
        # Definir unha lista de cores para os clusters (ata 10 clusters)
        cores = ['blue', 'orange', 'green', 'red', 'purple', 'brown', 'pink', 'gray', 'olive', 'cyan']
        
        # Calcular os centroides en formato DataFrame con nomes das columnas
        centroides = pd.DataFrame(modelo.cluster_centers_, columns=columnas_seleccionadas)
        
        # Para cada cluster, atopar o punto máis achegado ó seu centroide para usalo como exemplo representativo
        exemplo_por_cluster = {}
        for i in range(num_clusters):
            # Selecciona os puntos pertencentes ó cluster i
            mask = datos_escalados['Cluster'] == i
            puntos_cluster = datos_escalados.loc[mask, columnas_seleccionadas]
            # Se existen puntos (por precaución)
            if len(puntos_cluster) > 0:
                # Calcula as distancias euclidianas ó centroide
                dists = np.linalg.norm(puntos_cluster.values - centroides.iloc[i].values, axis=1)
                indice_min = dists.argmin()
                # Obtén o índice real do punto no DataFrame (mantén o índice orixinal)
                exemplo_idx = puntos_cluster.index[indice_min]
                exemplo_por_cluster[i] = exemplo_idx
            else:
                exemplo_por_cluster[i] = None
        
        # Visualización: Se se seleccionan 2 columnas (2D) ou 3 columnas (3D)
        if len(columnas_seleccionadas) == 2:
            fig, ax = plt.subplots(figsize=(10,8))
            # Plot para cada cluster
            for i in range(num_clusters):
                mask = datos_escalados['Cluster'] == i
                pontos = datos_escalados.loc[mask, columnas_seleccionadas]
                cor = cores[i % len(cores)]
                ax.scatter(pontos[columnas_seleccionadas[0]], pontos[columnas_seleccionadas[1]],
                           c=cor, label=f"Cluster {i} (cor: {cor}, ex: idx {exemplo_por_cluster[i]})", alpha=0.6)
                # Ponto do centroide
                ax.scatter(centroides.iloc[i, 0], centroides.iloc[i, 1],
                           marker='X', s=200, c=cor, edgecolor='black')
            
            ax.set_xlabel(columnas_seleccionadas[0])
            ax.set_ylabel(columnas_seleccionadas[1])
            ax.set_title("Visualización Global dos Clusters (2D)")
            ax.legend(loc="best", fontsize=9)
            plt.grid(True)
            plt.show()
            
        elif len(columnas_seleccionadas) == 3:
            fig = plt.figure(figsize=(12,10))
            ax = fig.add_subplot(111, projection='3d')
            for i in range(num_clusters):
                mask = datos_escalados['Cluster'] == i
                pontos = datos_escalados.loc[mask, columnas_seleccionadas]
                cor = cores[i % len(cores)]
                ax.scatter(pontos[columnas_seleccionadas[0]], pontos[columnas_seleccionadas[1]], pontos[columnas_seleccionadas[2]],
                           c=cor, label=f"Cluster {i} (cor: {cor}, ex: idx {exemplo_por_cluster[i]})", alpha=0.6)
                # Ponto do centroide
                ax.scatter(centroides.iloc[i, 0], centroides.iloc[i, 1], centroides.iloc[i, 2],
                           marker='X', s=200, c=cor, edgecolor='black')
            
            ax.set_xlabel(columnas_seleccionadas[0])
            ax.set_ylabel(columnas_seleccionadas[1])
            ax.set_zlabel(columnas_seleccionadas[2])
            ax.set_title("Visualización Global dos Clusters (3D)")
            ax.legend(loc="best", fontsize=9)
            plt.show()
            
        else:
            print("Visualización non soportada para este número de columnas.")
        
        print("\n-----------------------------------------------------")
        print("FIN DO PROGRAMA")
        print("Observa como a máquina, empregando estatísticas e máis, pode detectar patróns e relacións entre datos\n"
              "que a simple vista non se verían. Este é o poder do Big Data: abrir unha porta a novas posibilidades de\n"
              "descubrimento e innovación. Grazas por participar!")
        print("-----------------------------------------------------")
    
    # Execución do programa
    clustering_interactivo()