chemistrykit.analytical#
chemistrykit.analytical: analytical chemistry.
Acid-base titration curves are provided by
chemistrykit.solutions.systems.titration; this subpackage adds
redox and complexometric (EDTA) titration-curve simulation with
equivalence-point detection; chromatographic plate theory and the van
Deemter equation (resolution, selectivity); linear-regression calibration
curves with IUPAC-convention limits of detection/quantitation; and
propagation-of-uncertainty formulas plus Dixon’s Q-test for outlier
rejection. Also: Gran plots, Kovats retention indices, Purnell’s
resolution equation, Student’s t confidence intervals, Grubbs’ outlier
test, the Horwitz precision function, and Savitzky-Golay smoothing.
- class chemistrykit.analytical.ConfidenceIntervalResult(mean, std, n, t_critical, half_width)[source]#
Bases:
objectResult of a
t_confidence_interval()call.
- class chemistrykit.analytical.EDTATitration(C_metal, V_metal, K_conditional, C_edta)[source]#
Bases:
TitrationCurveA complexometric titration of a metal ion M by EDTA, via the conditional formation constant.
Models the 1:1 complexation \(M+Y\rightleftharpoons MY\) exactly (no large-K approximation needed, unlike
RedoxTitration), using the conditional formation constant \(K_f'\) – the effective (pH- and side-reaction-corrected) formation constant at the titration’s fixed pH (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 12.2; folding pH-dependence into a single conditional constant, rather than modeling EDTA’s stepwise protonation equilibria explicitly, is itself the standard simplification this class makes, flagged here as an approximation).Mass balance on total metal \(C_M\) and total EDTA added \(C_Y\) (both diluted by the growing total volume), with \(x=[MY]\), \([M]=C_M-x\), \([Y]=C_Y-x\), and \(K_f'=x/[(C_M-x)(C_Y-x)]\), gives the exact quadratic
\[K_f'x^2-\left[K_f'(C_M+C_Y)+1\right]x+K_f'C_MC_Y=0\]solved here in closed form for the physical root \(0\le x\le \min(C_M,C_Y)\), then \(pM=-\log_{10}[M]\).
- Parameters:
Examples
At the equivalence volume (\(C_M=C_Y\)), pM approaches the textbook large-\(K_f'\) approximation \(pM\approx \frac12\log_{10}(K_f'/C_{M,eq})\) (Harris, Quantitative Chemical Analysis, 9th ed., eq. 12.2) as \(K_f'\) grows – verified here rather than assumed, since the approximation formula is itself only a limit of the exact quadratic solved above:
>>> import numpy as np >>> titration = EDTATitration(C_metal=0.010, V_metal=0.050, K_conditional=1e12, C_edta=0.010) >>> V_eq = titration.equivalence_volume() >>> pM_exact = float(titration.response_at(np.array([V_eq]))[0]) >>> C_M_eq = titration.C_metal * titration.V_metal / (titration.V_metal + V_eq) >>> pM_approx = 0.5 * np.log10(1e12 / C_M_eq) >>> bool(abs(pM_exact - pM_approx) < 0.01) True
- class chemistrykit.analytical.GranPlotResult(V, gran_function, slope, intercept)[source]#
Bases:
objectResult of a
gran_plot()call.
- class chemistrykit.analytical.GrubbsTestResult(suspect_value, G_statistic, G_critical, reject)[source]#
Bases:
objectResult of a
grubbs_test()call.- G_critical: float#
The critical value from
grubbs_critical_value().- Type:
- class chemistrykit.analytical.LinearCalibration(slope, intercept, r_squared, residual_std_error)[source]#
Bases:
objectA fitted instrument-response-vs-concentration calibration curve,
signal = slope*conc + intercept.- lod()[source]#
Limit of detection, \(\text{LOD}=3.3\,s_{y/x}/|m|\) (ICH Q2 convention).
- Return type:
- Returns:
float
- loq()[source]#
Limit of quantitation, \(\text{LOQ}=10\,s_{y/x}/|m|\) (ICH Q2 convention).
- Return type:
- Returns:
float
- predict_concentration(signal)[source]#
Invert the calibration to estimate concentration from a measured signal.
\[\hat c = (y_{measured} - b)/m\]
- class chemistrykit.analytical.QTestResult(suspect_value, Q_statistic, Q_critical, reject)[source]#
Bases:
objectResult of a
dixon_q_test()call.
- class chemistrykit.analytical.RedoxTitration(E1_standard, n1, E2_standard, n2, C_analyte, V_analyte, C_titrant, T=298.15)[source]#
Bases:
TitrationCurveA potentiometric redox titration: a reduced analyte titrated by an oxidizing titrant.
Models the general reaction \(n_2\,\text{Red}_1 + n_1\,\text{Ox}_2 \rightarrow n_2\,\text{Ox}_1 + n_1\,\text{Red}_2\) in the standard large-equilibrium-constant (“quantitative reaction”) approximation (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 16), under which electron-balance stoichiometry alone (not a numerical equilibrium solve) fixes the composition at every titrant volume:
Before the equivalence volume \(V_{eq}=n_1C_{analyte}V_{analyte}/(n_2C_{titrant})\), the analyte couple sets the potential via the Nernst equation, using the fraction of analyte already oxidized (fixed by the moles of titrant added, via the electron balance \(n_1x=n_2C_{titrant}V\)):
\[E = E^\circ_1 - \frac{RT}{n_1F}\ln\frac{[\text{Red}_1]}{[\text{Ox}_1]}\]At the equivalence point, the classical weighted-average result (from adding the two Nernst equations):
\[E_{eq} = \frac{n_1E^\circ_1+n_2E^\circ_2}{n_1+n_2}\]Past the equivalence volume, the titrant couple sets the potential from the (now excess) unreacted titrant:
\[E = E^\circ_2 - \frac{RT}{n_2F}\ln\frac{[\text{Red}_2]}{[\text{Ox}_2]}\]
For the common case \(n_1=n_2\) (e.g. Fe2+/Ce4+, both 1-electron couples) this reduces to the textbook symmetric titration curve with \(E_{eq}=(E^\circ_1+E^\circ_2)/2\).
- Parameters:
E1_standard (
float) – Standard reduction potential of the analyte couple (Ox1/Red1), in V.n1 (
int) – Electrons transferred in the analyte half-reaction.E2_standard (
float) – Standard reduction potential of the titrant couple (Ox2/Red2), in V.n2 (
int) – Electrons transferred in the titrant half-reaction.C_analyte (
float) – Initial analyte (Red1) concentration, in mol/L.V_analyte (
float) – Initial analyte volume, in L.C_titrant (
float) – Titrant (Ox2) concentration, in mol/L.T (
float) – Absolute temperature, in K.
Examples
Fe2+ (E=0.771 V) titrated by Ce4+ (E=1.72 V), both 1-electron couples: at the equivalence volume, E is exactly the arithmetic mean of the two standard potentials – the textbook symmetric-curve result:
>>> titration = RedoxTitration(E1_standard=0.771, n1=1, E2_standard=1.72, n2=1, C_analyte=0.10, V_analyte=0.050, C_titrant=0.10) >>> V_eq = titration.equivalence_volume() >>> round(float(titration.response_at(np.array([V_eq]))[0]), 4) == round((0.771 + 1.72) / 2.0, 4) True
- class chemistrykit.analytical.TitrationCurve[source]#
Bases:
ABCCommon base for a potentiometric titration-curve model whose response is not pH.
Concrete subclasses implement
response_at();curve()andfind_equivalence_point()are then available for free, mirroringchemistrykit.solutions.core.base_system.Titration.curve()/find_equivalence_point().- curve(V)[source]#
Compute the full titration curve over a range of titrant volumes.
- Parameters:
V (array-like of float) – Volumes of titrant added, in L.
- Return type:
- Returns:
TitrationCurveResult
- class chemistrykit.analytical.TitrationCurveResult(V, response)[source]#
Bases:
objectContainer for the output of a
TitrationCurve.curve()call.
- chemistrykit.analytical.dixon_q_test(data, confidence=0.95, suspect='auto')[source]#
Test whether the smallest or largest value in a small data set is an outlier.
\[Q = \frac{\text{gap}}{\text{range}}\]where “gap” is the absolute difference between the suspect value and its nearest neighbor, and “range” is the difference between the largest and smallest values in the set (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 4.6). The suspect value is rejected as an outlier if \(Q>Q_{crit}\) for the sample size and confidence level chosen.
- Parameters:
- Return type:
- Returns:
QTestResult
- Raises:
ValueError – If data has fewer than 3 or more than 10 points, or confidence is not one of the tabulated levels.
Examples
A clear outlier in a small data set is correctly rejected at 95% confidence:
>>> result = dixon_q_test([10.0, 10.1, 10.2, 10.3, 15.0], confidence=0.95) >>> result.suspect_value 15.0 >>> result.reject True
A borderline value that is not extreme enough is retained:
>>> result = dixon_q_test([10.0, 10.1, 10.2, 10.3, 10.9], confidence=0.99) >>> result.reject False
- chemistrykit.analytical.fit_calibration(concentration, signal)[source]#
Fit a linear calibration curve (signal vs. concentration) by ordinary least squares.
- Parameters:
- Return type:
- Returns:
LinearCalibration
Examples
LOQ is always exactly \(10/3.3\) times LOD, by definition, regardless of the data:
>>> conc = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] >>> signal = [0.02, 1.05, 1.98, 3.10, 3.95, 5.08] >>> cal = fit_calibration(conc, signal) >>> round(cal.loq() / cal.lod(), 6) == round(10.0 / 3.3, 6) True
A perfect (noiseless) calibration has zero residual error and hence zero LOD/LOQ:
>>> cal_perfect = fit_calibration([0.0, 1.0, 2.0, 3.0], [1.0, 3.0, 5.0, 7.0]) >>> cal_perfect.lod() < 1e-9 True
- chemistrykit.analytical.gran_plot(V, pH)[source]#
Gran plot for a weak acid titrated by a strong base: locate the equivalence point by linear extrapolation.
Before the equivalence volume \(V_e\), the buffer region obeys \([H^+]=K_a\,n_{HA}/n_{A^-}=K_a(V_e-V_b)/V_b\), so the Gran function
\[V_b\,10^{-pH} = K_a\,(V_e - V_b)\]is a straight line in \(V_b\) whose x-intercept is \(V_e\) and whose slope is \(-K_a\) (G. Gran, Analyst 77, 661 (1952)). This turns the equivalence point into a linear extrapolation of data taken well before it, rather than a search for the steepest point of the sigmoidal curve (activity coefficients are neglected here).
- Parameters:
- Return type:
- Returns:
GranPlotResult
Examples
Ideal buffer-region data built from the Gran relation itself recover \(V_e\) and \(K_a\) exactly:
>>> import numpy as np >>> Ka, Ve = 1.8e-5, 0.050 >>> V = np.linspace(0.010, 0.045, 8) >>> pH = -np.log10(Ka * (Ve - V) / V) >>> result = gran_plot(V, pH) >>> round(result.equivalence_volume, 9), round(result.Ka / Ka, 9) (0.05, 1.0)
- chemistrykit.analytical.grubbs_critical_value(n, alpha=0.05)[source]#
Two-sided critical value of Grubbs’ statistic for a sample of size n.
\[G_{crit} = \frac{n-1}{\sqrt n}\sqrt{\frac{t^2}{n-2+t^2}}\]with t the upper \(\alpha/(2n)\) critical value of Student’s distribution with \(n-2\) degrees of freedom (F. E. Grubbs, Technometrics 11, 1 (1969)).
- Parameters:
- Return type:
- Returns:
float
Examples
Matches the tabulated two-sided 5% value for ten observations:
>>> round(grubbs_critical_value(10, alpha=0.05), 3) 2.29
- chemistrykit.analytical.grubbs_test(data, alpha=0.05)[source]#
Grubbs’ test for a single outlier: the most extreme value, in units of the sample standard deviation.
\[G = \frac{\max_i|x_i-\bar x|}{s}\]compared against
grubbs_critical_value()(F. E. Grubbs, Ann. Math. Statist. 21, 27 (1950)). Unlike Dixon’s gap-over-rangedixon_q_test(), G uses every point through the mean and standard deviation, and its critical value comes from a formula rather than a finite table, so any n >= 3 works.- Parameters:
- Return type:
- Returns:
GrubbsTestResult
Examples
>>> result = grubbs_test([10.0, 10.1, 10.2, 10.3, 15.0]) >>> result.suspect_value, result.reject (15.0, True) >>> grubbs_test([10.0, 10.1, 10.2, 10.3, 10.4]).reject False
- chemistrykit.analytical.horrat(observed_rsd, mass_fraction)[source]#
The HorRat ratio: an observed between-laboratory RSD divided by
horwitz_rsd().Values roughly between 0.5 and 2 are conventionally taken to indicate a method with normal, acceptable interlaboratory precision.
- Parameters:
- Returns:
float or ndarray
Examples
>>> round(float(horrat(8.0, 1e-6)), 6) 0.5
- chemistrykit.analytical.horwitz_rsd(mass_fraction)[source]#
Horwitz’s predicted between-laboratory relative standard deviation, in percent.
\[\text{RSD}_R(\%) = 2^{\,1-0.5\log_{10}C} = 2\,C^{-0.1505}\]with C the analyte concentration as a dimensionless mass fraction (1 for a pure substance, \(10^{-6}\) for 1 ppm) – the empirical “Horwitz trumpet” fitted to thousands of collaborative-study results (W. Horwitz, L. R. Kamps, K. W. Boyer, J. Assoc. Off. Anal. Chem. 63, 1344 (1980)): each 100-fold drop in concentration doubles the expected RSD.
- Parameters:
mass_fraction (float or array-like of float) – Analyte concentration as a mass fraction, in (0, 1].
- Returns:
float or ndarray
Examples
2% for a pure substance, 16% at 1 ppm:
>>> round(float(horwitz_rsd(1.0)), 6), round(float(horwitz_rsd(1e-6)), 6) (2.0, 16.0)
- chemistrykit.analytical.kovats_retention_index(t_x, t_n, t_N, n, N=None, dead_time=0.0)[source]#
Kováts retention index of an analyte, bracketed by two n-alkane standards.
Under isothermal conditions the logarithm of an n-alkane’s adjusted retention time \(t'=t_R-t_0\) grows linearly with its carbon number, so an analyte eluting between the n-alkanes with n and N carbons is assigned the logarithmically interpolated index (E. Kováts, Helv. Chim. Acta 41, 1915 (1958)):
\[I = 100\left[n + (N-n)\,\frac{\log t'_x-\log t'_n}{\log t'_N-\log t'_n}\right]\]With the usual choice \(N=n+1\) this is Kováts’ original definition; every n-alkane itself has, by construction, \(I=100n\).
- Parameters:
t_x (float or array-like of float) – Retention time(s) of the analyte.
t_n (float) – Retention times of the smaller (n carbons) and larger (N carbons) bracketing n-alkanes.
t_N (float) – Retention times of the smaller (n carbons) and larger (N carbons) bracketing n-alkanes.
n (
int) – Carbon number of the earlier-eluting n-alkane.N (
int|None) – Carbon number of the later-eluting n-alkane; defaults ton + 1.dead_time (
float) – Column dead time \(t_0\), subtracted from every retention time to form the adjusted retention times \(t'\).
- Returns:
float or ndarray
Examples
An analyte eluting at the geometric mean of the adjusted retention times of n-octane and n-nonane sits exactly halfway, at I = 850:
>>> import numpy as np >>> t0, t8, t9 = 1.0, 5.0, 9.0 >>> t_x = t0 + np.sqrt((t8 - t0) * (t9 - t0)) >>> round(float(kovats_retention_index(t_x, t8, t9, n=8, dead_time=t0)), 6) 850.0
- chemistrykit.analytical.minimum_plate_height(A, B, C)[source]#
The minimum plate height \(H_{min}=A+2\sqrt{BC}\), at the optimum flow velocity.
Substituting
optimum_flow_velocity()intovan_deemter_H()(Harris, Quantitative Chemical Analysis, 9th ed., Ch. 23.4).Examples
Matches direct evaluation of
van_deemter_H()at the optimum velocity fromoptimum_flow_velocity():>>> A, B, C = 1.0, 2.0, 0.05 >>> u_opt = optimum_flow_velocity(B, C) >>> bool(round(minimum_plate_height(A, B, C), 6) == round(float(van_deemter_H(u_opt, A, B, C)), 6)) True
- chemistrykit.analytical.optimum_flow_velocity(B, C)[source]#
The flow velocity \(u_{opt}=\sqrt{B/C}\) minimizing the van Deemter equation.
Found by \(dH/du=-B/u^2+C=0\) (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 23.4).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> round(optimum_flow_velocity(B=2.0, C=0.05), 6) 6.324555
- chemistrykit.analytical.plate_height(column_length, N)[source]#
Plate height (HETP) \(H=L/N\), the column length per theoretical plate.
- Parameters:
- Returns:
float or ndarray
Examples
>>> round(float(plate_height(column_length=25.0, N=6400.0)), 6) 0.003906
- chemistrykit.analytical.propagate_power(value, sigma, exponent)[source]#
Absolute uncertainty of \(y=x^n\), \(\sigma_y=|n|\,|x|^{n-1}\sigma_x\).
Equivalently \(\sigma_y/|y|=|n|\,\sigma_x/|x|\) (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 3.2).
- Parameters:
- Return type:
- Returns:
float
Examples
\(y=x^2\) with \(x=3.0\pm0.1\): relative uncertainty doubles:
>>> round(propagate_power(3.0, 0.1, 2.0), 6) 0.6
- chemistrykit.analytical.propagate_product(values, sigmas)[source]#
Absolute uncertainty of a product or quotient of independent quantities.
Relative uncertainties add in quadrature (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 3.2):
\[\frac{\sigma_y}{|y|}=\sqrt{\sum_i\left(\frac{\sigma_i}{x_i}\right)^2}, \qquad y=\prod_i x_i^{\pm1}\]- Parameters:
- Return type:
- Returns:
float – Absolute uncertainty of the product \(y=\prod_i\) values[i].
Examples
\(y=a\times b\) with \(a=2.0\pm0.1\), \(b=3.0\pm0.2\):
>>> y = 2.0 * 3.0 >>> sigma_y = propagate_product([2.0, 3.0], [0.1, 0.2]) >>> round(sigma_y, 6) 0.5
A quotient \(y=a/b\) is a product with b replaced by \(1/b\) (relative uncertainty of \(1/b\) equals that of b):
>>> a, sigma_a = 10.0, 0.5 >>> b, sigma_b = 4.0, 0.2 >>> y = a / b >>> sigma_y = propagate_product([a, 1.0 / b], [sigma_a, sigma_b / b**2]) >>> round(sigma_y, 6) 0.176777
- chemistrykit.analytical.propagate_sum(sigmas)[source]#
Absolute uncertainty of a sum or difference of independent quantities, \(\sigma=\sqrt{\sum_i\sigma_i^2}\).
Applies identically whether the terms are added or subtracted (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 3.2) – only the absolute uncertainties of each term matter, not their signs.
- Parameters:
sigmas (array-like of float) – Absolute uncertainty of each term.
- Return type:
- Returns:
float
Examples
Two equal uncertainties combine to \(\sigma\sqrt2\), not \(2\sigma\):
>>> round(propagate_sum([0.02, 0.02]), 6) 0.028284
- chemistrykit.analytical.propagate_uncertainty(func, values, sigmas, h=1e-06)[source]#
General first-order uncertainty propagation for an arbitrary differentiable function.
Evaluates \(\sigma_y=\sqrt{\sum_i(\partial f/\partial x_i)^2\sigma_i^2}\) (Harris, Quantitative Chemical Analysis, 9th ed., Ch. 3.2, eq. 3.2) with each partial derivative estimated by a central finite difference – useful for a function with no simple closed-form propagation rule (e.g. a pH from a Nernst equation, or a rate constant from an Arrhenius fit), at the cost of losing the exact closed-form results
propagate_sum()/propagate_product()/propagate_power()give for their specific operations.- Parameters:
- Return type:
- Returns:
float
Examples
Reproduces
propagate_product()for a simple product, since both implement the same general formula:>>> from chemistrykit.analytical.systems.uncertainty import propagate_product >>> sigma_general = propagate_uncertainty(lambda a, b: a * b, [2.0, 3.0], [0.1, 0.2]) >>> sigma_closed_form = propagate_product([2.0, 3.0], [0.1, 0.2]) >>> bool(abs(sigma_general - sigma_closed_form) < 1e-6) True
- chemistrykit.analytical.purnell_resolution(N, alpha, k2)[source]#
Purnell’s resolution equation: resolution in terms of efficiency, selectivity, and retention.
\[R_s = \frac{\sqrt N}{4}\,\frac{\alpha-1}{\alpha}\,\frac{k_2}{1+k_2}\](J. H. Purnell, J. Chem. Soc. 1960, 1268), with N the plate count, \(\alpha=k_2/k_1\) the selectivity factor, and \(k_2\) the retention factor of the later-eluting peak. It is exact when both peaks have the base width \(4t_{R,2}/\sqrt N\) of the second peak, and separates the three independent levers a chromatographer can pull: efficiency (\(\sqrt N\)), selectivity, and retention.
- Parameters:
- Returns:
float or ndarray
Examples
Agrees with the direct
resolution()formula for two peaks of equal base width \(4t_{R,2}/\sqrt N\):>>> import numpy as np >>> N, t0, k1, k2 = 10000.0, 1.0, 4.0, 4.4 >>> tR1, tR2 = t0 * (1 + k1), t0 * (1 + k2) >>> w = 4.0 * tR2 / np.sqrt(N) >>> bool(abs(purnell_resolution(N, alpha=k2 / k1, k2=k2) - resolution(tR1, tR2, w, w)) < 1e-12) True
Resolution grows only as \(\sqrt N\): quadrupling the plate count doubles it.
>>> round(float(purnell_resolution(40000.0, 1.1, 4.4) / purnell_resolution(10000.0, 1.1, 4.4)), 6) 2.0
- chemistrykit.analytical.resolution(tR1, tR2, w1, w2)[source]#
Chromatographic resolution \(R_s=2(t_{R,2}-t_{R,1})/(w_1+w_2)\) between two adjacent peaks.
\(R_s\ge1.5\) is the conventional criterion for baseline separation (Harris, Quantitative Chemical Analysis, 9th ed., eq. 23.21).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> round(resolution(tR1=9.0, tR2=10.0, w1=0.5, w2=0.5), 6) 2.0
- chemistrykit.analytical.retention_factor(retention_time, dead_time)[source]#
The retention (capacity) factor \(k=(t_R-t_0)/t_0\).
- Parameters:
- Returns:
float or ndarray
Examples
>>> round(float(retention_factor(retention_time=12.0, dead_time=2.0)), 6) 5.0
- chemistrykit.analytical.savitzky_golay(y, window_length, polyorder, deriv=0, delta=1.0)[source]#
Smooth (or differentiate) a signal with a Savitzky-Golay filter.
Interior points use the fixed convolution weights of
savitzky_golay_coefficients(); the first and last m points, which have no full centered window, are taken from the polynomial fitted to the first and last full window (the same edge treatment asscipy.signal.savgol_filter(..., mode="interp")).- Parameters:
y (array-like of float) – Evenly sampled signal, at least window_length points long.
window_length (
int) – As insavitzky_golay_coefficients().polyorder (
int) – As insavitzky_golay_coefficients().deriv (
int) – As insavitzky_golay_coefficients().delta (
float) – As insavitzky_golay_coefficients().
- Return type:
- Returns:
ndarray, same shape as y
Examples
A filter of polynomial order p passes any polynomial of degree <= p through unchanged, edges included:
>>> import numpy as np >>> x = np.linspace(0.0, 1.0, 21) >>> y = 3.0 * x**2 - x + 0.5 >>> bool(np.allclose(savitzky_golay(y, 7, 2), y)) True
- chemistrykit.analytical.savitzky_golay_coefficients(window_length, polyorder, deriv=0, delta=1.0)[source]#
Convolution weights of a Savitzky-Golay filter.
With window offsets \(z=-m,\ldots,m\) and the design matrix \(J_{ij}=z_i^{\,j}\) (\(j=0,\ldots,p\)), the least-squares polynomial coefficients are \((J^TJ)^{-1}J^T y\); row d of that matrix, times \(d!/\Delta^d\), gives the weights for the d-th derivative at the window center.
- Parameters:
- Return type:
- Returns:
ndarray, shape (window_length,) – Weights \(c_i\) such that the filtered value at point k is \(\sum_i c_i\,y_{k+i}\), i running from -m to m.
Examples
The classic 5-point quadratic smoothing weights from Savitzky and Golay’s Table I, \((-3, 12, 17, 12, -3)/35\):
>>> import numpy as np >>> np.round(savitzky_golay_coefficients(5, 2) * 35, 10) array([-3., 12., 17., 12., -3.])
- chemistrykit.analytical.selectivity_factor(k1, k2)[source]#
The selectivity (relative retention) factor \(\alpha=k_2/k_1\) (\(k_2\ge k_1\) by convention).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> round(selectivity_factor(k1=2.0, k2=5.0), 6) 2.5
- chemistrykit.analytical.simulate_chromatogram(t, centers, retention_times=None, N=10000.0, amplitudes=None)[source]#
Simulate a chromatogram as a sum of Gaussian elution peaks of plate-count-consistent width.
Reuses
chemistrykit.spectro.utils.lineshapes.gaussian()(each elution peak is, to a good approximation, Gaussian – the same plate-theory result that underliestheoretical_plates()), with each peak’s FWHM set by \(w_{1/2}=t_R\sqrt{8\ln2/N}\) (invertingtheoretical_plates()’s half-height form, \(N=5.545(t_R/w_{1/2})^2\) with \(5.545\approx8\ln2\)).- Parameters:
t (array-like of float) – Time grid to evaluate the chromatogram on.
centers (array-like of float) – Retention time of each peak (alias for retention_times, kept for a natural call signature with
chemistrykit.spectro.utils.lineshapes.broaden_stick_spectrum()).retention_times (array-like of float, optional) – If given, overrides centers.
N (
float) – Number of theoretical plates (assumed equal for every peak, the common simplifying assumption for a single column/method).amplitudes (array-like of float, optional) – Relative peak heights; defaults to 1.0 for every peak.
- Returns:
ndarray, shape matching t
Examples
A single simulated peak’s apparent plate count (recovered from its numerically-measured FWHM) matches the N it was built from:
>>> import numpy as np >>> t = np.linspace(8.0, 12.0, 200001) >>> N_true = 10000.0 >>> chrom = simulate_chromatogram(t, centers=[10.0], N=N_true) >>> half_max = chrom.max() / 2.0 >>> above = t[chrom >= half_max] >>> fwhm = above.max() - above.min() >>> N_recovered = theoretical_plates(10.0, fwhm, width_type="half_height") >>> bool(abs(N_recovered - N_true) / N_true < 0.01) True
- chemistrykit.analytical.t_confidence_interval(data, confidence=0.95)[source]#
Student’s t confidence interval for the true mean of a few replicate measurements.
\[\mu = \bar x \pm \frac{t\,s}{\sqrt n}\]with s the sample standard deviation and t the two-sided critical value of Student’s distribution with \(n-1\) degrees of freedom (“Student” [W. S. Gosset], Biometrika 6, 1 (1908)). For small n, t is much larger than the normal-distribution value 1.96, widening the interval to account for s itself being an uncertain estimate of the true standard deviation.
- Parameters:
- Return type:
- Returns:
ConfidenceIntervalResult
Examples
For five replicates, the 95% two-sided t value is the tabulated 2.776:
>>> ci = t_confidence_interval([10.1, 10.3, 9.9, 10.2, 10.0]) >>> round(ci.t_critical, 3), round(ci.mean, 6) (2.776, 10.1) >>> round(ci.half_width, 4) 0.1963
- chemistrykit.analytical.theoretical_plates(retention_time, peak_width, width_type='base')[source]#
Number of theoretical plates N from a peak’s retention time and width.
For a Gaussian elution peak (Harris, Quantitative Chemical Analysis, 9th ed., eq. 23.19-23.20):
\[N = 16\left(\frac{t_R}{w_{base}}\right)^2 = 5.545\left(\frac{t_R}{w_{1/2}}\right)^2\]where \(w_{base}\) is the width at the peak base (tangents to the inflection points) and \(w_{1/2}\) is the full width at half maximum – the numeric prefactor differs only because a Gaussian’s base width (4 standard deviations) and FWHM (\(2\sqrt{2\ln2}\,\sigma\)) are different multiples of sigma.
- Parameters:
- Returns:
float or ndarray
Examples
A peak eluting at 10.0 min with a 0.5 min base width:
>>> round(float(theoretical_plates(10.0, 0.5, width_type="base")), 1) 6400.0
The two width conventions must agree for a Gaussian peak of consistent shape (base width = 4 sigma, FWHM = \(2\sqrt{2\ln2}\,\sigma\approx2.3548\sigma\)):
>>> sigma = 0.125 >>> N_base = theoretical_plates(10.0, 4.0 * sigma, width_type="base") >>> N_half = theoretical_plates(10.0, 2.3548 * sigma, width_type="half_height") >>> bool(abs(N_base - N_half) / N_base < 1e-3) True
- chemistrykit.analytical.van_deemter_H(u, A, B, C)[source]#
The van Deemter equation: plate height H as a function of mobile-phase linear velocity u.
\[H = A + \frac{B}{u} + Cu\]with A the eddy-diffusion term (velocity-independent band broadening from unequal flow paths), B the longitudinal-molecular- diffusion term (dominant at low u), and C the mass-transfer- resistance term (dominant at high u) (J. J. van Deemter, F. J. Zuiderweg, A. Klinkenberg, Chem. Eng. Sci. 5, 271 (1956); Harris, Quantitative Chemical Analysis, 9th ed., Ch. 23.4).
- Parameters:
u (float or array-like of float) – Mobile-phase linear velocity.
A (
float) – Van Deemter coefficients (all non-negative for a physical column).B (
float) – Van Deemter coefficients (all non-negative for a physical column).C (
float) – Van Deemter coefficients (all non-negative for a physical column).
- Returns:
float or ndarray
Examples
>>> round(float(van_deemter_H(u=2.0, A=1.0, B=2.0, C=0.05)), 4) 2.1