update task3

This commit is contained in:
2026-01-20 01:55:46 +08:00
parent d49a10a042
commit 101e8b7e8e
14 changed files with 176 additions and 78 deletions

View File

@@ -1,10 +1,10 @@
"""
Task 3 - Step 8: 可视化Fig.1/2/4/5
Task 3 - Step 8: 可视化Fig.1/2/4/5/6
=====================================
输入:
- 01_distance.xlsx (sites: 经纬度、mu/sigma)
- 02_pairing.xlsx (selected_pairs: 34对配对连线)
- 02_pairing.xlsx (selected_pairs: 配对连线)
- 03_allocation.xlsx (allocation: q*, E_total)
- 05_calendar.xlsx (calendar: 365天×2槽位排程)
- 06_evaluate.xlsx (pair_risk: 缺口概率分布)
@@ -12,6 +12,7 @@ Task 3 - Step 8: 可视化Fig.1/2/4/5
输出:
- figures/fig1_pairing_map.png
- figures/fig2_allocation_scatter.png
- figures/fig6_fairness_satisfaction.png
- figures/fig4_calendar_heatmap.png
- figures/fig5_risk_distribution.png
"""
@@ -47,7 +48,6 @@ BASE_DIR = Path(__file__).resolve().parent
MORANDI = {
"bg": "#F5F2ED",
"grid": "#D7D1C7",
"text": "#3F3F3F",
"muted_blue": "#8FA3A7",
@@ -59,19 +59,22 @@ MORANDI = {
"terracotta": "#B07A6A",
}
FIG_BG = "#FFFFFF"
FIG_GRID = "#DADDE1"
def _apply_morandi_style() -> None:
plt.rcParams.update(
{
"figure.facecolor": MORANDI["bg"],
"axes.facecolor": MORANDI["bg"],
"savefig.facecolor": MORANDI["bg"],
"axes.edgecolor": MORANDI["grid"],
"figure.facecolor": FIG_BG,
"axes.facecolor": FIG_BG,
"savefig.facecolor": FIG_BG,
"axes.edgecolor": FIG_GRID,
"axes.labelcolor": MORANDI["text"],
"xtick.color": MORANDI["text"],
"ytick.color": MORANDI["text"],
"text.color": MORANDI["text"],
"grid.color": MORANDI["grid"],
"grid.color": FIG_GRID,
"grid.alpha": 0.55,
"axes.grid": True,
"axes.titleweight": "semibold",
@@ -79,8 +82,8 @@ def _apply_morandi_style() -> None:
"axes.labelsize": 10,
"legend.frameon": True,
"legend.framealpha": 0.9,
"legend.facecolor": MORANDI["bg"],
"legend.edgecolor": MORANDI["grid"],
"legend.facecolor": FIG_BG,
"legend.edgecolor": FIG_GRID,
"axes.prop_cycle": cycler(
"color",
[
@@ -188,7 +191,7 @@ def fig1_pairing_map(paths: Paths) -> Path:
zorder=3,
)
ax.set_title("Fig.1 Pairing map (sites + selected 34 links)")
ax.set_title(f"Fig.1 Pairing map (sites + selected n={len(pairs)} links)")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.grid(True, alpha=0.55)
@@ -196,7 +199,7 @@ def fig1_pairing_map(paths: Paths) -> Path:
out = paths.figures_dir / "fig1_pairing_map.png"
fig.tight_layout()
fig.savefig(out)
fig.savefig(out, facecolor=FIG_BG)
plt.close(fig)
return out
@@ -287,11 +290,11 @@ def fig2_allocation_scatter(paths: Paths) -> Path:
ax.grid(True, alpha=0.55)
axes[0].set_ylabel("$q^*/Q$")
fig.suptitle("Fig.2 Allocation strategy scatter (34 pairs)", y=1.02)
fig.suptitle(f"Fig.2 Allocation strategy scatter (n={len(df)} pairs)", y=1.02)
fig.tight_layout()
out = paths.figures_dir / "fig2_allocation_scatter.png"
fig.savefig(out, bbox_inches="tight")
fig.savefig(out, bbox_inches="tight", facecolor=FIG_BG)
plt.close(fig)
return out
@@ -336,7 +339,7 @@ def fig4_calendar_heatmap(paths: Paths) -> Path:
cmap = mcolors.LinearSegmentedColormap.from_list(
"morandi",
[
MORANDI["bg"],
FIG_BG,
"#E7E1D7",
MORANDI["sage"],
MORANDI["muted_blue"],
@@ -358,7 +361,7 @@ def fig4_calendar_heatmap(paths: Paths) -> Path:
fig.tight_layout()
out = paths.figures_dir / "fig4_calendar_heatmap.png"
fig.savefig(out, bbox_inches="tight")
fig.savefig(out, bbox_inches="tight", facecolor=FIG_BG)
plt.close(fig)
return out
@@ -373,7 +376,7 @@ def fig5_risk_distribution(paths: Paths) -> Path:
fig, axes = plt.subplots(1, 2, figsize=(11, 3.8), dpi=200)
ax = axes[0]
ax.hist(p, bins=10, color=MORANDI["sage"], alpha=0.9, edgecolor=MORANDI["bg"])
ax.hist(p, bins=10, color=MORANDI["sage"], alpha=0.9, edgecolor=FIG_BG)
ax.axvline(
float(np.mean(p)),
color=MORANDI["terracotta"],
@@ -396,7 +399,7 @@ def fig5_risk_distribution(paths: Paths) -> Path:
linewidth=1.6,
color=MORANDI["muted_blue_dark"],
markerfacecolor=MORANDI["muted_blue"],
markeredgecolor=MORANDI["bg"],
markeredgecolor=FIG_BG,
markeredgewidth=0.6,
)
ax.set_title("Sorted by risk (descending)")
@@ -404,10 +407,61 @@ def fig5_risk_distribution(paths: Paths) -> Path:
ax.set_ylabel("Shortfall probability")
ax.grid(True, alpha=0.55)
fig.suptitle("Fig.5 Risk distribution across 34 pairs", y=1.02)
fig.suptitle(f"Fig.5 Risk distribution across n={len(p)} pairs", y=1.02)
fig.tight_layout()
out = paths.figures_dir / "fig5_risk_distribution.png"
fig.savefig(out, bbox_inches="tight")
fig.savefig(out, bbox_inches="tight", facecolor=FIG_BG)
plt.close(fig)
return out
def fig6_fairness_satisfaction(paths: Paths) -> Path:
_apply_morandi_style()
df = pd.read_excel(paths.evaluate_xlsx, sheet_name="site_satisfaction").copy()
r = df["satisfaction_rate_r"].to_numpy(dtype=float)
r = r[np.isfinite(r)]
r_sorted = np.sort(r)
# Lorenz curve (site share vs satisfaction share)
n = len(r_sorted)
if n == 0 or np.sum(r_sorted) <= 0:
x = np.array([0.0, 1.0])
y = np.array([0.0, 1.0])
gini = 0.0
else:
cum = np.cumsum(r_sorted)
x = np.concatenate([[0.0], np.arange(1, n + 1) / n])
y = np.concatenate([[0.0], cum / cum[-1]])
# Gini via Lorenz area
gini = 1.0 - 2.0 * float(np.trapezoid(y, x))
fig, axes = plt.subplots(1, 2, figsize=(11, 3.8), dpi=200)
ax = axes[0]
ax.hist(r, bins=12, color=MORANDI["muted_blue"], alpha=0.9, edgecolor=FIG_BG)
ax.axvline(float(np.mean(r)), color=MORANDI["terracotta"], linewidth=2.2, label=f"mean={np.mean(r):.2f}")
ax.set_title("Satisfaction rate distribution")
ax.set_xlabel("Satisfaction rate $r_i$")
ax.set_ylabel("Count")
ax.grid(True, alpha=0.55)
ax.legend(loc="best", frameon=True)
ax = axes[1]
ax.plot(x, y, color=MORANDI["sage"], linewidth=2.2, label="Lorenz curve")
ax.plot([0, 1], [0, 1], color=MORANDI["warm_gray"], linestyle="--", linewidth=1.4, label="Equality line")
ax.set_title(f"Lorenz curve (Gini={gini:.3f})")
ax.set_xlabel("Cumulative share of sites")
ax.set_ylabel("Cumulative share of satisfaction")
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.grid(True, alpha=0.55)
ax.legend(loc="lower right", frameon=True)
fig.suptitle(f"Fig.6 Fairness diagnostics (n={len(r)} sites)", y=1.02)
fig.tight_layout()
out = paths.figures_dir / "fig6_fairness_satisfaction.png"
fig.savefig(out, bbox_inches="tight", facecolor=FIG_BG)
plt.close(fig)
return out
@@ -434,6 +488,8 @@ def main() -> None:
print(f"Saved: {out1_js.relative_to(BASE_DIR)}")
out2 = fig2_allocation_scatter(paths)
print(f"Saved: {out2.relative_to(BASE_DIR)}")
out6 = fig6_fairness_satisfaction(paths)
print(f"Saved: {out6.relative_to(BASE_DIR)}")
out4 = fig4_calendar_heatmap(paths)
print(f"Saved: {out4.relative_to(BASE_DIR)}")
out5 = fig5_risk_distribution(paths)