Dias et al. (2025): the autumn AHY main model (Table 3), filtering reading (c)¶

Dataset DRYAD:bvq83bkk9, version id 333377 (the only version; files and SHA-256 in the header of reproduce_dias_2025.ipynb). Paper doi:10.1098/rspb.2024.2556.

The hierarchical model of lm_mod.stan (Zenodo doi:10.5281/zenodo.14226724) for autumn AHY birds, as Script01 fits it: scaled wing on scaled tarsus per species-year, with sex effects per species; the species-year intercepts and slopes depend on PrevPrevPrecipWinMo, PrevTempBreedBase, PrevPrecipBreedBase, PrevMinTempFallMig, MinTempFallMig and year (per decade); NUTS, adapt_delta 0.85, 4 chains of 4000 iterations with 2000 warm-up, seed 9. Filtering reading (c): {"a": "the data as deposited", "c": "specimens more than 5 MAD from the median of their species and sex removed", "d": "specimens more than 5 unscaled MAD from the median of their species removed"}["c"].

Claims tested here, under this reading: table3-autumn-ahy-prior-breeding-temperature, table3-autumn-ahy-prior-autumn-temperature, table3-autumn-ahy-recent-autumn-temperature and the main-model part of table3-autumn-ahy-prior-wintering-precipitation. The autumn AHY numbers of claims that also need the HY and spring AHY models are printed for the record.

In [1]:
import tempfile
import time
import warnings
from pathlib import Path

import numpy as np
import pandas as pd

import wingallom as w

warnings.filterwarnings("ignore", module="rdata")
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
import logging

started = time.time()
for name, digest in w.check_staged().items():
    print(f"{name}: SHA-256 {digest} (matches Dryad's file list, version {w.DRYAD_VERSION} of {w.DRYAD_ID})")
t0 = time.time()
print("CmdStan", w.CMDSTAN_VERSION, "at", w.install_cmdstan(Path(tempfile.gettempdir()) / "cmdstan"),
      f"({time.time() - t0:.0f} s to install or find)")
logging.getLogger("cmdstanpy").setLevel(logging.ERROR)   # sampler messages; divergences are counted below
code_zip = w.code_zip()
original = code_zip.read("Code/lm_mod.stan").decode()
model_code = w.port_stan(original)
workdir = Path(tempfile.mkdtemp())
t0 = time.time()
model = w.compile_stan(model_code, workdir)
cpu = next((line.split(":", 1)[1].strip() for line in open("/proc/cpuinfo") if line.startswith("model name")), "unknown")
print("CPU:", cpu, "(the model is compiled with -march=native, as Script01 does, so the draws depend on the CPU)")
print(f"lm_mod.stan compiled in {time.time() - t0:.0f} s")

