Validation¶
Model validation against OECD principle 4, which asks for three distinct things — goodness-of-fit, robustness and predictivity. A single \(R^2\) addresses only the first, and it is the number most likely to be quoted.
Class |
Answers |
|---|---|
Does it predict? Cross-validated \(Q^2\), with out-of-fold predictions. |
|
Is the fit real? Could the model score this well on permuted labels? |
|
How precise is the score? An out-of-bag interval rather than a point estimate. |
|
Does it predict on compounds it never saw? The full QSAR metric set plus the Golbraikh-Tropsha criteria. |
Cross-validation¶
>>> from qsarkit.models import QSARRegressor
>>> from qsarkit.validation import CrossValidator
>>> X, y = demo_fingerprints(512), DEMO_Y
>>> model = QSARRegressor("rf", random_state=0)
>>> report = CrossValidator(n_splits=5, random_state=0).evaluate(model, X, y)
>>> round(report["q2"], 1), round(report["rmse_cv"], 1)
(0.7, 0.6)
The report carries the out-of-fold predictions, so any further statistic or plot needs no refitting:
>>> report["y_pred_cv"].shape
(24,)
>>> report["method"], report["n_splits"]
('kfold', 5)
n_splits comes from the splitter, not the constructor argument —
leave-one-out on 24 compounds is 24 splits, and a report claiming 5 would
be wrong:
>>> CrossValidator(method="loo").evaluate(QSARRegressor("ridge"), X, y)["n_splits"]
24
"repeated_kfold" averages over several partitions, which matters on
small datasets where one k-fold estimate is dominated by the luck of the
split. "leave_group_out" is the one to use when compounds come in
groups — a scaffold series, an assay batch, a source publication.
Robustness: y-scrambling¶
Refit the model on randomly permuted activities. If the scrambled models score anywhere near the real one, the apparent performance came from the model’s flexibility relative to the dataset size, not from a structure-activity relationship.
>>> from qsarkit.validation import YScrambling
>>> scramble = YScrambling(n_iterations=50, random_state=0).run(model, X, y)
>>> round(scramble["real_score"], 3)
0.953
>>> round(scramble["mean_scrambled_score"], 3)
0.839
>>> scramble["p_value"] < 0.05
True
Danger
Read those numbers again. The model fits randomly permuted activities to \(R^2 = 0.84\) on average, against 0.95 on the real ones. The p-value clears 0.05, but the honest reading is that most of this model’s apparent fit is capacity: 24 compounds described by 512 features will fit almost anything.
This is exactly the failure y-scrambling exists to expose, and it is invisible in the training \(R^2\) that would otherwise be reported.
The p-value can never be exactly zero — a permutation test cannot distinguish “very unlikely” from “impossible”, so reporting 0 would claim more than was measured:
>>> small = YScrambling(n_iterations=20, random_state=0).run(model, X, y)
>>> round(small["p_value"], 4) == round(1 / 21, 4)
True
YScrambling.plot() shows the scrambled distribution with the real
score marked, which is more informative than either number alone:
>>> scrambler = YScrambling(n_iterations=20, random_state=0)
>>> _ = scrambler.run(model, X, y)
>>> type(scrambler.plot()).__name__
'Figure'
Precision: the bootstrap¶
A single cross-validated \(Q^2\) is one number with no error bar. Resampling the training set and scoring out-of-bag gives the spread:
>>> from qsarkit.validation import BootstrapValidator
>>> boot = BootstrapValidator(n_iterations=30, random_state=0).run(model, X, y)
>>> round(boot["mean_score"], 2)
0.34
>>> round(boot["ci_upper"] - boot["ci_lower"], 1)
1.9
An interval nearly two \(R^2\) units wide. Any comparison between two models on this dataset that turns on less than that is noise — and the interval is the only thing that says so.
Scoring is out-of-bag, not in-bag: about 36.8% of the data is left out of each resample, and scoring there rather than on the fitted rows is what makes this an estimate of generalization instead of of fit.
Predictivity: external validation¶
>>> from qsarkit.model_selection import RandomSplitter
>>> from qsarkit.validation import ExternalValidator
>>> train, test = next(RandomSplitter(test_size=0.25, random_state=0).split(X, y))
>>> cv = CrossValidator(n_splits=5, random_state=0).evaluate(model, X[train], y[train])
>>> fitted = QSARRegressor("rf", random_state=0).fit(X[train], y[train])
>>> result = ExternalValidator(q2=cv["q2"]).validate(
... fitted, X[test], y[test], y[train])
>>> round(result["r2"], 2), round(result["q2_f1"], 2)
(0.82, 0.82)
Supply y_train so Q²F1 is scaled by the training set variance, which
is what makes it comparable across differently-centred test sets. Supply
q2 so Golbraikh-Tropsha criterion 1 can be evaluated rather than
reporting None:
>>> result["golbraikh_tropsha"]["passed"]
False
>>> gt = result["golbraikh_tropsha"]
>>> [k for k in sorted(gt) if k.startswith("criterion") and gt[k] is False]
['criterion_1_q2', 'criterion_3_r0']
An \(R^2\) of 0.82 on the test set looks respectable and would have been reported as a success. Criterion 1 fails because the cross-validated \(Q^2\) of 0.24 is below the 0.5 threshold; criterion 3 concerns regression through the origin — the predictions correlate with the truth but are systematically offset. Running the full check is what turns a respectable-looking number into an accurate picture.
Note
None of these validators mutates the estimator you hand them: each clones it before fitting, so the same configured model can be passed to all four.
>>> template = QSARRegressor("ridge")
>>> _ = YScrambling(n_iterations=5, random_state=0).run(template, X, y)
>>> hasattr(template, "estimator_")
False
Choosing the metric¶
All four validators take scoring. It defaults to \(R^2\), which is
right for a regression QSAR and wrong for everything else: a toxicity
classifier has to be argued in ROC-AUC or average precision, and a regulator
asking for RMSE is not asking for \(R^2\) reported next to it.
>>> from qsarkit.validation import available_metrics
>>> len(available_metrics())
18
>>> [m for m in available_metrics() if "auc" in m]
['pr_auc', 'roc_auc']
Pass several and every score becomes an array in the order given, so one pass reports them all:
>>> from qsarkit.models import QSARRegressor
>>> from qsarkit.validation import CrossValidator
>>> cv = CrossValidator(n_splits=5, random_state=0, scoring=["r2", "rmse", "mae"])
>>> result = cv.evaluate(QSARRegressor("rf", random_state=0), X, y)
>>> result["metric"]
('r2', 'rmse', 'mae')
>>> result["score"].round(2)
array([0.69, 0.6 , 0.44])
One metric returns a float; an iterable returns an array even when it holds a single entry, so adding a second metric never changes the shape of your code.
Classification metrics see probabilities¶
roc_auc, pr_auc and brier rank or calibrate, so they are given
predict_proba’s positive-class column, never a thresholded label.
Thresholding first throws away the ranking that ROC-AUC exists to measure.
>>> import numpy as np
>>> from qsarkit.models import QSARClassifier
>>> labels = (DEMO_Y > np.median(DEMO_Y)).astype(int)
>>> model = QSARClassifier("rf", random_state=0)
>>> cv = CrossValidator(n_splits=5, random_state=0,
... scoring=["roc_auc", "pr_auc", "mcc"])
>>> report = cv.evaluate(model, X, labels)
>>> report["score"].round(2)
array([0.93, 0.94, 0.75])
>>> report["y_score_cv"].shape # out-of-fold probabilities
(24,)
For a metric that is not on the list, or one that needs probabilities:
>>> from sklearn.metrics import average_precision_score
>>> from qsarkit.validation import make_scorer
>>> scorer = make_scorer(average_precision_score, needs_proba=True, name="ap")
>>> CrossValidator(n_splits=5, random_state=0, scoring=scorer).evaluate(
... model, X, labels)["metric"]
'ap'
A bare callable is assumed to take (y_true, y_pred) and to improve as it
grows; make_scorer() is how the other cases are
declared. Declaring a loss matters more than it looks:
greater_is_better=False is what keeps “did the scrambled model do at least
as well” comparing in the right direction, so a y-randomization p-value
computed on RMSE is not reported backwards.
>>> from qsarkit.validation import YScrambling
>>> scramble = YScrambling(n_iterations=20, random_state=0,
... scoring=["r2", "rmse"]).run(
... QSARRegressor("rf", random_state=0), X, y)
>>> scramble["p_value"].round(3) # same verdict from a gain and a loss
array([0.048, 0.048])
Warning
Score out of fold before reading anything into a ranking metric.
YScrambling scores the apparent, in-sample fit
by default, which is what earlier releases did and what makes the classic
over-fitting demonstration work for \(R^2\). It cannot work for
ROC-AUC: a random forest separates permuted labels in-sample as perfectly
as real ones, so both sides read near 1.0 and the test reports nothing.
>>> in_sample = YScrambling(n_iterations=20, random_state=0,
... scoring="roc_auc").run(model, X, labels)
>>> round(in_sample["real_score"], 2), round(in_sample["mean_scrambled_score"], 2)
(1.0, 1.0)
Pass cv — and stratify=True on an imbalanced endpoint — and the
same test becomes informative:
>>> honest = YScrambling(n_iterations=20, random_state=0, scoring="roc_auc",
... cv=5, stratify=True).run(model, X, labels)
>>> round(honest["real_score"], 2), round(honest["mean_scrambled_score"], 2)
(0.93, 0.43)
>>> honest["scored_out_of_fold"]
True
See OECD validation for how these fit together into a reportable validation, and Metrics for the statistics they compute.
API¶
Model validation against the OECD principles.
Every validator here takes a scoring argument. It defaults to
\(R^2\), which is the right default for a regression QSAR and wrong for
everything else – a toxicity classifier has to be argued in ROC-AUC or
average precision. Name a metric from available_metrics(), pass a
(y_true, y_pred) callable, or wrap one with make_scorer() when it
needs probabilities or is a loss. Pass several metrics and every score in the
result becomes an array in the order given, so one pass reports them all:
CrossValidator(scoring=["roc_auc", "pr_auc", "mcc"]).evaluate(model, X, y)
References
OECD (2007). “Guidance Document on the Validation of (Quantitative) Structure-Activity Relationship [(Q)SAR] Models.” OECD Series on Testing and Assessment No. 69, ENV/JM/MONO(2007)2. https://doi.org/10.1787/9789264085442-en
Golbraikh, A. & Tropsha, A. (2002). “Beware of q2!” J. Mol. Graph. Model., 20(4), 269-276. https://doi.org/10.1016/S1093-3263(01)00123-1
Rucker, C., Rucker, G. & Meringer, M. (2007). “y-Randomization and Its Variants in QSPR/QSAR.” J. Chem. Inf. Model., 47(6), 2345-2357. https://doi.org/10.1021/ci700157b
- class qsarkit.validation.CrossValidator(method='kfold', n_splits=5, n_repeats=10, random_state=None, scoring=None)[source]¶
Bases:
objectCross-validated Q^2, RMSE_CV and MAE_CV for a scikit-learn estimator.
Supports the four fold-splitting schemes routinely used in QSAR validation studies: k-fold, leave-one-out, repeated k-fold (to average away the fold-assignment randomness of a single k-fold run) and leave-one-group-out (for scaffold- or assay-based splits passed via
groups). Every fold clones the estimator withsklearn.base.clonebefore fitting, so the caller’s own estimator object is never mutated.- Parameters:
method (
Literal['kfold','loo','repeated_kfold','leave_group_out']) – Cross-validation scheme.n_splits (
int) – Number of folds for"kfold"and"repeated_kfold". Ignored by"loo"and"leave_group_out"(both use one fold per sample / per group).n_repeats (
int) – Number of repeats for"repeated_kfold". Ignored otherwise.random_state (
Optional[int]) – Seed controlling the fold shuffling of"kfold"and"repeated_kfold".
Examples
>>> import numpy as np >>> from sklearn.datasets import make_regression >>> from sklearn.linear_model import Ridge >>> from qsarkit.validation import CrossValidator >>> X, y = make_regression(n_samples=60, n_features=5, noise=1.0, random_state=0) >>> result = CrossValidator(method="kfold", n_splits=5, random_state=0).evaluate( ... Ridge(), X, y ... ) >>> result["q2"] > 0.5 True
References
Todeschini, R. & Consonni, V. (2009). “Molecular Descriptors for Chemoinformatics,” 2nd ed. Wiley-VCH. https://doi.org/10.1002/9783527628766
Consonni, V., Ballabio, D. & Todeschini, R. (2009). J. Chem. Inf. Model., 49(7), 1669-1678. https://doi.org/10.1021/ci900115y
- evaluate(estimator, X, y, groups=None)[source]¶
Cross-validate
estimatorand compute Q^2, RMSE_CV and MAE_CV.- Parameters:
estimator (
BaseEstimator) – Any object implementing.fit(X, y)/.predict(X). A fresh clone is fit on each fold; the passed-in object is never mutated.X (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]]) – Descriptor matrix.y (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]]) – Observed responses.groups (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str],None]) – Group labels, required whenmethod="leave_group_out".
- Returns:
"q2"(float),"rmse_cv"(float),"mae_cv"(float),"y_pred_cv"(ndarray of out-of-fold predictions aligned to the original row order – for"repeated_kfold"this is the average out-of-fold prediction over all repeats),"method"(str) and"n_splits"(int – the number of folds actually run, which for"loo"is the sample count and for"leave_group_out"the number of groups).- Return type:
- Raises:
ValueError – If
method="leave_group_out"andgroupsisNone, or ifmethodis not one of the four supported schemes.
Examples
>>> import numpy as np >>> from sklearn.datasets import make_regression >>> from sklearn.linear_model import Ridge >>> from qsarkit.validation import CrossValidator >>> X, y = make_regression(n_samples=50, n_features=4, noise=1.0, random_state=0) >>> result = CrossValidator(method="loo").evaluate(Ridge(), X, y) >>> sorted(result) ['mae_cv', 'method', 'n_splits', 'q2', 'rmse_cv', 'y_pred_cv']
- class qsarkit.validation.YScrambling(n_iterations=100, random_state=None, scoring=None, cv=None, stratify=False)[source]¶
Bases:
objectTest whether a model can fit randomly permuted labels as well as real ones.
Also called y-randomization. Refit the model many times on shuffled activities: if the scrambled models score anywhere near the real one, the apparent performance came from the model’s flexibility relative to the dataset size, not from a structure-activity relationship.
This is the check that catches the classic QSAR failure – a few dozen compounds described by thousands of descriptors, where something will always correlate. It is required evidence under OECD principle 4, and it is cheap, so there is no excuse for omitting it.
- Parameters:
n_iterations (
int) – Number of permutations. The smallest p-value obtainable is1 / (n_iterations + 1), so 100 iterations cannot report anything below 0.0099.scoring (
Union[None,str,Scorer,Callable[...,float],Iterable[Union[str,Scorer,Callable[...,float]]]]) – The metric to argue in. Defaults to \(R^2\). Name one ofavailable_metrics(), pass a(y_true, y_pred)callable, or usemake_scorer()for a metric that needs probabilities or is a loss. Pass several and every score in the result becomes an array in the order given.cv (
Optional[int]) – Score out of fold over this many folds instead of on the training data. Strongly recommended for any flexible model, and required for a ranking metric to mean anything: a random forest reaches an in-sample ROC-AUC near 1.0 on permuted labels just as it does on real ones, so the in-sample comparison shows no gap and the test reports nothing. The default isNone– the apparent, in-sample fit – because that is what earlier releases computed.stratify (
bool) – Use stratified folds whencvis set. Needed on an imbalanced classification endpoint, where an unstratified fold can contain no positives at all.
- Variables:
real_score (
floatorndarray) – The model’s score on the true labels: a float for one metric, an array in the given order for several.scrambled_scores (
ndarray) – Shape(n_iterations,)for one metric,(n_iterations, n_metrics)for several.
Examples
A real relationship survives the test:
>>> import numpy as np >>> from sklearn.linear_model import Ridge >>> from qsarkit.validation import YScrambling >>> rng = np.random.default_rng(0) >>> X = rng.normal(size=(60, 4)) >>> y = X[:, 0] * 3.0 + rng.normal(scale=0.2, size=60) >>> result = YScrambling(n_iterations=50, random_state=0).run(Ridge(), X, y) >>> result["p_value"] < 0.05 True
Pure noise does not:
>>> y_noise = rng.normal(size=60) >>> noise = YScrambling(n_iterations=50, random_state=0).run(Ridge(), X, y_noise) >>> noise["p_value"] > 0.05 True
The diagnostic value is in the gap between the two scores:
>>> round(result["real_score"] - result["mean_scrambled_score"], 2) > 0.5 True
References
Rucker, C., Rucker, G. & Meringer, M. (2007). “y-Randomization and Its Variants in QSPR/QSAR.” J. Chem. Inf. Model., 47(6), 2345-2357. https://doi.org/10.1021/ci700157b
Tropsha, A., Gramatica, P. & Gombar, V. K. (2003). “The Importance of Being Earnest.” QSAR Comb. Sci., 22(1), 69-77. https://doi.org/10.1002/qsar.200390007
OECD (2007). ENV/JM/MONO(2007)2. https://doi.org/10.1787/9789264085442-en
- run(estimator, X, y)[source]¶
Fit on the real labels and on
n_iterationspermutations of them.- Parameters:
estimator (
BaseEstimator) – Cloned before each fit, so the object passed is never modified.X (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]])y (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]])
- Returns:
real_score,mean_scrambled_score,std_scrambled_score,max_scrambled_score,best_scrambled_score(the largest for a metric where more is better, the smallest for a loss),p_value(the fraction of permutations scoring at least as well as the real fit, with the conventional +1 correction),n_iterations,metric(the name, or the tuple of names) andscored_out_of_fold.Every score is a float when one metric was requested and an ndarray in the requested order when several were.
- Return type:
- Raises:
ValueError – If
n_iterationsis not positive.
- class qsarkit.validation.ExternalValidator(q2=None, scoring=None)[source]¶
Bases:
objectScore a fitted model on a held-out set with QSAR-appropriate metrics.
OECD principle 4’s predictivity requirement. Wraps
qsar_regression_report()and the Golbraikh-Tropsha criteria so an external evaluation reports the same statistics every time, rather than whichever ones happened to look best.- Parameters:
q2 (
Optional[float]) – A cross-validated Q² from the training set. Supply it so that Golbraikh-Tropsha criterion 1 can be evaluated; without it that criterion reportsNonerather than silently passing.scoring (
Union[None,str,Scorer,Callable[...,float],Iterable[Union[str,Scorer,Callable[...,float]]]]) – Report these metrics instead of the regression report. The default (None) keeps the QSAR regression report and the Golbraikh-Tropsha criteria, which is what a regression submission needs; naming metrics is how a classification endpoint is validated, and thenscoreandmetricreplace the report.
Examples
>>> import numpy as np >>> from sklearn.linear_model import Ridge >>> from qsarkit.validation import ExternalValidator >>> rng = np.random.default_rng(0) >>> X = rng.normal(size=(60, 4)) >>> y = X[:, 0] * 3.0 + rng.normal(scale=0.2, size=60) >>> model = Ridge().fit(X[:45], y[:45]) >>> report = ExternalValidator(q2=0.9).validate(model, X[45:], y[45:], y[:45]) >>> report["r2"] > 0.9 True >>> report["golbraikh_tropsha"]["passed"] True
References
Golbraikh, A. & Tropsha, A. (2002). “Beware of q2!” J. Mol. Graph. Model., 20(4), 269-276. https://doi.org/10.1016/S1093-3263(01)00123-1
Consonni, V., Ballabio, D. & Todeschini, R. (2009). “Comments on the Definition of the Q2 Parameter for QSAR Validation.” J. Chem. Inf. Model., 49(7), 1669-1678. https://doi.org/10.1021/ci900115y
- validate(estimator, X_test, y_test, y_train=None)[source]¶
Evaluate a fitted model on the test set.
- Parameters:
estimator (
BaseEstimator) – An already-fitted model.X_test (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]]) – The held-out set.y_test (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]]) – The held-out set.y_train (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str],None]) – Training activities. Supplied, Q²F1 and Q²F2 are computed against the training mean, which is what makes them comparable across differently-centred test sets.
- Returns:
The regression report, plus
q2_f1wheny_trainis given andgolbraikh_tropsha.- Return type:
- class qsarkit.validation.BootstrapValidator(n_iterations=100, random_state=None, scoring=None)[source]¶
Bases:
objectBootstrap the training set to estimate how stable a score is.
A single cross-validated Q² is one number with no error bar. Resampling the training set with replacement and refitting gives the spread, which is what tells you whether a 0.02 difference between two models means anything on this much data – usually it does not.
- Parameters:
- Variables:
scores (
ndarrayofshape (n_iterations,)) – Out-of-bag score from each resample.
Examples
>>> import numpy as np >>> from sklearn.linear_model import Ridge >>> from qsarkit.validation import BootstrapValidator >>> rng = np.random.default_rng(0) >>> X = rng.normal(size=(60, 4)) >>> y = X[:, 0] * 3.0 + rng.normal(scale=0.2, size=60) >>> result = BootstrapValidator(n_iterations=25, random_state=0).run(Ridge(), X, y) >>> result["mean_score"] > 0.9 True >>> result["ci_lower"] <= result["mean_score"] <= result["ci_upper"] True
References
Efron, B. & Tibshirani, R. J. (1993). “An Introduction to the Bootstrap.” Chapman & Hall. https://doi.org/10.1201/9780429246593
Wehrens, R., Putter, H. & Buydens, L. M. C. (2000). “The Bootstrap: A Tutorial.” Chemom. Intell. Lab. Syst., 54(1), 35-52. https://doi.org/10.1016/S0169-7439(00)00102-7
- run(estimator, X, y, confidence=0.95)[source]¶
Refit on bootstrap resamples and score on the out-of-bag remainder.
- Parameters:
estimator (
BaseEstimator) – Cloned before each fit.X (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]])y (
Union[Buffer,_SupportsArray[dtype[Any]],_NestedSequence[_SupportsArray[dtype[Any]]],complex,bytes,str,_NestedSequence[complex|bytes|str]])confidence (
float) – Width of the reported percentile interval.
- Returns:
mean_score,std_score,ci_lower,ci_upper,confidence,n_iterations,n_effective(resamples that produced a usable out-of-bag set) andmetric.Every score is a float when one metric was requested and an ndarray in the requested order when several were.
- Return type:
- Raises:
ValueError – If
n_iterationsis not positive,confidenceis not in (0, 1), or no resample left any out-of-bag samples.
- class qsarkit.validation.Scorer(name, func, needs_proba=False, greater_is_better=True)[source]¶
Bases:
objectOne metric, with what a validation method needs to know about it.
- Variables:
name (
str) – How the metric is reported.func (
callable) –func(y_true, y_pred_or_score) -> float.needs_proba (
bool) – Whetherfuncwantspredict_proba(X)[:, 1]rather thanpredict(X).greater_is_better (
bool) – Whether a larger value is a better model.Falsefor losses such as RMSE, which is what keeps a permutation p-value the right way round.
- qsarkit.validation.make_scorer(func, *, needs_proba=False, greater_is_better=True, name=None)[source]¶
Wrap a metric for use as
scoring=.- Parameters:
func (
Callable[...,float]) –func(y_true, y_pred) -> float, orfunc(y_true, y_score)whenneeds_probais set.needs_proba (
bool) – Pass the positive-class probability instead of the predicted label.greater_is_better (
bool) – SetFalsefor a loss, so that “at least as good” compares the right way round.
- Return type:
Examples
A metric a validation method can use directly:
>>> from sklearn.metrics import average_precision_score >>> from qsarkit.validation import make_scorer >>> scorer = make_scorer(average_precision_score, needs_proba=True) >>> scorer.name 'average_precision_score'
A loss, declared as one:
>>> from qsarkit.metrics import rmse >>> make_scorer(rmse, greater_is_better=False).greater_is_better False
References¶
OECD (2007). Guidance Document on the Validation of (Quantitative) Structure-Activity Relationship [(Q)SAR] Models, ENV/JM/MONO(2007)2. doi:10.1787/9789264085442-en
Golbraikh, A. & Tropsha, A. (2002). “Beware of q2!” J. Mol. Graph. Model., 20(4), 269-276. doi:10.1016/S1093-3263(01)00123-1
Rücker, C., Rücker, G. & Meringer, M. (2007). “y-Randomization and Its Variants in QSPR/QSAR.” J. Chem. Inf. Model., 47(6), 2345-2357. doi:10.1021/ci700157b
Tropsha, A., Gramatica, P. & Gombar, V. K. (2003). “The Importance of Being Earnest.” QSAR Comb. Sci., 22(1), 69-77. doi:10.1002/qsar.200390007
Efron, B. & Tibshirani, R. J. (1993). An Introduction to the Bootstrap. Chapman & Hall. doi:10.1201/9780429246593
Consonni, V., Ballabio, D. & Todeschini, R. (2009). “Comments on the Definition of the Q2 Parameter for QSAR Validation.” J. Chem. Inf. Model., 49(7), 1669-1678. doi:10.1021/ci900115y
Varma, S. & Simon, R. (2006). “Bias in Error Estimation When Using Cross-Validation for Model Selection.” BMC Bioinformatics, 7, 91. doi:10.1186/1471-2105-7-91