AI Agents · Lección

Agentes de resúmenes estadísticos

Análisis de distribuciones, correlaciones y valores atípicos en informes generados por agentes.

Lección 4 de 413 pasos

Agentes de resúmenes estadísticos es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

Por qué son importantes los resúmenes estadísticos

Los datos sin procesar rara vez son útiles por sí mismos. Un agente de análisis de datos se vuelve realmente valioso cuando puede calcular automáticamente resúmenes estadísticos y convertir los números en conclusiones en lenguaje sencillo.

Esta lección cubre las operaciones estadísticas fundamentales: descripción, correlación, detección de valores atípicos, identificación de distribuciones y generación automatizada de conclusiones.

df.describe(): el punto de partida

df.describe() calcula el recuento, la media, la desviación estándar, el mínimo, los cuartiles y el máximo de todas las columnas numéricas en una sola llamada. Es el primer paso estándar en cualquier análisis exploratorio de datos.

import pandas as pd

df = pd.read_csv('sales_data.csv')

# Basic numeric summary
print(df.describe())

# Also describe categorical columns
print(df.describe(include='object'))

# Custom percentiles
print(df.describe(percentiles=[0.05, 0.25, 0.5, 0.75, 0.95]))

# Convert to clean dict for agent use
def get_numeric_summary(df):
    desc = df.describe().round(2)
    return {
        col: desc[col].to_dict()
        for col in desc.columns
    }

Matriz de correlación

La matriz de correlación muestra las relaciones lineales por pares entre todas las columnas numéricas. Los valores oscilan entre -1 (correlación negativa fuerte) y +1 (correlación positiva fuerte). Los valores cercanos a 0 indican que no existe una relación lineal.

import pandas as pd
import numpy as np

def compute_correlation_matrix(df):
    numeric = df.select_dtypes(include='number')
    corr = numeric.corr().round(3)
    return corr.to_dict()

def find_strong_correlations(df, threshold=0.7):
    numeric = df.select_dtypes(include='number')
    corr = numeric.corr()
    strong = []
    cols = corr.columns
    for i in range(len(cols)):
        for j in range(i + 1, len(cols)):  # upper triangle only
            val = corr.iloc[i, j]
            if abs(val) >= threshold:
                strong.append({
                    'col_a': cols[i],
                    'col_b': cols[j],
                    'correlation': round(val, 3),
                    'direction': 'positive' if val > 0 else 'negative'
                })
    return sorted(strong, key=lambda x: abs(x['correlation']), reverse=True)

Detección de valores atípicos: método del IQR

El método del rango intercuartílico (IQR) identifica los valores atípicos como aquellos que se encuentran más de 1,5 veces el IQR por encima de Q3 o por debajo de Q1. Es resistente a los valores extremos; a diferencia de z-score, funciona bien incluso cuando hay valores atípicos en los datos.

import pandas as pd

def detect_outliers_iqr(df, columns=None, multiplier=1.5):
    numeric = df.select_dtypes(include='number')
    if columns:
        numeric = numeric[columns]

    outlier_report = {}
    for col in numeric.columns:
        q1 = numeric[col].quantile(0.25)
        q3 = numeric[col].quantile(0.75)
        iqr = q3 - q1
        lower = q1 - multiplier * iqr
        upper = q3 + multiplier * iqr

        outliers = numeric[(numeric[col] < lower) | (numeric[col] > upper)][col]
        outlier_report[col] = {
            'q1': round(q1, 3),
            'q3': round(q3, 3),
            'iqr': round(iqr, 3),
            'lower_bound': round(lower, 3),
            'upper_bound': round(upper, 3),
            'outlier_count': len(outliers),
            'outlier_pct': round(len(outliers) / len(df) * 100, 1),
            'outlier_values': outliers.tolist()[:10]  # first 10
        }
    return outlier_report

Identificación del tipo de distribución

Identificar el tipo de distribución (normal, sesgada, bimodal o uniforme) ayuda al agente a elegir las pruebas estadísticas adecuadas y describir los datos con precisión.

Utilice la asimetría y la curtosis como heurísticas rápidas antes de ejecutar pruebas formales.

import pandas as pd
from scipy import stats

