"""Figures for the expansion_analogs study (static PNG, light surface).""" import json import os import numpy as np import pandas as pd from common import C, DATA, FIG, jeffreys, style STATUS_COL = {"persisted": C["blue"], "abandoned": C["orange"]} def _lon(x): return np.mod(np.asarray(x, float), 360.0) def fig_map(u, pac): plt = style() fig = plt.figure(figsize=(11.8, 5.9)) ax = fig.add_axes([0.05, 0.17, 0.60, 0.74]) ix = {k: i for i, k in enumerate(u.unit)} # evidence-based parent links for _, r in u.iterrows(): if isinstance(r.parent, str) and r.region == "E": p = u.iloc[ix[r.parent]] ax.plot(_lon([p.lon, r.lon]), [p.lat, r.lat], color=C["ink2"], lw=0.9, alpha=0.75, zorder=1) # the long corner hops that only exist through abandoned stepping stones (dashed) for a, b in (("Teraina", "Hawaiian Islands"), ("Henderson", "Rapa Nui")): pa, pb = u.iloc[ix[a]], u.iloc[ix[b]] ax.plot(_lon([pa.lon, pb.lon]), [pa.lat, pb.lat], color=C["orange"], lw=1.1, ls=(0, (4, 3)), zorder=1) size = 18 + 26 * np.log10(np.maximum(u.land_km2.values, 1)) for st, lab in (("none", "No settlement found"), ("traces", "Traces only")): m = (u.status == st).values ax.scatter(_lon(u.lon[m]), u.lat[m], s=size[m] * 0.7, facecolors="none", edgecolors=C["muted"], lw=1.0 if st == "none" else 1.6, marker="o" if st == "none" else "D", label=lab, zorder=2) doubt = (u.evidence == "doubtful").values for st, lab in (("persisted", "Inhabited at European contact"), ("abandoned", "Settled, then abandoned")): m = (u.status == st).values & ~doubt ax.scatter(_lon(u.lon[m]), u.lat[m], s=size[m], color=STATUS_COL[st], edgecolors=C["surface"], lw=1.2, label=lab, zorder=3) ax.scatter(_lon(u.lon[doubt]), u.lat[doubt], s=size[doubt], facecolors="none", edgecolors=C["orange"], lw=1.8, label="Abandoned, settlement doubtful", zorder=3) labels = {"Samoa": (-2, 10), "Tonga": (-6, -15), "Fiji": (-9, 3, "right"), "Southern Cooks": (-34, -15), "Society Islands": (3, -15), "Tuamotus": (9, 4), "Marquesas": (9, 4), "Hawaiian Islands": (11, 2), "Rapa Nui": (-22, 11), "New Zealand": (-12, -8, "right"), "Chatham Islands": (10, -8), "Mangareva (Gambier)": (-30, -15), "Kiritimati": (10, -1), "Norfolk": (-8, 7, "right"), "Raoul (Kermadec)": (9, 3), "Lord Howe": (8, -3), "Auckland Islands": (-9, -3, "right"), "Galapagos": (-30, 11), "Juan Fernandez": (-58, -15), "Nihoa": (-14, 9), "Henderson": (5, 9), "Rapa Iti": (7, -12), "Howland": (-8, 8, "right")} short = {"Mangareva (Gambier)": "Mangareva", "Raoul (Kermadec)": "Raoul", "Society Islands": "Societies"} for k, off in labels.items(): r = u.iloc[ix[k]] ax.annotate(short.get(k, k), (float(_lon(r.lon)), r.lat), xytext=off[:2], textcoords="offset points", fontsize=7.6, color=C["ink2"], zorder=4, ha=off[2] if len(off) > 2 else "left") sa = pac["hops"]["rapa_nui_to_south_america_km"] ax.annotate(f"South America: {sa:,.0f} km from Rapa Nui", (289.5, -21.5), fontsize=7.6, color=C["ink2"], ha="right", va="center") ax.annotate("", xy=(290.5, -23.6), xytext=(256, -23.6), arrowprops=dict(arrowstyle="->", color=C["muted"], lw=0.8)) ax.set_xlim(147, 291); ax.set_ylim(-58, 31) ax.set_xticks([160, 180, 200, 220, 240, 260, 280]); ax.set_xticklabels(["160°E", "180°", "160°W", "140°W", "120°W", "100°W", "80°W"]) ax.set_yticks([-40, -20, 0, 20]); ax.set_yticklabels(["40°S", "20°S", "0°", "20°N"]) ax.set_title("Polynesian settlement units: what was reached, what lasted") ax.legend(loc="upper left", bbox_to_anchor=(-0.01, -0.07), ncol=5, fontsize=7.6, handletextpad=0.2, columnspacing=1.0) ax.text(0.99, 0.975, "Grey lines: source-to-daughter links supported by genetics or archaeology.\n" "Dashed: longest hops needed, via islands later abandoned. Marker size: land area.", transform=ax.transAxes, fontsize=7.2, color=C["ink2"], ha="right", va="top") # panel b: hop distances bx = fig.add_axes([0.72, 0.17, 0.265, 0.74]) Es = ((u.region == "E") & u.settled).values nn = u.nearest_settled_km[Es].values ev = u.parent_km[Es & u.parent.notna().values].values bins = np.arange(0, 2801, 200) bx.hist(nn, bins=bins, color=C["blue"], alpha=0.9, rwidth=0.86, label=f"Nearest settled neighbour ({len(nn)} units)") bx.hist(ev, bins=bins, histtype="step", color=C["ink"], lw=1.6, label=f"Documented source-to-daughter hop ({len(ev)})") D = pac["hops"]["network_bottleneck_km"] bx.axvline(D, color=C["orange"], lw=1.4, ls=(0, (4, 3))) bx.set_ylim(0, 13.5) bx.text(D - 50, 7.2, f"Longest hop the\nsettled network\nneeds: {D:,.0f} km\n(defined from\nthe outcome)", fontsize=7.4, color=C["ink2"], ha="right") bx.set_xlabel("Open-sea hop distance (km)"); bx.set_ylabel("Number of settlement units") bx.set_yticks([0, 2, 4, 6, 8, 10]) bx.set_xticks([0, 1000, 2000]); bx.set_xticklabels(["0", "1,000", "2,000"]) bx.set_title("Most hops were short") bx.legend(loc="upper right", fontsize=7.4) fig.savefig(os.path.join(FIG, "fig1_pacific_map_and_hops.png")) plt.close(fig) def fig_timeline(u, pac, norse): plt = style() cur = pd.read_csv(os.path.join(DATA, "pacific_cumulative_settlement_curves.csv")) fig, (ax, bx) = plt.subplots(1, 2, figsize=(11.8, 4.7), gridspec_kw=dict(width_ratios=[1.35, 1], wspace=0.36)) west = 3 # Fiji, Tonga, Samoa: the dated Lapita-period units in the table for chron, col, lab in (("S", C["blue"], "Short radiocarbon chronology"), ("A", C["orange"], "Alternative (genetic and model-based) chronology")): c = cur[cur.year >= 700] c = c[c.chronology == chron] ax.fill_between(c.year, c.lo + west, c.hi + west, color=col, alpha=0.18, lw=0) ax.plot(c.year, c.med + west, color=col, label=lab) # Lapita steps (Fiji about 1050 BC, Tonga about 850 BC, Samoa about 800 BC), then no new dated unit until the eastern pulse ax.plot([-1200, -1050, -1050, -850, -850, -800, -800, 700], [0, 0, 1, 1, 2, 2, 3, 3], color=C["ink"], lw=1.6, zorder=3) ax.text(-1180, 4.3, "Lapita reaches\nFiji, Tonga, Samoa", fontsize=8, color=C["ink2"]) pS, pA = pac["timing"]["S"]["pause_years"][2], pac["timing"]["A"]["pause_years"][2] ax.annotate("", xy=(840, 1.7), xytext=(-800, 1.7), arrowprops=dict(arrowstyle="<->", color=C["muted"], lw=0.9)) ax.text(20, 0.45, f"Pause in West Polynesia: about {pA:,.0f} to {pS:,.0f} years", fontsize=8, color=C["ink2"], ha="center") dS, dA = pac["timing"]["S"]["pulse_10_90_years"][2], pac["timing"]["A"]["pulse_10_90_years"][2] ax.text(760, 17.3, f"Eastern pulse:\n10% to 90% of dated\nunits in {dS:.0f} to {dA:.0f} years", fontsize=8, color=C["ink2"], ha="right") ax.text(-1180, 8.6, "20 of 29 settled units have date windows;\n5 of those windows are generic.\nBands: 16% to 84% over the windows.", fontsize=7.2, color=C["ink2"], ha="left", va="bottom") ax.set_xlim(-1200, 1800); ax.set_ylim(0, 26.5) ax.set_xticks([-1000, -500, 0, 500, 1000, 1500]); ax.set_xticklabels(["1000 BC", "500 BC", "AD 1", "500", "1000", "1500"]) ax.set_ylabel("Dated settlement units occupied (cumulative)") ax.set_title("A long pause, then a pulse of a few centuries") ax.legend(loc="upper left", fontsize=7.8) # panel b: lifetimes of settlements that failed rows = [] for k, v in pac["abandonment"]["lifetime_years_p16_p50_p84"].items(): rows.append((k.replace(" (Kermadec)", ""), v[0], v[1], v[2], "Pacific")) short = {"Greenland Eastern Settlement": "Greenland (East)", "Greenland Western Settlement": "Greenland (West)", "Vinland (L'Anse aux Meadows)": "Vinland"} for k, v in norse["lifetimes_years"].items(): rows.append((short.get(k, k), v[0], v[1], v[2], "Norse")) rows.sort(key=lambda r: r[2]) y = np.arange(len(rows)) generic = set(pac["abandonment"]["lifetime_usable_dates"]["dropped_generic_window"]) rows = [(r[0] + (" *" if any(r[0].startswith(g.split(" ")[0]) for g in generic) else ""),) + r[1:] for r in rows] for i, r in enumerate(rows): col = C["blue"] if r[4] == "Pacific" else C["aqua"] bx.barh(i, r[2], height=0.56, color=col, zorder=2, alpha=0.45 if r[0].endswith("*") else 1.0) bx.plot([r[1], r[3]], [i, i], color=C["ink"], lw=1.0, zorder=3) bx.text(max(r[3], r[2]) + 12, i, f"{r[2]:.0f}", va="center", fontsize=7.8, color=C["ink2"]) bx.set_yticks(y); bx.set_yticklabels([r[0] for r in rows], fontsize=8) bx.set_xlabel("Years from first settlement to last dated occupation") bx.set_xlim(0, 580) bx.grid(axis="y", visible=False) from matplotlib.patches import Patch bx.legend(handles=[Patch(color=C["blue"], label="Pacific"), Patch(color=C["aqua"], label="Norse North Atlantic")], loc="lower right", fontsize=7.8) bx.set_title("Failed settlements: indicative lifetimes") bx.text(0.98, 0.30, "* first-settlement window is generic,\nnot specific to the island.\nLines: 16% to 84% range over\nthe first-settlement window.", transform=bx.transAxes, fontsize=7.2, color=C["ink2"], ha="right", va="top") fig.savefig(os.path.join(FIG, "fig2_timeline_and_lifetimes.png")) plt.close(fig) def fig_abandonment(u, pac, isl): """Abandonment at island grain: small AND alone. Replaces the first-pass figure, whose 'size, not isolation' reading was an artefact of merging persisted islands into archipelago units.""" plt = style() from matplotlib.ticker import NullFormatter fig, (ax, bx, cx) = plt.subplots(1, 3, figsize=(12.6, 4.7), gridspec_kw=dict(width_ratios=[1.45, 0.95, 0.95], wspace=0.33)) il = pac["abandonment"]["island_level"] cols = {"inhabited": C["blue"], "abandoned": C["orange"]} for st, lab in (("inhabited", "Inhabited at European contact"), ("abandoned", "Abandoned before contact")): m = (isl.contact == st).values ax.scatter(isl.land_km2[m], isl.nn_settled_km[m], s=40, color=cols[st], edgecolors=C["surface"], lw=1.1, label=lab, zorder=3) m = (isl.contact == "doubtful").values ax.scatter(isl.land_km2[m], isl.nn_settled_km[m], s=34, facecolors="none", edgecolors=C["muted"], lw=1.2, label="In a persisting group, contact status doubtful", zorder=2) lab = {"Rapa Nui": (7, -3), "Rapa Iti": (7, -3), "Penrhyn": (7, -10), "Kiritimati": (7, -3), "Necker": (7, -3), "Suwarrow": (0, 7, "center"), "Howland": (7, 2), "Raoul": (7, 2), "Norfolk": (7, -8), "Malden": (-7, 5, "right"), "North Island": (-6, 7, "right"), "Chatham Island": (7, 3), "Nihoa": (7, -3), "Henderson": (-7, -11, "right"), "Enderby": (7, -1)} for k, off in lab.items(): r = isl[isl.island == k].iloc[0] ax.annotate(k, (r.land_km2, r.nn_settled_km), xytext=off[:2], textcoords="offset points", fontsize=7.2, color=C["ink2"], ha=off[2] if len(off) > 2 else "left") ax.set_xscale("log"); ax.set_yscale("log") ax.set_xlim(0.1, 4e5); ax.set_ylim(25, 3200) ax.set_yticks([30, 100, 300, 1000, 3000]); ax.set_yticklabels(["30", "100", "300", "1,000", "3,000"]) ax.yaxis.set_minor_formatter(NullFormatter()) ax.set_xticks([0.1, 1, 10, 100, 1000, 1e4, 1e5]); ax.set_xticklabels(["0.1", "1", "10", "100", "1,000", "10,000", "100,000"], fontsize=7.6) ax.set_xlabel("Land area of the island (km², log scale)") ax.set_ylabel("Distance to the nearest other settled island (km)") ax.set_title("Abandoned islands were small\nand had no near neighbour") ax.legend(loc="upper left", bbox_to_anchor=(-0.02, -0.16), ncol=1, fontsize=7.6) # panel b: the unit artefact - share abandoned by area class, unit grain against island grain classes = [(0, 10, "under\n10"), (10, 100, "10 to\n100"), (100, 1000, "100 to\n1,000"), (1000, 1e9, "over\n1,000")] Es = ((u.region == "E") & u.settled).values d = u[Es] w = 0.36 for i, (lo, hi, name) in enumerate(classes): mu = (d.land_km2 >= lo) & (d.land_km2 < hi) k, n = int((d.status[mu] == "abandoned").sum()), int(mu.sum()) row = il["by_area_class"][i] k2, n2 = row["abandoned"], row["islands"] bx.bar(i - w / 2 - 0.02, k / n, width=w, color=C["gray"], zorder=2, label="Archipelagos as one unit (first pass)" if i == 0 else None) bx.bar(i + w / 2 + 0.02, k2 / n2, width=w, color=C["orange"], zorder=2, label="Island by island" if i == 0 else None) bx.text(i - w / 2 - 0.02, k / n + 0.02, f"{k}/{n}", ha="center", fontsize=7.4, color=C["ink2"]) bx.text(i + w / 2 + 0.02, k2 / n2 + 0.02, f"{k2}/{n2}", ha="center", fontsize=7.4, color=C["ink2"]) bx.set_xticks(range(len(classes))); bx.set_xticklabels([c[2] for c in classes], fontsize=8) bx.set_ylim(0, 1.05); bx.set_yticks([0, 0.25, 0.5, 0.75, 1.0]); bx.set_yticklabels(["0%", "25%", "50%", "75%", "100%"]) bx.set_xlabel("Land area (km²)"); bx.set_ylabel("Share abandoned before contact") bx.grid(axis="x", visible=False) bx.legend(loc="upper right", fontsize=7.6) bx.set_title("The size effect shrinks\nat island grain") # panel c: aggregation-radius profile prof = il["aggregation_radius_profile"] xs = np.arange(len(prof)) auc = [p["leave_one_unit_out_auc"] for p in prof] cx.plot(xs, auc, color=C["blue"], lw=2, marker="o", ms=6, mec=C["surface"], mew=1.2, zorder=3) for x_, a_ in zip(xs, auc): end = x_ in (xs[0], xs[-1]) # end labels go below the point, clear of the line cx.text(x_, a_ - 0.026 if end else a_ + 0.012, f"{a_:.2f}", ha="center", fontsize=7.4, color=C["ink2"]) cx.axhline(0.5, color=C["muted"], lw=1.0, ls=(0, (4, 3))) cx.text(xs[-1], 0.508, "no skill", fontsize=7.4, color=C["ink2"], ha="right") cx.set_xticks(xs); cx.set_xticklabels(["island\nonly"] + [f"{p['radius_km']:,}" for p in prof[1:]], fontsize=7.8) cx.set_ylim(0.45, 0.85) cx.set_xlabel("Land counted within this distance (km)") cx.set_ylabel("Out-of-sample skill (AUC, whole groups held out)") cx.set_title("Land within 50 to 300 km\npredicts persistence best") fig.savefig(os.path.join(FIG, "fig3_abandonment_vs_size.png")) plt.close(fig) def fig_percolation(u, pac): plt = style() fig, (ax, bx) = plt.subplots(1, 2, figsize=(12.2, 4.7), gridspec_kw=dict(width_ratios=[1.0, 1.15], wspace=0.5)) ramp = {2100: "#86b6ef", 2600: "#2a78d6", 3500: "#104281"} # one hue, light to dark = shorter to longer assumed range ends = {} for R, col in ramp.items(): prof = pac["percolation"][str(R)]["all_land_units_with_habitability"]["profile"] ps = np.array([float(k) for k in prof]); vs = np.array(list(prof.values())) ax.plot(ps, vs, color=col, lw=2, marker="o", ms=3.5, label=f"{R:,} km") ends[R] = vs[2] ax.axhline(np.exp(-2), color=C["muted"], lw=0.9, ls=(0, (1, 2))) ax.text(0.99, np.exp(-2) + 0.02, "support limit (likelihood ratio e⁻²)", fontsize=7.2, color=C["ink2"], ha="right") ax.axvline(1 / 3, color=C["muted"], lw=1.0, ls=(0, (4, 3))) ax.text(1 / 3 + 0.015, 1.12, "Landis's illustrative p = 1/3", fontsize=7.6, color=C["ink2"], ha="left", va="top") ax.set_xlim(0, 1.0); ax.set_ylim(0, 1.15) ax.set_xlabel("Probability that a settlement goes on to found others (p)") ax.set_ylabel("Profile likelihood relative to its maximum") ax.set_title("The Pacific bounds p only from below,\nand only if voyaging range was short") ax.legend(loc="upper left", bbox_to_anchor=(-0.02, -0.15), ncol=3, fontsize=7.8, title="Assumed voyaging range", title_fontsize=7.8, columnspacing=1.2) ax.text(0.99, 0.02, "All 50 land units of East Polynesia;\nhabitability fitted from land area.", fontsize=7.2, color=C["ink2"], ha="right", va="bottom") # panel b: share of targets settled, by definition of 'target' defs = pac["reach"]["by_definition"] names = ["Every land unit in reach", "Excluding rocks and\nsubantarctic islands", "10 km² or more, excluding\nrocks and subantarctic", "Held a civilian settlement for\n20 years or more after contact", "Carries a self-supporting\ncommunity since contact", "Settled, or self-supporting later\n(first-pass definition: circular)"] y = np.arange(len(defs))[::-1] for yi, d_, nm in zip(y, defs, names): circ = d_["denominator_depends_on_outcome"] bx.barh(yi, d_["fraction"], height=0.56, color=C["gray"] if circ else C["blue"], zorder=2) bx.barh(yi, d_["inhabited_at_contact"] / d_["targets"], height=0.56, color=C["ink2"] if circ else "#104281", zorder=3) bx.text(d_["fraction"] + 0.015, yi, f"{d_['settled']} of {d_['targets']}", va="center", fontsize=8, color=C["ink2"]) bx.set_yticks(y); bx.set_yticklabels(names, fontsize=7.8) bx.set_xlim(0, 1.18); bx.set_xticks([0, 0.25, 0.5, 0.75, 1.0]); bx.set_xticklabels(["0%", "25%", "50%", "75%", "100%"]) bx.set_xlabel("Share of targets settled before European contact") bx.grid(axis="y", visible=False) from matplotlib.patches import Patch bx.legend(handles=[Patch(color=C["blue"], label="Settled at some time"), Patch(color="#104281", label="Still inhabited at contact")], loc="upper left", bbox_to_anchor=(-0.02, -0.15), ncol=2, fontsize=7.8) bx.set_title("How full the Pacific was depends on what counts as a target") fig.savefig(os.path.join(FIG, "fig4_percolation_fit_and_reach.png")) plt.close(fig) def fig_invasions_prior(inv, tab, coefs, syn): plt = style() fig, (ax, bx) = plt.subplots(1, 2, figsize=(11.8, 4.6), gridspec_kw=dict(width_ratios=[1, 1.15], wspace=0.27)) x = tab.geo_mid.values ax.errorbar(x, tab.fraction, yerr=[tab.fraction - tab.ci_lo, tab.ci_hi - tab.fraction], fmt="o", color=C["blue"], ecolor=C["ink"], elinewidth=1.0, ms=6.5, mec=C["surface"], mew=1.2, zorder=3) for _, r in tab.iterrows(): ax.text(r.geo_mid, r.ci_hi + 0.02, f"n={int(r.n)}", fontsize=7.4, color=C["ink2"], ha="center") xs = np.logspace(np.log10(2), 3, 100) b3 = coefs[1] ax.plot(xs, 1 / (1 + np.exp(-(b3[0] + b3[1] * np.log10(xs)))), color=C["blue"], lw=1.6, alpha=0.8) ax.axhline(0.10, color=C["muted"], lw=1.0, ls=(0, (4, 3))) ax.text(28000, 0.115, "'Tens rule': 10%", fontsize=7.8, color=C["ink2"], ha="right") ax.set_xscale("log"); ax.set_xlim(2, 30000); ax.set_ylim(0, 0.8) ax.set_xticks([10, 100, 1000, 10000]); ax.set_xticklabels(["10", "100", "1,000", "10,000"]) ax.set_yticks([0, 0.2, 0.4, 0.6, 0.8]); ax.set_yticklabels(["0%", "20%", "40%", "60%", "80%"]) ax.set_xlabel("Birds released per introduction event (log scale)") ax.set_ylabel("Share of introductions that established") p = inv["propagule"] ax.set_title(f"{p['n_events']} bird introductions: {100 * p['overall_fraction']:.0f}% established") ax.text(0.98, 0.30, "Points: binned events, 95% intervals.\nCurve: logistic fit, releases up to 1,000.", transform=ax.transAxes, fontsize=7.4, color=C["ink2"], va="top", ha="right") # panel b: the low tail of the first-pass 'prior' is set by the assumed feasibility floor s = np.load(os.path.join(DATA, "prior_samples.npz")) grid = np.linspace(-12, 0, 400) cdf = lambda v: np.searchsorted(np.sort(v), grid) / len(v) bx.plot(grid, cdf(s["log10_p_base"]), color=C["ink"], lw=2.0, label="Project model's current prior") ramp = {"1e_2": ("#f5b99b", "10⁻²"), "1e_4": (C["orange"], "10⁻⁴"), "1e_6": ("#9c3a12", "10⁻⁶")} for k, (col, lab) in ramp.items(): v = cdf(s["log10_floor_" + k]) bx.plot(grid, v, color=col, lw=2.0, label=f"Illustrative product × discount, floor {lab}") bx.plot(grid, cdf(s["log10_product_mixture"]), color=C["blue"], lw=2.0, label="Illustrative product alone (given feasibility)") bx.axvline(-3, color=C["muted"], lw=1.0, ls=(0, (4, 3))) fl = syn["feasibility_floor_sensitivity"] bx.text(-3.15, 0.99, "share below 10⁻³:\n" + "\n".join(f"floor {lab}: {100 * fl[k.replace('_', '-')]['share_below_1e_3']:.0f}%" for k, (_, lab) in ramp.items()) + f"\nmodel prior: {100 * syn['model_priors']['base W=(1,30)']['share_below_1e_3']:.0f}%", fontsize=7.4, color=C["ink2"], ha="right", va="top") bx.set_xlim(-12, 0); bx.set_ylim(0, 1.02) bx.set_xticks([-12, -10, -8, -6, -4, -2, 0]); bx.set_xticklabels(["10⁻¹²", "10⁻¹⁰", "10⁻⁸", "10⁻⁶", "10⁻⁴", "10⁻²", "1"]) bx.set_yticks([0, 0.25, 0.5, 0.75, 1.0]); bx.set_yticklabels(["0%", "25%", "50%", "75%", "100%"]) bx.set_xlabel("Probability that a civilisation becomes a sustained expander") bx.set_ylabel("Probability of being below this value") bx.set_title("The low tail is set by an assumed floor, not by data") bx.legend(loc="upper left", bbox_to_anchor=(-0.02, -0.15), ncol=2, fontsize=7.4, columnspacing=1.0) fig.savefig(os.path.join(FIG, "fig5_invasion_base_rates_and_prior.png")) plt.close(fig) def run(u, pac, norse, inv, tab, coefs, syn, isl): os.makedirs(FIG, exist_ok=True) fig_map(u, pac) fig_timeline(u, pac, norse) fig_abandonment(u, pac, isl) fig_percolation(u, pac) fig_invasions_prior(inv, tab, coefs, syn)