You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
10 KiB
Python

from __future__ import annotations
from typing import Callable, Iterable, Optional, Tuple, Union
import matplotlib.pyplot as plt
import numpy as np
FilterFn = Callable[[object], object]
def plot_xy(df, x_col, y_col, label:Optional[str] = None, ax=None,
xlim: Optional[Tuple[float, float]] = None,
ylim: Optional[Tuple[float, float]] = None,
marker: str = "o",
linestyle: str = "-",
legen_loc=None,):
"""
Gráfica y vs x para un DataFrame.
- ax: para superponer curvas
- xlim/ylim: zoom visual (No filtrado datos)
"""
if ax is None:
_, ax = plt.subplots(figsize=(7,5))
ax.plot(df[x_col], df[y_col], marker=marker, linestyle=linestyle, label=label)
ax.set_xlabel(x_col)
ax.set_ylabel(y_col)
ax.grid(True)
if xlim is not None:
ax.set_xlim(xlim)
if ylim is not None:
ax.set_ylim(ylim)
if legen_loc is not None:
ax.legend(loc=legen_loc)
elif label:
ax.legend()
return ax
def comparar_rondas(data: dict, experimento: int, x_col: str, y_col: str,
rondas: Iterable[int] = (1,2),
filter_fn: Optional[FilterFn] = None,
xlim: Optional[Tuple[float, float]] = None,
ylim: Optional[Tuple[float, float]] = None,
title: Optional[str] = None,
legend_loc: Optional[str] = None,):
"""
Compara el mismo experimento entre rondas:
data[(ronda, experimento)] -> df
filter_fn: funcion opcional que recibe df y devuelve df filtrado.
Ejemplo: filter_fn = lambda df: apply_filter(df, row_start=10, row_end=40)
"""
fig, ax = plt.subplots(figsize=(7,5))
for ronda in rondas:
df = data[(ronda, experimento)]
if filter_fn is not None:
df = filter_fn(df)
plot_xy(df, x_col, y_col, label=f"Ronda {ronda}", ax=ax,
xlim=xlim, ylim=ylim, legen_loc=legend_loc)
if title is "False":
pass
else:
ax.set_title(title or f"Experimento {experimento} - Comparación de Rondas")
plt.show()
return fig, ax
def comparar_experimentos(data: dict, ronda: int, x_col: str, y_col: str,
experimentos: Iterable[int] = (1,2,3,4),
show: bool = False,
filter_fn: Optional[FilterFn] = None,
xlim: Optional[Tuple[float, float]] = None,
ylim: Optional[Tuple[float, float]] = None,
title: Optional[str] = None,
legend_loc: Optional[str] = None,
):
"""
Comparar varios experimentos dentro de una misma ronda.
"""
fig, ax = plt.subplots(figsize=(7, 5))
for exp in experimentos:
df = data[(ronda, exp)]
if filter_fn is not None:
df = filter_fn(df)
plot_xy(df, x_col, y_col, label=f"Experimento {exp}", ax=ax, xlim=xlim, ylim=ylim, legen_loc=legend_loc)
if title is "False":
pass
else:
ax.set_title(title or f"Ronda {ronda} - Comparación de Experimentos")
if show is True:
plt.show()
return fig, ax
def plot_con_ajuste(df, x_col: str, y_col: str, label_datos: str = "Datos",
label_ajuste: str = "Ajuste lineal",
show: bool = True
):
"""
Grafica scatter + ajuste lineal (y = mx + b). Devuelve (m, b)
"""
x = np.asarray(df[x_col].values, dtype=float)
y = np.asarray(df[y_col].values, dtype=float)
m, b = np.polyfit(x, y, 1)
fig, ax = plt.subplots(figsize=(7, 5))
ax.scatter(x, y, label=label_datos)
ax.plot(x, m * x + b, label=f"{label_ajuste} (m={m:.4f})")
ax.set_xlabel(x_col)
ax.set_ylabel(y_col)
ax.grid(True)
ax.legend()
if show:
plt.show()
return m, b, fig, ax
def plot_3D(df, x_col: str, y_col: str, z_col: str,
title: Optional[str] = None,):
"""
Gráfica 3D: (x, y, z) como dispersión.
"""
from mpl_toolkits.mplot3d import Axes3D #noqa: F401
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection="3d")
ax.scatter(df[x_col], df[y_col], df[z_col])
ax.set_xlabel(x_col)
ax.set_ylabel(y_col)
ax.set_zlabel(z_col)
ax.set_title(title or f"3D: {x_col} vs {y_col} vs {z_col}")
plt.show()
return fig, ax
def plot_color_map(df, x_col: str, y_col: str, z_col: str,
title: Optional[str] = None, ):
"""
Scatter 2D con tercera variable como color
"""
fig, ax = plt.subplots(figsize=(7, 5))
sc = ax.scatter(df[x_col], df[y_col], c=df[z_col], cmap="viridis")
cbar = plt.colorbar(sc, ax=ax)
cbar.set_label(z_col)
ax.set_xlabel(x_col)
ax.set_ylabel(y_col)
ax.grid(True)
ax.set_title(title or f"{y_col} vs {x_col} (color={z_col})")
plt.show()
return fig, ax
def plot_dual_axis(df, x_col: str, y1_col: str, y2_col: str, ax=None,
label1: Optional[str] = None,
label2: Optional[str] = None,
title: Optional[str] = None,
show: bool = True):
"""
Gráfica con doble eje Y:
y1 vs x (izquierda) y y2 vs x (derecha)
"""
if ax is None:
fig, ax1 = plt.subplots(figsize=(7, 5))
else:
ax1 = ax
fig = ax1.figure
ax2 = ax1.twinx()
ax1.plot(df[x_col], df[y1_col], marker="o", linestyle="-", color="b",label= label1 or y1_col)
ax2.plot(df[x_col], df[y2_col], marker="s", linestyle="--", color="r",label= label2 or y2_col)
ax1.set_xlabel(x_col)
ax1.set_ylabel(y1_col)
ax2.set_ylabel(y2_col)
ax1.grid(True)
ax1.set_title(title or f"{y1_col} y {y2_col} vs {x_col}")
# Leyendas Combinadas
lines, labels = [], []
for a in (ax1, ax2):
l, lab = a.get_legend_handles_labels()
lines += l
labels += lab
ax1.legend(lines, labels, loc="best")
if show is not True:
plt.show()
return fig, ax1, ax2
def marcar_zonas(ax, limites, labels=None, colors=None):
"""
Marca zonas en una gráfica con líneas verticales.
limites: lista de voltajes donde cambian las zonas
lables: nombre de cada zona
colors: colores opcionales
"""
if colors is None:
colors = ["gray", "blue", "green", "red"]
for i, v in enumerate(limites):
ax.axvline(v, linestyle="--", color="black", alpha=0.6)
if labels and i < len(labels):
ax.text(
v,
ax.get_ylim()[1]*0.65,
labels[i],
rotation=90,
verticalalignment="top",
horizontalalignment="right",
fontsize=12
)
def sombrear_zonas(ax, limites, colores=None, alpha=0.15):
"""
Sombrea las zonas del experimento
"""
if colores is None:
colores = ["gray", "blue", "green", "red"]
for i in range(len(limites)-1):
ax.axvspan(
limites[i],
limites[i+1],
color=colores[i],
alpha=alpha
)
def percent_change(data:dict, ronda: int, x_col: str, y_col: str,
experimentos: Iterable[int] = (1,2,3,4),
show: bool = False,
btween_exp: bool = False,
filter_fn: Optional[FilterFn] = None,
xlim: Optional[Tuple[float, float]] = None,
ylim: Optional[Tuple[float, float]] = None,
title: Optional[str] = None, ):
fig, ax = plt.subplots(figsize=(7,5))
if btween_exp is False:
change_value = {}
sum_mean = 0
for exp in experimentos:
df = data[(ronda, exp)]
if filter_fn is not None:
df = filter_fn(df)
R = df[y_col].values
x_ = df[x_col].values
for i in range(len(R) - 1):
change_value[i] = ((R[i+1] - R[i]) / R[i]) * 100
pct = change_value[i]
sum_mean = sum_mean + abs(pct)
print(f"Δ% Experiment:{exp} between data {i} and {i+1}: {pct:.2f}%")
if show is True:
ax.annotate(f"{pct:.1f}%",
(x_[i+1], R[i+1]),
textcoords="offset points",
xytext=(0,8),
ha='center',
fontsize=8,
color="red")
mean = sum_mean/len(change_value)
change_mx_min = ((R[(len(R)-1)] - R[0])/R[0]) * 100
print(f"Last_Value:{R[(len(R)-1)]:.3f} Fst_value:{R[0]:.3f}")
print(f"The mean percent change value in Experiment {exp} are : {mean:.2f}%")
print(f"The percent change between the last and first value of experiment {exp}: {change_mx_min:.2f}%")
plot_xy(df, x_col, y_col, label=f"Experimento {exp}", ax=ax, xlim=xlim, ylim=ylim)
sum_mean = 0
else:
change_value = {}
df = {}
i = 0
for exp in experimentos:
df[i] = data[(ronda, exp)]
if filter_fn is not None:
df[i] = filter_fn(df[i])
i = i + 1
R1 = df[0][y_col].values
R2 = df[1][y_col].values
x_1 = df[0][x_col].values
x_2 = df[1][x_col].values
for x in range(len(R1)):
change_value[x] = ((R2[x] - R1[x]) / R1[x]) * 100
pct = change_value[x]
print(f"Δ% Punto {x}: Exp{experimentos[0]} vs Exp{experimentos[1]} = {pct:.2f}%")
if show is True:
# Line that connect each point
ax.plot([x_1[x], x_2[x]], [R1[x], R2[x]], color="black", linestyle="-", linewidth=1)
# Text with the change percent in the middle of the line
ax.annotate(f"{pct:.1f}%",
xy=((x_1[x]+x_2[x])/2, (R1[x]+R2[x])/2),
textcoords="offset points",
xytext=(0,3),
ha='center', fontsize=8, color="red")
plot_xy(df[0], x_col, y_col, label=f"Experimento {experimentos[0]}", ax=ax, xlim=xlim, ylim=ylim)
plot_xy(df[1], x_col, y_col, label=f"Experimento {experimentos[1]}", ax=ax, xlim=xlim, ylim=ylim)
ax.set_title(title or f"Round {ronda} - Experiments Comparative")
if show is True:
plt.show()
return fig, ax