def identify_distribution(series):
    '''Classify distribution type using skewness, kurtosis, and normality test.'''
    clean = series.dropna()
    if len(clean) < 8:
        return {'type': 'insufficient_data'}

    skewness = clean.skew()
    kurtosis = clean.kurtosis()

    # Shapiro-Wilk normality test (reliable up to ~5000 samples)
    sample = clean.sample(min(500, len(clean)), random_state=42)
    _, p_value = stats.shapiro(sample)

    distribution = 'normal' if p_value > 0.05 else 'non-normal'

    if abs(skewness) < 0.5 and p_value > 0.05:
        dist_type = 'normal'
    elif skewness > 1.0:
        dist_type = 'right-skewed (long right tail)'
    elif skewness < -1.0:
        dist_type = 'left-skewed (long left tail)'
    elif abs(skewness) < 0.5 and kurtosis < -1:
        dist_type = 'uniform-like'
    else:
        dist_type = 'approximately normal'

    return {
        'type': dist_type,
        'skewness': round(skewness, 3),
        'kurtosis': round(kurtosis, 3),
        'normality_pvalue': round(p_value, 4),
        'is_normal': p_value > 0.05
    }

Pruebas de normalidad

Existen varias pruebas formales de normalidad. Shapiro-Wilk es la más adecuada para muestras pequeñas y D'Agostino-Pearson, para conjuntos de datos más grandes. El umbral del valor p suele ser 0.05: se rechaza la normalidad si p < 0.05.

from scipy import stats
import numpy as np

def test_normality(series, alpha=0.05):
    clean = series.dropna().values
    n = len(clean)
    results = {}

    # Shapiro-Wilk (best for n < 5000)
    if n <= 5000:
        stat, p = stats.shapiro(clean)
        results['shapiro_wilk'] = {
            'statistic': round(stat, 4),
            'p_value': round(p, 4),
            'is_normal': p > alpha
        }

    # D'Agostino-Pearson (n >= 20)
    if n >= 20:
        stat, p = stats.normaltest(clean)
        results['dagostino_pearson'] = {
            'statistic': round(stat, 4),
            'p_value': round(p, 4),
            'is_normal': p > alpha
        }

    # Kolmogorov-Smirnov
    mean, std = np.mean(clean), np.std(clean)
    stat, p = stats.kstest(clean, 'norm', args=(mean, std))
    results['kolmogorov_smirnov'] = {
        'statistic': round(stat, 4),
        'p_value': round(p, 4),
        'is_normal': p > alpha
    }

    return results

Perfil estadístico completo

Combine todos los análisis estadísticos en una única función full_profile() que devuelva un resumen estadístico completo de cada columna numérica de un dataframe.

def full_statistical_profile(df):
    numeric = df.select_dtypes(include='number')
    profiles = {}

    for col in numeric.columns:
        series = numeric[col].dropna()
        profile = {
            'count': len(series),
            'null_count': df[col].isnull().sum(),
            'mean': round(series.mean(), 4),
            'median': round(series.median(), 4),
            'std': round(series.std(), 4),
            'min': round(series.min(), 4),
            'max': round(series.max(), 4),
            'range': round(series.max() - series.min(), 4),
            'q1': round(series.quantile(0.25), 4),
            'q3': round(series.quantile(0.75), 4)
        }
        profile['distribution'] = identify_distribution(series)
        outliers = detect_outliers_iqr(df, columns=[col])
        profile['outliers'] = outliers.get(col, {})
        profiles[col] = profile

    return {
        'profiles': profiles,
        'correlations': find_strong_correlations(df),
        'shape': {'rows': len(df), 'columns': len(df.columns)}
    }

Generación automatizada de conclusiones

Pase el perfil estadístico al LLM y pídale que genere conclusiones en lenguaje sencillo. El modelo identifica patrones, señala anomalías y destaca hallazgos relevantes para un usuario empresarial.

import json

INSIGHT_PROMPT = '''You are a data analyst. Generate 3-5 key insights from this statistical summary.

Dataset: {dataset_name}
Statistical profile:
{profile_json}

For each insight:
- Be specific (use actual numbers)
- Focus on business relevance
- Flag any data quality concerns (high null counts, extreme outliers)
- Note unexpected patterns

Format as bullet points.'''

def generate_insights(df, dataset_name='dataset'):
    profile = full_statistical_profile(df)
    profile_text = json.dumps(profile, indent=2, default=str)[:3000]
    return llm_call(INSIGHT_PROMPT.format(
        dataset_name=dataset_name,
        profile_json=profile_text
    ))

# Example output:
# - Revenue column has 3.2% outliers; max value ($48,500) is 12x the median ($3,900)
# - Customer age follows a right-skewed distribution (skewness: 1.4) with most users under 35
# - Strong positive correlation (r=0.87) between session_count and lifetime_value

Detección de tendencias en series temporales

Para datos de series temporales, detecte la tendencia general (creciente, decreciente o plana) mediante regresión lineal. Esto permite que los agentes indiquen: «Los ingresos crecen a un ritmo de 2.400 $ al mes».

import numpy as np
from scipy import stats

