Reject design matrices the MLE model cannot fit - #25
Conversation
DesignMatCache.save_record() fits from orig_design_mat[1:,1:]: the first column is consumed as the baseline term and the first row as the reference sample, so that row's condition entries are never read. Nothing checked either assumption, so a matrix that violated them ran to completion and produced a silently wrong answer -- whatever sample was listed first became the reference and the conditions assigned to it were discarded. Moving the baseline row of an otherwise-correct matrix to the bottom shifted a common-essential beta from -1.37 to -0.93 with no error and no warning. Add validate_designmat(), called from both mle postargs paths before any read counts are loaded, rejecting: a baseline column that is not 1 for every sample, a matrix with no baseline-only sample, and a first row that is not one. Errors name the offending sample. Multiple baseline-only samples stay legal -- they are pooled into one shared reference (see #23) -- and are now reported as such. The --day0-label path builds its matrix baseline-first by construction and was never affected; this only concerns user-supplied -d matrices. Also fix a Python 2 era implicit relative import in mlemageck_bayes.py that would have raised ModuleNotFoundError on the same code path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d4e3d9ba0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if 0 not in basesample: | ||
| logging.error('The first row of the design matrix must be a baseline sample, but '+label(0) |
There was a problem hiding this comment.
Update the now-invalid design-matrix example
When users follow mageck2 mle --help, mageck2/argsParser.py:171 still recommends 1,1;1,0, but this new check rejects that exact matrix because its first row is not baseline-only. The same stale example remains in mleargparse.py:127 and mlemageck_bayes.py:100. Update these examples to a baseline-first matrix such as 1,0;1,1 so the documented simplest invocation remains usable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — confirmed and fixed in b47dfb2.
Reproduced it first: mageck2 mle -d "1,1;1,0" ... now fails with
ERROR: The first row of the design matrix must be a baseline sample, but 'T0'
has non-zero entries in condition column(s).
so following the help text verbatim errored out. Swapped all three parsers (argsParser.py:171, mleargparse.py:127, mlemageck_bayes.py:100) to "1,0;1,1" and made the requirement explicit rather than implied by the example: "where the first row is the baseline sample".
Also added a regression test, since the underlying problem is that the help text and the validation rules can drift independently. It runs mle --help, extracts whichever example is currently advertised, and pushes it through parse_designmat + validate_designmat. I checked it actually bites by reverting argsParser.py to the old string — the test fails with SystemExit: -1 and the validator's own error message.
One note on scope: only argsParser.py feeds the user-visible mageck2 mle --help, so that's the one the test covers. The other two were updated for consistency, but mlemageck_bayes.py is unreachable while the bayes module is disabled at mlemageck.py:43.
Three gaps in the design-matrix rules, all surfacing in davidliwei/mageck2#23: - Multiple baseline-only samples (row sum 1) are pooled into one averaged reference, which is exactly the two-cell-line demo (HL60.initial + KBM7.initial). A user reasonably asked whether MAGeCK distinguishes them by cell line. It does not. Note that this assumes a shared starting representation, and that genuinely different references should be run separately. - A baseline sample must be the *first row*. The model consumes row 0 as the reference and never reads the conditions assigned to it, so a treated sample listed first silently measures every beta against the wrong reference. davidliwei/mageck2#25 makes mle reject such a matrix; document the rule. - Beta scores are natural-log, not log2 fold changes. The text said only "behaves like a log fold change", which invites reading a 4-fold dropout as -2 rather than -1.39. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`mle --help` advertised "1,1;1,0" as the simplest invocation, but that matrix leads with a condition sample, so validate_designmat rejects it -- following the documentation verbatim now errors out. Swap it for "1,0;1,1" and say explicitly that the first row is the baseline sample. Same stale example appeared in all three parsers (argsParser, mleargparse, mlemageck_bayes). Add a test that pulls whichever example `mle --help` currently advertises and runs it through parse_designmat + validate_designmat, so the help text and the validation rules cannot drift apart again. Verified it fails against the old "1,1;1,0" example. Reported by Codex review on #25. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the design-matrix questions in #23.
The problem
DesignMatCache.save_recordfits the model fromorig_design_mat[1:,1:](mledesignmat.py): the first column is consumed as the baseline term and the first row as the reference sample, so that row's condition entries are never read. Nothing downstream checked either assumption, so a matrix that violated them ran to completion and produced a silently wrong answer — whatever sample happened to be listed first became the reference, and the conditions assigned to it were discarded.Concretely, taking a correct 4-genotype matrix and moving the baseline row from the top to the bottom shifted a common-essential beta from -1.37 to -0.93, with no error and no warning. Nothing in the output distinguishes that run from a good one.
--day0-labelbuilds its matrix baseline-first by construction (sample_order = args.day0_label + nonday0sample) and was never affected. This only concerns user-supplied-dmatrices.The change
A shared
validate_designmat(desmat, samples), called from bothmlepostargs paths before any read counts are loaded, so a bad matrix fails in under a second with nothing half-computed. It rejects:1for every sample,Errors name the offending sample rather than describing the rule abstractly:
Multiple baseline-only samples remain legal — they are pooled into a single shared reference, which is what #23 was originally asking about — and are now reported as such at INFO level rather than being silent.
It errors rather than auto-reordering. Permuting the baseline row to the top would be safe (rows and labels move together, and
read_gene_from_filematches count-table columns by name), but silently rewriting a user's matrix hides the case where they meant something the model can't express. If the convenience is wanted later, it belongs behind an opt-in flag.Tests
Four tests in
tests/test_smoke.py, covering each rejection and — importantly — that the two-baseline demo matrix still runs, so the validation can't over-reject the pooled-baseline design.Local run: 14 passed. The 4
mageckGSEAfailures in my environment are a stale local binary and reproduce identically on unmodifiedmain.Also
Fixes a Python 2 era implicit relative import (
from mledesignmat import parse_designmat) inmlemageck_bayes.py, which would have raisedModuleNotFoundErroron the same code path. That module is currently disabled atmlemageck.py:43, so this is latent rather than user-visible, but the new call site sits next to it.Not addressed here: the docs side.
TUTORIAL.mdstates "at least one initial/reference sample should have1only in thebaselinecolumn" without mentioning ordering — worth folding into davidliwei/mageck2-doc#8, which is already open against that same rules list.🤖 Generated with Claude Code