Autor: Fernando

  • Cadenas y Agentes en LangChain

    Para construir aplicaciones con IA, utilizando LangChain necesitas entender tres piezas clave:

    • Cadenas (Chains)
    • Memoria (Memory)
    • Agentes (Agents)

    Este artículo explica cómo funcionan, cómo se conectan aplicándolo en nuestro entorno RAG híbrido.

    Cadenas en LangChain

    En LangChain, una cadena es simplemente una secuencia de llamadas donde la salida de una se convierte en la entrada del siguiente para crear un flujo de información continuo en un pipeline.

    2. Ejemplo completo: sistema de recetas por ubicación

    Vamos a recrear el ejemplo del video paso a paso.

    🎯 Objetivo

    Dado un país, queremos:

    1. Obtener un plato típico
    2. Generar la receta
    3. Calcular el tiempo de cocción

    🔹 Cadena 1: obtener el plato

    Entrada:

    China
    

    Prompt:

    Dime un plato famoso de {location}
    

    Salida esperada:

    Pato de Pekín
    

    🔹 Cadena 2: generar la receta

    Usa la salida anterior como entrada.

    Entrada:

    Pato de Pekín
    

    Prompt:

    Dame una receta sencilla para {meal}
    

    Salida:

    Receta paso a paso del plato
    

    🔹 Cadena 3: estimar tiempo de cocción

    Entrada:

    Receta generada
    

    Prompt:

    ¿Cuánto tiempo se tarda en cocinar esta receta? {recipe}
    

    Salida:

    Tiempo estimado (ej: 90 minutos)
    

    🔗 Resultado final (cadena completa)

    China → Pato de Pekín → Receta → 90 minutos
    

    Esto es una cadena secuencial.


    🧠 Qué está pasando realmente

    • Cada paso reduce incertidumbre
    • El modelo trabaja con contexto más concreto
    • El resultado final es mucho más preciso

    3. Cómo se construye esto en código (simplificado)

    # Cadena 1
    location_chain → output: meal
    
    # Cadena 2
    dish_chain → input: meal → output: recipe
    
    # Cadena 3
    recipe_chain → input: recipe → output: time
    
    # Cadena completa
    overall_chain = location_chain → dish_chain → recipe_chain
    

    🔍 Tip clave

    Usar verbose=True te permite ver todo el flujo:

    • Qué entra
    • Qué sale
    • Cómo se transforma

    Esto es clave para depurar.


    4. Memoria en LangChain: el contexto continuo

    Aquí viene uno de los puntos más importantes.

    Los LLM por sí solos no recuerdan nada.

    LangChain introduce memoria para resolver esto.


    🧠 Qué hace la memoria

    • Lee contexto previo
    • Mejora la entrada actual
    • Guarda la conversación

    🔄 Flujo con memoria

    Entrada usuario → Memoria → Cadena → Resultado → Memoria
    

    📦 Ejemplo real

    IA: Hola
    Usuario: ¿Cuál es la capital de Francia?
    

    La memoria guarda:

    • Mensaje IA
    • Mensaje usuario

    Y las siguientes respuestas usan ese historial.


    🧩 Clase clave

    LangChain usa:

    ChatMessageHistory
    

    Permite:

    • Guardar mensajes humanos
    • Guardar mensajes de IA
    • Mantener contexto conversacional

    🧠 Resultado

    El sistema deja de ser reactivo…
    y pasa a ser conversacional.


    5. Agentes en LangChain: el salto a sistemas inteligentes

    Aquí es donde todo cambia.

    Un agente es:

    Un sistema donde el modelo decide qué hacer y qué herramientas usar.


    🔥 Diferencia clave

    CadenasAgentes
    Flujo fijoFlujo dinámico
    Tú defines pasosEl modelo decide pasos
    PredecibleAdaptativo

    6. Cómo funciona un agente

    Un agente:

    1. Recibe una pregunta
    2. Decide qué hacer
    3. Usa herramientas externas
    4. Devuelve una respuesta

    🧠 Ejemplo conceptual

    Pregunta:

    ¿Cuál es la población de Italia?
    

    El agente:

    1. Decide buscar datos
    2. Consulta base de datos o API
    3. Procesa resultado
    4. Responde

    ⚠️ Importante

    El modelo:

    • Decide acciones
    • Genera texto

    Pero no ejecuta directamente → usa herramientas.


    7. Ejemplo real: agente con DataFrame (muy potente)

    Este es especialmente relevante para análisis de datos.


    🎯 Objetivo

    Consultar datos usando lenguaje natural.


    🧩 Configuración

    create_pandas_dataframe_agent(modelo, dataframe)
    

    💬 Consulta

    ¿Cuántas filas hay en el dataframe?
    

    ⚙️ Qué ocurre por dentro

    1. El modelo interpreta la pregunta
    2. Genera código Python
    3. Ejecuta ese código
    4. Devuelve el resultado

    📊 Resultado

    Hay 139 filas en el DataFrame
    

    🔥 Esto es clave

    Estás pasando de:

    ❌ Preguntar
    ✅ Ejecutar lógica real sobre datos


    8. Cómo encajan todo junto

    Flujo completo de una aplicación

    Usuario → Memoria → Cadena o Agente → Herramientas → Resultado → Memoria
    

    9. Cuándo usar cada cosa

    Usa Cadenas cuando:

    • El flujo es claro
    • Sabes los pasos
    • Quieres control

    Usa Memoria cuando:

    • Hay conversación
    • Necesitas contexto
    • Hay múltiples interacciones

    Usa Agentes cuando:

    • No sabes los pasos exactos
    • Necesitas decisiones dinámicas
    • Integras herramientas externas

    10. Recapitulación

    • Las cadenas conectan pasos de procesamiento
    • La memoria mantiene contexto entre interacciones
    • Los agentes toman decisiones y usan herramientas
    • Juntos forman la base de aplicaciones reales con IA

    Cierre

    Aquí está el cambio clave:

    👉 Con cadenas organizas el pensamiento
    👉 Con memoria mantienes el contexto
    👉 Con agentes automatizas decisiones

    Y cuando combinas los tres…
    ya no estás usando IA.

    Estás construyendo sistemas inteligentes.

  • Método LCEL en LangChain

    Después de ver los componentes de LangChain. Ahora toca entender cómo se conectan realmente para construir aplicaciones. Aquí entra el LangChain Expression Language (LCEL): el enfoque moderno y recomendado para diseñar flujos de trabajo con LLMs.

    Construir una cadena con LCEL

    LCEL es un patrón de construcción que permite conectar componentes mediante el operador de tubería (|). Es una forma declarativa de construir pipelines donde los datos fluyen de un componente a otro de forma explícita y legible.

    LCEL aporta:

    • Mayor componibilidad
    • Flujo de datos más claro
    • Más flexibilidad
    • Código más mantenible

    Un flujo típico sigue estos pasos:

    1. Definir una plantilla con variables ({})
    2. Crear una instancia de prompt
    3. Conectar componentes con |
    4. Ejecutar la cadena con inputs

    El núcleo: el operador de tubería (|)

    El operador | Conecta componentes en secuencia.

    prompt | modelo | parser
    1. Se construye el prompt
    2. Se envía al modelo
    3. Se procesa la salida

    Runnables: las piezas básicas

    En LCEL todo se basa en runnables. Estas son interfaces que permiten que cualquier componente sea conectable dentro de un pipeline. Ejemplos:

    • Modelos (LLM)
    • Funciones
    • Parsers
    • Recuperadores

    Tipos de composición

    Ejecución secuencial (RunnableSequence)

    Cada paso depende del anterior:

    Entrada → Paso 1 → Paso 2 → Salida

    En LCEL:

    paso1 | paso2

    Ejecución paralela (RunnableParallel)

    Un mismo input se procesa en múltiples ramas al mismo tiempo.

    {
      "summary": tarea1,
      "translation": tarea2,
      "sentiment": tarea3
    }

    Coerción automática de tipos

    Una de las grandes ventajas de LCEL. Es que convierte automáticamente:

    • Diccionarios → RunnableParallel
    • Funciones → RunnableLambda

    Por lo que no necesitas crear manualmente cada componente.

    Ejemplo completo: pipeline simple

    Caso: generar un contenido:

    formatear_prompt | modelo | parser

    Qué ocurre

    1. Una función formatea el prompt (RunnableLambda)
    2. El modelo genera la respuesta
    3. El parser limpia o estructura la salida

    Ejemplo: procesamiento paralelo

    Entrada:

    "El producto es bueno pero llegó tarde"

    Pipeline:

    {
      "summary": prompt1 | modelo,
      "translation": prompt2 | modelo,
      "sentiment": prompt3 | modelo
    }

    Resultado

    • Resumen
    • Traducción
    • Sentimiento

    Todo en una sola ejecución.

    Flujo interno de datos

    LCEL hace explícito el flujo:

    Input → Transformación → Modelo → Output → Post-procesado

    Esto mejora:

    • Debugging
    • Escalabilidad
    • Control

    Ventajas clave de LCEL

    • Claridad: El flujo se entiende leyendo el código.
    • Reutilización: Puedes crear componentes reutilizables y pipelines estándar
    • Flexibilidad: secuencias, paralelismo y transformaciones
    • Capacidades avanzadas: LCEL permite ejecución paralela, soporte asíncrono, streaming y trazabilidad automática

    Cuándo usar LCEL

    Ideal para:

    • Flujos de prompts
    • Automatización de tareas
    • Sistemas modulares
    • Procesamiento de texto

    No ideal para:

    • Flujos con lógica compleja (condicionales, estados, etc.). En ese caso, usar: LangGraph. Pero LCEL sigue siendo la base dentro de cada nodo.

  • Componentes principales de LangChain

    Antes de trabajar con pipelines avanzados o LCEL, necesitas dominar los bloques fundamentales de LangChain. Aquí entiendes qué componentes existen y cómo encajan entre sí.

    Componentes principales de LangChain

    LangChain no es una sola cosa, es un ecosistema de componentes. Los principales son:

    • Documents
    • Chains
    • Agents
    • Language model (LLMs)
      • Chat Model
      • Embedding Models
    • Chat Message
    • Prompt Templates
    • Output Parsers

    Modelos de Lenguaje

    Son la base de los LLMs, reciben y generan texto. Sirven para generar contenido, resumir documentos y completar tareas. LangChain utiliza OpenAI, Google, Meta e IBM como lenguajes primarios.

    Los modelos se pueden personalizar ajustando parámetros que modifican su forma de responder, algunos son:

    • temperature: Aleatoriedad del modelo, más temperura = más creatividad.
    • max_tokens: Longitud máxima de la respuesta en tokens
    • top_p: Diversidad de palabras considerando la probabilidad acumulada.
    • top_k: Diversidad de palabras considerando la frecuencia
    • frequency_penalty: Penalización de frecuencia
    • presence_penalty: Penalización de presencia

    Modelos de Chat

    Son una evolución de los LLM tradicionales. Convierten texto plano en conversaciones estructuradas, entienden contexto y mantienen coherencia entre mensajes.

    Embedding Models

    What Are Vector Embeddings? Models & More Explained

    Estos modelos no generan texto. Su única función es transformar contenido en vectores (listas de números) que representan el significado semántico.

    Se utilizan exclusivamente para RAG (Retrieval Augmented Generation). Sirven para buscar información en bases de datos vectoriales comparando qué tan “cerca” está la pregunta del usuario de tus documentos.

    CaracterísticaChat ModelEmbedding Model
    InputLista de MensajesTexto plano
    OutputUn mensaje de texto Un vector (lista de floats)
    FunciónRazonar, generar, decidirComparar similitudes
    Uso principalEl “cerebro” del agenteLa “búsqueda” de memoria

    Cargar un modelo:

    from langchain_openai import ChatOpenAI
    
    # Cargar variables de entorno
    load_dotenv()
    
    # Crear modelo directamente
    llm = ChatOpenAI(
        model="deepseek-chat",
        openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
        openai_api_base="https://api.deepseek.com",
    
        # Parámetros de generación
        temperature=0.3,
        max_tokens=100,
        top_p=0.9,
        frequency_penalty=0.2,
        presence_penalty=0.1
    )

    En la variable llm se carga el modelo que después se llama con el método .invoke()

    response = llm.invoke(messages)
    print(response.content)

    Mensajes de chat

    Un modelo de chat no trabaja con texto plano, sino con mensajes estructurados. Cada tipo de mensaje tiene un rol (quién habla) y un contenido (qué dice).

    Tipos de mensajes

    • "system"→ define comportamiento y contexto del modelo
    • "human"→ entrada del usuario
    • "ai"→ respuesta del modelo
    • “tool”→ interacción con sistemas externos
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "Eres experto en análisis de datos."),
    
        # Usuario
        ("human", "{question}"),
    
        # Respuesta previa del modelo (contexto conversacional)
        ("ai", "La varianza mide la dispersión de los datos."),
    
        # Resultado de una herramienta (tool calling)
        ("tool", '{"mean": 10, "std": 2}', {"tool_name": "stats_calculator"})
    ])

    No todos se usan siempre:

    • En el 90% de los casos → solo "system" + "human"
    • "ai" → cuando hay memoria conversacional
    • "tool" → solo en flujos con herramientas (agents / tool calling)

    Plantillas de prompts

    Las plantillas convierten entradas en instrucciones claras:

    • Estandarizan prompts
    • Permiten reutilización
    • Introducen variables dinámicas
    prompt = ChatPromptTemplate.from_messages([
        ("system", "Eres un experto en {domain}. Responde en tono {tone}."),
        ("human", "Pregunta: {question}\nContexto: {context}")
    ])
    
    chain = prompt | llm
    
    response = chain.invoke({
        "domain": "machine learning",
        "tone": "conciso",
        "question": "¿Qué es overfitting?",
        "context": "Modelo entrenado con pocos datos"
    })
    
    print(response.content)

    Tipos principales

    • Plantillas de cadena (string): para texto simple.
    • Plantillas de chat: para estructuras conversacionales.
      • Mensajes del sistema
      • Mensajes humanos
      • Mensajes de IA
    • Plantillas de pocos ejemplos (few-shot): incluyen ejemplos para guiar al modelo.

    Output Parsers

    Los Output Parsers en LangChain son el mecanismo para convertir la salida del LLM (texto libre) en estructuras controladas y utilizables (JSON, objetos tipados, listas, etc.).

    Son críticos cuando necesitas fiabilidad, integración con código y postprocesado determinista. Se guardan en una variable que se añade al final de la cadena.

    parser = JsonOutputParser()

    Chains (Cadenas)

    En LangChain, las Chains son la forma de encadenar los componentes (prompts, modelos, parsers, funciones) para construir un flujo completo desde entrada → transformación → salida. En el enfoque moderno (LCEL), una chain no es una clase rígida, sino una composición declarativa de “runnables”.

    prompt = ChatPromptTemplate.from_messages([
        ("system", "Responde como experto en estadística."),
        ("human", "{question}")
    ])
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    parser = StrOutputParser()
    
    chain = prompt | llm | parser
    
    response = chain.invoke({"question": "¿Qué es la media?"})
    
    print(response)

    Documents

    En LangChain, un Document es la unidad estándar de datos que encapsula contenido (page_content) y metadatos (metadata), y actúa como formato común para que todos los módulos sean interoperables.

    • Se crea al ingerir información externa.
    • Se fragmenta para optimizar el procesamiento.
    • Mantiene siempre su metadata para permitir filtrado, ranking y trazabilidad.
    • Se transforma en vectores sin perder contexto.
    • Finalmente se recupera para inyectarse en prompts dentro de arquitecturas RAG.

    Agentes

    En LangChain, los Agentes (Agents) son el componente que permite a un LLM tomar decisiones iterativas y usar herramientas para resolver tareas complejas. A diferencia de una chain (pipeline fija), un agente decide dinámicamente qué hacer en cada paso.

    Un Agente es un orquestador basado en LLM que:

    1. razona sobre el problema
    2. elige acciones (tools)
    3. ejecuta
    4. observa resultados
    5. repite hasta llegar a una respuesta

    Diferencia agente vs Chains

    ConceptoChainAgent
    FlujoFijoDinámico
    ControlDeterministaAdaptativo
    Uso de toolsManualAutomático
    ComplejidadBaja-mediaAlta
  • Introducción a LangChain y LCEL

    Con LangChain pasamos de usar modelos de lenguaje a construir aplicaciones completas con ellos. Es un framework de código abierto en Python diseñado para facilitar el desarrollo de aplicaciones basadas en modelos de lenguaje (LLM). Proporciona componentes e interfaces para integrar modelos de lenguaje dentro de sistemas reales.

    Permite:

    • Localizar información relevante en grandes volúmenes de texto
    • Responder preguntas complejas combinando datos y generación
    • Automatizar procesos que implican múltiples pasos

    LangChain funciona encadenando operaciones como:

    1. Recuperar información
    2. Extraer datos relevantes
    3. Procesar el contenido
    4. Generar una respuesta

    Este flujo convierte un modelo de lenguaje en un sistema completo.

    Beneficios principales de LangChain

    • Modularidad: LangChain está diseñado como un sistema de piezas que reduce drásticamente el tiempo de desarrollo. Puedes construir por bloques, reutilizar componentes y modificar partes sin romper el conjunto
    • Extensibilidad: Añade nuevas funcionalidades fácilmente integrándose con APIS y sistemas externos sin rehacer toda la arquitectura.
    • Descomposición de problemas: LangChain replica un patrón humano: divide tareas complejas en pasos más pequeños. Esto mejorando la precisión de las respuestas, el control del proceso y la interpretabilidad del sistema.
    • Integración con bases de datos vectoriales: LangChain se integra con bases de datos vectoriales para realizar búsquedas semánticas, recuperar eficientemente la información con rápido acceso a grandes volúmenes de datos.

    Esto permite construir sistemas como:

    • Chat con documentos
    • Asistentes inteligentes
    • Sistemas de conocimiento empresarial

    Cómo funciona un sistema con LangChain

    Un flujo típico sería:

    1. Usuario hace una pregunta
    2. El sistema busca información relevante
    3. Procesa los datos
    4. Genera un prompt optimizado
    5. El modelo responde

    Este enfoque combina:

    • Recuperación de información
    • Procesamiento
    • Generación de lenguaje

    Casos de uso reales

    • Resumen de contenido: Analizar documentos largos, extraer lo importante y simplificar contenido complejo
    • Extracción de datos: Convierte texto en información estructurada: estadísticas clave, métricas y datos accionables
    • Sistemas de preguntas y respuestas: responden sobre bases de conocimiento, mantienen contexto conversacional y refinan respuestas dinámicamente
    • Generación automatizada de contenido: redacción de emails, generación de ideas y documentación técnica
    • Trabajar con otros tipos de datos: Aunque LangChain está centrado en texto, también puede trabajar con audio, imágenes y vídeo

    Embeddings y búsqueda semántica

    LangChain utiliza embeddings para representar información como vectores y comparar significado en lugar de palabras exactas. Esto permite encontrar información relevante aunque esté expresada de forma diferente para mejorar la calidad de las respuestas

    LangChain Expression Language (LCEL)

    LCEL es el enfoque moderno dentro de LangChain para construir pipelines de IA. Es un patrón que permite conectar componentes mediante el operador de tubería (|) para crear flujos de datos claros y legibles.
    Lo que permite estructurar esos sistemas de forma limpia, componible y escalable. LCEL aporta:

    • Mayor claridad en el flujo de datos
    • Mejor componibilidad
    • Más flexibilidad
    • Código más limpio

    LCEL funciona como una tubería de datos. Cada componente recibe una entrada, la transforma y pasa el resultado al siguiente

    prompt | modelo | parser

    Construir una cadena con LCEL

    Un patrón típico incluye:

    1. Definir una plantilla de prompt
    2. Crear el prompt con variables
    3. Conectar componentes con |
    4. Ejecutar la cadena con inputs

    Ventajas de LCEL

    Deja de pensar en “hacer prompts”. Empieza a pensar en: diseñar pipelines de transformación de información

    • Legibilidad: El flujo es explícito: input → transformación → modelo → salida
    • Reutilización: Puedes reusar bloques, crear patrones estándar y escalar fácilmente
    • Flexibilidad: Puedes combinar: cadenas en secuencial, paralelo y transformaciones personalizadas
    • Automatización avanzada: LCEL permite ejecución paralela, soporte asíncrono, streaming de respuestas y trazabilidad automática
  • Aprendizaje en Contexto y Métodos Avanzados de Ingeniería de Prompts

    Cuando trabajas con modelos de lenguaje como GPT-3.5, no estás “programando” en el sentido tradicional. Estás guiando el comportamiento del modelo a través del lenguaje. Y aquí es donde entran dos conceptos fundamentales:

    • Aprendizaje en contexto (In-Context Learning)
    • Ingeniería de prompts (Prompt Engineering)

    ¿Qué es el Aprendizaje en Contexto?

    El aprendizaje en contexto es una técnica mediante la cual un modelo aprende a realizar una tarea sin necesidad de ser reentrenado, simplemente a partir de ejemplos incluidos dentro del propio prompt, lo que haces es:

    • Incluir ejemplos dentro del prompt
    • Mostrarle al modelo qué tipo de salida esperas
    • Dejar que el modelo generalice ese patrón en tiempo de inferencia

    Ejemplo sencillo

    Clasifica el sentimiento:
    
    Ejemplo 1:
    Texto: Me encanta este producto
    Sentimiento: Positivo
    
    Ejemplo 2:
    Texto: No funciona como esperaba
    Sentimiento: Negativo
    
    Ahora:
    Texto: Es aceptable, pero mejorable
    Sentimiento:

    El modelo no ha sido entrenado específicamente para esto en ese momento. Está aprendiendo del contexto que le das.

    Ventajas y Limitaciones

    Ventajas clave

    • No requiere entrenamiento adicional
    • Reduce costes computacionales y tiempo
    • Permite adaptar modelos rápidamente a nuevas tareas
    • Mejora el rendimiento sin tocar el modelo base

    Limitaciones importantes

    • Capacidad limitada del contexto (no puedes meter infinitos ejemplos)
    • Tareas complejas pueden requerir:
      • Fine-tuning
      • Ajustes con gradiente (machine learning tradicional)

    Componentes del Prompt

    Un prompt bien diseñado tiene cuatro elementos clave:

    1. Instrucciones: Indican claramente qué debe hacer el modelo.
    2. Contexto: Proporciona información adicional para interpretar la tarea.
    3. Datos de entrada: Es la información concreta que el modelo debe procesar.
    4. Indicador de salida: Marca dónde debe responder el modelo.

    Ejemplo:

    Instrucción:
    Clasifica la siguiente reseña...
    
    Contexto:
    Producto recién lanzado...
    
    Entrada:
    "El producto llegó tarde pero la calidad superó mis expectativas."
    
    Salida:
    Sentimiento:
    

    Ingeniería de prompts

    La ingeniería de prompts es la disciplina que se encarga de crear, refinar y estructurar prompts para obtener respuestas más precisas, relevantes y útiles de un modelo de lenguaje:

    • Formular correctamente el problema
    • Reducir ambigüedad
    • Guiar el razonamiento del modelo
    • Controlar el tipo de salida

    Una buena ingeniería de prompts:

    • Aumenta la precisión
    • Mejora la relevancia de las respuestas
    • Reduce errores y malentendidos
    • Permite prescindir de fine-tuning en muchos casos

    Esto es especialmente útil en aplicaciones como:

    • Atención al cliente automatizada
    • Análisis de datos
    • Generación de contenido
    • Investigación

    Métodos Avanzados de Ingeniería de Prompts

    Optimizar un prompt para obtener resultados consistentes, precisos y escalables. Estos métodos marcan la diferencia entre un uso básico de la IA y un uso profesional.

    Zero-Shot Prompting

    El zero-shot prompting consiste en pedirle al modelo que realice una tarea sin proporcionarle ejemplos previos. El modelo utiliza únicamente:

    • Su conocimiento previo
    • La instrucción que le das

    Ejemplo

    Clasifica como verdadero o falso:
    
    La Torre Eiffel está en Berlín.
    
    Cuándo usarlo
    • Tareas simples
    • Preguntas directas
    • Cuando quieres rapidez y no necesitas precisión extrema
    Limitación
    • Menor control sobre la salida
    • Mayor probabilidad de error en tareas complejas

    One-Shot Prompting

    El one-shot prompting añade un único ejemplo para enseñar al modelo el patrón esperado. Introduce una plantilla mental y reduce ambigüedad

    Ejemplo

    Traduce del inglés al francés:
    
    Ejemplo:
    Hello → Bonjour
    
    Ahora:
    Where is the nearest supermarket?
    
    Cuándo usarlo
    • Cuando el formato importa
    • Cuando quieres consistencia básica
    • Para tareas repetitivas

    Few-Shot Prompting

    El few-shot prompting amplía el concepto anterior: le das al modelo varios ejemplos antes de la tarea real.

    • Mejora la generalización
    • Aumenta la precisión
    • Reduce errores de interpretación

    Ejemplo

    Clasifica la emoción:
    
    Ejemplo 1:
    Texto: Estoy muy feliz hoy
    Emoción: Alegría
    
    Ejemplo 2:
    Texto: Esto me da miedo
    Emoción: Miedo
    
    Ejemplo 3:
    Texto: Estoy frustrado con este resultado
    Emoción: Frustración
    
    Ahora:
    Texto: Esa película fue tan aterradora que tuve que taparme los ojos
    Emoción:
    
    Cuándo usarlo
    • Clasificación
    • Análisis de texto
    • Tareas donde el contexto es clave

    Chain of Thought (CoT)

    El Chain of Thought Prompting obliga al modelo a explicar su razonamiento paso a paso. Los LLM no “piensan” como humanos, pero simulan razonamiento mejor cuando lo estructuran en pasos.

    Ejemplo

    Una tienda tenía 22 manzanas.
    Vendió 15.
    Luego recibió 8 más.
    
    ¿Cuántas tiene ahora? Explica paso a paso.
    
    Ventajas
    • Mayor precisión en problemas complejos
    • Transparencia en la respuesta
    • Menos errores lógicos
    Cuándo usarlo
    • Matemáticas
    • Lógica
    • Análisis paso a paso
    • Decisiones complejas

    Autoconsistencia

    La autoconsistencia es una técnica para aumentar la fiabilidad:

    • Generas varias respuestas independientes
    • Comparas resultados
    • Te quedas con la más consistente

    Ejemplo

    • Problema:
      • Cuando yo tenía 6 años, mi hermana tenía la mitad. Ahora tengo 70. ¿Qué edad tiene ella?
    • Se le pide al modelo:
      • Resolverlo varias veces
      • Explicar cada razonamiento
    • Luego:
      • Se comparan las respuestas
      • Se valida la coherencia
    Qué aporta:
    • Reduce errores
    • Mejora confianza en el resultado
    • Detecta inconsistencias

    Herramientas clave en ingeniería de prompts

    Para trabajar de forma profesional, no basta con escribir prompts en un chat. Necesitas herramientas.

    Plataformas principales

    • OpenAI Playground
    • LangChain
    • Hugging Face Model Hub
    • IBM AI Classroom

    Qué permiten hacer

    • Probar prompts en tiempo real
    • Comparar resultados entre modelos
    • Iterar rápidamente
    • Medir rendimiento

    Cómo elegir el método adecuado

    No todos los prompts sirven para todo.

    MétodoCuándo usarlo
    Zero-shotTareas simples
    One-shotFormato específico
    Few-shotPrecisión y consistencia
    Chain of ThoughtProblemas complejos
    AutoconsistenciaValidación crítica

  • Entorno híbrido (Ollama + DeepSeek) con LangChain.

    Por qué un entorno RAG híbrido (local + API)

    Este entorno se ha diseñado como base de trabajo para realizar prácticas del curso IBM RAG and Agentic AI, pero desvinculando la implementación de la plataforma watsonx de IBM.

    El objetivo no es replicar la herramienta, sino replicar la arquitectura. En el curso, muchos de los ejercicios se apoyan en servicios gestionados en el ecosistema de IBM, lo que facilita el aprendizaje inicial, pero introduce una fuerte dependencia del proveedor. Para un aprendizaje más profundo y aplicable, es necesario trasladar esos mismos conceptos a un entorno abierto y controlado.

    Entorno: Conda + Python

    Se utiliza Anaconda como gestor de entornos por varias razones:

    • aislamiento de dependencias (evita conflictos entre librerías)
    • compatibilidad con librerías de data science (NumPy, Pandas, sklearn)
    • control de versiones reproducible
    • integración natural con notebooks y workflows analíticos

    Framework: LangChain

    Se utiliza LangChain como capa de orquestación:

    • Abstrae el uso del LLM
    • Permite cambiar de modelo sin cambiar el pipeline
    • Integra: loaders, chunking, embeddings, vector stores, chains

    Uso de IA local: Ollama

    Se utiliza Ollama para ejecutar modelos en local.

    VentajasLimitacionesRol en el sistema
    Independencia total de internet
    Coste cero
    Privacidad de datos
    Control completo del entorno
    Rendimiento limitado por hardware
    Modelos más pequeños
    Menor precisión en tareas complejas
    Desarrollo
    Testing
    Validación de arquitectura
    Entornos offline

    Uso de IA en API: DeepSeek

    Se utiliza DeepSeek como modelo en la nube.

    VentajasLimitacionesRol en el sistema
    Mayor calidad de respuesta
    Mejor razonamiento
    Contexto más amplio
    Coste bajo
    Dependencia de red
    Coste (aunque bajo)
    Menor control
    Validación de calidad
    Comparación con modelos locales
    Ejecución en escenarios reales

    Coste aproximado: Para el modelo por defecto que se va a usar (deepseek-chat):

    • Entrada: ~$0.14 – $0.28 por 1 millón de tokens
    • Salida: ~$0.28 – $0.42 por 1 millón de tokens

    Por qué un enfoque híbrido

    El uso combinado de ambos modelos permite:

    1. Separar arquitectura de proveedor. El sistema RAG se diseña una vez y el LLM se cambia según necesidad:
      • mismo pipeline → distinto modelo
    2. Optimizar coste vs rendimiento
      • local → coste 0
      • API → alta calidad cuando es necesario
    3. Comparación y evaluación. Permite evaluar:
      • calidad de respuestas
      • Impacto del modelo en RAG
      • Diferencias entre local y cloud

    Preparación del entorno RAG híbrido

    Creación del entorno (Anaconda)

    Se crea un entorno aislado para evitar conflictos de dependencias. En la consola de conda ejecuta:

    conda create -n rag_env python=3.10 -y
    conda activate rag_env

    Se utiliza Python 3.10 (máxima compatibilidad con LangChain ahora mismo).

    Instalación de dependencias

    En este entorno se combinan Conda y pip para la instalación de dependencias. Aunque mezclar ambos gestores puede generar conflictos si no se hace correctamente, se sigue un patrón controlado que evita problemas.

    Primero se utilizan paquetes instalados con Conda para la base científica (como NumPy, Pandas o Scikit-learn), ya que garantizan compatibilidad a nivel de sistema. A continuación, se emplea pip para instalar librerías más recientes del ecosistema de IA, como LangChain o herramientas de embeddings, que suelen estar más actualizadas fuera de Conda.

    La regla clave es mantener el orden: instalar primero con Conda y después con pip, evitando volver a usar Conda sobre el mismo entorno una vez que pip ha añadido dependencias. De esta forma, se consigue un entorno estable, reproducible y compatible con las necesidades del desarrollo de sistemas RAG.

    Base científica (Conda)

    conda install -c conda-forge numpy pandas scikit-learn -y

    Librerías de IA, Machine Learning, LangChain (pip)

    pip install langchain langchain-community langchain-core
    pip install -U langchain-ollama
    pip install langchain-openai
    pip install faiss-cpu
    pip install sentence-transformers
    pip install pypdf
    pip install python-dotenv
    pip install requests
    pip install ipykernel
    pip install BeautifullSoup4
    pip install chromadb

    Esta combinación evita problemas de compatibilidad.

    Crear proyecto

    1. Crea una carpeta del proyecto.
    2. En el IDE de preferencia crea el proyecto desde la carpeta creada.
    3. Selecciona el entorno creado en Anaconda
    4. Opcional y recomendable iniciar repositorio y conectar con github.
    5. Crear estructura base
    rag-local/
     ├── .env
     ├── main.py
     ├── data/
     └── notebooks/

    Configuración de IA local (Ollama)

    Descargar modelo:

    ollama pull qwen:4b

    Cuando termine la descarga del modelo, ejecuta:

    ollama run qwen:4b

    Si todo fue correcto, te aparece un prompt interactivo y puedes hacer cualquier pregunta para verificar que el modelo está instalado.

    Configuración de IA en API (DeepSeek)

    Visita https://platform.deepseek.com/ inicia sesión y crea una api_key.

    Crear archivo .env

    DEEPSEEK_API_KEY=tu_api_key

    Cargar las variables en main.py

    # Agrega al inicio
    from dotenv import load_dotenv
    import os
    
    # Cargar variables
    load_dotenv()
    api_key = os.getenv("DEEPSEEK_API_KEY")

    Configuración de LangChain

    Conexión a modelo local

    # Agrega al inicio
    from langchain_ollama import OllamaLLM
    
    
    # LLM local
    llm_local = OllamaLLM(model="qwen:4b")

    Conexión a modelo API

    # agregar al inicio
    from langchain_openai import ChatOpenAI
    
    
    # LLM API
    llm_api = ChatOpenAI(
        openai_api_key=api_key,
        openai_api_base="https://api.deepseek.com",
        model="deepseek-chat"
    )

    Selección de modelo

    Se define un selector para poder cambiar de modelo sin modificar el resto del sistema.

    # Selector de modelo
    usar_api = False
    
    # Elegir Modelo
    llm = llm_api if usar_api else llm_local

    Verificación del entorno

    Código mínimo de prueba al final de main.py:

    respuesta = llm.invoke("Explica qué es RAG en 2 líneas")
    print(respuesta)

    El modelo local responde con usar_api = False:

    RAG significa "Remo de Armas" en inglés.

    El modelo con API responde al usar_api = True

    content='RAG (Retrieval-Augmented Generation) es una técnica que combina la recuperación de información relevante desde una base de datos externa con un modelo de lenguaje, permitiendo generar respuestas más precisas y actualizadas sin necesidad de reentrenar el modelo.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 57, 'prompt_tokens': 15, 'total_tokens': 72, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 15}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '27107c79-dc76-40ab-b461-32439139c445', 'finish_reason': 'stop', 'logprobs': None} id='lc_run--019dcfd2-0298-7482-b39b-c069b9f5e30a-0' tool_calls=[] invalid_tool_calls=[] usage_metadata={'input_tokens': 15, 'output_tokens': 57, 'total_tokens': 72, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}

    Las respuestas de un LLM no solo contienen el texto generado (content), sino también metadatos relevantes como el uso de tokens, el modelo utilizado y el estado de finalización. Esta información es fundamental para analizar costes, rendimiento y comportamiento del sistema, especialmente en arquitecturas RAG.

    Creación de un módulo reutilizable

    El objetivo de este módulo es centralizar la configuración de los modelos LLM, permitiendo utilizar indistintamente un modelo local (Ollama) o un modelo en API (DeepSeek), evitando así la duplicación de código en notebooks o scripts.

    En lugar de definir la configuración del modelo en cada punto del proyecto, se encapsula esta lógica en un único módulo reutilizable. Esto facilita el cambio de modelo en cualquier momento y mejora la organización del código.

    Este enfoque resulta especialmente útil en sistemas RAG, donde el LLM forma parte de múltiples componentes del pipeline. Al desacoplar su configuración, se consigue un entorno más flexible, mantenible y preparado para evolucionar sin necesidad de modificar cada parte del sistema.

    Aquí tienes una sección lista para integrar en tu artículo, con enfoque didáctico y alineada con tu entorno híbrido.

    Parámetros de generación en DeepSeek y Ollama

    Los modelos de lenguaje, son configurables desde la API con parametros de generación que determinan el estilo, longitud y comportamiento de las respuestas. En este entorno híbrido (Ollama + DeepSeek), vamos a unificar conceptos para poder experimentar sin fricciones.

    Visita la documentación de la API de cada LLM

    Parámetros comunes

    Estos parámetros existen en ambos sistemas:

    Temperature (temperatura)

    Controla el nivel de aleatoriedad del modelo.

    • 0.0 → 0.3 → respuestas deterministas (más exactas)
    • 0.5 → 0.7 → equilibrio
    • 0.8+ → creatividad alta (más variabilidad)
    Max tokens / longitud de salida

    Limita el tamaño de la respuesta. Los LLM que estamos trabajando utilizan denominaciones diferentes:

    • DeepSeek → max_tokens
    • Ollama → num_predict
    Top-p (nucleus sampling)

    Controla la diversidad de palabras considerando la probabilidad acumulada.

    • 0.1 → 0.3 → respuestas más conservadoras
    • 0.8 → 1.0 → mayor diversidad
    Top-k (solo en Ollama)

    Limita el número de posibles palabras candidatas. DeepSeek no soporta este parámetro.

    • Bajo → más determinista
    • Alto → más diversidad
    Parámetros de penalización

    Solo DeepSeek (API tipo OpenAI) permite controlar repetición:

    • frequency_penalty → evita repetir palabras
    • presence_penalty → fomenta introducir nuevos temas
    Modos de razonamiento (DeepSeek)

    DeepSeek introduce capacidades avanzadas que permiten separar razonamiento interno de respuesta final

    • deepseek-reasoner
    • modo thinking

    Parámetros claves resumidos

    ParámetroDeepSeekOllamaUso recomendado
    temperature✔️✔️siempre
    max_tokens✔️usar como estándar
    num_predict✔️interno (no usar directamente)
    top_p✔️✔️recomendado
    top_k✔️opcional (solo local)
    penalties✔️avanzado
    reasoning mode✔️avanzado

    Agregar los parámetros al módulo

    Para evitar complejidad innecesaria, usamos una interfaz común y luego traducimos internamente:

    • max_tokens → num_predict (Ollama)
    • top_k solo si usamos Ollama

    Implementacion

    Crea en la raíz del proyecto el fichero llm_config.py

    from dotenv import load_dotenv
    import os
    
    from langchain_ollama import OllamaLLM
    from langchain_openai import ChatOpenAI
    
    # cargar variables de entorno
    load_dotenv()
    
    def get_llm(llm="dsk" , params=None):
        """
        Devuelve un modelo LLM configurado.
        
        Parameters:
        - usar_api (bool): si True usa DeepSeek, si False usa Ollama
        
        Returns:
        - instancia de LLM
        """
        
        default_params = {
            "temperature": 1, # Aleatoriedad del modelo.
            "max_tokens": 50, # Longitud de la respuesta en DeepSeek
            "top_p": 1, # diversidad de palabras considerando la probabilidad acumulada 
            "top_k": 40, # Diversidad de palabras considerando la frecuencia 
            "frequency_penalty": 0, # Penalizacion de frecuencia - DeepSeek
            "presence_penalty": 0, # Penalizacion de presencia - DeepSeek
            "repeat_penalty": 1.1 # Penalizacion de repeticion - Ollama
        }
    
        if params:
            default_params.update(params)
        
        if llm == "dsk":
            # DeepSeek
            return ChatOpenAI(
                model="deepseek-chat", 
                openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
                openai_api_base="https://api.deepseek.com",
                temperature=default_params["temperature"],
                max_tokens=default_params["max_tokens"],
                top_p=default_params["top_p"],
                frequency_penalty=default_params["frequency_penalty"],
                presence_penalty=default_params["presence_penalty"]
            )
    
        elif llm == "oll":
            # Ollama
            return OllamaLLM(
                model="qwen:4b",
                temperature=default_params["temperature"],
                num_predict=default_params["max_tokens"],
                top_p=default_params["top_p"],
                top_k=default_params["top_k"],
                repeat_penalty=default_params["repeat_penalty"]
            )

    Test del módulo en un cuaderno Jupyter

    En la carpeta notebooks crea un cuaderno de Jupyter rag_test.ipynb. Antes de poder trabajar en el entorno rag_env debes registrar el kernel en Jupyter desde la terminal.

    Selecciona el kernel del entorno y añade al cuaderno las rutas para añadir la raíz al path,

    import sys
    import os
    
    sys.path.append(os.path.abspath(".."))

    Carga las variables del entorno

    from dotenv import load_dotenv
    load_dotenv()

    Importa la configuración de LLM

    from llm_config import get_llm

    Elige entre modelos cambiando el valor de llm

    llm = get_llm(llm="dsk")

    Realiza el test

    respuesta = llm.invoke("Explica qué es RAG en IA en 2 líneas")
    print(respuesta)

    Con esta configuración se ha establecido un entorno de trabajo completo para el desarrollo de sistemas RAG híbridos, combinando modelos locales y modelos en API dentro de una misma arquitectura.

    A lo largo de este proceso se ha definido una base técnica que permite trabajar de forma independiente a plataformas cerradas, replicando los conceptos del curso IBM RAG and Agentic AI en un entorno abierto, controlado y extensible. La integración de herramientas como LangChain, junto con el uso de modelos locales mediante Ollama y modelos en la nube como DeepSeek, proporciona la flexibilidad necesaria para experimentar, comparar resultados y optimizar el sistema según las necesidades.

    Este entorno no solo permite realizar las prácticas del curso, sino que sienta las bases para desarrollar soluciones reales, donde es posible equilibrar coste, rendimiento y control de los datos.

    A partir de este punto, el siguiente paso consiste en implementar el pipeline RAG completo, donde todas las piezas configuradas comenzarán a trabajar de forma conjunta y se podrá observar el verdadero valor de esta arquitectura.

    Actualización de entorno: modelo de embeddings

    En el entorno actual ya contamos con modelos de lenguaje (LLM) como Qwen o DeepSeek, que están diseñados para generación de texto. Sin embargo, para trabajar con arquitecturas RAG es imprescindible incorporar un segundo tipo de modelo: los modelos de embeddings.

    Estos modelos no generan texto, sino que transforman fragmentos de información en vectores numéricos que representan su significado semántico. Gracias a esto, es posible realizar búsquedas inteligentes, identificar similitudes entre textos y recuperar información relevante de forma eficiente.

    A diferencia de los LLM, los modelos de embeddings suelen ser más ligeros, pero trabajan de forma intensiva con memoria, especialmente cuando se generan vectores de múltiples documentos. En tu caso, según los recursos disponibles (16 GB de RAM ), es importante elegir un modelo que mantenga un buen equilibrio entre rendimiento y consumo.

    Para este entorno, la opción más recomendable es:

    nomic-embed-text

    Este modelo destaca por:

    • Buen rendimiento semántico para RAG
    • Bajo consumo de recursos
    • Ejecución fluida en entornos locales
    • Integración directa con Ollama

    Como alternativa más potente (pero más exigente en recursos) está: mxbai-embed-large
    (ideal si más adelante amplías RAM o trabajas con menos carga simultánea)

    Instalación del modelo de embeddings en Ollama

    Ejecuta en tu terminal:

    ollama pull nomic-embed-text
    Verificación (opcional pero recomendable)
    ollama list

    Deberías ver algo como:

    NAME                       ID              SIZE      MODIFIED
    nomic-embed-text:latest    0a109f422b47    274 MB    2 minutes ago
    qwen:4b                    d53d04290064    2.3 GB    3 days ago

    Separación de responsabilidades en el código

    Siguiendo buenas prácticas, no es recomendable mezclar la configuración de embeddings con la de los modelos generativos. Por ello, se introduce un nuevo módulo específico dentro del proyecto:

    embeddings_config.py

    De esta forma, cada tipo de modelo queda encapsulado en su propia capa, facilitando mantenimiento, escalabilidad y pruebas.

    Implementación del módulo de embeddings

    Se crea el archivo embeddings_config.py con una función que permite instanciar el modelo de embeddings de forma centralizada:

    from langchain_community.embeddings import OllamaEmbeddings
    
    def get_embeddings(provider="oll", model=None):
        """
        Devuelve un modelo de embeddings configurado.
        """
    
        if provider == "oll":
            return OllamaEmbeddings(
                model=model or "nomic-embed-text"
            )
    
        else:
            raise ValueError(f"Proveedor de embeddings no soportado: {provider}")

    Este enfoque permite desacoplar completamente el sistema y facilita futuros cambios, como probar otros modelos de embeddings sin modificar el resto del código.

    Uso dentro del flujo de trabajo

    Una vez definido el módulo, el uso es directo desde cualquier parte del proyecto:

    from config.embeddings_config import get_embeddings
    
    embeddings = get_embeddings()
    
    vector = embeddings.embed_query("¿Qué es RAG?")

    Con esto, ya es posible generar representaciones vectoriales tanto de consultas como de documentos.

    Resultado en la arquitectura híbrida

    Tras esta actualización, el entorno queda estructurado de forma clara:

    Embeddings → Ollama (local)
    
    LLM → DeepSeek / Ollama

    Esta separación es fundamental y refleja cómo se diseñan los sistemas RAG en entornos reales de producción, donde cada componente cumple una función específica dentro del pipeline.

  • Matrices en Python con Numpy

    NumPy (Numerical Python) es un paquete de código abierto ampliamente utilizado en ciencia e ingeniería.

    Realiza una amplia variedad de operaciones matemáticas avanzadas con alta eficiencia. En este artículo se verán varias funciones clave de NumPy como la creación de arrays, segmentación (slicing), indexación, cambio de forma (reshape) y apilamiento.

    Arrays con Numpy

    Los arrays son una de las estructuras de datos fundamentales de la librería NumPy, esenciales para organizar tus datos. Puedes pensar en ellos como una cuadrícula de valores, todos del mismo tipo.

    Si has utilizado listas de Python anteriormente, recordarás que son convenientes, ya que puedes almacenar diferentes tipos de datos. Sin embargo, las listas de Python tienen funciones limitadas y ocupan más espacio y tiempo de procesamiento que los arreglos de NumPy.

    NumPy proporciona un objeto array que es mucho más rápido y compacto que las listas de Python. A través de su amplia integración de API, la librería ofrece muchas funciones incorporadas que facilitan mucho el cómputo con solo unas pocas líneas de código. Esto puede ser una ventaja enorme al realizar operaciones matemáticas en grandes conjuntos de datos.

    El objeto de array en NumPy se llama ndarray, que significa “array n-dimensional”. Un arreglo 1-D representa una lista estándar de valores en una dimensión. Recuerda que en NumPy, todos los elementos dentro de un array son del mismo tipo.

    Crear arrays

    Puedes crear un array de una dimensión utilizando simplemente la función array() la cual recibe una lista de valores como argumento.

    a = np.array([1, 2, 3])
    print(a)
    [1 2 3]

    Otra forma de implementar un array es utilizando np.arange(). Esta función devolverá un arreglo de valores espaciados uniformemente dentro de un intervalo dado.

    b = np.arange(3)
    print(b)
    [0 1 2]

    Otro ejemplo usando las características de np.arange():

    # Crear un array que comienza por 1, termina en 20, incrementando valores por 3.
    c = np.arange(1, 20, 3)
    print(c)
    [ 1  4  7 10 13 16 19]

    La función np.linspace() puede generar valores espaciados uniformemente en un intervalo. En este ejemplo el intervalo es entre 0 y 100 y el número de elementos en el array, 5.

    lin_spaced_arr = np.linspace(0, 100, 5)
    print(lin_spaced_arr)
    [  0.  25.  50.  75. 100.]

    Por defecto los valores devueltos por np.linspace son de punto flotante (np.float64). Puedes especificar fácilmente tu tipo de datos usando dtype.

    c_int = np.arange(1, 20, 3, dtype=int)
    print(c_int)
    [ 1  4  7 10 13 16 19]

    Más arrays de NumPy

    Puedes crear arreglos fácilmente con funciones integradas tales como: 

    • np.ones(shape) – Devuelve un nuevo arreglo estableciendo los valores en uno.
    • np.zeros(shape) – Devuelve un nuevo arreglo estableciendo los valores en cero.
    • np.empty(shape) – Devuelve un nuevo arreglo sin inicializar.
    • np.random.rand(shape) – Devuelve un nuevo arreglo con valores randoms. 

    Arrays multidimensionales

    Con NumPy también puedes crear arreglos con más de una dimensión. En los ejemplos anteriores, trabajaste con arreglos 1-D, donde puedes acceder a sus elementos utilizando un único índice. Un arreglo multidimensional tiene más de una columna. Piensa en un arreglo multidimensional como una hoja de Excel donde cada fila/columna representa una dimensión.

    # Crear array de dos dimensiones (2-D)
    two_dim_arr = np.array([[1,2,3], [4,5,6]])
    print(two_dim_arr)
    [[1 2 3]
     [4 5 6]]

    Una forma alternativa de crear un arreglo multidimensional es cambiando la forma del arreglo 1-D inicial. Utilizando np.reshape() puedes reorganizar los elementos del arreglo anterior en una nueva forma.

    # 1-D array 
    one_dim_arr = np.array([1, 2, 3, 4, 5, 6])
    
    # Multidimensional array using reshape()
    multi_dim_arr = np.reshape(
                    one_dim_arr, # the array to be reshaped
                   (2,3) # dimensions of the new array
                  )
    # Print the new 2-D array with two rows and three columns
    print(multi_dim_arr)
    [[1 2 3]
     [4 5 6]]

    Encontrar el tamaño, la forma y la dimensión.

    Necesitarás saber cómo encontrar el tamaño, la dimensión y la forma de un array. Todos estos son atributos de un ndarray y se puede acceder a ellos de la siguiente manera:

    • ndarray.ndim – Almacena el número de dimensiones del arreglo.
    • ndarray.shape – Almacena la forma del arreglo. Cada número en la tupla denota la longitud de cada dimensión correspondiente.
    • ndarray.size – Almacena el número de elementos en el arreglo.

    Operaciones matemáticas con arrays

    En esta sección, verás que NumPy te permite realizar rápidamente sumas, restas, multiplicaciones y divisiones elemento por elemento, tanto para arreglos 1-D como multidimensionales.

    Las operaciones se realizan utilizando el símbolo matemático correspondiente: ‘+’, ‘-‘ y ‘*’. Recuerda que la suma de listas de Python funciona de manera completamente diferente, ya que concatena las listas creando una lista más larga; además, la resta y la multiplicación de listas de Python no funcionan.

    arr_1 = np.array([2, 4, 6])
    arr_2 = np.array([1, 3, 5])
    
    # Adding two 1-D arrays
    addition = arr_1 + arr_2
    print(addition)
    
    # Subtracting two 1-D arrays
    subtraction = arr_1 - arr_2
    print(subtraction)
    
    # Multiplying two 1-D arrays elementwise
    multiplication = arr_1 * arr_2
    print(multiplication)
    [ 3  7 11]
    [1 1 1]
    [ 2 12 30]

    Multiplicación de un vector por un escalar (broadcasting)

    Supón que necesitas convertir millas a kilómetros. Para hacerlo, puedes usar las funciones de arreglos de NumPy que has aprendido hasta ahora. Puedes lograrlo realizando una operación entre un arreglo (millas) y un solo número (la tasa de conversión, que es un escalar). Dado que 1 milla = 1.6 km, NumPy calcula cada multiplicación dentro de cada celda. 

    Este concepto se llama broadcasting (transmisión), el cual te permite realizar operaciones específicamente en arreglos de diferentes formas.

    vector = np.array([1, 2])
    vector * 1.6
    array([1.6, 3.2])

    Indexación y segmentación (slicing)

    La indexación es muy útil ya que te permite seleccionar elementos específicos de un array. También te permite seleccionar filas, columnas o planos completos, como verás en futuras tareas para arreglos multidimensionales. 

    Indexación

    Seleccionemos elementos específicos de los arreglos dados. 

    # Seleccionar el tercer elemento del array.
    a = ([1, 2, 3, 4, 5])
    print(a[2])
    
    # Seleccionar el primer elemento.
    print(a[0])
    3
    1

    Para arrays multidimensionales de forma \(n\), para indexar un elemento específico, debes ingresar \(n\) índices, uno por cada dimensión.

    # Indexing on a 2-D array
    two_dim = np.array(([1, 2, 3],
              [4, 5, 6], 
              [7, 8, 9]))
    
    # Select element number 8 from the 2-D array using indices i, j.
    print(two_dim[2][1])
    8

    Segmentación (Slicing)

    La segmentación te devuelve una sublista de los elementos que especifiques del array. La notación de segmentación define un valor de inicio y uno de fin, y copia la lista desde el inicio hasta el final, pero sin incluir este último (el final es exclusivo).

    La sintaxis es:

    arreglo[inicio:fin:paso]

    Si no se pasa ningún valor al inicio, se asume que inicio = 0; si no se pasa ningún valor para el fin, se asume que fin = longitud del arreglo y si no se pasa ningún valor para el paso, se asume que paso = 1.

    # Slice the array a to get the array [2,3,4]
    sliced_arr = a[1:4]
    print(sliced_arr)
    [2, 3, 4]

    Apilamiento (Stacking)

    Finalmente, el apilamiento es una característica de NumPy que permite una mayor personalización de los arrays. Consiste en unir dos o más arreglos, ya sea de forma horizontal o vertical, lo que significa que se realiza a lo largo de un nuevo eje.

    • np.vstack() – apila verticalmente.
    • np.hstack() – apila horizontalmente.
    • np.hsplit() – divide un arreglo en varios arreglos más pequeños.
    a1 = np.array([[1,1], 
                   [2,2]])
    a2 = np.array([[3,3],
                  [4,4]])
    print(f'a1:\n{a1}')
    print(f'a2:\n{a2}')
    a1:
    [[1 1]
     [2 2]]
    a2:
    [[3 3]
     [4 4]]

    Apilar los arrays verticalmente

    # Stack the arrays vertically
    vert_stack = np.vstack((a1, a2))
    print(vert_stack)
    [[1 1]
     [2 2]
     [3 3]
     [4 4]]

    Apilar horizontalmente:

    # Stack the arrays horizontally
    horz_stack = np.hstack((a1, a2))
    print(horz_stack)
    [[1 1 3 3]
     [2 2 4 4]]

  • Singularidad, dependencia lineal y el determinante

    Uno de los mayores errores al aprender álgebra lineal es pensar que trata de números, fórmulas y cálculos. Esa es solo la superficie. En realidad, lo que estás aprendiendo es algo mucho más poderoso: cómo interpretar y combinar información.

    El lenguaje de las ecuaciones

    Para entenderlo, hay que hacer un pequeño cambio de perspectiva. Una ecuación no es una operación. Es una afirmación sobre la realidad. Es una sentencia, \( x+y=10 \), está diciendo: “hay dos cantidades cuya suma es 10”.

    Con base en esta afirmación, piensa en una ecuación como una oración que describe al mundo:

    • El perro es negro.
    • El gato es naranja.

    Ambas son sentencias que aportan información y al combinarlas tienes un conjunto de afirmaciones que combinadas te permiten deducir información al igual que un sistema de acuaciones.

    Cuando aparece el sistema: combinar información

    Imagina que estás resolviendo un problema como si fuera un pequeño caso de investigación. Tienes varias pistas y cada una aporta información parcial. Una pista puede decirte algo genérico, otra puede acotar un poco más, y juntas empiezan a dibujar una imagen más precisa. Eso es exactamente lo que hace un sistema de ecuaciones: combinar piezas de información para reducir la incertidumbre.

    Pero no todas las combinaciones de información funcionan igual. Y aquí es donde el álgebra deja de ser mecánica y empieza a ser lógica. Cuando juntas varias sentencias, solo pueden pasar tres cosas:

    1. Que cada nueva ecuación aporte algo distinto y coherente con lo anterior. En ese caso, el sistema converge hacia una única solución, se denomina sistema no singular. En términos matemáticos, el sistema es completo, consistente y determinado. En términos prácticos, tienes suficiente información bien estructurada.
    2. Las sentencias no se contradicen, pero tampoco aportan nada nuevo. Es como si alguien repitiera la misma pista con otras palabras; se denomina sistema singular. En matemáticas esto se traduce en ecuaciones dependientes, y el resultado es que no hay una única solución, sino infinitas.
    3. Las sentencias se contradicen. Una dice una cosa y otra dice lo contrario, se denomina también sistema singular. En ese momento, el sistema deja de tener sentido. Has construido una realidad imposible.

    Los tres comportamientos posibles

    Sistema completo, no singular

    Cada ecuación aporta información nueva. No se repiten, no se contradicen. Se refuerzan. En ese caso, el sistema converge hacia una única solución. Esto significa que has descrito la realidad con suficiente precisión como para identificar un único resultado. Geométricamente, si quieres visualizarlo, es el punto donde las líneas se cruzan.

    En un sistema de ecuaciones, por ejemplo, que simula una compra de peras y bananas, donde tenemos que descubrir el precio de cada una. El primer día se compran 1 pera y 1 banana por 10€; el día 2 se compran 1 pera y 2 bananas por 12€.

    $$ a + b = 10$$

    $$ a + 2b = 12$$

    Solución única: \( a = 8, b = 2 \)

    Sistema redundante, singular

    Aquí ocurre algo más sutil. Las ecuaciones no están mal. No hay contradicción. Pero una no aporta nada nuevo respecto a la otra. En ese caso, el sistema no falla, pero tampoco se define completamente. Tienes infinitas soluciones. No porque el sistema sea incorrecto, sino porque no tienes suficiente información independiente.

    En este caso la compra se expresa así:

    $$ a + b = 10$$

    $$ 2a + 2b = 20$$

    Infinitas soluciones, todos los conjuntos de dos números que sumen 10.

    Sistema contradictorio, singular

    Aquí el sistema se rompe. Una ecuación afirma algo, la otra afirma lo contrario. No existe ningún valor que pueda satisfacer ambas simultáneamente. Y esto no es un problema de cálculo. Es un problema lógico.

    En este caso la compra se expresa así:

    $$ a + b = 10$$

    $$ 2a + 2b = 24$$

    No hay soluciones posibles.

    Veamos este sistema de sentencias en un pequeño test:

    • Entre el perro, el gato y el ave, uno es rojo
    • Entre el perro, el gato, uno es naranja
    • El perro es negro

    Preguntas:

    • ¿De qué color es el ave?
    • ¿Es un sistema singular o no singular?

    Cada sentencia del sistema va descubriendo con la solución de cada sentencia. Ninguna de las sentencias por sí sola permite llegar a la solución. Es la combinación progresiva de información la que va acotando el problema hasta hacerlo determinable.

    Desde el punto de vista del álgebra lineal, este sistema es no singular. ¿Por qué? Porque cada sentencia aporta información nueva y no redundante. No hay contradicciones ni repeticiones. El sistema está bien definido y conduce a una única solución posible.

    En un dataset real, cada registro funciona como una sentencia. Cada variable añade contexto, y el modelo lo único que hace es combinar esas “afirmaciones” para encontrar patrones. Si las sentencias son coherentes y aportan información distinta, el modelo puede aprender. Si son redundantes, el modelo se vuelve ineficiente. Y si son contradictorias, el modelo simplemente no puede construir una representación fiable de la realidad.

    Los sistemas de ecuaciones representados gráficamente

    La singularidad solo depende de la matriz de coeficientes:

    Las constantes no importan a la hora de determinar la singularidad. Si los lleváramos todos a 0, mantendrían la singularidad.

    Ecuaciones dependientes e independientes

    La dependencia lineal está relacionada con lo que vimos anteriormente sobre si las ecuaciones aportan información nueva o están repitiendo lo mismo.

    Dependencia Lineal

    Un conjunto de ecuaciones (o una matriz) es linealmente dependiente si:

    • Una de ellas puede obtenerse a partir de las otras mediante multiplicaciones o sumas.

    Ejemplo básico:

    $$ x + y = 1$$

    $$2x + 2y = 2$$

    o lo que es lo mismo

    $$A = \begin {bmatrix} 1 & 1 \\ 2 & 3 \end{bmatrix}$$

    Revela que la segunda ecuación es simplemente: \( 2 \cdot (x + y)\)

    • No aporta información nueva
    • Es redundante

    Conclusión: dependencia lineal

    Caso más sutil (nivel siguiente)

    No siempre es un múltiplo directo:

    $$\text{Fila 2} = \frac{\text{Fila 1} + \text{Fila 3}}{2}$$

    • Sigue siendo dependencia lineal
    • Porque una fila se construye con otras

    Independencia lineal

    Un conjunto de ecuaciones (o una matriz) es linealmente independiente si:

    • Ninguna ecuación puede construirse a partir de las demás

    Ejemplo básico:

    $$x + y = 1$$

    $$x – y = 3$$

    en forma matricial:

    $$A = \begin {bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}$$

    No existe ningún número que permita convertir una ecuación en la otra.

    • Cada una aporta información diferente
    • Conclusión: independencia lineal

    Relación con singularidad

    • Dependencia lineal → matriz singular
    • Independencia lineal → matriz no singular

    En ciencia de datos:

    • Dependencia → variables redundantes
    • Independencia → variables útiles

    El rango: cómo medir la información de un sistema

    Hasta ahora has visto algo muy importante: no todas las frases (o ecuaciones) aportan lo mismo. Algunas añaden información nueva y otras simplemente repiten lo que ya sabemos. Pero en matemáticas —y especialmente en machine learning— necesitamos medir esa información de forma precisa. Esa información nos la brinda el rango de una matriz

    Recuerda los sistemas de frases que vimos:

    • “El perro es negro”
    • “El gato es naranja”

    Este sistema aporta dos piezas de información independientes.

    Sin embargo:

    • “El perro es negro”
    • “El perro es negro”

    Aquí solo hay una pieza de información real.

    Y en el caso más extremo, otros sistemas no aportan ninguna información útil para el problema que queremos resolver.

    ¿Qué es el rango?

    El rango traduce exactamente esa idea al lenguaje matemático: El rango de un sistema (o de su matriz) es la cantidad de información independiente que contiene.

    • Cuenta cuántas ecuaciones realmente aportan algo nuevo.
    • Ignora las que son redundantes o no aportan información útil.

    Conexión con sistemas de ecuaciones

    RangoQué significaSoluciones
    AltoMucha informaciónMuy restringidas
    MedioAlgo de redundanciaInfinitas soluciones
    BajoPoca o ninguna informaciónMáxima libertad

    Interpretación geométrica (clave en machine learning)

    Este concepto no es solo algebraico, también es geométrico:

    • Rango máximo → las soluciones se reducen a un punto
    • Rango intermedio → las soluciones forman una línea
    • Rango bajo → las soluciones ocupan un plano o más dimensiones

    Esto es exactamente lo que ocurre cuando entrenas modelos:

    • Más información → modelo más definido
    • Menos información → más incertidumbre

    Cómo se calcula el rango en la práctica

    Para calcular el rango no necesitas analizar ecuación por ecuación.

    En su lugar, haces lo siguiente:

    1. Transformas la matriz usando operaciones de fila
    2. La llevas a una forma más simple (forma escalonada)
    3. Cuentas cuántas filas siguen siendo útiles

    En machine learning, este concepto aparece constantemente:

    • Compresión de imágenes
    • Reducción de dimensionalidad
    • Eliminación de ruido
    • Modelos más eficientes

    De hecho, muchas técnicas avanzadas se basan en reducir el rango sin perder la información esencial.

    El determinante

    Otra forma para saber si un sistema es singular es a través del determinante. Un número que se calcula a partir la matriz y que te dice inmediatamente:

    • Si es 0 → matriz singular
      • Una fila es combinación de otra
      • Hay dependencia lineal
      • El sistema no tiene solución única
    • Si es ≠ 0 → matriz no singular
      • Filas independientes
      • Solucion unica

    Calcular el determinante

    Dada una matriz 2×2:

    $$\begin{bmatrix} a & b \\ c & d \end{bmatrix}$$

    El determinante es: \( ad – bc \).

    Matrices mas grandes

    El proceso es similar, pero con más diagonales,

    $$\begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix}$$

    Primer diagonal: \( a * e * i \).

    Segundo diagonal: \( b * f * g \).

    Tercer diagonal: \( c * d * h \).

    Luego se realiza la operación de sustracción a la inversa:

    $$\begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix}$$

    $$Det = (a * e * i) + (b * f * g) + (c * d * h)$$

    $$- (c * e * g) – (f * h * a) – (b * d * i)$$

    Atajo para el de las matrices con ceros debajo de la diagonal.

    Cuando una matriz tiene solo ceros debajo de la diagonal central, hace que todos los cálculos de las diagonales restantes sean 0 excepto el de la diagonal principal y el determinante será el resultado de este.

    Ejemplo: el resultado del determinante en esta matriz es 6 (1 *2*3).

    ¿Cómo se ve esto en el mundo de los datos?

    Lenguaje NaturalÁlgebra LinealCiencia de Datos
    SentenciaEcuación (Fila)Registro / Observación
    SujetosVariables Características (Features)
    ConsistenciaDeterminante Datos limpios y útiles
    RedundanciaDependencia LinealMulticolinealidad (Ruido)

    Y cuando operas con matrices, lo que estás haciendo en realidad es manipular información estructurada. Si programas un modelo de Machine Learning sin entender esto, tendrás problemas:

    1. Datos Redundantes: Si le das a tu modelo el “Salario Mensual” y el “Salario Anual”, no está aprendiendo más. Estás desperdiciando potencia de cómputo en información repetida (Multicolinealidad).
    2. Datos Contradictorios: Si en tu base de datos el mismo cliente aparece con 20 años y con 70 años en la misma compra, el modelo se confunde y falla.
    3. El Rango de la Matriz: En álgebra, el “Rango” es simplemente el número de pistas reales y únicas que tienes. Si tienes 100 ecuaciones pero el rango es 2, en realidad solo tienes 2 pistas útiles y 98 ecos.

  • Anaconda: guía práctica para gestionar entornos, paquetes y flujos de trabajo

    Instalar Anaconda te da acceso a conda, Python y miles de otras herramientas populares. Instala automáticamente más de 300 paquetes listos para el trabajo en Data Science.

    Anaconda Navigator proporciona una interfaz gráfica de usuario: Anaconda Desktop para interactuar con conda, sin utilizar la línea de comandos. Si prefiere trabajar desde la línea de comandos, puede interactuar con conda directamente a través de Anaconda Prompt.

    Manejo de entornos

    El entorno (base) NO es para proyectos, úsalo solamente para la gestión de conda y tareas globales. El proceso de instalación de conda crea un entorno llamado base, donde se instala conda. Sin embargo, al comenzar un nuevo proyecto, se recomienda crear un nuevo entorno. Esto facilita el mantenimiento y la reproducibilidad de los entornos, además de mantener su estabilidad.

    Crear un nuevo entorno

    conda create --name <ENV_NAME> <PACKAGE>=<VERSION> <PACKAGE> <PACKAGE>

    Puedes añadir paquetes al entorno especificando o no la versión. Ejemplo:

    conda create --name hello-env python=3.14 pandas beautifulsoup4 

    Activar un entorno

    conda activate <ENV_NAME>

    Cambiar entre entornos y visualizar entornos existentes

    conda info --envs
    conda activate <ENV_NAME>

    Bloquear un entorno

    Bloquear un entorno crea un entorno completamente especificado, con todos los paquetes utilizados en el proyecto y sus dependencias configuradas a una versión específica. Para bloquear tu proyecto, necesitas conda-project instalado el paquete en el entorno que deseas bloquear. Instala el paquete ejecutando los siguientes comandos:

    conda activate <ENV>
    conda install conda-project

    Si tu proyecto no contiene un archivo, environment.yml créalo ejecutando el siguiente comando:

    conda-project init

    A continuación, puedes bloquear el entorno de tu proyecto ejecutando el siguiente comando:

    conda-project lock

    Al bloquear tu proyecto, se genera un archivo conda-lock.default.yml que puedes exportar para compartirlo con otros.

    Compartir un entorno

    Compartir tu entorno con otra persona le permite usar conda para recrear tu entorno en su máquina. Para compartir un entorno y sus paquetes de software, debe exportar las configuraciones de su entorno a un archivo .yml en un entorno que este activado con el comando.

    conda env export > environment.yml

    El archivo se guarda en su directorio de trabajo y se puede compartir.

    Desactivar un entorno

    Lo más recomendable es desactivar el entorno cuando hayas terminado de trabajar en él. Cuando desactivas un entorno, conda vuelve al entorno que estaba activado anteriormente. Para desactivar su entorno activo, ejecute el siguiente comando:

    conda deactivate

    Eliminar un entorno

    Para eliminar un entorno, ejecute el siguiente comando. Si, por cualquier motivo, necesita eliminar manualmente un directorio de entorno, no utilice el explorador de archivos . Si lo hace, eliminará el contenido del entorno y liberará espacio en su equipo, pero la ruta del entorno permanecerá en su archivo environments.txt.

    conda remove --name <ENV_NAME> --all

    Gestión de Canales

    En el ecosistema de Conda, los canales son repositorios remotos donde se alojan los paquetes. Los tipos de canales más comunes:

    1. Defaults: Es el canal oficial gestionado por Anaconda. Contiene paquetes probados y validados para ser estables y compatibles entre sí.
    2. Conda-Forge: Es un canal comunitario (el más grande y popular). Al ser mantenido por la comunidad, suele tener versiones más actualizadas de las librerías y una variedad mucho mayor de paquetes que el canal por defecto.
    3. Canales específicos: Existen repositorios especializados para nichos científicos o técnicos, como bioconda (para bioinformática).

    Conda permite tener varios canales activos a la vez, pero sigue un orden jerárquico. Si un mismo paquete (por ejemplo, pandas) está disponible en dos canales distintos, Conda lo descargará del que tenga mayor prioridad (el que aparezca primero en tu configuración).

    Visualización de los canales disponibles

    Para ver qué canales están configurados actualmente en conda ejecute el siguiente comando:

    conda config --show channels

    Configuración de canales

    Conda lee la configuración de sus canales desde el archivo .condarc. Para añadir, eliminar o reordenar canales, deberá editar este archivo. Para encontrarlo utilice el comando:

    conda config --show-sources

    Puedes añadir o eliminar canales de tu lista channels: usando comandos de conda en o editando manualmente tu el archivo .condarc.

    conda config <FLAG> channels <CHANNEL>
    FlagAcción
    --addAgrega un canal al principio de channels:.
    --prependAgrega un canal al principio de channels:.
    --appendAgrega un canal al final de channels:.
    --removeElimina un canal de channels:.

    Ejemplo:

    conda config --add channels conda-forge

    Configurar valores predeterminados

    La entrada defaults en tu lista de channels: es un alias especial. Cuando conda detecta busca en los canales listados default_channels: en orden descendente. Este  también se puede configurar utilizando comandos de conda o editando manualmente .condarc.

    conda config --add default_channels <CHANNEL>

    Reemplaza <CHANNEL> con la URL del canal que deseas agregar.

    Instalación de paquetes desde un canal específico

    El uso de la sintaxis de dos puntos dobles instala el paquete desde el canal especificado, pero instala las dependencias de ese paquete desde los canales que aparecen en el  .condarc siguiendo el orden de prioridad de los canales.

    conda install <CHANNEL>::<PACKAGE>

    Gestión de paquetes

    Buscando paquetes conda

    La función de búsqueda de Conda le permite buscar a través de canales para comprobar si un paquete específico está disponible, qué versiones existen en diferentes canales o qué ya está instalado en su entorno local.

    conda search <PACKAGE>

    Buscar un canal específico

    conda search <CHANNEL>::<PACKAGE>

    Búsqueda en entornos locales

    Utilice la bandera --envs para buscar un paquete en sus entornos locales:

    conda search --envs <PACKAGE>

    Instalar paquetes

    Para instalar un solo paquete, ejecute el siguiente comando:

    conda install <PACKAGE>

    Para instalar un paquete en un entorno que no sea su entorno activo actual, especifique el nombre del entorno:

    conda install <PACKAGE> --name <ENVIRONMENT>

    Especificar un canal:

    conda install <CHANNEL>::<PACKAGE>

    Especificación de versiones de paquetes

    Por defecto, al instalar paquetes desde la línea de comandos, conda recupera las versiones más recientes de los paquetes solicitados (y sus dependencias) que sean compatibles con el entorno actual.

    conda install <PACKAGE>=<VERSION>

    Actualización de paquetes

    Para actualizar un solo paquete, ejecute el siguiente comando:

    conda update <PACKAGE>

    Cuando actualizas un paquete, conda también puede actualizar otros paquetes del entorno para mantener la compatibilidad, o instalar nuevos paquetes necesarios para las dependencias actualizadas. Esto ayuda a evitar que tu entorno se dañe debido a cambios en las dependencias. Para evitar que conda actualice cualquier paquete que no sea el que especifiques, usa la bandera --no-update-deps

    Actualizar varios paquetes

    conda update <PACKAGE> <PACKAGE> <PACKAGE>

    Especificar un canal para las actualizaciones de paquetes

    conda update <PACKAGE> --override-channels --channel <CHANNEL>

    Actualizar todos los paquetes

    Es posible que la ejecución de este comando no actualice todos los paquetes de un entorno determinado a sus últimas versiones. Si la última versión de un paquete es incompatible con otros paquetes instalados en el entorno, conda solo actualizará ese paquete a la última versión compatible .

    conda update --all

    Usando pip en Conda

    La mayoría de los paquetes populares del repositorio PyPI están disponibles en el repositorio público de Anaconda , Anaconda.org o conda-forge . Sin embargo, es posible que necesite usar pip si un paquete o una versión específica no está disponible a través de conda. Instalar paquetes usando pip modifica tu entorno conda. Sin embargo, conda desconoce estas modificaciones. Como resultado, cuando conda intenta modificar el entorno posteriormente, existe una alta probabilidad de que surjan conflictos de dependencias entre los paquetes controlados por conda y los paquetes de pip no controlados, lo que puede provocar un entorno dañado.

    Comprender conda y pip

    Aunque algunas funcionalidades de conda y pip se solapan (en concreto, la capacidad de instalar paquetes de Python), fueron diseñadas y deben usarse con fines diferentes.

    • Pip es la herramienta recomendada por la Autoridad de Empaquetado de Python para instalar paquetes del Índice de Paquetes de Python (PyPI).
    • Conda, por otro lado, es un gestor de paquetes y entornos multiplataforma que instala y gestiona paquetes tanto del repositorio público de Anaconda como de Anaconda.org.

    Otras diferencias clave entre conda y pip incluyen:

    condapip
    Formato de distribución del paqueteBinariosWheels o fuente
    ¿Requiere compiladores?No
    Tipos de paquetesCualquiera (Python, R, C++, etc.)Solo Python
    ¿Creación de entorno?Sí, incorporadoNo, requiere virtualenv o venv.
    ¿Resolución de dependencias?No
    Fuentes de paquetesRepositorio de Anaconda, Anaconda.orgPyPI

    Crear un entorno conda que incluya paquetes pip

    Para crear un entorno estable que incluya paquetes pip, Anaconda recomienda escribir un archivo environment.yml y luego construir un entorno a partir de ese archivo. Aunque este método requiere más tiempo de configuración, ofrece varias ventajas:

    • Control sobre el orden de compilación, las versiones y los canales de los paquetes.
    • Actualizaciones de entorno sencillas
    • Mayor reproducibilidad y facilidad para compartir mediante un archivo .yml.

    Al escribir el archivo, asegúrese de agregar pip y sus dependencias al final, ya que conda crea los entornos en el orden en que aparecen. Ejemplo:

    name: myenv           # Nombre del entorno
    dependencies:         # Lista de los paquetes que se incluyen
        - python=3.12    
        - bokeh>=2.4.2
        - flask
        - pip             # Install pip en el entorno
        - pip:            # Incluir los paquetes pip al final
            - Flask-Testing

    La documentación oficial de conda incluye más información sobre cómo crear archivos de entorno manualmente , así como especificaciones de coincidencia de paquetes .

    Creación de un entorno a partir de un archivo environment.yml

    Para crear un entorno a partir de un archivo environment.yml, ejecute el siguiente comando desde el directorio que contiene el archivo:

    conda env create --file environment.yml

    Actualizar un entorno con un archivo environment.yml

    Si alguna vez necesita agregar paquetes a su entorno, realizar cambios en las versiones de los paquetes o eliminar paquetes, actualice el archivo environment.yml y luego vuelva a construir el entorno ejecutando el siguiente comando desde el directorio que contiene el archivo:

    conda env update --file environment.yml --prune

    --prune elimina cualquier paquete huérfano del entorno.

    Uso de pip install en un entorno conda

    Debido a que conda no reconoce las actualizaciones de entorno realizadas por pip, el uso de pip en su entorno debe ser la última acción que se realice al construir el entorno.

    conda install <PACKAGE> <PACKAGE> pip

    No ejecute pip install en su entorno base. Cree un entorno conda independiente para aislar los cambios.

    Solucionador de dependencias

    Una de las principales características de conda es su capacidad para gestionar paquetes de software y sus dependencias. La gestión de dependencias puede resultar compleja, sobre todo cuando un paquete tiene muchas dependencias. Conda utiliza un algoritmo llamado 
    solucionador de dependencias , que determina qué versiones de qué paquetes deben instalarse para satisfacer (o resolver) todas las dependencias sin conflictos de versiones.

    Gestión de errores del solucionador

    Aunque el solucionador de conda está diseñado para instalar paquetes y sus dependencias sin generar conflictos de versiones, estos pueden ocurrir. Este tutorial explicará los posibles escenarios de conflicto del solucionador y cómo solucionarlos.

    Gestionar Python en entornos conda

    Por defecto, al instalar o actualizar paquetes en un ambiente, conda recupera las versiones más recientes posibles del/de los paquete(s) solicitado(s) y sus dependencias que sean compatibles con el entorno actual.

    Algunos paquetes (o versiones de paquetes) solo son compatibles con ciertas versiones de Python. Si intentas instalar un paquete que no es compatible con la versión de Python que usas en tu entorno, conda no lo instalará.

    En este caso, suele ser mejor crear un nuevo entorno que use la versión de Python requerida, junto con los demás paquetes que necesitas.Para crear un nuevo entorno con una versión específica de Python y todos los paquetes que necesitas, ejecuta el siguiente comando:

    conda create --name <ENVIRONMENT> python=<VERSION> <PACKAGE> <PACKAGE> <PACKAGE>

    Integraciones de conda

    Consulta esta página para integrar conda con:

    • Docker
    • Authenticated Docker builds
    • Snowflare Snowpark
    • TensorFlow

    Más de Anaconda

    • Herramientas Anaconda: diseñadas para optimizar tus flujos de trabajo de ciencia de datos, aprendizaje automático e inteligencia artificial. 
    • Plataforma Anaconda (Nube): Anaconda Platform proporciona una plataforma segura y repositorio centralizado. Dónde puedes controlar el acceso de tu organización a paquetes software de código abierto y realizar un seguimiento de las vulnerabilidades del software.
    • Data Science & AI Workbench: Workbench es una plataforma de ciencia de datos escalable, segura y preparada para entornos empresariales que permite a los equipos gestionar los activos de ciencia de datos, colaborar e implementar sus proyectos de ciencia de datos.

    Flujo de trabajo

    Crea el nuevo entorno para el proyecto y actívalo

    conda create --name <ENV_NAME>
    conda activate <ENV_NAME>

    Instala los paquetes necesarios para el proyecto

    conda install pandas scipy numpy matplotlib seaborn scikit-learn 

    Trabajar con JupyterLab – conda

    Para que el entorno aparezca en la lista de opciones de tu cuaderno, ejecuta este comando y luego puedes seleccionarlo en la lista de kernels disponibles.

    python -m ipykernel install --user --name=<ENV_NAME> --display-name "NAME_ALIAS"
    

    Trabajar con Visual Studio Code – conda

    Presiona Ctrl+Mayús+P, busca Python: Select Interpreter y elige tu entorno de la lista. Puedes instalar la extensión nb_conda_kernels en tu entorno base (conda install nb_conda_kernels), la cual detectará automáticamente todos tus entornos de Conda que tengan ipykernel instalado. 

  • AdventureWorks Power BI Data Analyst

    AdventureWorks Power BI Data Analyst

    Proyecto de Dashboard 360° para AdventureWorks 2022. Se extrajeron y exploraron los datos con SQL, se limpiaron y transformaron usando Python y Pandas, y finalmente se visualizaron en Power BI. El proyecto abarca todo el proceso ETL: consultas y modelado de datos en SQL, manipulación y preparación de datos en Python, y construcción de visualizaciones interactivas en Power BI para análisis integral de finanzas, ventas, clientes y productos.

    El proyecto de curso original solo incluía elaborar un dashboard con datos provenientes de tablas de Excel aportadas por el curso.

    Tecnologías utilizadas

    • SQL Server (base transaccional)
    • SQL Server Management Studio + VS Code (extensión MSSQL)
    • Python (pandas, pyodbc, sqlalchemy, seaborn)
    • Power BI Desktop + Power BI Service

    Diagrama de ETL del proyecto

    1. Exploración profunda de la base de datos

    Lo primero y más importante: nunca empieces a extraer datos sin entenderlos al 100 %.

    Acciones realizadas:

    • Generé un script SQL individual de exploración para cada una de las 18 tablas relevantes. Cada script incluye:
      • Descripción de las variables
      • Rango de fechas
      • Valores únicos y ejemplos de las columnas clave
      • Identificación de claves primarias y foráneas reales (aunque no estén definidas en la BD)

    Como resultado se creó un Diccionario de Datos completo en Excel y un diagrama relacional.

    2. Diseño del Modelo Analítico

    Definición de un modelo dimensional robusto, siguiendo las mejores prácticas de arquitectura BI. Para garantizar un rendimiento óptimo en Power BI y un análisis consistente, se diseñó un modelo en estrella (Star Schema), manteniendo únicamente algunas jerarquías naturales con forma de snowflake cuando aportaban claridad sin afectar el rendimiento.

    Definición de la Granularidad del Hecho

    Establecer la granularidad exacta de cada tabla de hechos:

    • Ventas: transacción por línea de pedido.
    • Inventario: snapshot por producto y almacén.
    • Finanzas: nivel contable por periodo.
    • Encuestas: respuesta por cliente.

    Definición de Métricas y KPIs Clave

    Se seleccionaron los indicadores esenciales para la visión 360°:

    • Ventas Totales (Sales Amount)
    • Coste del Producto (Total Product Cost)
    • Beneficio Bruto (Profit)
    • Margen (%)
    • Tendencias temporales: variaciones y crecimiento frente a periodos anteriores (YOY/MOM)
    • Valor Medio del Pedido (Average Order Value / AOV)
    • Rotación de Inventario y Alertas de Bajo Stock
    • Presupuesto vs Real (indicadores financieros)
    • Efectividad de Promociones

    Entregables Generados

    Durante esta fase se produjeron los documentos técnicos clave del proyecto:

    • Modelo_Dimensional: esquema visual del modelo en estrella.
    • Documento de Requerimientos Funcionales (.pdf): base formal para validar las necesidades del negocio con cada área.

    3. Extracción desde SQL Server

    Preparación de datos mediante vistas SQL

    Se crearon vistas en SQL Server, segun el documento de Requerimientos, que estructuran y preparan los datos para su posterior procesamiento en Python y análisis en Power BI.

    Las vistas incluyen:

    • Claves y relaciones: Se conservaron solo las necesarias para unir hechos y dimensiones sin duplicar información.
    • Atributos descriptivos y demográficos: Se mantuvieron columnas relevantes para análisis y segmentación (edad, género, ingresos, educación, categoría de producto), mientras que la información sensible de clientes se anonimizó o eliminó.
    • Métricas y valores numéricos: Se seleccionaron columnas necesarias para calcular KPIs como ventas, margen, ticket promedio, inventario y cumplimiento de cuotas.

    Además, se aplicaron transformaciones básicas dentro de las vistas, como el cálculo de edad a partir de la fecha de nacimiento y la unificación de jerarquías de productos, simplificando la carga y limpieza de datos en Python.

    Resultado: Una capa de datos limpia, consistente y segura, lista para análisis avanzado en Python y visualización interactiva en Power BI, cumpliendo los objetivos del dashboard ejecutivo.

    4. Limpieza y Transformación de Datos

    En esta fase se consolidan los datos de SQL Server y se preparan para su análisis en Power BI, asegurando datasets limpios, consistentes y listos para modelado.

    Extracción de Datos

    Se crearon cuadernos de Jupyter para extraer vistas SQL a CSV:

    • Conexión a SQL Server mediante SQLAlchemy.
    • Exportación de cada vista a /data/raw, eliminando prefijos vw_ en los nombres.
    • Mensajes de seguimiento en consola para validar éxito o fallo de cada extracción.

    Carga y Exploración

    Los CSV se cargaron en DataFrames de Pandas para exploración:

    • Inspección de registros y tipos de datos.
    • Identificación de valores nulos e inconsistencias.
    • Funciones reutilizables (CheckData) para revisión rápida de los datasets.

    Limpieza y Transformación

    Se aplicaron transformaciones consistentes por tabla de hechos:

    • Conversión de tipos, fechas y valores categóricos.
    • Tratamiento de nulos mediante reglas específicas según columna y tipo.
    • Estilo de limpieza coherente con los notebooks iniciales, mostrando resúmenes y primeras filas.

    Automatización del ETL

    Se desarrollaron scripts para consolidar el flujo:

    1. config.py: define rutas de trabajo y conexión a SQL Server con SQLAlchemy.
    2. etl_pipeline.py: ejecuta automáticamente:
      • Carga consultas SQL.
      • Genera DataFrames.
      • Limpia los datos según reglas definidas.
      • Guarda datasets limpios en /data/processed.
      • Proporciona logs y seguimiento en consola.

    Este enfoque permite reproducir todo el proceso con un solo comando, manteniendo la trazabilidad y calidad de los datos para análisis en Power BI.

    5. Exportación de Datasets Limpios

    Tras la limpieza y transformación, los datasets se preparan para su consumo en Power BI:

    • Se exportan en formato Parquet (recomendado por eficiencia) o CSV comprimido para reducir espacio.
    • Cada tabla del modelo se guarda en un archivo independiente: dimCliente.parquet, factVentas.parquet, etc.
    • Se incluye la fecha de generación en el nombre del archivo o en una tabla de control.
    • Se mantiene un registro maestro de datasets (datasets_control.xlsx) que documenta: nombre del archivo, número de filas y columnas, fecha de exportación.
    • Todos los archivos se almacenan en /data/final/, listos para el análisis.

    Este procedimiento asegura que los datos sean consistentes, auditables y fácilmente reutilizables por cualquier miembro del equipo BI.

    6. Importación y Modelo en Power BI

    La fase final consiste en construir el modelo semántico para análisis y reporting:

    • Se importan todos los archivos Parquet al proyecto de Power BI.
    • Se define un modelo estrella con relaciones claras entre hechos y dimensiones.
    • Columnas técnicas o auxiliares se ocultan para mejorar la experiencia del usuario.
    • Se crean medidas DAX avanzadas para KPIs clave: ventas, margen, cuotas, inventario, etc.
    • Se aplican optimizaciones de rendimiento: agregaciones, filtrado y segmentación, para mejorar la velocidad y la eficiencia.

    El resultado es un archivo Proyecto_Ventas.pbix completamente funcional y un documento de Modelo de Datos (Modelo de Datos en Power BI.pdf) con capturas de relaciones, medidas y estructura general, que permite navegar, analizar y tomar decisiones basadas en datos de manera rápida y confiable.