def detect_trend(values):
    if len(values) < 3:
        return {'trend': 'insufficient_data'}

    x = np.arange(len(values))
    y = np.array(values, dtype=float)
    slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)

    if p_value > 0.05:
        trend = 'flat (no significant trend)'
    elif slope > 0:
        trend = 'increasing'
    else:
        trend = 'decreasing'

    return {
        'trend': trend,
        'slope_per_period': round(slope, 4),
        'r_squared': round(r_value ** 2, 4),
        'p_value': round(p_value, 4),
        'significant': p_value < 0.05
    }

# Example: monthly revenue trend
monthly_revenue = [10000, 11200, 10800, 12500, 13100, 14200, 15000]
print(detect_trend(monthly_revenue))
# {'trend': 'increasing', 'slope_per_period': 834.2, 'r_squared': 0.95, ...}

Estadísticas comparativas

A menudo, la conclusión más útil consiste en comparar grupos: «El segmento A tiene un valor medio de pedido un 40 % superior al del segmento B». Utilice pruebas t o Mann-Whitney para comparar grupos.

from scipy import stats
import pandas as pd

def compare_groups(df, group_column, value_column):
    groups = df.groupby(group_column)[value_column].apply(list)
    group_names = list(groups.index)

    comparison = []
    for i, g1 in enumerate(group_names):
        for j, g2 in enumerate(group_names):
            if j <= i:
                continue
            a, b = groups[g1], groups[g2]
            mean_a = pd.Series(a).mean()
            mean_b = pd.Series(b).mean()

            # Mann-Whitney U (non-parametric, no normality assumption)
            stat, p = stats.mannwhitneyu(a, b, alternative='two-sided')
            pct_diff = (mean_a - mean_b) / mean_b * 100 if mean_b != 0 else 0

            comparison.append({
                'group_a': g1, 'group_b': g2,
                'mean_a': round(mean_a, 2),
                'mean_b': round(mean_b, 2),
                'pct_difference': round(pct_diff, 1),
                'p_value': round(p, 4),
                'significant': p < 0.05
            })
    return comparison

Análisis de valores ausentes

Antes de calcular cualquier estadística, analice los valores ausentes. Una proporción elevada de valores nulos en una columna hace que no sean válidas las estadísticas calculadas a partir de ella. Informe de los recuentos y porcentajes de valores nulos, así como de sus patrones (¿son aleatorios o sistemáticos?).

import pandas as pd
import numpy as np

def analyze_missing_values(df):
    total_rows = len(df)
    missing_report = {}

    for col in df.columns:
        null_count = df[col].isnull().sum()
        if null_count == 0:
            continue
        null_pct = null_count / total_rows * 100

        # Check if nulls correlate with another column (systematic pattern)
        pattern = 'random'
        if null_pct > 5:
            for other_col in df.select_dtypes(include='object').columns:
                if other_col == col:
                    continue
                null_rates = df.groupby(other_col)[col].apply(
                    lambda x: x.isnull().mean()
                )
                if null_rates.max() - null_rates.min() > 0.3:  # 30% difference
                    pattern = f'correlated_with_{other_col}'
                    break

        missing_report[col] = {
            'null_count': int(null_count),
            'null_pct': round(null_pct, 1),
            'severity': 'high' if null_pct > 20 else 'medium' if null_pct > 5 else 'low',
            'pattern': pattern
        }

    return missing_report

Comprobación de conocimientos

¿Por qué se prefiere el método del IQR al z-score para detectar valores atípicos en aplicaciones de agentes?

Recapitulación: agentes de resúmenes estadísticos

Los agentes de resúmenes estadísticos combinan: df.describe() para las estadísticas básicas, matrices de correlación para descubrir relaciones, detección de valores atípicos mediante IQR (resistente a valores extremos), identificación de distribuciones mediante asimetría y pruebas de normalidad, y detección de tendencias mediante regresión lineal.

Pase el perfil estadístico al LLM para generar conclusiones automáticamente y convertir los números en hallazgos empresariales expresados en lenguaje sencillo. Las estadísticas comparativas (comparaciones de grupos con pruebas de significación) permiten descubrir las conclusiones más útiles para la toma de decisiones.

Gratis para empezar

Aprende AI Agents con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
60
Lecciones
239

Preguntas frecuentes

¿La lección «Agentes de resúmenes estadísticos» es gratis?

Sí — el texto completo de «Agentes de resúmenes estadísticos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Agentes de resúmenes estadísticos»?

Análisis de distribuciones, correlaciones y valores atípicos en informes generados por agentes. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Agentes de resúmenes estadísticos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Patrón de intérprete de código para análisis de datos
  2. Herramientas de agentes basadas en Pandas
  3. Generación automatizada de gráficos y visualizaciones
  4. Agentes de resúmenes estadísticos
← Volver a AI Agents