# Fraction of the regional loss since 1850 attributable to anthropogenic # forcing, for the HIST members of the attribution dataset. # # Loss since 1850 of a run: L(t) = X(1850) - X(t) (annual values, no window). # Anthropogenic fraction = 1 - L_nat / L_hist, with L_nat from: # - "ensemble": every NAT member's own loss since 1850 (each from its own # 1850 state) -> median and 5-95% range; # - "paired": the NAT run of the same member. It is identical to HIST up to # 1850, so L_hist - L_nat = X_nat(t) - X_hist(t) is the effect of the # post-1850 forcing for that glacier state, but for one natural realisation. # The fraction is only shown from FRAC_START: before that, the loss since # 1850 is small (ens183 is back near its 1850 size in ~1925) and the ratio # blows up. # # Run from round4/attribution/ with the oggm_env python. import os import numpy as np import pandas as pd import matplotlib.pyplot as plt EXP = os.environ.get('EXP', 'lam8_jjas_dyn') MEMBER = os.environ.get('MEMBER', 'ens183') COMPARE = {'ens124': 'NAT 1850: P14', 'ens183': 'P41', 'ens178': 'P98'} COLORS = {'ens124': '#f1948a', 'ens183': '#c0392b', 'ens178': '#641e16'} REF, T_END, FRAC_START = 1850, 2021, 1950 TIMES = [1950, 1975, 2000, 2015, 2021] FILTERED = f'../ensemble/filtered/{EXP}' NAT_C, NAT_DARK, HIST_C = '#2e86c1', '#1b4f72', '#c0392b' VARS = [('volume', 'km3', 'km$^3$'), ('area', 'km2', 'km$^2$')] os.makedirs('figs', exist_ok=True) def read(var, short): nat = pd.read_csv(f'{FILTERED}/{var}_{short}_nat_ens_reg11.csv', index_col=0) hist = pd.read_csv(f'{FILTERED}/{var}_{short}_hist_ens_reg11.csv', index_col=0) for df in (nat, hist): df.index = df.index.astype(int) yrs = np.arange(REF, T_END + 1) return nat.loc[REF] - nat.loc[yrs], hist.loc[REF] - hist.loc[yrs] def fractions(l_nat, l_hist, m): fe = 1 - l_nat.div(l_hist[m], axis=0) q = fe.quantile([0.05, 0.5, 0.95], axis=1).T return pd.DataFrame({'loss_hist': l_hist[m], 'loss_nat_median': l_nat.median(axis=1), 'loss_nat_same_member': l_nat[m], 'frac_ens_p05': q[0.05], 'frac_ens_median': q[0.5], 'frac_ens_p95': q[0.95], 'frac_paired': 1 - l_nat[m] / l_hist[m], 'n_nat': l_nat.notnull().sum(axis=1)}) # %% Figure 1: one member, losses (top) and the fraction (bottom) fig, axes = plt.subplots(2, 2, figsize=(13, 7.5), sharex=True, gridspec_kw=dict(height_ratios=[3, 2], hspace=0.08)) out = [] for col, (var, short, unit) in enumerate(VARS): l_nat, l_hist = read(var, short) fr = fractions(l_nat, l_hist, MEMBER) out.append(fr.loc[TIMES].assign(var=var)) yrs = fr.index ax, ax2 = axes[:, col] q = l_nat.quantile([0.05, 0.5, 0.95], axis=1).T ax.fill_between(yrs, q[0.05], q[0.95], color=NAT_C, alpha=0.18, lw=0, label='Natural only: NAT ensemble, 5–95%') ax.plot(yrs, q[0.5], color=NAT_DARK, lw=2, label='Natural only: NAT median') ax.plot(yrs, l_nat[MEMBER], color=NAT_DARK, lw=1.2, ls='--', label=f'Natural only: NAT {MEMBER} (same 1850 state)') ax.plot(yrs, l_hist[MEMBER], color=HIST_C, lw=2.5, label=f'HIST {MEMBER}') ax.fill_between(yrs, q[0.5], l_hist[MEMBER], where=(l_hist[MEMBER] > q[0.5]) & (yrs >= FRAC_START), color=HIST_C, alpha=0.12, lw=0, label='Anthropogenic part (vs NAT median)') ax.axhline(0, color='0.4', lw=0.8) ax.set_title(f'Regional {var}', fontweight='bold') ax.set_ylabel(f'{var.capitalize()} loss since {REF} ({unit})') ax.grid(alpha=0.3) f = fr.loc[FRAC_START:] ax2.fill_between(f.index, 100 * f.frac_ens_p05, 100 * f.frac_ens_p95, color=HIST_C, alpha=0.15, lw=0, label='Ensemble estimate, 5–95%') ax2.plot(f.index, 100 * f.frac_ens_median, color=HIST_C, lw=2.5, label='Ensemble estimate, median') ax2.plot(f.index, 100 * f.frac_paired, color=HIST_C, lw=1.2, ls='--', label=f'Paired estimate (NAT {MEMBER})') for y in [2000, T_END]: v = 100 * fr.frac_ens_median[y] ax2.annotate(f'{v:.0f}%', (y, v), textcoords='offset points', xytext=(-4 if y == T_END else 0, 9), ha='right' if y == T_END else 'center', fontsize=10, fontweight='bold', color=HIST_C) ax2.plot(y, v, 'o', color=HIST_C, ms=6) ax2.axhline(100, color='0.4', lw=0.8) ax2.axhline(0, color='0.4', lw=0.8) ax2.set_ylim(-60, 140) ax2.set_ylabel(f'Anthropogenic share of\nloss since {REF} (%)') ax2.set_xlabel('Year') ax2.grid(alpha=0.3) ax2.set_xlim(REF, T_END) ax2.text(0.01, 0.93, f'(shown from {FRAC_START}: before, the loss since {REF} is too ' 'small for a ratio)', transform=ax2.transAxes, fontsize=8, color='0.4') axes[0, 0].legend(loc='upper left', fontsize=8.5, frameon=False) axes[1, 0].legend(loc='lower right', fontsize=8.5, frameon=False) fig.suptitle(f'How much of the Alpine glacier loss since {REF} is anthropogenic? ' f'HIST {MEMBER} vs NAT ({EXP})', y=0.94) fig.savefig(f'figs/attr_fraction_{EXP}_{MEMBER}.png', dpi=150, bbox_inches='tight') plt.close(fig) out = pd.concat(out).rename_axis('year').set_index('var', append=True) out.to_csv(f'figs/attr_fraction_{EXP}_{MEMBER}.csv') with pd.option_context('display.width', 200, 'display.precision', 2): print(out) # %% Figure 2: the fraction for the three members fig, axes = plt.subplots(1, 2, figsize=(13, 4.2), sharey=True) for ax, (var, short, unit) in zip(axes, VARS): l_nat, l_hist = read(var, short) for m, lab in COMPARE.items(): f = fractions(l_nat, l_hist, m).loc[FRAC_START:] if m == MEMBER: ax.fill_between(f.index, 100 * f.frac_ens_p05, 100 * f.frac_ens_p95, color=COLORS[m], alpha=0.12, lw=0) ax.plot(f.index, 100 * f.frac_ens_median, color=COLORS[m], lw=2.2, label=f'{m} ({lab}): ensemble median') ax.plot(f.index, 100 * f.frac_paired, color=COLORS[m], lw=1.2, ls='--') ax.plot([], [], color='0.4', lw=1.2, ls='--', label='paired (same-member NAT)') ax.axhline(100, color='0.4', lw=0.8) ax.axhline(0, color='0.4', lw=0.8) ax.set_ylim(-60, 140) ax.set_title(f'Regional {var}', fontweight='bold') ax.set_xlabel('Year') ax.grid(alpha=0.3) axes[0].set_ylabel(f'Anthropogenic share of\nloss since {REF} (%)') axes[0].legend(loc='lower right', fontsize=8.5, frameon=False) fig.suptitle(f'Anthropogenic share of the loss since {REF}, three HIST members ' f'(band: 5–95% for {MEMBER})', y=1.0) fig.savefig(f'figs/attr_fraction_compare_{EXP}.png', dpi=150, bbox_inches='tight')