codexelectropherogram-peak-area.py
electropherogram-peak-area.py
"""Synthetic electropherogram: area, height and migration time differ.
Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7. Writes PNG into cwd.
Not experimental data or an absolute concentration calibration.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def gaussian_peak(time, area, center, width):
return area / (width * np.sqrt(2 * np.pi)) * np.exp(-0.5 * ((time - center) / width)**2)
def main():
time = np.linspace(0, 16, 2401)
first = gaussian_peak(time, area=1, center=5, width=0.4)
second = gaussian_peak(time, area=2, center=10, width=0.8)
baseline = 0.03
fig, ax = plt.subplots(figsize=(7.2, 4.8), dpi=100, facecolor="white")
ax.set_facecolor("white")
ax.fill_between(time, baseline, baseline + first, color="#4e90b3", alpha=0.65,
label="Component A: area 1 signal·min")
ax.fill_between(time, baseline, baseline + second, color="#d99549", alpha=0.65,
label="Component B: area 2 signal·min")
ax.plot(time, baseline + first + second, color="#292929", lw=1.5,
label="Total recorded trace")
ax.axhline(baseline, color="#777777", ls=":", lw=1, label="Baseline")
ax.text(5, 1.1, "A: 5 min, area 1", ha="center", fontsize=9)
ax.text(10, 1.1, "B: 10 min, area 2", ha="center", fontsize=9)
ax.set(xlim=(0, 16), ylim=(0, 1.24), xlabel="Migration time (min)",
ylabel="Detector signal (arbitrary units)",
title="Equal peak heights can hide different integrated signals")
ax.grid(alpha=0.15)
fig.text(0.5, 0.015, "Original Gaussian model: widths 0.4 and 0.8 min; equal component peak heights.\n"
"Area / migration time gives 1/5 = 2/10 here; neither raw height nor area is an amount by itself.",
ha="center", fontsize=8)
fig.tight_layout(rect=(0, 0.07, 1, 1))
fig.savefig(Path("electropherogram-peak-area.png"), facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()