Assigning standard deviations

This is the step that decides your answer, and it is a modelling decision rather than a formality. mix_ml() needs a standard deviation for every species in every end-member and every sample.

The asymmetry matters more than the values

stage

which sigma is larger

why

estimating end-members from the mixtures

end-member σ > sample σ

you want the mixtures to reshape the end-members

end-members already established, fitting new samples

sample σ > end-member σ

you do not want new data to undo the previous fit

Tubau et al. do exactly this: loose end-members in the river step, tight ones in the groundwater step.

What σ should express

Not the laboratory’s repeatability. It should carry

  1. analytical error,

  2. sampling error, and

  3. conceptual error — the share of the mixture that may belong to a source you did not include, to a reaction, or to an incomplete dataset.

For end-members, (3) usually dominates. Using the lab’s precision instead typically understates the uncertainty by an order of magnitude and produces intervals you cannot defend.

The device worth stealing

Give a species you do not trust a σ hundreds or thousands of times larger, rather than dropping it. It then contributes nothing to the fit but still receives a predicted end-member composition. Tubau et al. obtained expected compositions for PO₄, F, B, TOC, Br, As, Fe and O₂ this way — species they had barely measured.

sigma = 0.05 * samples.data
sigma[:, samples.species.index("NO3")] *= 1000   # carried, not trusted

The bundled load_tubau_besos() ships their published scheme, factors and all.

Always sweep it

from mescla.uncertainty.identifiability import sigma_sweep

sigma_sweep(endmembers, samples, factors=(0.1, 0.5, 1.0, 2.0, 10.0))

If the mean contributions move by more than a few percent across the sweep, the variance assumptions are doing the work rather than your data. Report the sweep either way.