Applicability domain

OECD validation principle 3: the region of chemical space in which a model’s predictions can be trusted. Eleven domain definitions sharing one interface, plus coverage and accuracy-versus-coverage analysis.

A prediction outside the domain is not wrong — it is unsupported by the training data, which is a different claim and the one regulators ask about.

One interface, eleven definitions

Every domain implements fit(X) and predict(X) -> bool array:

>>> from qsarkit.applicability import KNNApplicabilityDomain
>>> from qsarkit.model_selection import RandomSplitter
>>> X, y = demo_fingerprints(256), DEMO_Y
>>> train, test = next(RandomSplitter(test_size=0.25, random_state=0).split(X, y))
>>> domain = KNNApplicabilityDomain(n_neighbors=3).fit(X[train])
>>> domain.predict(X[test]).tolist()
[True, True, True, True, True, True]

What the domain is actually measuring

Swap the random split for a scaffold split and the same domain, the same model and the same data give the opposite answer:

>>> from qsarkit.model_selection import ScaffoldSplitter
>>> train, test = next(ScaffoldSplitter(test_size=0.25).split_mols(demo_mols, y))
>>> KNNApplicabilityDomain(n_neighbors=3).fit(X[train]).predict(X[test]).tolist()
[False, False, False, False, False, False]

Nothing changed except which compounds are held out. A random split leaves every test compound with a close analogue in training, so everything is in-domain and the AD looks vacuous. A scaffold split holds out whole chemotypes, and the domain correctly reports that it has never seen anything like them.

That is the entire point: an applicability domain is only informative when the evaluation is honest about novelty. If your AD marks everything in-domain, suspect the split before congratulating the model.

Choosing a definition

>>> from qsarkit.applicability import (
...     LeverageAD, RangeAD, TanimotoSimilarityAD)
>>> train, test = next(RandomSplitter(test_size=0.25, random_state=0).split(X, y))
>>> int(TanimotoSimilarityAD(threshold=0.3).fit(X[train]).predict(X[test]).sum())
6
>>> bool(LeverageAD().fit(X[train]).predict(X[test]).all())
True

The definitions are not interchangeable:

LeverageAD

The classical Williams-plot leverage, h*. Assumes a linear model and continuous descriptors; on a 2048-bit fingerprint it is close to meaningless, because the hat matrix is degenerate.

TanimotoSimilarityAD

Similarity to the nearest training compound. The right choice for fingerprints, and the one that matches how a chemist would judge novelty.

KNNApplicabilityDomain

Mean distance to the k nearest neighbours. Less sensitive to a single close analogue than the Tanimoto rule.

RangeAD / BoundingBoxAD / PCABoundingBoxAD

Descriptor-range checks. Cheap and interpretable, but a bounding box admits the empty interior of a hollow distribution.

ConvexHullAD

Exact but exponential in dimension; usable only after aggressive dimensionality reduction.

KernelDensityAD / IsolationForestAD

Density-based, making no shape assumption.

EnsembleAD

Combines several, requiring agreement.

Does the domain earn its keep?

A domain is only useful if predictions inside it are actually better than predictions outside it. ADAnalyzer measures that directly:

>>> from qsarkit.applicability import ADAnalyzer
>>> from qsarkit.models import QSARRegressor
>>> model = QSARRegressor("rf", random_state=0).fit(X[train], y[train])
>>> analyzer = ADAnalyzer(KNNApplicabilityDomain(n_neighbors=3)).fit(X[train])
>>> report = analyzer.report(X[test], y[test], model.predict(X[test]))
>>> report["coverage"], report["n_inside"], report["n_outside"]
(1.0, 6, 0)

rmse_ratio is the number to read: greater than 1 means errors outside the domain really are larger, so the domain is separating reliable predictions from unreliable ones. Here every test compound is inside, so there is nothing to compare against and the ratio is undefined:

>>> import numpy as np
>>> bool(np.isnan(report["rmse_ratio"]))
True

A domain with 100% coverage is not a good result — it is a domain that has told you nothing.

API

Applicability domain estimation (OECD validation principle 3).

Every estimator shares one interface: fit(X), score_samples(X) (larger = further outside), predict(X) (True = inside) and decision_function(X) (positive = inside).

Examples

>>> import numpy as np
>>> from qsarkit.applicability import LeverageAD
>>> X = np.random.RandomState(0).normal(size=(50, 3))
>>> ad = LeverageAD().fit(X)
>>> bool(ad.predict(np.zeros((1, 3)))[0])
True

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

  • Sahigara, F. et al. (2012). “Comparison of Different Approaches to Define the Applicability Domain of QSAR Models.” Molecules, 17(5), 4791-4810. https://doi.org/10.3390/molecules17054791

