By Peptide.Express (peptide.express)
If you work with synthetic peptides, sooner or later you need to know what mass to expect from a mass spectrometer. The math is simple: add up the residues, add one water, and divide by the charge. It is also easy to get slightly wrong. This post builds a small calculator from scratch, explains why monoisotopic and average masses differ, and checks the result against an open-source package.
Everything below was run in a fresh virtual environment with Python 3.13.
Two kinds of "mass"
- Monoisotopic mass uses only the most abundant isotope of each element (¹²C, ¹H, ¹⁴N, ¹⁶O, ³²S). This is the mass of the first peak in a high-resolution isotope envelope, and it is the number you compare against high-resolution MS data.
- Average mass uses each element's standard atomic weight, which is the natural isotope mix averaged together. It matches the centroid of a low-resolution peak, and it is what you use for anything weighed on a balance.
For small peptides the two differ by less than a dalton. For larger ones the gap grows, because every carbon atom adds a little ¹³C.
Where the element masses come from
I used two primary sources and opened both to check the values:
- Monoisotopic masses: NIST, Atomic Weights and Isotopic Compositions. For example, ¹H = 1.00782503223 u and ¹⁶O = 15.99491461957 u.
- Average masses: IUPAC CIAAW, Abridged Standard Atomic Weights. H = 1.0080, C = 12.011, N = 14.007, O = 15.999, S = 32.06.
- Proton mass: NIST CODATA, proton mass in u, 1.0072764665789 u.
A from-scratch calculator
Rather than hard-coding residue masses, I store each residue as an elemental formula. A residue is the amino acid minus H₂O, which is the water lost when a peptide bond forms. The full peptide is the sum of its residues plus one H₂O for the free N- and C-termini.
# scratch.py
# Monoisotopic masses: NIST "Atomic Weights and Isotopic Compositions"
MONO = {"C": 12.0, "H": 1.00782503223, "N": 14.00307400443,
"O": 15.99491461957, "S": 31.9720711744}
# Average masses: IUPAC/CIAAW abridged standard atomic weights
AVG = {"C": 12.011, "H": 1.0080, "N": 14.007, "O": 15.999, "S": 32.06}
PROTON = 1.0072764665789 # CODATA 2022 proton mass, u
# Residue = amino acid minus H2O, as elemental composition (C, H, N, O, S)
RESIDUES = {
"G": (2, 3, 1, 1, 0), "A": (3, 5, 1, 1, 0), "S": (3, 5, 1, 2, 0),
"P": (5, 7, 1, 1, 0), "V": (5, 9, 1, 1, 0), "T": (4, 7, 1, 2, 0),
"C": (3, 5, 1, 1, 1), "L": (6, 11, 1, 1, 0), "I": (6, 11, 1, 1, 0),
"N": (4, 6, 2, 2, 0), "D": (4, 5, 1, 3, 0), "Q": (5, 8, 2, 2, 0),
"K": (6, 12, 2, 1, 0), "E": (5, 7, 1, 3, 0), "M": (5, 9, 1, 1, 1),
"H": (6, 7, 3, 1, 0), "F": (9, 9, 1, 1, 0), "R": (6, 12, 4, 1, 0),
"Y": (9, 9, 1, 2, 0), "W": (11, 10, 2, 1, 0),
}
ELEMENTS = ("C", "H", "N", "O", "S")
def composition(seq):
total = [0, 2, 0, 1, 0] # start with one H2O for the termini
for aa in seq.upper():
total = [t + r for t, r in zip(total, RESIDUES[aa])]
return dict(zip(ELEMENTS, total))
def peptide_mass(seq, table):
return sum(n * table[el] for el, n in composition(seq).items())
def mz(mono, z):
return (mono + z * PROTON) / z
Working at the formula level has a side benefit: you get the molecular formula for free, and switching element tables is a one-argument change.
The mz function gives the electrospray ion [M+zH]z+. You add z protons, not z hydrogen atoms, since the ion has gained protons without their electrons, and then divide by the charge.
The open-source peptide-mass package
peptide-mass is a small, dependency-free MIT-licensed package on PyPI:
python3 -m venv venv
./venv/bin/pip install peptide-mass
Version 0.1.0 installs a single module. I read its source, and its public API (__all__) is four names: mass, mz, RESIDUE and MODS.
-
mass(seq, n_acetyl=False, c_amide=False, pyroglu=False, disulfides=0)returns a(monoisotopic, average)tuple for the neutral free peptide. It raisesValueErrorfor anything outside the 20 standard residues. -
mz(mono, z)returns(mono + z * PROTON) / z. -
RESIDUEmaps one-letter codes to(monoisotopic, average)residue masses. These are hard-coded numbers, not formulas. -
MODSholds mass deltas for N-acetylation, C-terminal amidation, pyroglutamate (from Q or E), and disulfide bonds (−2H each).
It also installs a peptide-mass command-line tool, which can process a CSV through --csv.
Worked example and cross-check
The test sequence is a generic 8-mer, GASVLPTK:
# demo.py
from scratch import composition, peptide_mass, mz, MONO, AVG
import peptide_mass as pm
seq = "GASVLPTK"
print("formula:", composition(seq))
mono, avg = peptide_mass(seq, MONO), peptide_mass(seq, AVG)
print(f"scratch mono {mono:.4f} avg {avg:.4f}")
pmono, pavg = pm.mass(seq)
print(f"package mono {pmono:.4f} avg {pavg:.4f}")
print(f"delta mono {mono-pmono:+.6f} avg {avg-pavg:+.4f}")
for z in (1, 2, 3):
print(f"[M+{z}H]{z}+ scratch {mz(mono, z):.4f} package {pm.mz(pmono, z):.4f}")
Output:
formula: {'C': 34, 'H': 61, 'N': 9, 'O': 11, 'S': 0}
scratch mono 771.4491 avg 771.9140
package mono 771.4491 avg 771.9019
delta mono -0.000001 avg +0.0121
[M+1H]1+ scratch 772.4563 package 772.4563
[M+2H]2+ scratch 386.7318 package 386.7318
[M+3H]3+ scratch 258.1570 package 258.1570
The CLI gives the same answer:
$ peptide-mass GASVLPTK
monoisotopic 771.4491 Da | average 771.90 Da
[M+1H]1+ m/z 772.4563
[M+2H]2+ m/z 386.7318
[M+3H]3+ m/z 258.1570
[M+4H]4+ m/z 193.8695
[M+5H]5+ m/z 155.2971
Monoisotopic masses and m/z agree to four decimal places. The difference of about 1 µDa comes from the package rounding its residue masses to six decimals.
Average masses differ by 0.012 Da. That is expected and not a bug. The package's residue table and my CIAAW abridged weights (for example H = 1.0080, C = 12.011) are not the same element table, and with 61 hydrogens and 34 carbons the small per-atom differences add up. The package's own documentation warns about ±0.01–0.05 Da differences between calculators for this reason. When you compare average masses, write down which table you used.
The reference dataset
The package's home page is the GitHub repo Peptide-Express/peptide-reference-data, which is also archived on Zenodo as 10.5281/zenodo.23284819 ("Peptide reference masses and CoA identity calculator"). It contains:
-
data/peptides.csv: 23 peptides with sequence, terminal modifications, disulfide count, a source reference, and notes -
data/masses.csv: computed monoisotopic and average masses, plus[M+H]+through[M+5H]5+m/z -
scripts/peptide_mass.py: the same standalone calculator -
docs/reading-the-identity-section.md: a short guide to comparing a measured mass against the theoretical value
It deliberately leaves out peptides with non-standard residues or lipidation instead of approximating them. Data is licensed CC BY 4.0 and code is MIT. That makes it handy as a regression fixture: run your own calculator over peptides.csv and diff the results against masses.csv.
Extending it
Some easy next steps:
- Add modifications as formula deltas, for example amidation as
{"O": -1, "N": +1, "H": +1}. - Generate the full isotope envelope by convolving the element isotope distributions.
- Add input validation that rejects non-standard letters with a clear error, as the package does.
The key lesson is to keep monoisotopic and average masses clearly apart, and to state which element table you used.
For laboratory research use only. Not for human or veterinary use.
Top comments (0)