1

Ajoute les visualisations temporelles des palettes

This commit is contained in:
Richard Dern 2025-12-01 22:44:05 +01:00
parent fb2ef5f16f
commit 640c6333f0
4 changed files with 163 additions and 0 deletions

View File

@ -177,3 +177,10 @@ Le script agrège `data/intermediate/parts_filtered.csv` avec les libellés de c
2. `python -m scripts.compute_colors_timeline` 2. `python -m scripts.compute_colors_timeline`
Le script lit `data/intermediate/colors_by_set.csv` et produit deux agrégats : `data/intermediate/colors_timeline.csv` (statistiques annuelles : nombre de couleurs distinctes, nouvelles, perdues, part des translucides, top couleurs) et `data/intermediate/colors_year_color_matrix.csv` (quantités totales année × couleur) pour préparer heatmaps et analyses temporelles. Le script lit `data/intermediate/colors_by_set.csv` et produit deux agrégats : `data/intermediate/colors_timeline.csv` (statistiques annuelles : nombre de couleurs distinctes, nouvelles, perdues, part des translucides, top couleurs) et `data/intermediate/colors_year_color_matrix.csv` (quantités totales année × couleur) pour préparer heatmaps et analyses temporelles.
### Étape 15 : visualiser l'évolution des palettes
1. `source .venv/bin/activate`
2. `python -m scripts.plot_colors_timeline`
Le script lit les deux agrégats de l'étape précédente et produit `figures/step14/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes) ainsi que `figures/step14/colors_heatmap.png` (heatmap année × couleur basée sur les quantités totales en échelle log1p).

View File

@ -0,0 +1,92 @@
"""Visualisations temporelles des palettes de couleurs."""
from pathlib import Path
from typing import Dict, Iterable, List, Tuple
import matplotlib.pyplot as plt
import numpy as np
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.stats import read_rows
def load_rows(path: Path) -> List[dict]:
"""Charge un CSV simple en mémoire sous forme de dictionnaires."""
return read_rows(path)
def plot_translucent_share(timeline_path: Path, destination_path: Path) -> None:
"""Trace l'évolution de la part de pièces translucides et du nombre de couleurs."""
rows = load_rows(timeline_path)
years = [int(row["year"]) for row in rows]
shares = [float(row["share_translucent"]) for row in rows]
distinct_counts = [int(row["colors_distinct"]) for row in rows]
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(years, shares, color="#1f77b4", marker="o", linewidth=2.2, label="Part des translucides")
ax.fill_between(years, shares, color="#1f77b4", alpha=0.15)
ax.set_ylabel("Part translucide")
ax.set_ylim(0, min(1.0, max(shares) * 1.1))
ax.set_xlabel("Année")
ax.grid(True, linestyle="--", alpha=0.3)
ax2 = ax.twinx()
ax2.plot(years, distinct_counts, color="#ff7f0e", marker="s", linewidth=1.8, label="Couleurs distinctes")
ax2.set_ylabel("Nombre de couleurs distinctes")
handles = [
plt.Line2D([0], [0], color="#1f77b4", marker="o", label="Part des translucides"),
plt.Line2D([0], [0], color="#ff7f0e", marker="s", label="Couleurs distinctes"),
]
ax.legend(handles=handles, loc="upper left")
ax.set_title("Evolution des palettes : translucides vs. diversité des couleurs")
ax.set_xticks(years)
ax.tick_params(axis="x", labelrotation=45)
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=160)
plt.close(fig)
def build_heatmap_data(rows: Iterable[dict]) -> Tuple[List[int], List[str], np.ndarray]:
"""Construit les vecteurs année, labels de couleur et matrice de quantités."""
years = sorted({int(row["year"]) for row in rows})
color_totals: Dict[Tuple[str, str, str], int] = {}
for row in rows:
key = (row["color_name"], row["color_rgb"], row["is_translucent"])
color_totals[key] = color_totals.get(key, 0) + int(row["quantity_total"])
sorted_colors = sorted(
color_totals.items(),
key=lambda item: (-item[1], item[0][0], item[0][1]),
)
color_keys = [color for color, _ in sorted_colors]
labels = [f"{name} ({'trans' if is_trans == 'true' else 'opaque'})" for name, _, is_trans in color_keys]
matrix = np.zeros((len(color_keys), len(years)), dtype=float)
index_by_year = {year: idx for idx, year in enumerate(years)}
index_by_color = {color: idx for idx, color in enumerate(color_keys)}
for row in rows:
color_key = (row["color_name"], row["color_rgb"], row["is_translucent"])
y_index = index_by_color[color_key]
x_index = index_by_year[int(row["year"])]
matrix[y_index, x_index] += int(row["quantity_total"])
return years, labels, matrix
def plot_colors_heatmap(matrix_path: Path, destination_path: Path) -> None:
"""Génère une heatmap année × couleur basée sur les quantités totales."""
rows = load_rows(matrix_path)
years, labels, matrix = build_heatmap_data(rows)
values = np.log1p(matrix)
fig, ax = plt.subplots(figsize=(14, max(6, len(labels) * 0.24)))
heatmap = ax.imshow(values, aspect="auto", cmap="magma", origin="lower")
ax.set_xticks(range(len(years)))
ax.set_xticklabels(years, rotation=45)
ax.set_yticks(range(len(labels)))
ax.set_yticklabels(labels)
ax.set_xlabel("Année")
ax.set_title("Intensité des couleurs par année (log1p des quantités)")
cbar = fig.colorbar(heatmap, ax=ax, shrink=0.82)
cbar.set_label("log1p(quantité totale)")
ensure_parent_dir(destination_path)
fig.tight_layout()
fig.savefig(destination_path, dpi=170)
plt.close(fig)