class qsarkit.applicability.BaseApplicabilityDomain[source]

Bases: BaseEstimator, ABC

Common interface for every applicability-domain estimator.

OECD validation principle 3 requires a QSAR model to declare the chemical space in which its predictions are reliable. Every subclass answers that question with the same three methods:

  • fit(X) learns the domain from the training descriptors.

  • score_samples(X) returns a continuous “how far outside” score, where larger means further outside the domain.

  • predict(X) returns a boolean array: True = inside the domain.

decision_function(X) is provided for scikit-learn compatibility and returns threshold_ - score, so positive means inside — the sign convention sklearn’s outlier detectors use.

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

  • Sahigara, F. et al. (2012). “Comparison of Different Approaches to Define the Applicability Domain of QSAR Models.” Molecules, 17(5), 4791-4810. https://doi.org/10.3390/molecules17054791

  • Jaworska, J., Nikolova-Jeliazkova, N. & Aldenberg, T. (2005). “QSAR Applicability Domain Estimation by Projection of the Training Set in Descriptor Space: A Review.” ATLA, 33(5), 445-459. https://doi.org/10.1177/026119290503300508

  • Netzeva, T. I. et al. (2005). “Current Status of Methods for Defining the Applicability Domain of (Q)SARs.” ATLA, 33(2), 155-173. https://doi.org/10.1177/026119290503300209

threshold_: float
n_features_in_: int
abstractmethod fit(X, y=None)[source]

Learn the domain from training descriptors.

Return type:

BaseApplicabilityDomain

abstractmethod score_samples(X)[source]

Return per-sample distance-from-domain scores (larger = further out).

Return type:

ndarray[tuple[Any, ...], dtype[double]]

predict(X)[source]

Return True for samples inside the applicability domain.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Return type:

ndarray[tuple[Any, ...], dtype[bool]]

decision_function(X)[source]

Signed margin to the domain boundary; positive means inside.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Return type:

ndarray[tuple[Any, ...], dtype[double]]

coverage(X)[source]

Fraction of X that falls inside the domain.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Returns:

Value in [0, 1].

Return type:

float

class qsarkit.applicability.LeverageAD(threshold_factor=3.0)[source]

Bases: BaseApplicabilityDomain

Leverage (hat-matrix) applicability domain — the Williams-plot method.

The leverage of a compound is its diagonal element of the hat matrix H = X(X'X)^-1 X', i.e. how much influence it exerts on the fitted regression. Compounds whose leverage exceeds the warning value h* = 3(p+1)/n (p = descriptors, n = training compounds) sit in a sparse region of descriptor space where the model is extrapolating.

This is the domain definition assumed by the Williams plot (standardized residual vs leverage), the standard regulatory presentation of QSAR reliability.

Parameters:

threshold_factor (float) – Numerator factor in h* = factor * (p+1)/n. 3 is the conventional warning leverage; 2 is sometimes used for large training sets.

Variables:
  • threshold (float) – The computed h*.

  • n_features_in (int)

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(50, 3))
>>> ad = LeverageAD().fit(X)
>>> bool(ad.predict(np.zeros((1, 3)))[0])
True

References

  • Gramatica, P. (2007). “Principles of QSAR Models Validation: Internal and External.” QSAR Comb. Sci., 26(5), 694-701. https://doi.org/10.1002/qsar.200610151

  • Eriksson, L. et al. (2003). “Methods for Reliability and Uncertainty Assessment and for Applicability Evaluations of Classification- and Regression-Based QSARs.” Environ. Health Perspect., 111(10), 1361-1375. https://doi.org/10.1289/ehp.5758

  • Atkinson, A. C. (1985). “Plots, Transformations and Regression.” Oxford University Press.

fit(X, y=None)[source]

Compute (X'X)^-1 and the warning leverage from training data.

Return type:

LeverageAD

score_samples(X)[source]

Leverage h_i = x_i' (X'X)^-1 x_i for each sample.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.DistanceToModelAD(metric='euclidean', percentile=95.0)[source]

Bases: BaseApplicabilityDomain

Distance-to-centroid applicability domain.

Scores each compound by its distance to the centroid of the training set, with the boundary set at a percentile of the training distribution. Mahalanobis distance accounts for descriptor correlation and scale, which plain Euclidean distance does not.

Parameters:
  • metric (Literal['euclidean', 'mahalanobis', 'cityblock']) – Distance measure.

  • percentile (float) – Percentile of the training distance distribution used as the domain boundary.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(50, 3))
