update task3
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user