def histograma_doble_notas(ax, a, b, etiqueta_a, etiqueta_b, titulo):
"""Histograma superpuesto de dos grupos, con líneas verticales en cada media."""
bins = np.linspace(0, 100, 21) # intervalos de 5 puntos
ax.hist(a, bins=bins, alpha=0.65, color="C0", label=etiqueta_a)
ax.hist(b, bins=bins, alpha=0.65, color="C1", hatch="//", edgecolor="white", label=etiqueta_b)
ax.axvline(np.mean(a), color="C0", linestyle="--", linewidth=1.5)
ax.axvline(np.mean(b), color="C1", linestyle=":", linewidth=1.5)
ax.set_xlim(30, 100)
ax.set_xlabel("Nota (0–100)")
ax.set_ylabel("Frecuencia")
ax.set_title(titulo)
ax.legend(fontsize=9)
def graficar_panorama_inicial(notas_baja_hist, notas_alta_hist, media_baja_hist, media_alta_hist,
tiempos_ms):
"""Panorama de Etapa 1: histogramas de notas (misma media, distinta dispersión;
medias similares, formas distintas) y de tiempos de respuesta."""
fig, axes = plt.subplots(3, 1, figsize=(9, 8), constrained_layout=True)
histograma_doble_notas(
axes[0], notas_baja_hist, notas_alta_hist,
"Baja dispersión (muestra amplia)", "Alta dispersión (muestra amplia)",
"Histogramas: misma media aproximada, distinta dispersión",
)
histograma_doble_notas(
axes[1], media_baja_hist, media_alta_hist,
"Media similar · baja var.", "Media similar · alta var.",
"Histogramas: medias cercanas (rayas), formas distintas",
)
axes[2].hist(tiempos_ms, bins=8, alpha=0.75, edgecolor="black")
media_t = float(np.mean(tiempos_ms))
axes[2].axvline(media_t, linestyle="--", linewidth=1.5, label=f"Media = {media_t:.1f} ms")
axes[2].set_xlabel("Tiempo (ms)")
axes[2].set_ylabel("Frecuencia")
axes[2].set_title("Tiempos de respuesta: histograma y valor extremo")
axes[2].legend(fontsize=9)
plt.show()
def graficar_rango(a, b, etiqueta_a="Baja dispersión", etiqueta_b="Alta dispersión"):
"""Segmentos horizontales entre mínimo y máximo de dos grupos (Etapa 2: Rango)."""
fig, ax = plt.subplots(figsize=(9, 3.5))
min_a, max_a = np.min(a), np.max(a)
min_b, max_b = np.min(b), np.max(b)
ax.hlines(y=1, xmin=min_a, xmax=max_a, linewidth=6, color="C0", label=etiqueta_a)
ax.hlines(y=2, xmin=min_b, xmax=max_b, linewidth=6, color="C1", label=etiqueta_b)
ax.scatter([min_a, max_a], [1, 1], s=80, color="C0")
ax.scatter([min_b, max_b], [2, 2], s=80, color="C1")
ax.set_ylim(0.5, 2.5)
ax.set_yticks([1, 2])
ax.set_yticklabels([etiqueta_a, etiqueta_b])
ax.set_xlabel('Nota')
ax.set_title('Rango: longitud entre mínimo y máximo')
ax.grid(axis='x', alpha=0.3)
ax.legend(fontsize=9)
plt.show()
print(f"Rango {etiqueta_a.lower()}: {max_a - min_a}")
print(f"Rango {etiqueta_b.lower()}: {max_b - min_b}")
def graficar_desviaciones(x, titulo, ax=None):
"""Distancia de cada punto a la media (base visual de la varianza, Etapa 3)."""
standalone = ax is None
if standalone:
fig, ax = plt.subplots(figsize=(6, 4))
m = np.mean(x)
idx = np.arange(1, len(x) + 1)
ax.scatter(idx, x, s=55, label='Datos')
ax.hlines(m, 0.5, len(x) + 0.5, linestyle='--', linewidth=1.5, label=f'Media = {m:.2f}')
for i, xi in zip(idx, x):
ax.plot([i, i], [m, xi], color="C3", linewidth=2, alpha=0.8)
ax.set_xticks(idx)
ax.set_xlabel('Observación')
ax.set_ylabel('Valor')
ax.set_title(titulo)
ax.legend(fontsize=8)
if standalone:
plt.show()
def graficar_desviaciones_comparadas(a, b, titulo_a, titulo_b):
# sharey=True: misma escala vertical; si no, las distancias chicas se verían tan largas como las grandes
fig, axes = plt.subplots(1, 2, figsize=(11, 4), sharey=True, constrained_layout=True)
graficar_desviaciones(a, titulo_a, ax=axes[0])
graficar_desviaciones(b, titulo_b, ax=axes[1])
plt.show()
def graficar_bandas_std(a, b, titulo_a="Baja desviación estándar", titulo_b="Alta desviación estándar"):
"""Bandas ±1σ alrededor de la media, comparando dos grupos (Etapa 4)."""
fig, axes = plt.subplots(1, 2, figsize=(11, 4.5), sharey=True, constrained_layout=True)
for ax, x, titulo in [(axes[0], a, titulo_a), (axes[1], b, titulo_b)]:
idx = np.arange(1, len(x) + 1)
m, s = np.mean(x), np.std(x, ddof=1)
ax.plot(idx, x, marker='o', linewidth=2, label='Datos')
ax.axhline(m, linestyle='--', linewidth=1.5, label=f'Media = {m:.2f}')
ax.axhline(m + s, linestyle=':', linewidth=1.5, label=f'+1 s = {m+s:.2f}')
ax.axhline(m - s, linestyle='-.', linewidth=1.5, label=f'−1 s = {m-s:.2f}')
ax.set_title(titulo)
ax.set_xlabel('Observación')
ax.set_ylabel('Valor')
ax.set_xticks(idx)
ax.grid(alpha=0.25)
ax.legend(fontsize=8)
plt.show()
def graficar_comparacion_distribuciones(dist_a, dist_b, titulo, etiqueta_a, etiqueta_b,
bins_orig=None):
"""Histograma comparativo de dos distribuciones simuladas, con lectura de medias
y desviaciones estándar impresas debajo. Se reutiliza tanto para 'misma media,
distinta varianza' como para 'misma varianza, distinta media'."""
if bins_orig is None:
lo = min(dist_a.min(), dist_b.min())
hi = max(dist_a.max(), dist_b.max())
bins_orig = np.linspace(lo, hi, 26)
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(dist_a, bins=bins_orig, alpha=0.6, color='C0', label=etiqueta_a)
ax.hist(dist_b, bins=bins_orig, alpha=0.6, color='C1', hatch='//', edgecolor='white', label=etiqueta_b)
ax.axvline(np.mean(dist_a), color='C0', linestyle='--', linewidth=1.5)
ax.axvline(np.mean(dist_b), color='C1', linestyle=':', linewidth=1.5)
ax.set_title(titulo)
ax.set_xlabel('Valor')
ax.set_ylabel('Frecuencia')
ax.legend(fontsize=9)
plt.show()
print(f"Media {etiqueta_a}: {np.mean(dist_a):.2f} | Desv. estándar: {np.std(dist_a, ddof=1):.2f}")
print(f"Media {etiqueta_b}: {np.mean(dist_b):.2f} | Desv. estándar: {np.std(dist_b, ddof=1):.2f}")
def graficar_cv_barras(labels, valores):
"""Barras de coeficiente de variación (%) para varios conjuntos (Etapa 5)."""
fig, ax = plt.subplots(figsize=(10, 4))
ax.bar(labels, valores)
ax.set_ylabel('CV (%)')
ax.set_title('Coeficiente de variación: dispersión relativa')
ax.tick_params(axis='x', rotation=20)
ax.grid(axis='y', alpha=0.25)
plt.tight_layout()
plt.show()
def graficar_hist_boxplot(a, b, etiqueta_a, etiqueta_b, bins_a=None, bins_b=None):
"""Histograma + boxplot lado a lado para comparar dos grupos con media similar
y dispersión distinta (Etapa 6)."""
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
media_a, media_b = np.mean(a), np.mean(b)
# Con 6 datos por grupo se dibuja cada nota (un histograma de 6 valores dice poco)
for y, x, color, media in [(1, a, "C0", media_a), (2, b, "C1", media_b)]:
axes[0].scatter(x, np.full(len(x), y), s=70, color=color, alpha=0.8)
axes[0].vlines(media, y - 0.3, y + 0.3, color=color, linestyle="--", label=f"Media = {media:.2f}")
axes[0].set_yticks([1, 2])
axes[0].set_yticklabels([etiqueta_a, etiqueta_b])
axes[0].set_ylim(0.4, 2.6)
axes[0].set_xlabel("Nota")
axes[0].set_title("Cada punto es una nota (rayas: medias)")
axes[0].legend(fontsize=8)
axes[1].boxplot([a, b], positions=[1, 2], orientation="horizontal")
axes[1].set_yticks([1, 2])
axes[1].set_yticklabels([etiqueta_a, etiqueta_b])
axes[1].set_xlabel("Nota")
axes[1].set_title("Boxplot: la caja contiene el 50% central")
axes[1].set_xlim(axes[0].get_xlim())
plt.tight_layout()
plt.show()
def graficar_comparacion_modelos(x_test, y_test_dict, y_pred_dict):
"""Dispersión + línea de predicción para dos escenarios de ruido (Etapa 8)."""
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5), sharey=True, constrained_layout=True)
orden = np.argsort(x_test)
for ax, (nombre, y_test) in zip(axes, y_test_dict.items()):
ax.scatter(x_test, y_test, alpha=0.6, label='Datos test')
ax.plot(x_test[orden], y_pred_dict[nombre][orden], color='C3', linewidth=2, label='Predicción modelo')
ax.set_title(f'ML con {nombre.lower()}')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend(fontsize=8)
plt.show()
def graficar_caso_real_validacion(tiempos_crudo, tiempos_validado, etiqueta_proveedor):
"""Boxplot comparando la métrica de dispersión antes/después de validar los datos
(Caso real de cierre)."""
fig, ax = plt.subplots(figsize=(7, 4))
ax.boxplot([tiempos_crudo, tiempos_validado], positions=[1, 2])
ax.set_xticks([1, 2])
ax.set_xticklabels(["Sin validar", "Validado"])
ax.set_ylabel("Tiempo de respuesta (ms)")
ax.set_title(f"{etiqueta_proveedor}: efecto de validar los datos sobre la dispersión")
plt.tight_layout()
plt.show()