View File

@ -0,0 +1,21 @@
"""Génère les visuels temporels des palettes de couleurs."""
from pathlib import Path
from lib.plots.colors_timeline import plot_colors_heatmap, plot_translucent_share
TIMELINE_PATH = Path("data/intermediate/colors_timeline.csv")
MATRIX_PATH = Path("data/intermediate/colors_year_color_matrix.csv")
TRANSLUCENT_DESTINATION = Path("figures/step14/colors_translucent_share.png")
HEATMAP_DESTINATION = Path("figures/step14/colors_heatmap.png")
def main() -> None:
"""Construit les visuels d'évolution annuelle des palettes."""
plot_translucent_share(TIMELINE_PATH, TRANSLUCENT_DESTINATION)
plot_colors_heatmap(MATRIX_PATH, HEATMAP_DESTINATION)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,43 @@
"""Tests des visualisations temporelles des couleurs."""
import matplotlib
from pathlib import Path
from lib.plots.colors_timeline import plot_colors_heatmap, plot_translucent_share
matplotlib.use("Agg")
def test_plot_translucent_share(tmp_path: Path) -> None:
"""Produit un graphique de part de translucides et diversité des couleurs."""
timeline_path = tmp_path / "colors_timeline.csv"
destination = tmp_path / "figures" / "step14" / "colors_translucent_share.png"
timeline_path.write_text(
"year,colors_distinct,colors_new,colors_lost,share_translucent,total_quantity,top_colors\n"
"2020,2,2,0,0.25,100,Blue (60),Red (40)\n"
"2021,3,1,0,0.40,120,Blue (50),Trans-Black (48)\n"
)
plot_translucent_share(timeline_path, destination)
assert destination.exists()
assert destination.stat().st_size > 0
def test_plot_colors_heatmap(tmp_path: Path) -> None:
"""Génère une heatmap année × couleur."""
matrix_path = tmp_path / "colors_year_color_matrix.csv"
destination = tmp_path / "figures" / "step14" / "colors_heatmap.png"
matrix_path.write_text(
"year,color_rgb,is_translucent,color_name,quantity_total\n"
"2020,AAAAAA,false,Gray,5\n"
"2020,BBBBBB,true,Trans-Black,2\n"
"2021,AAAAAA,false,Gray,3\n"
"2021,CCCCCC,false,Blue,4\n"
)
plot_colors_heatmap(matrix_path, destination)
assert destination.exists()
assert destination.stat().st_size > 0