You've already forked etude_lego_jurassic_world
Ajoute la richesse chromatique par set
This commit is contained in:
130
lib/plots/color_richness.py
Normal file
130
lib/plots/color_richness.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""Visualisations de la richesse chromatique par set."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from matplotlib.patches import Patch
|
||||
|
||||
from lib.filesystem import ensure_parent_dir
|
||||
from lib.rebrickable.stats import read_rows
|
||||
|
||||
|
||||
def load_richness_rows(path: Path) -> List[dict]:
|
||||
"""Charge les métriques de richesse chromatique."""
|
||||
return read_rows(path)
|
||||
|
||||
|
||||
def build_boxplot_data(rows: Iterable[dict]) -> Tuple[List[List[int]], List[str]]:
|
||||
"""Prépare les valeurs de boxplot par année."""
|
||||
grouped: dict[str, List[int]] = {}
|
||||
for row in rows:
|
||||
year_rows = grouped.get(row["year"])
|
||||
if year_rows is None:
|
||||
year_rows = []
|
||||
grouped[row["year"]] = year_rows
|
||||
year_rows.append(int(row["colors_distinct"]))
|
||||
years = sorted(grouped.keys(), key=int)
|
||||
data = [grouped[year] for year in years]
|
||||
return data, years
|
||||
|
||||
|
||||
def plot_richness_boxplot(richness_path: Path, destination_path: Path) -> None:
|
||||
"""Trace le boxplot du nombre de couleurs distinctes par set et par année."""
|
||||
rows = load_richness_rows(richness_path)
|
||||
if not rows:
|
||||
return
|
||||
data, years = build_boxplot_data(rows)
|
||||
fig, ax = plt.subplots(figsize=(12, 7))
|
||||
box = ax.boxplot(
|
||||
data,
|
||||
orientation="vertical",
|
||||
patch_artist=True,
|
||||
tick_labels=years,
|
||||
boxprops=dict(facecolor="#1f77b4", alpha=0.3),
|
||||
medianprops=dict(color="#0d0d0d", linewidth=1.5),
|
||||
whiskerprops=dict(color="#555555", linestyle="--"),
|
||||
capprops=dict(color="#555555"),
|
||||
)
|
||||
for patch in box["boxes"]:
|
||||
patch.set_edgecolor("#1f77b4")
|
||||
ax.set_xlabel("Année")
|
||||
ax.set_ylabel("Nombre de couleurs distinctes (hors rechanges)")
|
||||
ax.set_title("Richesse chromatique par set (répartition annuelle)")
|
||||
ax.grid(axis="y", linestyle="--", alpha=0.3)
|
||||
|
||||
ensure_parent_dir(destination_path)
|
||||
fig.tight_layout()
|
||||
fig.savefig(destination_path, dpi=170)
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def select_top_sets(rows: Iterable[dict], limit: int = 15) -> List[dict]:
|
||||
"""Retient les sets les plus colorés et les plus concentrés."""
|
||||
sorted_rows = sorted(
|
||||
rows,
|
||||
key=lambda row: (-int(row["colors_distinct"]), float(row["top3_share"]), row["set_num"]),
|
||||
)
|
||||
return sorted_rows[:limit]
|
||||
|
||||
|
||||
def plot_richness_top_sets(richness_path: Path, destination_path: Path) -> None:
|
||||
"""Trace le top des sets les plus riches en couleurs."""
|
||||
rows = load_richness_rows(richness_path)
|
||||
if not rows:
|
||||
return
|
||||
top_rows = select_top_sets(rows)
|
||||
y_positions = np.arange(len(top_rows))
|
||||
counts = [int(row["colors_distinct"]) for row in top_rows]
|
||||
labels = [f"{row['set_num']} · {row['name']} ({row['year']})" for row in top_rows]
|
||||
owned_mask = [row["in_collection"] == "true" for row in top_rows]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(11, 8))
|
||||
for y, value, owned in zip(y_positions, counts, owned_mask):
|
||||
alpha = 0.92 if owned else 0.45
|
||||
ax.barh(y, value, color="#2ca02c", alpha=alpha)
|
||||
ax.set_yticks(y_positions)
|
||||
ax.set_yticklabels(labels)
|
||||
ax.invert_yaxis()
|
||||
ax.set_xlabel("Couleurs distinctes (hors rechanges)")
|
||||
ax.set_title("Top des sets les plus colorés")
|
||||
ax.grid(axis="x", linestyle="--", alpha=0.3)
|
||||
legend = [
|
||||
Patch(facecolor="#2ca02c", edgecolor="none", alpha=0.92, label="Set possédé"),
|
||||
Patch(facecolor="#2ca02c", edgecolor="none", alpha=0.45, label="Set manquant"),
|
||||
]
|
||||
ax.legend(handles=legend, loc="lower right", frameon=False)
|
||||
|
||||
ensure_parent_dir(destination_path)
|
||||
fig.tight_layout()
|
||||
fig.savefig(destination_path, dpi=170)
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def plot_concentration_scatter(richness_path: Path, destination_path: Path) -> None:
|
||||
"""Visualise la concentration de palette vs nombre de couleurs."""
|
||||
rows = load_richness_rows(richness_path)
|
||||
if not rows:
|
||||
return
|
||||
x_values = [int(row["colors_distinct"]) for row in rows]
|
||||
y_values = [float(row["top3_share"]) for row in rows]
|
||||
owned_mask = [row["in_collection"] == "true" for row in rows]
|
||||
colors = ["#1f77b4" if owned else "#bbbbbb" for owned in owned_mask]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 7))
|
||||
ax.scatter(x_values, y_values, c=colors, alpha=0.7, s=32)
|
||||
ax.set_xlabel("Nombre de couleurs distinctes (hors rechanges)")
|
||||
ax.set_ylabel("Part des 3 couleurs principales")
|
||||
ax.set_title("Concentration des palettes")
|
||||
ax.grid(True, linestyle="--", alpha=0.3)
|
||||
legend = [
|
||||
Patch(facecolor="#1f77b4", edgecolor="none", alpha=0.7, label="Set possédé"),
|
||||
Patch(facecolor="#bbbbbb", edgecolor="none", alpha=0.7, label="Set manquant"),
|
||||
]
|
||||
ax.legend(handles=legend, loc="upper right", frameon=False)
|
||||
|
||||
ensure_parent_dir(destination_path)
|
||||
fig.tight_layout()
|
||||
fig.savefig(destination_path, dpi=170)
|
||||
plt.close(fig)
|
||||
Reference in New Issue
Block a user