>>> ad = DistanceToModelAD(metric="mahalanobis").fit(X)
>>> bool(ad.predict(np.full((1, 3), 50.0))[0])
False

References

fit(X, y=None)[source]

Learn the centroid, covariance and distance threshold.

Return type:

DistanceToModelAD

score_samples(X)[source]

Distance from each sample to the training centroid.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.KNNApplicabilityDomain(n_neighbors=5, metric='euclidean', percentile=95.0)[source]

Bases: BaseApplicabilityDomain

k-nearest-neighbour applicability domain.

Scores a compound by the mean distance to its k nearest training neighbours, with the boundary at a percentile of the training distribution. Unlike leverage or centroid distance this makes no assumption that the training set forms a single convex cloud, so it handles the clustered, multi-series datasets typical of real QSAR work — which is why it is usually the best-performing AD definition in comparative studies.

Parameters:
  • n_neighbors (int) – Number of neighbours averaged.

  • metric (Literal['euclidean', 'tanimoto']) – "tanimoto" uses Jaccard distance and is the right choice for binary fingerprints.

  • percentile (float) – Percentile of the training score distribution used as the boundary.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(50, 3))
>>> ad = KNNApplicabilityDomain(n_neighbors=3).fit(X)
>>> bool(ad.predict(np.full((1, 3), 50.0))[0])
False

References

fit(X, y=None)[source]

Store the training set and calibrate the distance threshold.

Return type:

KNNApplicabilityDomain

score_samples(X)[source]

Mean distance to the k nearest training compounds.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.RangeAD(tolerance=0.0)[source]

Bases: BaseApplicabilityDomain

Per-descriptor range (bounding-box) applicability domain.

The simplest and most conservative definition: a compound is inside the domain only if every descriptor falls within the training range, optionally widened by a tolerance. Cheap and completely transparent — which is why regulators like it — but it accepts the empty corners of the bounding box, so it over-estimates the domain in high dimensions.

Parameters:

tolerance (float) – Fractional widening of each descriptor’s range, relative to that descriptor’s training span. 0.1 widens each side by 10%.

Variables:
  • threshold (float) – Always 0.0: the score counts range violations, so any violation puts a compound outside.

  • n_features_in (int)

Examples

>>> import numpy as np
>>> X = np.array([[0.0, 0.0], [1.0, 1.0]])
>>> ad = RangeAD().fit(X)
>>> bool(ad.predict(np.array([[0.5, 0.5]]))[0])
True
>>> bool(ad.predict(np.array([[9.0, 0.5]]))[0])
False

References

fit(X, y=None)[source]

Record the per-descriptor training range.

Return type:

RangeAD

score_samples(X)[source]

Number of descriptors falling outside the training range.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

qsarkit.applicability.BoundingBoxAD

alias of RangeAD

class qsarkit.applicability.PCABoundingBoxAD(n_components=0.95, tolerance=0.0)[source]

Bases: BaseApplicabilityDomain

Bounding box in principal-component space.

Projects onto the leading principal components before applying a range test. Because PCs are uncorrelated and ordered by variance, this fits the training cloud far more tightly than a bounding box in the raw (correlated) descriptor space, while staying just as cheap to evaluate.

Parameters:
  • n_components (Any) – Passed to sklearn.decomposition.PCA: an int selects that many components, a float in (0, 1) selects enough to retain that fraction of variance.

  • tolerance (float) – Fractional widening of each component’s range.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(50, 4))
>>> ad = PCABoundingBoxAD(n_components=2).fit(X)
>>> bool(ad.predict(np.full((1, 4), 50.0))[0])
False

References

fit(X, y=None)[source]

Fit the PCA projection and the per-component range.

Return type:

PCABoundingBoxAD

score_samples(X)[source]

Number of principal components falling outside the training range.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.ConvexHullAD(n_components=3, tolerance=1e-10)[source]

Bases: BaseApplicabilityDomain

Convex-hull applicability domain.

A compound is inside the domain if it lies within the convex hull of the training set — the tightest interpolation region there is, with no empty corners. The hull becomes intractable above roughly ten dimensions (and needs more points than dimensions to exist at all), so this class projects onto the leading principal components first.

Parameters:
  • n_components (int) – Number of principal components the hull is built in.

  • tolerance (float) – Numerical slack when testing hull inequalities.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(50, 3))
>>> ad = ConvexHullAD(n_components=2).fit(X)
>>> bool(ad.predict(np.full((1, 3), 50.0))[0])
False

References

fit(X, y=None)[source]

Build the convex hull of the projected training set.

Return type:

ConvexHullAD

score_samples(X)[source]

