You've already forked etude_lego_jurassic_world
Premiers éléments de l'étude
This commit is contained in:
110
lib/plots/parts_per_set.py
Normal file
110
lib/plots/parts_per_set.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""Graphiques sur la taille moyenne des sets (pièces par set)."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Tuple
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from lib.filesystem import ensure_parent_dir
|
||||
from lib.milestones import load_milestones
|
||||
from lib.rebrickable.stats import read_rows
|
||||
|
||||
|
||||
def compute_average_parts_per_set(rows: Iterable[dict]) -> List[Tuple[int, float]]:
|
||||
"""Calcule la moyenne annuelle de pièces par set."""
|
||||
per_year: Dict[int, Dict[str, int]] = {}
|
||||
for row in rows:
|
||||
year = int(row["year"])
|
||||
per_year[year] = per_year.get(year, {"parts": 0, "sets": 0})
|
||||
per_year[year]["parts"] += int(row["num_parts"])
|
||||
per_year[year]["sets"] += 1
|
||||
results: List[Tuple[int, float]] = []
|
||||
for year in sorted(per_year):
|
||||
totals = per_year[year]
|
||||
results.append((year, totals["parts"] / totals["sets"]))
|
||||
return results
|
||||
|
||||
|
||||
def compute_rolling_mean(series: List[Tuple[int, float]], window: int) -> List[Tuple[int, float]]:
|
||||
"""Calcule la moyenne glissante sur une fenêtre donnée."""
|
||||
values = [value for _, value in series]
|
||||
years = [year for year, _ in series]
|
||||
rolling: List[Tuple[int, float]] = []
|
||||
for index in range(len(values)):
|
||||
if index + 1 < window:
|
||||
rolling.append((years[index], 0.0))
|
||||
else:
|
||||
window_values = values[index - window + 1 : index + 1]
|
||||
rolling.append((years[index], sum(window_values) / window))
|
||||
return rolling
|
||||
|
||||
|
||||
def plot_parts_per_set(
|
||||
enriched_sets_path: Path,
|
||||
milestones_path: Path,
|
||||
destination_path: Path,
|
||||
rolling_window: int = 3,
|
||||
) -> None:
|
||||
"""Génère un graphique de la moyenne annuelle et glissante des pièces par set."""
|
||||
sets_rows = read_rows(enriched_sets_path)
|
||||
milestones = load_milestones(milestones_path)
|
||||
annual_series = compute_average_parts_per_set(sets_rows)
|
||||
rolling_series = compute_rolling_mean(annual_series, rolling_window)
|
||||
years = [year for year, _ in annual_series]
|
||||
annual_values = [value for _, value in annual_series]
|
||||
rolling_values = [value for _, value in rolling_series]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
ax.plot(years, annual_values, marker="o", color="#2ca02c", label="Moyenne annuelle (pièces/set)")
|
||||
ax.plot(
|
||||
years,
|
||||
rolling_values,
|
||||
marker="^",
|
||||
color="#9467bd",
|
||||
label=f"Moyenne glissante {rolling_window} ans (pièces/set)",
|
||||
)
|
||||
ax.set_xlabel("Année")
|
||||
ax.set_ylabel("Pièces par set")
|
||||
ax.set_title("Évolution de la taille moyenne des sets (thèmes filtrés)")
|
||||
ax.grid(True, linestyle="--", alpha=0.3)
|
||||
ax.set_xlim(min(years) - 0.4, max(years) + 0.4)
|
||||
ax.set_xticks(list(range(min(years), max(years) + 1)))
|
||||
ax.tick_params(axis="x", labelrotation=45)
|
||||
|
||||
peak = max(max(annual_values), max(rolling_values))
|
||||
top_limit = peak * 2
|
||||
milestones_in_range = sorted(
|
||||
[m for m in milestones if min(years) <= m["year"] <= max(years)],
|
||||
key=lambda m: (m["year"], m["description"]),
|
||||
)
|
||||
milestone_offsets: Dict[int, int] = {}
|
||||
offset_step = 0.4
|
||||
max_offset = 0
|
||||
for milestone in milestones_in_range:
|
||||
year = milestone["year"]
|
||||
count_for_year = milestone_offsets.get(year, 0)
|
||||
milestone_offsets[year] = count_for_year + 1
|
||||
horizontal_offset = offset_step * (count_for_year // 2 + 1)
|
||||
max_offset = max(max_offset, count_for_year)
|
||||
if count_for_year % 2 == 1:
|
||||
horizontal_offset *= -1
|
||||
text_x = year + horizontal_offset
|
||||
ax.axvline(year, color="#d62728", linestyle="--", linewidth=1, alpha=0.65)
|
||||
ax.text(
|
||||
text_x,
|
||||
top_limit,
|
||||
milestone["description"],
|
||||
rotation=90,
|
||||
verticalalignment="top",
|
||||
horizontalalignment="center",
|
||||
fontsize=8,
|
||||
color="#d62728",
|
||||
)
|
||||
|
||||
ax.set_ylim(0, top_limit * (1 + max_offset * 0.02))
|
||||
ax.legend(loc="upper left", bbox_to_anchor=(1.12, 1))
|
||||
|
||||
ensure_parent_dir(destination_path)
|
||||
fig.tight_layout()
|
||||
fig.savefig(destination_path, dpi=150)
|
||||
plt.close(fig)
|
||||
Reference in New Issue
Block a user