READING = "c"
Data.zip: SHA-256 2185ac50e9a59c3a1dff59473216de2ab001d0d0044b1fa15442ae18a520d554 (matches Dryad's file list, version 333377 of bvq83bkk9)
README.md: SHA-256 6c19de67b1c6c52802be42a6593c175a02bee0dab0ca2f6f958f08a0a3fa0482 (matches Dryad's file list, version 333377 of bvq83bkk9)
CmdStan 2.40.0 at /tmp/cmdstan/cmdstan-2.40.0 (0 s to install or find)
/home/runner/work/_temp/executed/.venv/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
03:39:44 - cmdstanpy - INFO - compiling stan file /tmp/tmprecf6m42/lm_mod.stan to exe file /tmp/tmprecf6m42/lm_mod
03:40:08 - cmdstanpy - INFO - compiled model executable: /tmp/tmprecf6m42/lm_mod
CPU: AMD EPYC 7763 64-Core Processor (the model is compiled with -march=native, as Script01 does, so the draws depend on the CPU)
lm_mod.stan compiled in 24 s

Data check¶

In [2]:
morph = w.morph_prepared(READING)
removed = w.outlier_mask(w.read_csv("morphdata.csv"), READING)
p = w.prepare(morph, "autumn AHY", "main")
data = w.stan_data(p, w.MAIN_PREDICTORS["autumn AHY"])
md_ = p["md"]
print(f"reading ({READING}): {int(removed.sum())} specimens removed by the filter; {len(morph)} kept")
print(f"autumn AHY: {data['N']} birds in {data['J']} species-years of {data['K']} species (paper: 3250 and 171)")
print(f"species-years dropped for a missing climate value: rows {p['missing_rows']} of {p['n_merged']}")
print("\nBirds per species and sex:")
print(pd.crosstab(md_.Taxon, md_.Sex).to_string())
print(f"\nYears {p['G'].year.min()} to {p['G'].year.max()}; year column {data['G'][:, -1].min():.2f} to {data['G'][:, -1].max():.2f} decades")
print(f"scaled wing {data['WL'].min():.2f} to {data['WL'].max():.2f}, scaled tarsus {data['X'][:, 1].min():.2f} to {data['X'][:, 1].max():.2f}")
print("climate columns of G (scaled within species), min and max:")
print(pd.DataFrame(data["G"][:, 1:-1], columns=w.MAIN_PREDICTORS["autumn AHY"][:-1]).agg(["min", "max"]).round(2).to_string())
reading (c): 17 specimens removed by the filter; 27469 kept
autumn AHY: 3249 birds in 171 species-years of 5 species (paper: 3250 and 171)
species-years dropped for a missing climate value: rows [np.int64(1), np.int64(36), np.int64(106), np.int64(111), np.int64(112), np.int64(142)] of 177

Birds per species and sex:
Sex                       f    m
Taxon                           
Junco_hyemalis          250  279
Melospiza_georgiana     353  440
Melospiza_melodia        78  112
Seiurus_aurocapilla     260  314
Zonotrichia_albicollis  627  536

Years 1982 to 2016; year column -1.60 to 1.80 decades
scaled wing -4.02 to 3.04, scaled tarsus -4.28 to 3.79
climate columns of G (scaled within species), min and max:
     PrevPrevPrecipWinMo  PrevTempBreedBase  PrevPrecipBreedBase  PrevMinTempFallMig  MinTempFallMig
min                -2.74              -1.92                -2.24               -2.95           -2.95
max                 2.65               2.00                 1.77                2.23            2.47

Fit¶

In [3]:
t0 = time.time()
fit = w.fit_stan(model, data, w.STAN_SEEDS["autumn AHY"], workdir)
fit_minutes = (time.time() - t0) / 60
print(f"fitted in {fit_minutes:.1f} minutes")
print(f"divergent transitions after warm-up: {fit['divergent']} of 8000 draws (chains 1 to 4: {fit['divergent_per_chain']}); "
      f"largest R-hat over mu_i, mu_s, mu_stl and the standard deviations: {fit['max_rhat']:.3f}; smallest bulk ESS: {fit['min_ess_bulk']:.0f}")
names = ["Intercept", *w.MAIN_PREDICTORS["autumn AHY"]]
diag = fit["mu_summary"].copy()
diag.index = [f"{'mu_i' if i.startswith('mu_i') else 'mu_s'} {names[int(i.split('[')[1][:-1]) - 1]}" for i in diag.index]
print("\nCmdStan's diagnostics of mu_i (intercept) and mu_s (slope):")
print(diag.round(4).to_string())
table3 = w.effect_table(fit, w.MAIN_PREDICTORS["autumn AHY"])
print("\nTable 3 as fitted here (mean, s.d. and pd of mu_i, effects on the allometric intercept, and mu_s, on the slope):")
print(table3[["int_mean", "int_sd", "int_pd", "slope_mean", "slope_sd", "slope_pd"]].round(3).to_string())
print("\nMonte Carlo error of each pd (from the effective sample size of the sign indicator) and the pd of each chain:")
mc = pd.DataFrame({"int_pd": table3.int_pd, "int_pd_mcse": table3.int_pd_mcse, "int_pd_ess": table3.int_pd_ess,
                   "int_pd_chains": [[round(x, 3) for x in v] for v in table3.int_pd_chains],
                   "slope_pd": table3.slope_pd, "slope_pd_mcse": table3.slope_pd_mcse, "slope_pd_ess": table3.slope_pd_ess,
                   "slope_pd_chains": [[round(x, 3) for x in v] for v in table3.slope_pd_chains]})
print(mc.round(4).to_string())
stuck = w.stuck_chains(fit)
if stuck:
    moving = w.effect_table(w.drop_chains(fit, stuck), w.MAIN_PREDICTORS["autumn AHY"])
    print(f"\nChain(s) {', '.join(str(c + 1) for c in stuck)} stuck (every kept draw divergent). The pd over the "
          f"{4 - len(stuck)} chains that move, pooled, with its Monte Carlo error:")
    print(moving[["int_mean", "int_pd", "int_pd_mcse", "slope_mean", "slope_pd", "slope_pd_mcse"]].round(4).to_string())
else:
    moving = None
    print("\nNo chain is stuck (none has every kept draw divergent).")


def show(term, paper):
    """The claim's numbers for one term: mean, s.d. and pd with its Monte Carlo error and chains,
    against the paper's mean, tolerance and pd."""
    row = table3.loc[term]
    print(f"{term}, reading ({READING}):")
    for part in ("int", "slope"):
        m, sd, pd_, se = row[part + "_mean"], row[part + "_sd"], row[part + "_pd"], row[part + "_pd_mcse"]
        pm, ptol, ppd = paper[part]
        print(f"  {'intercept' if part == 'int' else 'slope':9s} mean {m:+.3f} (s.d. {sd:.3f}), pd {pd_:.2%};"
              f" paper {pm:+.2f} (tolerance {ptol}), pd {ppd}%; within tolerance: {abs(m - pm) <= ptol + 1e-9};"
              f" sign as paper: {np.sign(m) == np.sign(pm) or pm == 0}; pd >= 90%: {pd_ >= 0.9}")
        print(f"            Monte Carlo error of pd {se:.2%} (ESS {row[part + '_pd_ess']:.0f}), so pd - 90% is "
              f"{(pd_ - 0.9) / se:+.1f} errors; pd of chains 1 to 4: {', '.join(f'{x:.2%}' for x in row[part + '_pd_chains'])}")
        if moving is not None:
            mrow = moving.loc[term]
            mpd, mse = mrow[part + "_pd"], mrow[part + "_pd_mcse"]
            print(f"            chains that move, pooled: mean {mrow[part + '_mean']:+.3f}, pd {mpd:.2%}, Monte Carlo error {mse:.2%},"
                  f" so pd - 90% is {(mpd - 0.9) / mse:+.1f} errors; pd >= 90%: {mpd >= 0.9}")
03:40:08 - cmdstanpy - INFO - CmdStan start processing
03:40:08 - cmdstanpy - INFO - Chain [1] start processing
03:40:08 - cmdstanpy - INFO - Chain [2] start processing
03:40:08 - cmdstanpy - INFO - Chain [3] start processing
03:40:08 - cmdstanpy - INFO - Chain [4] start processing
03:49:10 - cmdstanpy - INFO - Chain [1] done processing
03:50:19 - cmdstanpy - INFO - Chain [4] done processing
03:51:18 - cmdstanpy - INFO - Chain [2] done processing
03:52:22 - cmdstanpy - INFO - Chain [3] done processing
03:52:22 - cmdstanpy - WARNING - Non-fatal error during sampling:
Exception: normal_lpdf: Scale parameter[1] is 0, but must be positive! (in 'lm_mod.stan', line 43, column 6 to column 35)
Exception: normal_lpdf: Scale parameter is 0, but must be positive! (in 'lm_mod.stan', line 38, column 4 to column 29)
	Exception: normal_lpdf: Scale parameter is 0, but must be positive! (in 'lm_mod.stan', line 38, column 4 to column 29)
Exception: normal_lpdf: Scale parameter is 0, but must be positive! (in 'lm_mod.stan', line 38, column 4 to column 29)
	Exception: normal_lpdf: Scale parameter is 0, but must be positive! (in 'lm_mod.stan', line 38, column 4 to column 29)
	Exception: normal_lpdf: Scale parameter[2] is 0, but must be positive! (in 'lm_mod.stan', line 43, column 6 to column 35)
Consider re-running with show_console=True if the above output is unclear!
03:52:23 - cmdstanpy - WARNING - Some chains may have failed to converge.
	Chain 1 had 887 divergent transitions (44.4%)
	Chain 2 had 126 divergent transitions (6.3%)
	Chain 2 had 52 iterations at max treedepth (2.6%)
	Chain 3 had 93 divergent transitions (4.7%)
	Chain 3 had 4 iterations at max treedepth (0.2%)
	Chain 4 had 424 divergent transitions (21.2%)
	Chain 4 had 13 iterations at max treedepth (0.7%)
	Use the "diagnose()" method on the CmdStanMCMC object to see further information.
fitted in 12.3 minutes
divergent transitions after warm-up: 1530 of 8000 draws (chains 1 to 4: [887, 126, 93, 424]); largest R-hat over mu_i, mu_s, mu_stl and the standard deviations: 1.188; smallest bulk ESS: 17

CmdStan's diagnostics of mu_i (intercept) and mu_s (slope):
                            Mean    MCSE   R_hat   ESS_bulk   ESS_tail
mu_i Intercept            1.0052  0.0014  1.0374  2318.0700  2352.6200
mu_i PrevPrevPrecipWinMo  0.0286  0.0006  1.0447  2486.7300  2376.9200
mu_i PrevTempBreedBase   -0.0906  0.0021  1.0450    63.8408  2492.4900
mu_i PrevPrecipBreedBase -0.0559  0.0064  1.0896    31.4090    82.2396
mu_i PrevMinTempFallMig  -0.0189  0.0013  1.0120   992.5370  1343.4500
mu_i MinTempFallMig       0.0269  0.0008  1.0148  1739.4800  2216.5200
mu_i y_id                 0.1967  0.0072  1.0782    33.6342   351.3760
mu_s Intercept            0.1034  0.0010  1.0252   735.4550  1878.7300
mu_s PrevPrevPrecipWinMo  0.0198  0.0080  1.1164    22.3894    19.8819
mu_s PrevTempBreedBase   -0.0256  0.0145  1.1387    19.6720  2713.0400
mu_s PrevPrecipBreedBase -0.0074  0.0015  1.0148   446.9370  2324.4300
mu_s PrevMinTempFallMig   0.0083  0.0010  1.0176  1005.9100  1233.8200
mu_s MinTempFallMig      -0.0324  0.0009  1.0520  1818.6900  2365.7300
mu_s y_id                 0.0144  0.0008  1.0474  1459.8400  1618.1300

Table 3 as fitted here (mean, s.d. and pd of mu_i, effects on the allometric intercept, and mu_s, on the slope):
                     int_mean  int_sd  int_pd  slope_mean  slope_sd  slope_pd
term                                                                         
Intercept               1.005   0.071   1.000       0.103     0.028     0.996
PrevPrevPrecipWinMo     0.029   0.027   0.892       0.020     0.035     0.815
PrevTempBreedBase      -0.091   0.029   0.994      -0.026     0.045     0.765
PrevPrecipBreedBase    -0.056   0.039   0.938      -0.007     0.043     0.562
PrevMinTempFallMig     -0.019   0.045   0.742       0.008     0.031     0.634
MinTempFallMig          0.027   0.029   0.889      -0.032     0.035     0.912
y_id                    0.197   0.049   0.995       0.014     0.028     0.731

Monte Carlo error of each pd (from the effective sample size of the sign indicator) and the pd of each chain:
                     int_pd  int_pd_mcse  int_pd_ess                 int_pd_chains  slope_pd  slope_pd_mcse  slope_pd_ess               slope_pd_chains
term                                                                                                                                                   
Intercept            1.0000          NaN         NaN          [1.0, 1.0, 1.0, 1.0]    0.9962         0.0022      807.9237    [0.996, 0.998, 0.992, 1.0]
PrevPrevPrecipWinMo  0.8915       0.0066   2197.6539   [0.921, 0.89, 0.889, 0.865]    0.8146         0.1047       13.7825  [0.598, 0.872, 0.901, 0.888]
PrevTempBreedBase    0.9939       0.0017   2041.1328  [0.999, 0.995, 0.995, 0.988]    0.7651         0.0116     1337.5031   [0.828, 0.757, 0.746, 0.73]
PrevPrecipBreedBase  0.9384       0.0054   1954.3067  [0.954, 0.941, 0.935, 0.923]    0.5615         0.0669       55.0120   [0.407, 0.62, 0.597, 0.622]
PrevMinTempFallMig   0.7418       0.0114   1472.9851   [0.746, 0.74, 0.738, 0.743]    0.6335         0.0196      602.7892  [0.553, 0.674, 0.627, 0.679]
MinTempFallMig       0.8892       0.0065   2333.2966  [0.906, 0.881, 0.887, 0.882]    0.9124         0.0054     2712.1007  [0.928, 0.907, 0.909, 0.904]
y_id                 0.9949       0.0019   1398.3169    [1.0, 0.991, 0.998, 0.991]    0.7309         0.0158      790.3912  [0.786, 0.734, 0.728, 0.676]

No chain is stuck (none has every kept draw divergent).

The sampler does not converge well: 1530 of 8000 draws are divergent (887 of them in chain 1) and the largest R-hat is 1.188 (smallest bulk ESS 17). Some effects of the table mix poorly: the slope effects of PrevPrevPrecipWinMo and PrevTempBreedBase and the intercept effects of PrevPrecipBreedBase and year have bulk ESS 20 to 34 and R-hat 1.08 to 1.14. The Monte Carlo error of the slope pd of PrevPrevPrecipWinMo is 10.47% (chains 59.80% to 90.10%). The Methods say convergence was confirmed with R-hat and trace plots; no diagnostic values are reported. The model is compiled with -march=native, as Script01 does, so the draws depend on the CPU printed above, and a run on another machine gives other draws with the same seed. The Monte Carlo error and the pd of each chain, printed above, show how far each pd that decides an outcome could move with other draws.

table3-autumn-ahy-prior-breeding-temperature¶

In autumn AHY birds, warmer temperature in the breeding season of their development (the year before the most recent) is again associated with a more negative allometric intercept (beta -0.09, pd 99.5%) and not with the slope (beta -0.01, pd 75.1%).

In [4]:
paper = {"int": (-0.09, 0.03, 99.5), "slope": (-0.01, 0.03, 75.1)}
show("PrevTempBreedBase", paper)
PrevTempBreedBase, reading (c):
  intercept mean -0.091 (s.d. 0.029), pd 99.39%; paper -0.09 (tolerance 0.03), pd 99.5%; within tolerance: True; sign as paper: True; pd >= 90%: True
            Monte Carlo error of pd 0.17% (ESS 2041), so pd - 90% is +54.4 errors; pd of chains 1 to 4: 99.85%, 99.45%, 99.45%, 98.80%
  slope     mean -0.026 (s.d. 0.045), pd 76.51%; paper -0.01 (tolerance 0.03), pd 75.1%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 1.16% (ESS 1338), so pd - 90% is -11.6 errors; pd of chains 1 to 4: 82.80%, 75.70%, 74.55%, 73.00%

Result: under reading (c), the claim holds. Intercept -0.091 (pd 99.39%), slope -0.026 (pd 76.51%), within tolerance.

table3-autumn-ahy-prior-autumn-temperature¶

In autumn AHY birds, the temperature of the autumn migration before the most recent one has no significant association with the allometric intercept (beta -0.02, pd 78.0%) or slope (beta 0.01, pd 70.9%).

In [5]:
paper = {"int": (-0.02, 0.07, 78.0), "slope": (0.01, 0.05, 70.9)}
show("PrevMinTempFallMig", paper)
PrevMinTempFallMig, reading (c):
  intercept mean -0.019 (s.d. 0.045), pd 74.17%; paper -0.02 (tolerance 0.07), pd 78.0%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 1.14% (ESS 1473), so pd - 90% is -13.9 errors; pd of chains 1 to 4: 74.60%, 74.00%, 73.80%, 74.30%
  slope     mean +0.008 (s.d. 0.031), pd 63.35%; paper +0.01 (tolerance 0.05), pd 70.9%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 1.96% (ESS 603), so pd - 90% is -13.6 errors; pd of chains 1 to 4: 55.30%, 67.45%, 62.75%, 67.90%

Result: under reading (c), the claim holds. Intercept -0.019 (pd 74.17%), slope 0.008 (pd 63.35%).

table3-autumn-ahy-recent-autumn-temperature¶

In autumn AHY birds, temperature during the autumn migration in which they were collected has no significant association with the allometric intercept (beta 0.03, pd 88.6%) but a negative association with the allometric slope (beta -0.03, pd 90.3%).

In [6]:
paper = {"int": (0.03, 0.04, 88.6), "slope": (-0.03, 0.05, 90.3)}
show("MinTempFallMig", paper)
MinTempFallMig, reading (c):
  intercept mean +0.027 (s.d. 0.029), pd 88.92%; paper +0.03 (tolerance 0.04), pd 88.6%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 0.65% (ESS 2333), so pd - 90% is -1.7 errors; pd of chains 1 to 4: 90.60%, 88.10%, 88.75%, 88.25%
  slope     mean -0.032 (s.d. 0.035), pd 91.24%; paper -0.03 (tolerance 0.05), pd 90.3%; within tolerance: True; sign as paper: True; pd >= 90%: True
            Monte Carlo error of pd 0.54% (ESS 2712), so pd - 90% is +2.3 errors; pd of chains 1 to 4: 92.85%, 90.70%, 90.95%, 90.45%

Result: under reading (c), the claim holds. Intercept 0.027 (pd 88.92%), slope -0.032 (pd 91.24%, 2.3 Monte Carlo errors above 90%), within tolerance.

table3-autumn-ahy-prior-wintering-precipitation¶

In autumn AHY birds the HY association does not hold. Wintering precipitation before development has a positive association with the allometric intercept (beta 0.03, pd 90.0%), none with the slope (beta 0.02, pd 88.7%), and pre-breeding precipitation before development is not associated with wing length in the preliminary model (Table S6). (The preliminary part is in reproduce_dias_2025.ipynb.)

In [7]:
paper = {"int": (0.03, 0.03, 90.0), "slope": (0.02, 0.03, 88.7)}
show("PrevPrevPrecipWinMo", paper)
PrevPrevPrecipWinMo, reading (c):
  intercept mean +0.029 (s.d. 0.027), pd 89.15%; paper +0.03 (tolerance 0.03), pd 90.0%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 0.66% (ESS 2198), so pd - 90% is -1.3 errors; pd of chains 1 to 4: 92.15%, 89.05%, 88.90%, 86.50%
  slope     mean +0.020 (s.d. 0.035), pd 81.46%; paper +0.02 (tolerance 0.03), pd 88.7%; within tolerance: True; sign as paper: True; pd >= 90%: False
            Monte Carlo error of pd 10.47% (ESS 14), so pd - 90% is -0.8 errors; pd of chains 1 to 4: 59.80%, 87.15%, 90.10%, 88.80%

Result: under reading (c), the main-model part falls just short. Intercept 0.029 (pd 89.15%, below 90% by 1.3 Monte Carlo errors; chains 86.50% to 92.15%), slope 0.020 (pd 81.46%); signs and means hold.

Autumn AHY parts of claims that also need the HY and spring AHY models¶

These claims are blocked for time (the HY and spring AHY fits are not run); the autumn AHY numbers are printed for the record: year (tables1-3-year-intercept, tables2-3-ahy-year-slope-none) and breeding precipitation before development (tables1-3-breeding-precipitation), and reading (a) of climate-explains-little (every climate effect on the intercept smaller than the year effect).

In [8]:
print(table3.loc[["y_id", "PrevPrecipBreedBase"], ["int_mean", "int_sd", "int_pd", "int_pd_mcse", "slope_mean", "slope_sd", "slope_pd", "slope_pd_mcse"]].round(3).to_string())
print("paper, Table 3: year intercept 0.19 (pd 99.8%), slope 0.02 (pd 75.9%); PrevPrecipBreedBase intercept -0.05 (pd 93.4%), slope -0.01 (pd 65.3%)")
climate = [t for t in w.MAIN_PREDICTORS["autumn AHY"] if t != "y_id"]
year = abs(table3.loc["y_id", "int_mean"])
print(f"\nclimate-explains-little, reading (a) of its criterion, autumn AHY part: |year effect on the intercept| {year:.3f}; "
      f"|climate effects on the intercept|: " + ", ".join(f"{t} {abs(table3.loc[t, 'int_mean']):.3f}" for t in climate))
print(f"every climate effect smaller than the year effect: {all(abs(table3.loc[t, 'int_mean']) < year for t in climate)}")
print(f"\nNotebook time: {(time.time() - started) / 60:.1f} minutes")
                     int_mean  int_sd  int_pd  int_pd_mcse  slope_mean  slope_sd  slope_pd  slope_pd_mcse
term                                                                                                     
y_id                    0.197   0.049   0.995        0.002       0.014     0.028     0.731          0.016
PrevPrecipBreedBase    -0.056   0.039   0.938        0.005      -0.007     0.043     0.562          0.067
paper, Table 3: year intercept 0.19 (pd 99.8%), slope 0.02 (pd 75.9%); PrevPrecipBreedBase intercept -0.05 (pd 93.4%), slope -0.01 (pd 65.3%)

climate-explains-little, reading (a) of its criterion, autumn AHY part: |year effect on the intercept| 0.197; |climate effects on the intercept|: PrevPrevPrecipWinMo 0.029, PrevTempBreedBase 0.091, PrevPrecipBreedBase 0.056, PrevMinTempFallMig 0.019, MinTempFallMig 0.027
every climate effect smaller than the year effect: True

Notebook time: 12.7 minutes