Largest positive violation of any hull face inequality (0 = inside).

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.TanimotoSimilarityAD(threshold=0.3, n_neighbors=1)[source]

Bases: BaseApplicabilityDomain

Fingerprint-similarity applicability domain.

Declares a compound inside the domain when its Tanimoto similarity to the nearest (or mean of the k nearest) training compound reaches a threshold. This is the domain definition that speaks the language chemists use — “is there anything like this in the training set?” — and the one to prefer whenever the model is built on fingerprints.

Parameters:
  • threshold (float) – Minimum similarity required to be inside the domain. The conventional ECFP4 value for “meaningfully similar” is 0.3-0.4 for AD purposes (much lower than the 0.7 used for hit expansion, because the question is coverage, not equivalence).

  • n_neighbors (int) – Number of nearest training compounds averaged. 1 uses the single nearest neighbour.

Variables:
  • threshold (float) – Stored as a distance (1 - threshold) to match the base class’s “larger is further out” convention.

  • n_features_in (int)

Examples

>>> import numpy as np
>>> X = np.array([[1, 1, 0, 0], [1, 1, 1, 0]], dtype=float)
>>> ad = TanimotoSimilarityAD(threshold=0.5).fit(X)
>>> bool(ad.predict(np.array([[1, 1, 0, 0]], dtype=float))[0])
True
>>> bool(ad.predict(np.array([[0, 0, 0, 1]], dtype=float))[0])
False

References

fit(X, y=None)[source]

Store the training fingerprints.

Return type:

TanimotoSimilarityAD

similarity_to_training(X)[source]

Mean Tanimoto similarity to the k nearest training compounds.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Returns:

Similarities in [0, 1].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

score_samples(X)[source]

Tanimoto distance to the nearest training compounds.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.KernelDensityAD(bandwidth='scott', kernel='gaussian', percentile=5.0)[source]

Bases: BaseApplicabilityDomain

Kernel-density applicability domain.

Estimates the training-set density in descriptor space and puts the boundary at a low-density percentile. Unlike leverage or centroid distance this handles multi-modal training sets — several distinct chemical series — without declaring the sparse space between the clusters to be inside the domain.

Parameters:
  • bandwidth (Any) – Kernel bandwidth, or a rule-of-thumb name passed through to sklearn.neighbors.KernelDensity when numeric.

  • kernel (str) – Kernel name accepted by KernelDensity.

  • percentile (float) – Training log-density percentile used as the boundary; samples below it are outside.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(60, 2))
>>> ad = KernelDensityAD().fit(X)
>>> bool(ad.predict(np.full((1, 2), 50.0))[0])
False

References

fit(X, y=None)[source]

Fit the density estimate and its low-density boundary.

Return type:

KernelDensityAD

score_samples(X)[source]

Negative log-density under the fitted kernel density estimate.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.IsolationForestAD(contamination=0.05, n_estimators=100, random_state=None)[source]

Bases: BaseApplicabilityDomain

Isolation-Forest applicability domain.

Treats “outside the domain” as “easy to isolate”: a tree ensemble partitions the descriptor space at random, and points separated in few splits are anomalies. It is nonparametric, handles multi-modal and non-convex training sets, and scales to large high-dimensional descriptor matrices where hull- and density-based definitions break down.

Parameters:
  • contamination (float) – Expected fraction of training compounds treated as outliers, which sets the boundary.

  • n_estimators (int) – Number of trees.

  • random_state (Optional[int]) – Seed for reproducibility.

Variables:

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(80, 3))
>>> ad = IsolationForestAD(random_state=0).fit(X)
>>> bool(ad.predict(np.full((1, 3), 50.0))[0])
False

References

fit(X, y=None)[source]

Fit the isolation forest on the training descriptors.

Return type:

IsolationForestAD

score_samples(X)[source]

Negated isolation-forest decision function (larger = more anomalous).

Return type:

ndarray[tuple[Any, ...], dtype[double]]

class qsarkit.applicability.EnsembleAD(estimators=None, voting='majority')[source]

Bases: BaseApplicabilityDomain

Consensus applicability domain over several AD definitions.

Different AD definitions disagree, and each has a characteristic failure mode — leverage assumes a single elliptical cloud, bounding boxes accept empty corners, k-NN is sensitive to k. Requiring agreement among several gives a more honest domain than trusting any one, and the fraction of members that agree is itself a graded confidence score.

Parameters:
  • estimators (Optional[Sequence[BaseApplicabilityDomain]]) – Members of the ensemble. Defaults to leverage, k-NN and range.

  • voting (Literal['majority', 'unanimous', 'any']) – How member votes combine into the final in-domain decision.

