clophfit.fitting.diagnostics#
Well-quality diagnostics for plate-reader titration data.
Two complementary entry points:
detect_bad_wells()— readsffit*.csvfit results (one label per file). Adds fit-quality criteria (K at bound, K outlier, poor fit) on top of the signal-quality checks.
Detection criteria#
K at bound : K equals the optimizer bound (default 3 or 11 for pH). Fit converged to a limit, not a true optimum.
K outlier : |K - median_K| >
k_mad_factor * MAD(K)across allwells on the plate. Identifies wells with biologically implausible K.
Poor fit : sK / K >
max_sk_ratio. Relative uncertainty so large that K is undetermined.Low signal / Flat curve : Outlier detection based on robust Theil-Sen regression between max signal and dynamic range. A well is flagged if its signal span is too low compared to the trend, or if its max signal is significantly below the plate median.
Inverted curve : S0 > S1 for pH or S0 < S1 for Cl – wrong polarity. Only checked in
detect_bad_wells()(requires fitted plateaus).High residuals : per-well residual MAD >
residual_mad_factortimes the plate median MAD. Requires the optionalresidual_statsDataFrame.
Functions#
|
Judge wells from the data alone, before any curve is fitted. |
|
How far a titration curve comes back down from its own peak. |
|
Flag unreliable wells from a ffit result DataFrame. |
Module Contents#
- clophfit.fitting.diagnostics.screen_wells(wells, x, *, bg_level, quality_label=_QUALITY_LABEL, low_signal_ratio=_LOW_SIGNAL_RATIO, flat_span_fraction=_FLAT_SPAN_FRACTION, concordant_corr=_CONCORDANT_CORR)#
Judge wells from the data alone, before any curve is fitted.
Fitting a well to discover whether it was worth fitting is avoidable. Across eleven plates every well with
sK/K > 0.3– all 55 of them – has a dim 485 nm channel, so a poor fit is a consequence of weak signal, and the signal can be read straight off the data.Three independent questions are asked, where the previous implementation collapsed two of them into a single trend-outlier test reported under two names:
low signal – does the channel clear the background it sits on? The upper quartile is compared with the background level, so the ratio is dimensionless and one threshold serves plates of very different brightness. The upper quartile rather than the maximum because a single spike otherwise rescues a dead well.
flat curve – does the channel move at all? A flat but bright 400 nm channel is a property of the construct, so this is reported and never treated as a failure.
concordant – do the channels move together? They should be opposed: the neutral form falls with pH as the anion rises, giving a median inter-label correlation of -0.955. Concordance is directly observable and, unlike a per-channel direction test, survives the 400 nm acid turnover – which is present in 60% of wells and defeats every endpoint-based polarity check.
The well-level verdict is taken on quality_label alone. A dim neutral channel beside a healthy anion channel is not a bad well: the per-label columns record it so the caller can exclude that channel, which is what the pre-fit detector does and what reviewers endorse.
This screens; it does not discard. Discarding is better decided either by
Titration.detect_and_discard_bad_wells()or, post-fit, bysK/K, which was the only criterion found to separate unusable wells from merely weak ones without taking the weak ones with it.- Parameters:
wells (Mapping[str, Mapping[str, np.ndarray]]) – Well key to label key to that label’s signal at each
x.x (np.ndarray) – Independent variable, in any order.
bg_level (Mapping[str, float]) – Per-label background level the signal is measured against.
quality_label (str) – Label whose signal decides the well-level verdict.
low_signal_ratio (float) – Upper-quartile-to-background ratio below which a channel is too dim.
flat_span_fraction (float) – Span, as a fraction of the channel’s peak, below which it is flat.
concordant_corr (float) – Inter-label correlation above which the channels count as concordant.
- Returns:
One row per well:
well; per labelsignal_ratio_{lbl},flag_flat_curve_{lbl}andturnover_{lbl}; and well-levelflag_low_signal(on quality_label alone),flag_flat_curve,label_corrandflag_concordant.- Return type:
pd.DataFrame
- clophfit.fitting.diagnostics.curve_turnover(x, y)#
How far a titration curve comes back down from its own peak.
The 400 nm neutral-form channel does not fall monotonically with pH: it turns over at the acid end. So a channel’s direction cannot be read off its endpoints, and a rule that tries produces false inversions on every well with a pronounced dip – on this campaign, 44 of them, all with interior maxima.
This measures the effect directly instead. Take the smaller of the two drops from the peak to the ends, over the observed range. A monotone curve has its peak at an end, so one drop is zero and so is the result; a curve that genuinely turns over scores by how far it returns on the side it need not. The range, not the peak, is the denominator, so the measure stays meaningful where the signal is negative – which it is on the dimmest wells, and where dividing by a peak near zero would not be.
- Parameters:
x (np.ndarray) – Independent variable, in any order. Tecan files store pH descending.
y (np.ndarray) – Signal at each
x. NaNs are ignored.
- Returns:
0.0 for a monotone or flat curve, up to 1.0 for one that returns fully to both ends. NaN when fewer than three points survive.
- Return type:
float
- clophfit.fitting.diagnostics.detect_bad_wells(ffit, *, k_min=3.0, k_max=11.0, k_mad_factor=5.0, max_sk_ratio=0.3, z_threshold=3.0, check_polarity=True, is_ph=True, ctr_cols=None, residual_stats=None, residual_mad_factor=5.0)#
Flag unreliable wells from a ffit result DataFrame.
- Parameters:
ffit (pd.DataFrame) – Per-well fit results with at minimum columns
well,K,sKand at least one pair ofS0_{lbl}/S1_{lbl}columns.k_min (float) – Lower optimizer bound for K (default 3.0 for pH).
k_max (float) – Upper optimizer bound for K (default 11.0 for pH).
k_mad_factor (float) – Outlier threshold: flag if
|K - median| > k_mad_factor * MAD.max_sk_ratio (float) – Maximum tolerated relative uncertainty sK/K (default 0.30).
z_threshold (float) – Z-score threshold for outlier detection on the max-vs-span trendline.
check_polarity (bool) – If True, flag wells where the signal direction is inverted relative to the expected biological response.
is_ph (bool) – If True (default), pH assay: expect S1 > S0 (signal rises with pH). If False, Cl assay: expect S0 > S1.
ctr_cols (list[int] | None) – Column numbers (1-based, e.g.
[1, 12]) reserved for control wells.residual_stats (pd.DataFrame | None) – Optional DataFrame from
residual_stats_*.csv.residual_mad_factor (float) – Flag if per-well residual MAD >
residual_mad_factortimes the plate median MAD (default 5.0).
- Returns:
One row per well with boolean flag columns.
- Return type:
pd.DataFrame