Repository navigation
Audit fixes, shared folds and faster cross-validation (3.17.2) - #125
Draft
ellispatrick wants to merge 54 commits into
Draft
ellispatrick wants to merge 54 commits into
ellispatrick wants to merge 54 commits into
Conversation
…le Packaged field Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…scale predict.dlda added log(prior) to the score it minimises, which favoured the smaller class; the discriminant is sum((x - mean)^2 / var) - 2 log(prior). The posterior multiplied per-feature densities, which underflowed to NaN with many features; it is now normalised with log-sum-exp. Training drops non-numeric columns as prediction does, and the "both" return type orders the posterior columns by class level, as "score" does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The formula class ~ . + 0 forced the linear predictor through the origin, so a feature far from zero could not separate the classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Protective features (negative Cox coefficients) ranked below features with zero coefficients. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
- crossValidate passes selectionMethod on for MultiAssayExperiment and list input. - Parallel parameters are chosen by .Platform.type, so macOS honours nCores and the seed. - extraParams are applied to each stage by one helper; a tuneParams list is tuned rather than passed on as a setting, and tuning ranges without a tuning mode are an error. - Tuning keeps the user's other training settings; nested CV tuning of classifier parameters works. - train() fits each model with runTest, as runTests fits its final model, for one or several assays and for merge, prevalidation and PCA; predict() names new data as train() does and gives each model the features it was trained with. - runTest: the MultiAssayExperiment method works; prepared training data is used for survival too. - Feature importance uses the cross-validation's performance type. - Percentage splits use the outcome given (and balance events for survival); nFeatures = 1 works. - prepareData: topNvariance and maxSimilarity work; the clinical-features warning can fire. - The kNN keyword works. - Tests for each of these in tests/testthat/test-core.R. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
CoxNet trained on MatrixModels::model.Matrix but predicted on glmnet::makeX, which encode factors differently and name non-syntactic features differently. Penalised GLM rebuilt the test design matrix on its own, so a factor level absent from the test samples gave a non-conformable error, and a different column order could silently use the wrong features. .encodeTrain records the features, their factor levels and the design matrix columns; .encodeTest puts test features in the training order, applies the training levels and returns exactly the training columns, with absent columns as zero. Training matrices are unchanged (same columns, values and groups), so fitted models are identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The resubstitution choice of lambda called predict once per lambda. One call with every candidate lambda gives the same predictions and the same chosen lambda (checked on 6 asthma subsets with alpha 1, 0.5 and 0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
xgboost 3 removed the xgboost(x, y, ...) call, so XGB failed for every input. The interface builds an xgb.DMatrix from the same sparse one-hot matrix and calls xgb.train with the same booster parameters (objective, num_class, colsample_bynode, nrounds, nthread = 1). Extra arguments are passed to xgb.train or xgb.DMatrix when they are theirs and are booster parameters otherwise, so a user-supplied nthread replaces the default instead of giving a "matched by multiple actual arguments" error. Test data use the training encoding (.encodeTest). Survival predictions are a named numeric vector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The model is fitted with a formula, but prediction passed model.matrix(~ ., test), which adds an intercept column, expands factors and backtick-quotes non-syntactic names, so prediction failed for categorical features or names such as "g-1". Predicting on the data frame lets e1071 encode the test data as it encoded the training data. Predictions for numeric features are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The predicted classes were a character vector, unlike every other classifier. With a single test sample (leave-one-out), apply() over the neighbour indices returned a vector and the voting step failed. Neighbour classes are built as a character matrix directly, and the predicted classes are a factor with the training levels. Votes and scores are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The three classifiers referred to trainingMatrix and testingMatrix, which were never defined, so they failed on any input. They now build them from their arguments. Poisson LDA passes the classes to PoiClaClu as the numbers 1 to K and maps its predictions back to a factor with the training levels. Fisher's pooled variance weighted each class's variance by the number of features minus one; it is weighted by the class size minus one. Fisher and k-TSP use only the first two levels of the classes, so they stop with an error for any other number of classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Both classifiers failed with weighting = "crossover distance". Naive Bayes used an undefined object `test` for the test samples. The horizontal distances were computed only when difference was "weighted" (naive Bayes) or when difference equalled "crossover distance", which never happens (mixtures), but they are always needed with that weighting, because they decide which features vote. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The sample-wise C-index counts concordant and discordant pairs with outer() per group. ROC rates come from cumulative sums over sorted scores, used by AUC and ROCplot's merge mode, and .calcArea works on vectors. Values are identical to the loop implementations, including tie handling and rounding. A class absent from a group gives a clear error instead of 'missing value where TRUE/FALSE needed'; a sample never predicted gets NA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Sorting the samples dropped a single-feature matrix to a vector, so CoxPH ranking failed for one feature. The "slow" option called coxph on a plain matrix response, compared class() with ==, filled failures with two values for three columns, did not transpose the apply() result and named the rows by sample. It now converts a time/event matrix to Surv, fits survival::coxph per feature and returns one row per feature, agreeing with the fast option. The documented return value is corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Subsetting the numeric columns without drop = FALSE, and transposing the row-wise apply(), failed for a single numeric feature. The subtraction uses sweep() on the numeric matrix, and the result keeps the original feature names (DataFrame() had made them syntactic, so "g-1" became "g.1"). Test columns are matched to the training columns by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…dict with stored models - .permutations keeps a matrix (drop = FALSE), so clinical plus one assay works in the default mode. - fixedAssays may be NULL or several assays; list input no longer calls colData(). - Models are named and looked up by assay; prediction uses each result's own predictParams. - precisionPathwaysPredict predicts every new sample with every stored fold model instead of re-splitting the new cohort with runTests; classes of new samples are optional. - A sample is charged for every tier it passed through (cumulative cost). - summary() matches weights by name and checks they sum to 1. - The confidence cut-off is a minimum (>=, with a floating point tolerance). - Each pathway carries its assay sequence; no strsplit on '-'. - bubblePlot and strataPlot no longer call theme_set(); bubblePlot keeps pathways below 0.5; strataPlot draws the true-class strip as its own layer. - Fold models drop the forImportance forests, which prediction does not use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…nly when used - TOP is required only when runTOP = TRUE. - Data sets are restricted to shared features (with a message) before training and testing. - The modelTest diagonal is the crossValidate run on that data set, not a leaky re-test. - Outcome type is checked for every data set; performanceType is resolved before tuning params. - Random features use max(nFeatures) when several values are tuned. - crissCrossPlot no longer attaches the result, labels the diagonal, and returns the plot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…nctions, show fixes
- PredictParams on a one-step classifier or unknown keyword gives a clear error.
- SelectParams('none') returns NULL; SelectParams accepts plain ranking functions.
- TrainParams(balancing = ) is an error pointing to ModellingParams.
- show methods no longer error on missing characteristics and end lines with newlines.
- Remove the unused misspelled class union; fix the 'higer' typo for AUC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…orrect source URL Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
- calcExternalPerformance matches predicted classes to actual classes by name instead of relabelling levels by position, errors on unknown predicted classes, and calculates every requested metric (the loop used the whole vector of metric names). - grouping = "fold" averages C-index and AUC within each permutation into a plain named numeric vector with permutations in numerical order. - easyHard drops samples with missing performance and matches assay rows by sample name; its assay and performanceType defaults work (they are no longer generic arguments); MultiAssayExperiment assays and "clinical" are usable and a matrix assay is converted to a DataFrame. - Documentation: the definitions of precision and recall were swapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…e assays - extractPrevalidation simplified the per-assay score matrices with sapply(), which only works for one column per assay, so prevalidation failed for more than two classes. The columns are bound instead and named <assay>_<class> when an assay contributes several. - The inner cross-validation seed was .Random.seed[1], the RNG kind code, which is the same for every call (and missing in a fresh session). It is drawn from the random number stream with sample.int(). - The clinical model was fitted twice; the second fit was never used. Only the non-clinical models are refitted for prediction. - Splitting the table by assay dropped a one-feature assay to a vector. - A first assay without a selection step (selectionMethod "none") made the tuning check fail on a NULL selectParams; also in selectMulti. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
append(fitParams, ...) matched the first extra argument to append()'s `values` (or failed for a named one such as weights) and the second to `after`. The extra arguments are now added with c(fitParams, list(...)). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
class(x) == "matrix" has length two for a matrix (c("matrix", "array")), so
the if() failed. is.matrix() is used instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
The previous features were intersected with the current ones before the overlap was computed, so the overlap was always 100%, and signalCondition() without a handler does nothing. The overlap is now the percentage of previously selected features present in the current data, and a warning is given when it is below minimumOverlapPercent. The selected features are the same as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
These rankings compare the first two class levels only, so with more classes they silently ignored the rest. They now stop with an error that says they are for two classes (as Fisher discriminant and k-TSP do). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…l of workers runTests is split into preparing a cross-validation (data checks, splits, final model), running its splits and assembling the ClassifyResult. crossValidate prepares every assay, classifier and combination first and runs all of their splits in one pool, handing tasks out one at a time. Each split uses the random number stream that bpmapply with the cross-validation's seed gives it, so results are identical to before for any number of cores. Nested cross-validations within a split run serially rather than forking again. Missing values are checked per column and constant features found without converting to a matrix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Levene's test centred on the median is a one-way F-test of the absolute deviations from the class medians. Computing the deviations for all features and calling genefilter::rowFtests gives the same ranking as calling car::leveneTest for each feature (identical order on the full asthma data, 10 subsets of 120 samples and a random three-class split), 17 times faster. car is no longer needed for this ranking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…ix PredictParams show test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…er pool; share folds and seed All assays, classifiers, selection methods and combinations in one crossValidate call use the first one's seed and training/test splits, so their results can be compared sample by sample. The final model of each is fitted in the worker pool from the random number state that follows making the splits. Worker pools are made once per session and reused, because making one takes about half a second on servers that allow many open files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
- Each result's sample-wise metric is matched to the first result's samples by name, and results must predict the same set of sample names. - comparison = "Cross-validation" uses the characteristic like any other. - showLegends = FALSE, a single result and a class with one sample work. - More than two classes get a white-to-dark gradient per class by default, one legend per class; too few user colours give an error. - The matrix method's class and group bars keep their first and last tiles (limits = c(1, n) dropped them) and its clustering works. - Legends are taken from small plots with one tile per colour; rendered output of the list method is byte-identical to before. - Rd: metric values listed "Sample Error" twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Average mode split a DataFrame and subset() with class = aClass (an unused argument). The plot theme is added to each plot instead of theme_set(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
rotate90 = TRUE replaced user yLimits by c(0, 1) and turned a character x characteristic into NA. Documentation of the box plot default, aggregate and the return value corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…t rankings Fonts were read from sizesList$fontSizes instead of the documented $fonts; row and column values compared to the whole list; ordering by performance lacked the metric; a ranking shorter than top counted NA as a shared feature. Theme added to the plot instead of theme_set(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Importance mode: undefined plot and colourVariable, keepFeatures compared names to counts, y mapped to a constant string, facets were subset by one characteristic at a time. Commonality: duplicate overlap column, and with a reference level the characteristics were looked up in the whole table and repeated the wrong number of times. alpha reaches the geoms; rotate90 works for importance. Theme added to each plot instead of theme_set(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…age spacing plotFeatureClasses returned an empty list after printing a message for a useFeatures not in the data; it now stops. Its theme is added to each plot. Adds tests for all plotting fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…mbdas at once .cvCoxnetC reproduces cv.glmnet(family = 'cox', type.measure = 'C'): the same folds, fits and lambda.min. It counts concordant pairs for every lambda of a fold at once instead of calling survival::concordance per lambda and fold. cv.glmnet is still used if settings of its own cross-validation are given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…frame An SVM is fitted to a matrix of numeric features, which gives the same model as the formula interface in less time; the design matrix of numeric features is built directly, the same as model.matrix makes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…tures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…ls functions, drop the C++11 request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…tions MulticoreParam forks a process for every chunk of tasks; one task per chunk made a full-size Procedure 1 run on 8 cores slower than serial. Results don't depend on the order or chunking, since each task sets its own random number stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…k of tasks BiocParallel's MulticoreParam forks a process for every chunk of tasks, and each forked process copies much of the main session's memory (about 0.5 GB with ClassifyR's dependencies loaded) when it first collects garbage. On Linux and macOS, .forkApply now forks the workers once, after the data are prepared, keeps the task function in the package environment so that only task numbers and results are sent, and hands tasks out two at a time as workers become free. Procedure 1 of the protocol at 20 cores: 16.5 s -> 10.6 s, with identical predictions. Windows still uses BiocParallel's SnowParam, and runTests still uses the BiocParallel parameters it is given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…nd removals from a maintainability review Parallel: crossValidate's workers are made once per call with the parallel package, forked on Linux and macOS and socket workers on Windows (sent the task function and its data once each), with tasks handed out two at a time. Results are the same as serial, and the same with forked or socket workers. runTests still uses the BiocParallel parameters it is given. Review fixes, without changes to results: - an unknown multiViewMethod is an error naming the choices; - the merge, prevalidation and PCA branches of crossValidate are one; - CV loses its unused and broken independent train/test path; - train and predict align the samples of a list of tables by name; - prepareDataFormals includes maxSimilarity; - prepareData's maxSimilarity uses stats::cor (dcanr dropped); - unused mergeTrainInterface, fastCox and simplifyResults removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…gitignore Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
This was referenced Oct 9, 2026
Merged
… at a time Why: a timeline of the protocol's Procedure 1 on 20 cores showed each worker busy for only 7.5-8.3 s of the 12.8 s the tasks took: with 1500 short tasks (median 75 ms) handed out two at a time, workers waited for the main process between chunks. With about eight chunks per worker: Procedure 1 13.2 s -> 8.9 s on 20 cores, 9.4 s -> 8.1 s on 32; Procedure 2 step 4, with longer tasks, unchanged within noise. Results are identical (each task sets its own random number stream). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…x instead of clashing with it cv.glmnet was always called with type = 'C', so extraParams = list(train = list(type.measure = 'deviance')) stopped with 'formal argument matched by multiple actual arguments'. Tuning by C-index remains the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
…by default (changes results) Why: lambdas near zero give nearly unpenalised Cox models, which overfit the few events of typical omics cohorts (39 relapses in the protocol's METABRIC data) and converge slowly. On the protocol's Procedure 2 step 7 (merge of five views, 31 combinations, 20 x 5 CV, 16 cores), compared with glmnet's default path: - mean C-index over the 31 combinations 0.602 vs 0.599; best in 21 of 31 combinations; - 1.4 min vs 9.2 min; - cross-validation had chosen a lambda below 0.05 of the largest in 12% of fold models; clipping those did not lower the C-index. Tuning by partial likelihood deviance was also compared and gave lower C-indexes (0.587), so tuning by C-index stays. ncvreg uses the same 0.05 when there are more features than samples. Users can still set lambda or lambda.min.ratio through extraParams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the bugs found in the October 2026 audit (
AUDIT.mdon branchaudit-2026-10) and speeds up the cross-validation machinery. The project notes, regression harness and decisions are in SydneyBioX/ProjectClassifyR (private).Results that change on purpose
crossValidatecall use the same splits, so they can be compared sample by sample (Ellis's decision). Later cross-validations in a call give different results from before.modelTestdiagonal was leaky.selectionMethodfor MultiAssayExperiment and list input, and tuning ranges.Speed: identical results for a fixed seed
crossValidatecall, cached per session, given a few chunks of interleaved tasks per worker. Each split sets the random stream it had underbpmapply, so results are the same for any number of cores. macOS honoursnCores.cv.glmnetexactly.Full-size timings on verona (devel → this branch):
Checks
Open questions
docs/QUESTIONS.mdin ProjectClassifyR. Examples:🤖 Generated with Claude Code
https://claude.ai/code/session_01TfsjwkbDbHkX4kJZNe4ER4