import seaborn as sns
import matplotlib.pyplot as plt
PALETTE = {"box": "#4C72B0", "mean": "#C44E52", "median": "#DD8452", "neutral": "#555555"}
def plot_delay_distribution(df: pd.DataFrame, col: str = "delay", unit: str = "min") -> plt.Figure:
"""Plota distribuição e dispersão de uma métrica contínua com anotação de tendência central.
Args:
df: DataFrame de origem.
col: Coluna numérica a analisar.
unit: Unidade exibida nos eixos (ex.: 'min').
Returns:
Figura matplotlib pronta para exibição ou export.
Raises:
KeyError: Se `col` não existir no DataFrame.
"""
if col not in df.columns:
raise KeyError(f"Coluna '{col}' ausente. Disponíveis: {list(df.columns)}")
serie = df[col].dropna()
media, mediana = serie.mean(), serie.median()
skew_pct = (media - mediana) / mediana * 100 # delta % média vs. mediana
sns.set_theme(style="white", context="notebook")
fig, (ax_box, ax_hist) = plt.subplots(
1, 2, figsize=(11, 4.2), gridspec_kw={"width_ratios": [1, 2.4]}
)
# --- Boxplot: dispersão e outliers (papel secundário, mais estreito) ---
sns.boxplot(
y=serie, ax=ax_box, color=PALETTE["box"], width=0.4, fliersize=2,
boxprops={"alpha": 0.85},
flierprops={"marker": "o", "alpha": 0.25,
"markerfacecolor": PALETTE["neutral"], "markeredgecolor": "none"},
)
ax_box.axhline(media, color=PALETTE["mean"], ls="--", lw=1.3)
ax_box.set_ylabel(f"Atraso ({unit})")
ax_box.set_title("Dispersão & Outliers", fontsize=11, loc="left", color=PALETTE["neutral"])
# --- Histograma: distribuição (papel principal) ---
sns.histplot(serie, ax=ax_hist, kde=True, color=PALETTE["box"],
alpha=0.55, edgecolor="none", line_kws={"lw": 1.4})
ax_hist.axvline(media, color=PALETTE["mean"], ls="--", lw=1.3)
ax_hist.axvline(mediana, color=PALETTE["median"], ls="--", lw=1.3)
ax_hist.set_xlabel(f"Atraso ({unit})")
ax_hist.set_ylabel("Nº de voos")
ax_hist.set_title("Distribuição", fontsize=11, loc="left", color=PALETTE["neutral"])
# Anotação direta: valor na própria linha, sem depender de legenda
topo = ax_hist.get_ylim()[1]
ax_hist.annotate(f"Média {media:.1f}", xy=(media, topo * 0.92),
color=PALETTE["mean"], fontsize=9, xytext=(4, 0), textcoords="offset points")
ax_hist.annotate(f"Mediana {mediana:.1f}", xy=(mediana, topo * 0.78),
color=PALETTE["median"], fontsize=9, ha="right",
xytext=(-4, 0), textcoords="offset points")
fig.suptitle(
f"Atrasos concentrados perto de 0, mas com cauda à direita • "
f"Média {skew_pct:+.0f}% acima da mediana",
fontsize=12, x=0.01, ha="left", weight="bold", color="#222222",
)
sns.despine(ax=ax_box)
sns.despine(ax=ax_hist)
fig.tight_layout(rect=[0, 0, 1, 0.95])
return fig
fig = plot_delay_distribution(dados)
plt.show()