Variables:
  • threshold (float) – Fraction-outside boundary implied by voting.

  • n_features_in (int)

Examples

>>> import numpy as np
>>> X = np.random.RandomState(0).normal(size=(60, 3))
>>> ad = EnsembleAD().fit(X)
>>> bool(ad.predict(np.full((1, 3), 50.0))[0])
False

References

fit(X, y=None)[source]

Fit every member on the same training data.

Return type:

EnsembleAD

score_samples(X)[source]

Fraction of member estimators calling each sample out-of-domain.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

member_predictions(X)[source]

Per-member in-domain decisions, for diagnosing disagreement.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Returns:

One boolean column per member, named after its class.

Return type:

DataFrame

class qsarkit.applicability.ADAnalyzer(domain)[source]

Bases: object

Quantify what an applicability domain buys you in prediction accuracy.

A domain definition is only useful if excluding the compounds it rejects actually improves accuracy on the ones it keeps. This class measures exactly that trade-off: as the domain is tightened, coverage falls and error should fall with it. A domain whose accuracy curve is flat is not carrying information, however statistically principled it looks.

Parameters:

domain (BaseApplicabilityDomain) – A fitted (or fittable) applicability-domain estimator.

Examples

>>> import numpy as np
>>> from qsarkit.applicability import KNNApplicabilityDomain
>>> rng = np.random.RandomState(0)
>>> X = rng.normal(size=(60, 3))
>>> analyzer = ADAnalyzer(KNNApplicabilityDomain(n_neighbors=3)).fit(X)
>>> 0.0 <= analyzer.coverage(X) <= 1.0
True

References

  • OECD (2007). “Guidance Document on the Validation of (Q)SAR Models.” OECD Series on Testing and Assessment No. 69, ENV/JM/MONO(2007)2. https://doi.org/10.1787/9789264085442-en

  • Dragos, H., Gilles, M. & Alexandre, V. (2009). “Predicting the Predictability: A Unified Approach to the Applicability Domain Problem of QSAR Models.” J. Chem. Inf. Model., 49(7), 1762-1776. https://doi.org/10.1021/ci9000579

  • Sheridan, R. P. (2012). “Three Useful Dimensions for Domain Applicability in QSAR Models Using Random Forest.” J. Chem. Inf. Model., 52(3), 814-823. https://doi.org/10.1021/ci300004n

fit(X, y=None)[source]

Fit the wrapped domain on training descriptors.

Parameters:
Return type:

ADAnalyzer

coverage(X)[source]

Fraction of X inside the domain.

Parameters:

X (Union[Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]])

Return type:

float

report(X, y_true, y_pred)[source]

Compare in-domain and out-of-domain prediction error.

Parameters:
Returns:

coverage, n_inside, n_outside, rmse_inside, rmse_outside, mae_inside, mae_outside and rmse_ratio (outside/inside; > 1 means the domain is doing its job). Error entries are nan when the corresponding subset is empty.

Return type:

Dict[str, float]

accuracy_vs_coverage(X, y_true, y_pred, n_points=20)[source]

Trace prediction error as the domain is progressively tightened.

Compounds are ranked by how far outside the domain they score; the curve then reports RMSE over the most-confident fraction at a series of coverage levels. A useful domain gives a curve that rises monotonically from left (strictest) to right (all compounds).

Parameters:
Returns:

Columns coverage, n_samples, rmse, mae.

Return type:

DataFrame

plot_accuracy_vs_coverage(X, y_true, y_pred, n_points=20)[source]

Plot the accuracy-vs-coverage curve.

Parameters:
Return type:

Figure

williams_plot(X, y_true, y_pred, residual_limit=3.0)[source]

Williams plot: standardized residuals against leverage.

The standard regulatory diagnostic. Points to the right of the vertical h* line are structural outliers (the model is extrapolating); points outside the horizontal +/-3 sigma lines are response outliers (the model is wrong). Both together mark predictions that should not be relied on.

Parameters:
Return type:

Figure

References

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

  • Sahigara, F. et al. (2012). “Comparison of Different Approaches to Define the Applicability Domain of QSAR Models.” Molecules, 17(5), 4791-4810. doi:10.3390/molecules17054791

  • Netzeva, T. I. et al. (2005). “Current Status of Methods for Defining the Applicability Domain of (Quantitative) Structure-Activity Relationships.” ATLA, 33(2), 155-173. doi:10.1177/026119290503300209

  • Sheridan, R. P. et al. (2004). “Similarity to Molecules in the Training Set Is a Good Discriminator for Prediction Accuracy in QSAR.” J. Chem. Inf. Comput. Sci., 44(6), 1912-1928. doi:10.1021/ci049782w