diff --git a/aaanalysis/__init__.py b/aaanalysis/__init__.py index 350c4ed2..e33d5e26 100644 --- a/aaanalysis/__init__.py +++ b/aaanalysis/__init__.py @@ -7,6 +7,7 @@ from .feature_engineering import AAclust, AAclustPlot, SequenceFeature, NumericalFeature, CPP, CPPGrid, CPPPlot from .pu_learning import dPULearn, dPULearnPlot from .explainable_ai import TreeModel +from .prediction import AAPred, AAPredPlot from .protein_engineering import AAMut, AAMutPlot, SeqMut, SeqMutPlot, SeqOpt, SeqOptPlot from .plotting import (plot_get_clist, plot_get_cmap, plot_get_cdict, plot_settings, plot_legend, plot_gcfs, plot_rank) @@ -60,6 +61,8 @@ "SeqOpt", "SeqOptPlot", "TreeModel", + "AAPred", + "AAPredPlot", # "ShapModel" # SHAP "plot_get_clist", "plot_get_cmap", diff --git a/aaanalysis/_constants.py b/aaanalysis/_constants.py index c340c7fc..543325bc 100644 --- a/aaanalysis/_constants.py +++ b/aaanalysis/_constants.py @@ -352,7 +352,14 @@ def _folder_path(super_folder, folder_name): MODEL_SVM = "svm" MODEL_RF = "rf" MODEL_LOG_REG = "log_reg" +MODEL_EXTRA_TREES = "extra_trees" LIST_CV_MODELS = [MODEL_SVM, MODEL_RF, MODEL_LOG_REG] +# Prediction-model registry names (AAPred). Resolved to sklearn estimators by +# ut.get_cv_model_. Kept deliberately small — the four standard families the package +# already uses (matching pipe.predict_samples' default set). Any other estimator +# (MLP, gradient boosting, xgboost, a voting/stacking ensemble, ...) is used by +# passing a configured sklearn estimator instance instead of a name. +LIST_PRED_MODELS = [MODEL_SVM, MODEL_RF, MODEL_EXTRA_TREES, MODEL_LOG_REG] DICT_VALUE_TYPE = {COL_ABS_AUC: "mean", COL_ABS_MEAN_DIF: "mean", COL_MEAN_DIF: "mean", @@ -441,6 +448,20 @@ def _folder_path(super_folder, folder_name): COL_AVG_ABS_AUC_NEG, COL_AVG_KLD_NEG] COLS_EVAL_DPULEARN = [COL_N_REL_NEG] + COLS_EVAL_DPULEARN_SIMILARITY + COLS_EVAL_DPULEARN_DISSIMILARITY +# AAPred (model evaluation / deployment) +COL_MODEL = "model" # model class short name (e.g. 'RandomForestClassifier') +COL_METRIC = "metric" # performance metric name (e.g. 'balanced_accuracy') +COL_PRINCIPLE = "principle" # evaluation principle: 'cv' (cross-validation) | 'holdout' +COL_SCORE_STD = "score_std" # std of the score (across CV folds; NaN for a single holdout estimate) +COL_GROUP = "group" # per-sample/per-protein group label used for coloring +COL_OFFSET = "offset" # AAPred.predict_domain — boundary shift applied to tmd_start/tmd_stop +COL_RESIDUE_POS = "position" # AAPred.predict_window — 1-based anchor position scored +STR_PRINCIPLE_CV = "cv" +STR_PRINCIPLE_HOLDOUT = "holdout" +LIST_PRINCIPLES = [STR_PRINCIPLE_CV, STR_PRINCIPLE_HOLDOUT] +LIST_METRICS_PRED = ["accuracy", "balanced_accuracy", "precision", "recall", "f1", "roc_auc"] +COLS_EVAL_PRED = [COL_MODEL, COL_METRIC, COL_PRINCIPLE, COL_SCORE, COL_SCORE_STD] + # Labels LABEL_FEAT_VAL = "Feature value" LABEL_HIST_COUNT = "Number of proteins" diff --git a/aaanalysis/_utils/models.py b/aaanalysis/_utils/models.py new file mode 100644 index 00000000..0b9b9299 --- /dev/null +++ b/aaanalysis/_utils/models.py @@ -0,0 +1,56 @@ +"""Shared classifier registry: map a model-name string to a configured estimator. + +One source of truth for the ``name -> sklearn estimator`` mapping used across the +package (AAPred, ``predict_samples``, ``find_features``), so the name list does not +drift between call sites. The roster is kept deliberately small — the four standard +families the package already uses. Any other estimator (MLP, gradient boosting, +xgboost, a voting/stacking ensemble, a full ``Pipeline``, ...) is used by passing a +configured sklearn estimator instance instead of a name; only names route here. The +default (``svm``) reproduces the linear-SVM recipe used throughout the γ-secretase +analysis. +""" +from .. import _constants as const + + +# I Helper Functions +def _svm(random_state=None): + from sklearn.svm import SVC + return SVC(kernel="linear", probability=True, random_state=random_state) + + +def _rf(random_state=None): + from sklearn.ensemble import RandomForestClassifier + return RandomForestClassifier(random_state=random_state) + + +def _extra_trees(random_state=None): + from sklearn.ensemble import ExtraTreesClassifier + return ExtraTreesClassifier(random_state=random_state) + + +def _log_reg(random_state=None): + from sklearn.linear_model import LogisticRegression + return LogisticRegression(max_iter=1000, random_state=random_state) + + +_FACTORIES = { + const.MODEL_SVM: _svm, + const.MODEL_RF: _rf, + const.MODEL_EXTRA_TREES: _extra_trees, + const.MODEL_LOG_REG: _log_reg, +} + + +# II Main Functions +def get_cv_model_(name=None, random_state=None): + """Return a fresh configured estimator for a registry ``name``. + + ``name`` is one of ``ut.LIST_PRED_MODELS``. ``random_state`` is injected where the + estimator supports it. Raises ``ValueError`` for an unknown name; pass a configured + sklearn estimator instance instead of a name to use any other model. + """ + if name not in _FACTORIES: + valid = ", ".join(list(_FACTORIES)) + raise ValueError(f"'model' name '{name}' is not in the registry. Valid names: {valid}. " + f"A configured sklearn estimator instance may be passed instead.") + return _FACTORIES[name](random_state=random_state) diff --git a/aaanalysis/explainable_ai/_tree_model.py b/aaanalysis/explainable_ai/_tree_model.py index 811660ba..3ebd2141 100644 --- a/aaanalysis/explainable_ai/_tree_model.py +++ b/aaanalysis/explainable_ai/_tree_model.py @@ -453,6 +453,11 @@ def predict_proba(self, .. note:: :meth:`TreeModel.fit` must be called before using this method. + .. note:: + ``TreeModel`` is focused on global Monte-Carlo feature *importance*. For training and + deploying prediction models (selecting estimators, tuning, and scoring at the sequence, + domain, and window level), :class:`AAPred` is the recommended entry point. + .. versionadded:: 0.1.0 Parameters diff --git a/aaanalysis/prediction/__init__.py b/aaanalysis/prediction/__init__.py new file mode 100644 index 00000000..6fdf3965 --- /dev/null +++ b/aaanalysis/prediction/__init__.py @@ -0,0 +1,20 @@ +""" +Prediction: evaluate and deploy sequence-based prediction models. + +Public objects: AAPred, AAPredPlot. +Downstream of feature engineering (``CPP`` / ``CPPGrid`` produce ``df_feat`` and the feature +matrix ``X``): ``AAPred`` evaluates one or more scikit-learn models across metrics by +cross-validation and an optional held-out set, and fits them for deployment +(``predict_proba`` / ``predict``); ``AAPredPlot`` visualizes the evaluation table and the +per-sample prediction scores. Complements ``explainable_ai.TreeModel`` (tree-ensemble +feature importance) — this subpackage owns the general evaluate-and-deploy path. + +See ``.claude/rules/code-conventions.md`` for conventions and ``CONTEXT.md`` for domain terms. +""" +from ._aa_pred import AAPred +from ._aa_pred_plot import AAPredPlot + +__all__ = [ + "AAPred", + "AAPredPlot", +] diff --git a/aaanalysis/prediction/_aa_pred.py b/aaanalysis/prediction/_aa_pred.py new file mode 100644 index 00000000..b5ade9f5 --- /dev/null +++ b/aaanalysis/prediction/_aa_pred.py @@ -0,0 +1,642 @@ +""" +This is a script for the frontend of the AAPred class for evaluating and deploying prediction models. +""" +from typing import Optional, Dict, List, Tuple, Type, Union +import numpy as np +import pandas as pd +from sklearn.base import ClassifierMixin, BaseEstimator, clone +from sklearn.ensemble import RandomForestClassifier + +import aaanalysis.utils as ut +from aaanalysis.template_classes import Wrapper + +from ._backend.aa_pred.aa_pred_fit import fit_models, predict_proba_models +from ._backend.aa_pred.aa_pred_eval import eval_models + + +# I Helper Functions +def _set_random_state_if_supported(estimator=None, random_state=None, only_if_unset=False): + """Inject ``random_state`` into an estimator that supports it (for reproducibility). + + ``only_if_unset`` protects an explicit seed the user already set on a passed instance; + a value is written only when the estimator's ``random_state`` is currently ``None``. + """ + params = estimator.get_params(deep=False) + if "random_state" in params and (not only_if_unset or params["random_state"] is None): + estimator.set_params(random_state=random_state) + return estimator + + +def check_metrics(metrics=None): + """Check that evaluation metrics are valid scikit-learn scorer names.""" + metrics = ut.check_list_like(name="metrics", val=metrics, accept_str=True, accept_none=False, min_len=1) + wrong = [m for m in metrics if m not in ut.LIST_METRICS_PRED] + if len(wrong) != 0: + raise ValueError(f"'metrics' ({wrong}) should each be one of: {ut.LIST_METRICS_PRED}") + return metrics + + +def check_n_cv(n_cv=None, labels=None): + """Check that n_cv is a valid integer not exceeding the smallest class count.""" + ut.check_number_range(name="n_cv", val=n_cv, min_val=2, just_int=True) + _, counts = np.unique(labels, return_counts=True) + min_class_count = int(min(counts)) + if n_cv > min_class_count: + raise ValueError(f"'n_cv' ({n_cv}) should not be greater than the smallest class count ({min_class_count}).") + + +def check_is_fitted(list_models=None): + """Check that AAPred.fit has been called.""" + if list_models is None: + raise ValueError("'AAPred' is not fitted; call 'AAPred.fit' first.") + + +def check_binary_labels(labels=None): + """Check that labels define exactly two classes (AAPred is a binary predictor).""" + classes = list(np.unique(labels)) + if len(classes) != 2: + raise ValueError(f"'labels' should define exactly two classes for 'AAPred', got {classes}.") + return classes + + +def check_featurizer(df_feat=None): + """Check that a feature definition is bound so raw sequences can be featurized.""" + if df_feat is None: + raise ValueError("'AAPred' has no bound 'df_feat'; pass 'df_feat=...' to the constructor to " + "enable sequence-level prediction (predict_seq/predict_domain/predict_window).") + + +def featurize_seq(df_feat=None, df_scales=None, df_seq=None, list_parts=None, **parts_kwargs): + """Featurize a ``df_seq`` into the CPP feature matrix ``X`` bound to the model. + + Uses the single-call ``feature_matrix(df_seq=, df_parts_kws=)`` path (which builds + ``df_parts`` internally) instead of the manual get_df_parts + feature_matrix pair. + """ + from aaanalysis.feature_engineering import SequenceFeature + sf = SequenceFeature() + df_parts_kws = dict(parts_kwargs) + if list_parts is not None: + df_parts_kws["list_parts"] = list_parts + X = sf.feature_matrix(features=df_feat, df_seq=df_seq, + df_parts_kws=df_parts_kws or None, df_scales=df_scales) + return np.asarray(X) + + +# II Main Functions +class AAPred(Wrapper): + """ + AAPred: evaluate and deploy sequence-based prediction models (Wrapper) [Breimann25]_. + + A thin, opinionated wrapper that closes the gap left by feature engineering: given a + feature matrix ``X`` and ``labels``, it **evaluates** one or more scikit-learn model + classes across metrics by cross-validation and an optional held-out set (:meth:`eval`), + and **deploys** them by fitting on all data and exposing prediction scores + (:meth:`fit` / :meth:`predict_proba` / :meth:`predict`). + + Unlike :class:`CPPGrid`, which optimizes the *feature space* and scores configurations + by feature separation, ``AAPred`` takes a *fixed* feature set and trains models that are + kept for deployment. It intentionally does **not** perform hyperparameter optimization — + pass configured estimators and it evaluates and deploys them. + + .. versionadded:: 1.1.0 + + Notes + ----- + * All fitted-state attributes carry a trailing underscore and are set by :meth:`fit`. + + See Also + -------- + * :class:`TreeModel` for tree-ensemble Monte-Carlo feature importance and selection. + * :class:`AAPredPlot` for visualizing evaluation and prediction results. + * :class:`CPPGrid` for optimizing the CPP feature space (upstream of this class). + """ + + def __init__(self, + models: Optional[Union[str, "BaseEstimator", List]] = None, + list_model_classes: Optional[List[Type[Union[ClassifierMixin, BaseEstimator]]]] = None, + list_model_kwargs: Optional[List[Dict]] = None, + list_metrics: Optional[List[str]] = None, + df_feat: Optional[pd.DataFrame] = None, + df_scales: Optional[pd.DataFrame] = None, + verbose: bool = True, + random_state: Optional[int] = None, + ): + """ + Parameters + ---------- + models : str, estimator, or list, optional + The models to evaluate and deploy, given as registry name strings (e.g. + ``"svm"``, ``"rf"``; see ``aaanalysis.utils.LIST_PRED_MODELS``) and/or configured + scikit-learn estimator instances, in any mix. This is the recommended way to + select models; it is mutually exclusive with ``list_model_classes``. Each model + must implement ``predict_proba``. + list_model_classes : list of Type[ClassifierMixin or BaseEstimator], default=[RandomForestClassifier] + Model classes to evaluate and deploy (legacy alternative to ``models``). Each + must implement ``predict_proba``. + list_model_kwargs : list of dict, optional + Keyword arguments for each model in ``list_model_classes`` (same length). + list_metrics : list of str, default=["accuracy", "balanced_accuracy", "f1", "roc_auc"] + Default performance metrics used by :meth:`eval` when ``metrics`` is not given. + Each should be one of ``accuracy``, ``balanced_accuracy``, ``precision``, + ``recall``, ``f1``, ``roc_auc``. + df_feat : pd.DataFrame, shape (n_features, n_feature_info), optional + CPP feature DataFrame (with a ``feature`` column) bound to the model. When given, the + feature matrix ``X`` is computed internally from a ``df_seq`` by the sequence-level + prediction methods (:meth:`predict_seq`, :meth:`predict_domain`, :meth:`predict_window`). + df_scales : pd.DataFrame, shape (n_letters, n_scales), optional + Amino acid scales used for internal featurization. Defaults to the bundled AAontology + scales when ``None``. + verbose : bool, default=True + If ``True``, verbose outputs are enabled. + random_state : int, optional + The seed used by the random number generator. If a positive integer, results of + stochastic processes are consistent, enabling reproducibility. If ``None``, + stochastic processes will be truly random. + + Examples + -------- + .. include:: examples/aapred.rst + """ + # Global parameters + verbose = ut.check_verbose(verbose) + random_state = ut.check_random_state(random_state=random_state) + # Resolve models into configured estimator INSTANCES (``_list_estimators``), which + # fit/eval clone before use. `models` (registry name strings and/or configured + # sklearn estimator instances/classes) is the primary API; storing the instance and + # cloning it (rather than round-tripping through get_params + type) is what makes + # meta-ensembles (voting/stacking) and **kwargs estimators (xgboost) work and keeps + # a passed instance's own configuration + random_state intact. + if models is not None: + if list_model_classes is not None or list_model_kwargs is not None: + raise ValueError("Pass either 'models' or 'list_model_classes'/'list_model_kwargs', not both.") + if not isinstance(models, list): + models = [models] + if len(models) == 0: + raise ValueError("'models' must contain at least one model name or estimator.") + list_estimators = [] + for m in models: + if isinstance(m, str): + est = ut.get_cv_model_(name=m, random_state=random_state) + elif isinstance(m, type): + est = _set_random_state_if_supported(m(), random_state, only_if_unset=False) + else: # a configured estimator instance: clone + seed only if the user left it unset + est = _set_random_state_if_supported(clone(m), random_state, only_if_unset=True) + ut.check_mode_class(model_class=type(est)) # ensure predict_proba + list_estimators.append(est) + list_model_classes = [type(e) for e in list_estimators] + _list_model_kwargs = [{} for _ in list_estimators] # introspection placeholder + else: + # Legacy path: class + kwargs, validated, then instantiated into estimators. + if list_model_classes is None: + list_model_classes = [RandomForestClassifier] + elif not isinstance(list_model_classes, list): + list_model_classes = [list_model_classes] + list_model_classes = ut.check_list_like(name="list_model_classes", val=list_model_classes, + accept_none=False, min_len=1) + list_model_kwargs = ut.check_list_like(name="list_model_kwargs", val=list_model_kwargs, accept_none=True) + if list_model_kwargs is None: + list_model_kwargs = [{} for _ in list_model_classes] + ut.check_match_list_model_classes_kwargs(list_model_classes=list_model_classes, + list_model_kwargs=list_model_kwargs) + _list_model_kwargs = [] + for model_class, model_kwargs in zip(list_model_classes, list_model_kwargs): + ut.check_mode_class(model_class=model_class) + model_kwargs = ut.check_model_kwargs(model_class=model_class, model_kwargs=model_kwargs, + method_to_check="predict_proba", random_state=random_state) + _list_model_kwargs.append(model_kwargs) + list_estimators = [cls(**kw) for cls, kw in zip(list_model_classes, _list_model_kwargs)] + # Metric parameters + if list_metrics is None: + list_metrics = ["accuracy", "balanced_accuracy", "f1", "roc_auc"] + list_metrics = check_metrics(metrics=list_metrics) + # Featurizer parameters + if df_feat is not None: + df_feat = ut.check_df_feat(df_feat=df_feat) + # Internal attributes + self._verbose = verbose + self._random_state = random_state + self._list_model_classes = list_model_classes + self._list_model_kwargs = _list_model_kwargs + self._list_estimators = list_estimators + self._list_metrics = list_metrics + self._df_feat = df_feat + self._df_scales = df_scales + # Output attributes (set during fitting) + self.list_models_: Optional[List[Union[ClassifierMixin, BaseEstimator]]] = None + self.label_pos_: Optional[int] = None + self.label_neg_: Optional[int] = None + + def fit(self, + X: ut.ArrayLike2D, + labels: ut.ArrayLike1D, + label_pos: int = 1, + optimize_hyperparams: bool = False, + param_grids: Optional[Union[Dict, List[Dict]]] = None, + n_cv: int = 5, + ) -> "AAPred": + """ + Fit every model on the full dataset for deployment. + + Each model class from the constructor is instantiated and fit on all of ``X`` / ``labels``; + the fitted estimators are kept in ``list_models_`` and reused by :meth:`predict_proba` / + :meth:`predict`. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + Feature matrix. Rows typically correspond to samples and columns to features. + labels : array-like, shape (n_samples,) + Class labels for samples in ``X`` (typically ``1`` for the positive class and + ``0`` for the negative class). + label_pos : int, default=1 + Label of the positive class whose probability :meth:`predict_proba` returns. + optimize_hyperparams : bool, default=False + If ``True``, each model is tuned by ``GridSearchCV`` (``n_cv`` folds) over its + ``param_grids`` entry, or a built-in default grid when none is given; the best + estimator is kept. If ``False``, models are fit with their given parameters. + param_grids : dict or list of dict, optional + Hyperparameter grid(s) for the optimization. A single dict is applied to every + model; a list must have one grid per model. Used only when + ``optimize_hyperparams=True``. + n_cv : int, default=5 + Number of stratified cross-validation folds used by the hyperparameter search. + + Returns + ------- + AAPred + The fitted ``AAPred`` instance (``self``). + + Examples + -------- + .. include:: examples/aapred_fit.rst + """ + # Check input + X = ut.check_X(X=X) + labels = ut.check_labels(labels=labels) + ut.check_match_X_labels(X=X, labels=labels) + classes = check_binary_labels(labels=labels) + ut.check_number_val(name="label_pos", val=label_pos, just_int=True) + if label_pos not in classes: + raise ValueError(f"'label_pos' ({label_pos}) should be one of the labels: {classes}") + ut.check_bool(name="optimize_hyperparams", val=optimize_hyperparams) + # A single dict applies to every model; a list must match the number of models. + if isinstance(param_grids, dict): + list_param_grids = [param_grids for _ in self._list_model_classes] + elif isinstance(param_grids, list): + if len(param_grids) != len(self._list_model_classes): + raise ValueError(f"'param_grids' list length ({len(param_grids)}) should match " + f"the number of models ({len(self._list_model_classes)}).") + list_param_grids = param_grids + else: + list_param_grids = None + if optimize_hyperparams: + check_n_cv(n_cv=n_cv, labels=labels) + # Fit models (optionally tuning hyperparameters via GridSearchCV) + self.list_models_ = fit_models(X=X, labels=labels, + list_estimators=self._list_estimators, + list_param_grids=list_param_grids, + optimize_hyperparams=optimize_hyperparams, + n_cv=n_cv, random_state=self._random_state) + self.label_pos_ = label_pos + self.label_neg_ = int([c for c in classes if c != label_pos][0]) + return self + + def eval(self, + X: ut.ArrayLike2D, + labels: ut.ArrayLike1D, + X_holdout: Optional[ut.ArrayLike2D] = None, + labels_holdout: Optional[ut.ArrayLike1D] = None, + metrics: Optional[List[str]] = None, + n_cv: int = 5, + ) -> pd.DataFrame: + """ + Evaluate every model across metrics by cross-validation and an optional held-out set. + + Two evaluation principles are reported: ``cv`` (stratified k-fold cross-validation on + ``X``) and, when a held-out set is provided, ``holdout`` (models fit on ``X`` and scored + on ``X_holdout``). The result is a long-format table with one row per + (model, metric, principle). + + .. versionadded:: 1.1.0 + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + Feature matrix used for cross-validation (and, for the holdout principle, training). + labels : array-like, shape (n_samples,) + Class labels for samples in ``X``. + X_holdout : array-like, shape (n_holdout, n_features), optional + Held-out feature matrix. If given, the ``holdout`` principle is added. + labels_holdout : array-like, shape (n_holdout,), optional + Class labels for ``X_holdout``. Required if ``X_holdout`` is given. + metrics : list of str, optional + Performance metrics to compute. Defaults to ``list_metrics`` from the constructor. + n_cv : int, default=5 + Number of stratified cross-validation folds (must not exceed the smallest class count). + + Returns + ------- + df_eval : pd.DataFrame, shape (n_rows, 5) + Long-format evaluation table with columns ``model``, ``metric``, ``principle``, + ``score``, and ``score_std`` (``score_std`` is ``NaN`` for the ``holdout`` principle). + + Examples + -------- + .. include:: examples/aapred_eval.rst + """ + # Check input + X = ut.check_X(X=X) + labels = ut.check_labels(labels=labels) + ut.check_match_X_labels(X=X, labels=labels) + check_binary_labels(labels=labels) + metrics = check_metrics(metrics=metrics) if metrics is not None else self._list_metrics + check_n_cv(n_cv=n_cv, labels=labels) + list_models = None + if X_holdout is not None: + X_holdout = ut.check_X(X=X_holdout, min_n_samples=1) + labels_holdout = ut.check_labels(labels=labels_holdout) + ut.check_match_X_labels(X=X_holdout, labels=labels_holdout) + if X_holdout.shape[1] != X.shape[1]: + raise ValueError(f"'X_holdout' n_features ({X_holdout.shape[1]}) should match " + f"'X' n_features ({X.shape[1]}).") + list_models = fit_models(X=X, labels=labels, list_estimators=self._list_estimators) + elif labels_holdout is not None: + raise ValueError("'labels_holdout' was given without 'X_holdout'.") + # Evaluate + df_eval = eval_models(X=X, labels=labels, + list_estimators=self._list_estimators, + list_models=list_models, metrics=metrics, n_cv=n_cv, + random_state=self._random_state, + X_holdout=X_holdout, labels_holdout=labels_holdout) + return df_eval + + def predict_proba(self, + X: ut.ArrayLike2D, + ) -> Tuple[np.ndarray, np.ndarray]: + """ + Obtain positive-class prediction scores for samples in ``X``. + + Scores are averaged across all fitted models from ``list_models_``. + + .. note:: + :meth:`AAPred.fit` must be called before using this method. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + Feature matrix. Rows typically correspond to samples and columns to features. + + Returns + ------- + pred : array-like, shape (n_samples,) + Average positive-class prediction score for each sample. + pred_std : array-like, shape (n_samples,) + Standard deviation of the score across models (``0`` for a single model). + + Examples + -------- + .. include:: examples/aapred_predict_proba.rst + """ + # Check input + check_is_fitted(list_models=self.list_models_) + X = ut.check_X(X=X, min_n_samples=1) + # Predict + pred, pred_std = predict_proba_models(X=X, list_models=self.list_models_, label_pos=self.label_pos_) + return pred, pred_std + + def predict(self, + X: ut.ArrayLike2D, + threshold: Union[int, float] = 0.5, + ) -> np.ndarray: + """ + Predict positive-class membership by thresholding the prediction score. + + The averaged positive-class score from :meth:`predict_proba` is compared against + ``threshold``: samples at or above it are labeled ``label_pos`` (from :meth:`fit`), the rest ``0``. + + .. note:: + :meth:`AAPred.fit` must be called before using this method. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + Feature matrix. Rows typically correspond to samples and columns to features. + threshold : int or float, default=0.5 + Score at or above which a sample is predicted positive (``label_pos`` from :meth:`fit`). + + Returns + ------- + pred_labels : array-like, shape (n_samples,) + Predicted labels (``label_pos`` where the score is ``>= threshold``, else the + negative class label seen during :meth:`fit`). + + Examples + -------- + .. include:: examples/aapred_predict.rst + """ + # Check input + check_is_fitted(list_models=self.list_models_) + X = ut.check_X(X=X, min_n_samples=1) + ut.check_number_range(name="threshold", val=threshold, min_val=0, max_val=1, just_int=False) + # Predict + pred, _ = predict_proba_models(X=X, list_models=self.list_models_, label_pos=self.label_pos_) + pred_labels = np.where(pred >= threshold, self.label_pos_, self.label_neg_) + return pred_labels + + def _predict_X(self, X): + """Averaged positive-class score for a feature matrix (fitted-model ensemble).""" + return predict_proba_models(X=X, list_models=self.list_models_, label_pos=self.label_pos_) + + def predict_seq(self, + df_seq: pd.DataFrame, + list_parts: Optional[List[str]] = None, + ) -> pd.DataFrame: + """ + Predict one score per protein (sequence level) from raw sequences. + + The feature matrix is computed internally from ``df_seq`` using the bound ``df_feat``, so + the caller does not build ``X`` by hand. + + .. note:: + Requires a ``df_feat`` bound at construction and a prior :meth:`fit`. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_seq : pd.DataFrame, shape (n_proteins, n_seq_info) + DataFrame containing an ``entry`` column with unique protein identifiers and the + boundary information consumed by :meth:`SequenceFeature.get_df_parts` (e.g. + ``tmd_start`` / ``tmd_stop``). + list_parts : list of str, optional + Sequence parts to build for featurization. Defaults to the standard CPP parts. + + Returns + ------- + df_pred : pd.DataFrame, shape (n_proteins, 3) + One row per protein with columns ``entry``, ``score`` (positive-class score), and + ``score_std`` (std across models). + + Examples + -------- + .. include:: examples/aapred_predict_seq.rst + """ + # Check input + check_is_fitted(list_models=self.list_models_) + check_featurizer(df_feat=self._df_feat) + ut.check_df_seq(df_seq=df_seq) + # Featurize + predict + X = featurize_seq(df_feat=self._df_feat, df_scales=self._df_scales, df_seq=df_seq, list_parts=list_parts) + pred, pred_std = self._predict_X(X) + df_pred = pd.DataFrame({ut.COL_ENTRY: df_seq[ut.COL_ENTRY].to_numpy(), + ut.COL_SCORE: pred, ut.COL_SCORE_STD: pred_std}) + return df_pred + + def predict_domain(self, + df_seq: pd.DataFrame, + window: int = 3, + list_parts: Optional[List[str]] = None, + ) -> pd.DataFrame: + """ + Predict a domain score with a boundary-sensitivity scan. + + The domain boundaries (``tmd_start`` / ``tmd_stop`` in ``df_seq``, user-adjustable) are + shifted by every offset in ``[-window, +window]``; each shifted definition is featurized + and scored, so the caller sees how the score depends on the exact boundary and which + definition scores highest. + + .. note:: + Requires a ``df_feat`` bound at construction and a prior :meth:`fit`. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_seq : pd.DataFrame, shape (n_proteins, n_seq_info) + DataFrame containing an ``entry`` column with unique protein identifiers, plus + ``sequence``, ``tmd_start`` and ``tmd_stop`` columns. + window : int, default=3 + Maximum absolute boundary shift (in residues) scanned on each side. + list_parts : list of str, optional + Sequence parts to build for featurization. Defaults to the standard CPP parts. + + Returns + ------- + df_domain : pd.DataFrame, shape (n_rows, 4) + Long-format table with columns ``entry``, ``offset`` (boundary shift), ``score``, and + ``is_best`` (``True`` for the highest-scoring offset per protein). Offsets whose shifted + boundary falls outside the sequence are omitted. + + Examples + -------- + .. include:: examples/aapred_predict_domain.rst + """ + # Check input + check_is_fitted(list_models=self.list_models_) + check_featurizer(df_feat=self._df_feat) + ut.check_df_seq(df_seq=df_seq) + missing = [c for c in [ut.COL_SEQ, ut.COL_TMD_START, ut.COL_TMD_STOP] if c not in df_seq.columns] + if missing: + raise ValueError(f"'df_seq' should contain columns {missing} for 'predict_domain'.") + ut.check_number_range(name="window", val=window, min_val=0, just_int=True) + # Position-based frame (drop precomputed part columns so shifted boundaries recompute) + cols = [ut.COL_ENTRY, ut.COL_SEQ, ut.COL_TMD_START, ut.COL_TMD_STOP] + base = df_seq[cols].copy().reset_index(drop=True) + seq_len = base[ut.COL_SEQ].str.len().to_numpy() + rows = [] + for offset in range(-window, window + 1): + d = base.copy() + d[ut.COL_TMD_START] = d[ut.COL_TMD_START] + offset + d[ut.COL_TMD_STOP] = d[ut.COL_TMD_STOP] + offset + valid = (d[ut.COL_TMD_START] >= 1) & (d[ut.COL_TMD_STOP] <= seq_len) + d = d[valid] + if len(d) == 0: + continue + X = featurize_seq(df_feat=self._df_feat, df_scales=self._df_scales, df_seq=d, list_parts=list_parts) + pred, _ = self._predict_X(X) + for entry, score in zip(d[ut.COL_ENTRY].to_numpy(), pred): + rows.append([entry, offset, float(score)]) + df_domain = pd.DataFrame(rows, columns=[ut.COL_ENTRY, ut.COL_OFFSET, ut.COL_SCORE]) + idx_best = df_domain.groupby(ut.COL_ENTRY)[ut.COL_SCORE].idxmax() + df_domain["is_best"] = df_domain.index.isin(idx_best) + return df_domain + + def predict_window(self, + df_seq: pd.DataFrame, + tmd_len: int, + step: int = 1, + jmd_n_len: int = 10, + jmd_c_len: int = 10, + list_parts: Optional[List[str]] = None, + ) -> pd.DataFrame: + """ + Predict a per-residue score profile by sliding a fixed-length window along each sequence. + + A length-``tmd_len`` domain is anchored at every valid position (spaced by ``step``) and + scored, yielding one score per position — the input to a per-residue prediction profile. + + .. note:: + Requires a ``df_feat`` bound at construction and a prior :meth:`fit`. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_seq : pd.DataFrame, shape (n_proteins, n_seq_info) + DataFrame containing an ``entry`` column with unique protein identifiers and a + ``sequence`` column. + tmd_len : int + Length (in residues) of the domain window anchored at each position. + step : int, default=1 + Spacing between consecutive anchor positions. + jmd_n_len : int, default=10 + Length of the N-terminal flanking region required around each window. + jmd_c_len : int, default=10 + Length of the C-terminal flanking region required around each window. + list_parts : list of str, optional + Sequence parts to build for featurization. Defaults to the standard CPP parts. + + Returns + ------- + df_window : pd.DataFrame, shape (n_rows, 4) + Long-format table with columns ``entry``, ``position`` (1-based anchor), ``score``, and + ``score_std``. Positions without enough flanking residues for the full window are omitted. + + Examples + -------- + .. include:: examples/aapred_predict_window.rst + """ + # Check input + check_is_fitted(list_models=self.list_models_) + check_featurizer(df_feat=self._df_feat) + ut.check_df_seq(df_seq=df_seq) + if ut.COL_SEQ not in df_seq.columns: + raise ValueError(f"'df_seq' should contain a '{ut.COL_SEQ}' column for 'predict_window'.") + ut.check_number_range(name="tmd_len", val=tmd_len, min_val=1, just_int=True) + ut.check_number_range(name="step", val=step, min_val=1, just_int=True) + ut.check_number_range(name="jmd_n_len", val=jmd_n_len, min_val=0, just_int=True) + ut.check_number_range(name="jmd_c_len", val=jmd_c_len, min_val=0, just_int=True) + half_left, half_right = ut.get_window_offsets(tmd_len) + rows = [] + for entry, seq in zip(df_seq[ut.COL_ENTRY].to_numpy(), df_seq[ut.COL_SEQ].to_numpy()): + lo = half_left + jmd_n_len + 1 + hi = len(seq) - half_right + 1 - jmd_c_len + positions = list(range(lo, hi + 1, step)) + if len(positions) == 0: + continue + d = pd.DataFrame({ut.COL_ENTRY: [f"{entry}__{p}" for p in positions], + ut.COL_SEQ: [seq] * len(positions), ut.COL_POS: positions}) + X = featurize_seq(df_feat=self._df_feat, df_scales=self._df_scales, df_seq=d, + list_parts=list_parts, tmd_len=tmd_len, + jmd_n_len=jmd_n_len, jmd_c_len=jmd_c_len) + pred, pred_std = self._predict_X(X) + for p, score, score_std in zip(positions, pred, pred_std): + rows.append([entry, p, float(score), float(score_std)]) + df_window = pd.DataFrame(rows, columns=[ut.COL_ENTRY, ut.COL_RESIDUE_POS, ut.COL_SCORE, ut.COL_SCORE_STD]) + return df_window diff --git a/aaanalysis/prediction/_aa_pred_plot.py b/aaanalysis/prediction/_aa_pred_plot.py new file mode 100644 index 00000000..2c4f59ec --- /dev/null +++ b/aaanalysis/prediction/_aa_pred_plot.py @@ -0,0 +1,884 @@ +""" +This is a script for the frontend of the AAPredPlot class for visualizing AAPred results. +""" +from typing import Optional, List, Dict, Union, Tuple +import numpy as np +import pandas as pd +import seaborn as sns +from matplotlib import pyplot as plt +from matplotlib.axes import Axes +from matplotlib.figure import Figure + +import aaanalysis.utils as ut +from ._backend.aa_pred.aa_pred_plot_comparison import plot_comparison_ +from ._backend.aa_pred.aa_pred_plot_ranking import plot_ranking_ +from ._backend.aa_pred.aa_pred_plot_clustermap import plot_clustermap_ + + +# I Helper Functions +def _new_ax(ax=None, figsize=(6, 5)): + """Return (fig, ax), creating a new figure if ax is None.""" + if ax is None: + fig, ax = plt.subplots(figsize=figsize) + else: + fig = ax.figure + return fig, ax + + +def check_match_scores_labels(scores=None, labels=None): + """Check that per-sample labels match the length of the scores array.""" + if labels is not None and len(labels) != len(scores): + raise ValueError(f"'labels' (n={len(labels)}) should match length of 'scores' (n={len(scores)}).") + + +# II Main Functions +class AAPredPlot: + """ + Plotting class for :class:`AAPred` evaluation and prediction results [Breimann25]_. + + The single home for prediction figures, grouped into three types: + + - **Positional** (one protein along its sequence): :meth:`window`, :meth:`domain`. + - **Cohort** (many proteins / sequence-level scores): :meth:`hist`, :meth:`ranking`, + :meth:`scatter`, :meth:`cutoff`, :meth:`clustermap`. + - **Evaluation** (models / feature sets): :meth:`eval`, :meth:`comparison`. + + .. versionadded:: 1.1.0 + + See Also + -------- + * :class:`AAPred`: the logic class whose results this visualizes. + * :func:`aaanalysis.plot_rank` for the per-protein rank scatter companion. + """ + + def __init__(self): + """ + See Also + -------- + * :class:`AAPred`: the logic class whose results this visualizes. + + Examples + -------- + .. include:: examples/aapred_plot.rst + """ + + @staticmethod + def eval(df_eval: pd.DataFrame, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (7, 5), + dict_color: Optional[Dict[str, str]] = None, + baseline: Optional[Union[int, float]] = None, + ylabel: str = "Score", + ) -> Tuple[Figure, Axes]: + """ + Grouped bar plot of the model x metric evaluation table. + + Bars are grouped by metric and colored by model; cross-validation bars carry error bars + from ``score_std`` and held-out bars (if present) are drawn hatched. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_eval : pd.DataFrame, shape (n_rows, 5) + Evaluation table from :meth:`AAPred.eval` with columns ``model``, ``metric``, + ``principle``, ``score``, and ``score_std``. + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(7, 5) + Figure size when ``ax`` is ``None``. + dict_color : dict, optional + Mapping ``model -> color``. Defaults to the house categorical palette. + baseline : int or float, optional + If given, a horizontal reference line is drawn at this score (e.g. ``0.5`` for chance). + ylabel : str, default="Score" + Label for the y-axis. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the grouped bar plot. + + Examples + -------- + .. include:: examples/aapred_plot_eval.rst + """ + # Check input + cols = ut.COLS_EVAL_PRED + ut.check_df(name="df_eval", df=df_eval, cols_required=cols) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_dict_color(name="dict_color", val=dict_color, accept_none=True) + if baseline is not None: + ut.check_number_val(name="baseline", val=baseline, just_int=False) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + # Resolve layout + metrics = list(dict.fromkeys(df_eval[ut.COL_METRIC].tolist())) + models = list(dict.fromkeys(df_eval[ut.COL_MODEL].tolist())) + principles = list(dict.fromkeys(df_eval[ut.COL_PRINCIPLE].tolist())) + clist = ut.plot_get_clist_(n_colors=max(len(models), 2)) + dict_color = dict(dict_color) if dict_color is not None else {} + dict_model_color = {m: dict_color.get(m, clist[i % len(clist)]) for i, m in enumerate(models)} + # Draw grouped bars + fig, ax = _new_ax(ax=ax, figsize=figsize) + n_groups = len(models) * len(principles) + width = 0.8 / max(n_groups, 1) + x = np.arange(len(metrics)) + idx = 0 + for model in models: + for principle in principles: + sub = df_eval[(df_eval[ut.COL_MODEL] == model) & (df_eval[ut.COL_PRINCIPLE] == principle)] + heights = [float(sub[sub[ut.COL_METRIC] == m][ut.COL_SCORE].mean()) for m in metrics] + errs = [float(sub[sub[ut.COL_METRIC] == m][ut.COL_SCORE_STD].mean()) for m in metrics] + errs = [0 if np.isnan(e) else e for e in errs] + hatch = "//" if principle == ut.STR_PRINCIPLE_HOLDOUT else None + label = model if principle == principles[0] else f"{model} ({principle})" + if len(principles) > 1: + label = f"{model} ({principle})" + ax.bar(x + (idx - (n_groups - 1) / 2) * width, heights, width=width, + color=dict_model_color[model], edgecolor="black", linewidth=0.6, + hatch=hatch, yerr=errs, capsize=2.5, label=label) + idx += 1 + if baseline is not None: + ax.axhline(baseline, color="grey", linestyle="--", linewidth=1) + ax.set_xticks(x) + ax.set_xticklabels(metrics, rotation=30, ha="right") + ax.set_ylabel(ylabel) + ax.legend(frameon=False, fontsize=8) + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) + + @staticmethod + def comparison(df_eval: pd.DataFrame, + group: str = "group", + condition: str = "condition", + value: str = "value", + baseline: Optional[Union[int, float]] = 50, + baseline_label: Optional[str] = None, + annotate: bool = True, + annotation_fmt: Optional[str] = None, + group_order: Optional[List[str]] = None, + condition_order: Optional[List[str]] = None, + colors: Optional[Union[List[str], Dict[str, str]]] = None, + bar_width: Union[int, float] = 0.8, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (7, 4.2), + xlabel: Optional[str] = None, + ylabel: str = "Score", + title: Optional[str] = None, + ylim: Optional[Tuple[Union[int, float], Union[int, float]]] = None, + fontsize_annotations: Union[int, float] = 10, + xtick_rotation: Union[int, float] = 0, + ) -> Tuple[Figure, Axes]: + """ + Plot a grouped method x condition comparison barplot with value labels and a baseline. + + Draws the recurring "benchmark result" figure from a tidy eval frame in one call: each + ``condition`` is an x-axis cluster, each ``group`` a colored bar within it (auto offsets / + widths for *N* groups), with optional per-bar value labels and an optional dashed chance / + baseline line. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_eval : pd.DataFrame, shape (n_rows, n_cols) + Tidy (long-form) frame with one row per (``group``, ``condition``); must contain the + ``group``, ``condition``, and ``value`` columns. Repeated cells are averaged. + group : str, default="group" + Column whose distinct values become the colored bars within each cluster (the legend). + condition : str, default="condition" + Column whose distinct values become the x-axis clusters. + value : str, default="value" + Column with the numeric bar heights (e.g. balanced accuracy in percent). + baseline : int or float, optional + y-value of a dashed chance / baseline line. If ``None``, no line is drawn. + baseline_label : str, optional + Legend label for the baseline. ``None`` generates ``"chance ()"``; ``""`` draws + the line without a legend entry. + annotate : bool, default=True + If ``True``, write each bar's value above it. + annotation_fmt : str, optional + Format string for the value labels; if ``None``, chosen from the data scale. + group_order : list of str, optional + Order of the groups (bars within a cluster). Defaults to first-appearance order. + condition_order : list of str, optional + Order of the conditions (x-axis clusters). Defaults to first-appearance order. + colors : list of str or dict, optional + Bar colors aligned to ``group_order`` or a ``group -> color`` dict; defaults to the + house categorical palette. + bar_width : int or float, default=0.8 + Total width of each cluster (split across the groups). Must be in (0, 1]. + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(7, 4.2) + Figure size when ``ax`` is ``None``. + xlabel : str, optional + x-axis label. + ylabel : str, default="Score" + y-axis label. + title : str, optional + Axes title. + ylim : tuple, optional + y-axis limits ``(bottom, top)``; if ``None``, the top leaves room for the value labels. + fontsize_annotations : int or float, default=10 + Font size of the per-bar value labels. + xtick_rotation : int or float, default=0 + Rotation (degrees) of the cluster tick labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the grouped comparison barplot. + + Examples + -------- + .. include:: examples/aapred_plot_comparison.rst + """ + # Check input + ut.check_str(name="group", val=group) + ut.check_str(name="condition", val=condition) + ut.check_str(name="value", val=value) + if len({group, condition, value}) < 3: + raise ValueError(f"'group', 'condition', and 'value' should be three distinct columns, " + f"got group={group!r}, condition={condition!r}, value={value!r}.") + ut.check_df(name="df_eval", df=df_eval, cols_required=[group, condition, value]) + if len(df_eval) == 0: + raise ValueError("'df_eval' (0 rows) should contain at least one row.") + if not pd.api.types.is_numeric_dtype(df_eval[value]): + raise ValueError(f"'{value}' column of 'df_eval' should be numeric, " + f"got dtype '{df_eval[value].dtype}'.") + ut.check_number_val(name="baseline", val=baseline, accept_none=True, just_int=False) + ut.check_str(name="baseline_label", val=baseline_label, accept_none=True) + ut.check_bool(name="annotate", val=annotate) + ut.check_str(name="annotation_fmt", val=annotation_fmt, accept_none=True) + ut.check_list_like(name="group_order", val=group_order, accept_none=True) + ut.check_list_like(name="condition_order", val=condition_order, accept_none=True) + ut.check_number_range(name="bar_width", val=bar_width, min_val=0, max_val=1, just_int=False) + if bar_width == 0: + raise ValueError("'bar_width' should be greater than 0.") + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + ut.check_str(name="title", val=title, accept_none=True) + ut.check_number_range(name="fontsize_annotations", val=fontsize_annotations, min_val=0, + just_int=False) + ut.check_number_val(name="xtick_rotation", val=xtick_rotation, just_int=False) + if ylim is not None: + ut.check_lim(name="ylim", val=ylim) + # Plot + fig, ax = plot_comparison_(df_eval=df_eval, group=group, condition=condition, value=value, + baseline=baseline, baseline_label=baseline_label, annotate=annotate, + annotation_fmt=annotation_fmt, group_order=group_order, + condition_order=condition_order, colors=colors, bar_width=bar_width, + ax=ax, figsize=figsize, xlabel=xlabel, ylabel=ylabel, title=title, + ylim=ylim, fontsize_annotations=fontsize_annotations, + xtick_rotation=xtick_rotation) + return ut.FigAxResult(fig, ax) + + @staticmethod + def ranking(df_pred: pd.DataFrame, + col_name: str = "name", + col_score: str = "score", + col_group: Optional[str] = None, + col_std: Optional[str] = None, + colors: Optional[Dict[str, str]] = None, + cutoffs: Optional[Tuple[Union[int, float], ...]] = (50, 80), + top_n: Optional[int] = None, + ascending: bool = False, + ax: Optional[Axes] = None, + figsize: Optional[Tuple[Union[int, float], Union[int, float]]] = None, + xlabel: str = "Prediction score", + title: Optional[str] = None, + ) -> Tuple[Figure, Axes]: + """ + Plot ranked candidates as horizontal bars colored by class, with cut-off lines. + + Ranks proteins/samples by a prediction ``col_score`` (highest on top) and draws one + horizontal bar each, colored by ``col_group`` (e.g. substrate vs non-substrate), with + optional per-item error bars (``col_std``) and dashed confidence cut-off lines. The + figure height grows with the number of items so each bar keeps a constant height. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_pred : pd.DataFrame, shape (n_samples, n_cols) + Per-sample prediction frame; must contain ``col_name`` and ``col_score`` (and + ``col_group`` / ``col_std`` when given). + col_name : str, default="name" + Column with the per-item labels shown as y-tick labels (e.g. gene names). + col_score : str, default="score" + Column with the numeric prediction score used to rank and size the bars. + col_group : str, optional + Column whose distinct values color the bars (adds a class legend). If ``None``, a + single color is used. + col_std : str, optional + Column with per-item standard deviations, drawn as horizontal error bars. + colors : dict, optional + A ``group -> color`` mapping; defaults to the house categorical palette. + cutoffs : tuple, optional + x-positions of dashed confidence cut-off lines. ``None`` or empty draws none. + top_n : int, optional + If given, keep only the top ``top_n`` ranked items. + ascending : bool, default=False + Sort order of the score; ``False`` ranks the highest score first (on top). + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, optional + Figure size when ``ax`` is ``None``. If ``None``, the height scales with the number + of items (``0.22 * n + 1``) and the width defaults to 5. + xlabel : str, default="Prediction score" + x-axis label. + title : str, optional + Axes title. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the ranked-candidate bars. + + Examples + -------- + .. include:: examples/aapred_plot_ranking.rst + """ + # Check input + ut.check_str(name="col_name", val=col_name) + ut.check_str(name="col_score", val=col_score) + ut.check_str(name="col_group", val=col_group, accept_none=True) + ut.check_str(name="col_std", val=col_std, accept_none=True) + cols_required = [c for c in [col_name, col_score, col_group, col_std] if c is not None] + ut.check_df(name="df_pred", df=df_pred, cols_required=cols_required) + if len(df_pred) == 0: + raise ValueError("'df_pred' (0 rows) should contain at least one row.") + if not pd.api.types.is_numeric_dtype(df_pred[col_score]): + raise ValueError(f"'{col_score}' column of 'df_pred' should be numeric.") + ut.check_bool(name="ascending", val=ascending) + if top_n is not None: + ut.check_number_range(name="top_n", val=top_n, min_val=1, just_int=True) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="title", val=title, accept_none=True) + # Plot + fig, ax = plot_ranking_(df_pred=df_pred, col_name=col_name, col_score=col_score, + col_group=col_group, col_std=col_std, colors=colors, + cutoffs=cutoffs, top_n=top_n, ascending=ascending, ax=ax, + figsize=figsize, xlabel=xlabel, title=title) + return ut.FigAxResult(fig, ax) + + @staticmethod + def clustermap(data: ut.ArrayLike2D, + names: Optional[List[str]] = None, + labels: Optional[ut.ArrayLike1D] = None, + colors: Optional[Dict[str, str]] = None, + cmap: str = "GnBu", + figsize: Tuple[Union[int, float], Union[int, float]] = (9, 9), + cbar_label: str = "Pearson correlation (r)", + title: Optional[str] = None, + ) -> Tuple[Figure, Axes]: + """ + Cluster samples by explanation similarity (correlation of per-sample importance vectors). + + Groups samples by *why* the model scores them: it correlates their per-sample + importance / SHAP vectors (Pearson) and draws a hierarchically-clustered heatmap of the + sample x sample correlation, with optional row/column class-color sidebars. Because it + consumes provided importance vectors, it needs no optional dependency; compute the SHAP + vectors with :class:`ShapModel` (``pro``) and pass them in. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + data : array-like, shape (n_samples, n_features) + Per-sample importance/explanation vectors (e.g. SHAP values), one row per sample. + names : list of str, optional + Per-sample labels shown as tick labels. Defaults to positional indices. + labels : array-like, shape (n_samples,), optional + Per-sample class labels used to color the row/column sidebars (adds a class legend). + colors : dict, optional + A ``label -> color`` mapping for the sidebars; defaults to the house palette. + cmap : str, default="GnBu" + Colormap for the correlation heatmap. + figsize : tuple, default=(9, 9) + Figure size. + cbar_label : str, default="Pearson correlation (r)" + Label of the colorbar. + title : str, optional + Figure title. + + Returns + ------- + fig : matplotlib.figure.Figure + The clustermap figure. + ax : matplotlib.axes.Axes + The heatmap axes of the clustermap. + + Examples + -------- + .. include:: examples/aapred_plot_clustermap.rst + """ + # Check input + data = ut.check_X(X=data, min_n_samples=2, min_n_features=1) + if names is not None: + ut.check_list_like(name="names", val=names) + if len(names) != data.shape[0]: + raise ValueError(f"'names' (n={len(names)}) should match n_samples ({data.shape[0]}).") + if labels is not None: + # Labels here are purely cosmetic (sidebar coloring), so any hashable class + # values are allowed (e.g. "substrate"/"non-substrate"), not only integers. + labels = ut.check_list_like(name="labels", val=labels, accept_none=False) + if len(labels) != data.shape[0]: + raise ValueError(f"'labels' (n={len(labels)}) should match n_samples ({data.shape[0]}).") + ut.check_str(name="cmap", val=cmap) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_str(name="cbar_label", val=cbar_label, accept_none=True) + ut.check_str(name="title", val=title, accept_none=True) + # Plot + fig, ax = plot_clustermap_(data=data, names=names, labels=labels, colors=colors, + cmap=cmap, figsize=figsize, cbar_label=cbar_label, title=title) + return ut.FigAxResult(fig, ax) + + @staticmethod + def hist(scores: ut.ArrayLike1D, + labels: Optional[ut.ArrayLike1D] = None, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (6, 4.5), + bins: int = 20, + thresholds: Optional[Union[int, float, List[Union[int, float]]]] = None, + dict_color: Optional[Dict[Union[int, str], str]] = None, + xlabel: str = "Prediction score", + ylabel: str = "Number of samples", + ) -> Tuple[Figure, Axes]: + """ + Class-separated histogram of per-sample prediction scores. + + Shows how the positive and negative classes are distributed across the score range, with + optional decision thresholds — the standard sanity check for a deployed classifier's margin. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + scores : array-like, shape (n_samples,) + Per-sample prediction scores (e.g. from :meth:`AAPred.predict_proba`). + labels : array-like, shape (n_samples,), optional + Class labels used to color/separate the distribution. If ``None``, one histogram is drawn. + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(6, 4.5) + Figure size when ``ax`` is ``None``. + bins : int, default=20 + Number of histogram bins. + thresholds : int, float, or list, optional + One or more score values drawn as vertical dashed lines. + dict_color : dict, optional + Mapping ``label -> color``. Defaults to the locked positive/negative sample palette. + xlabel, ylabel : str + Axis labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the histogram. + + Examples + -------- + .. include:: examples/aapred_plot_hist.rst + """ + # Check input + scores = ut.check_array_like(name="scores", val=scores, expected_dim=1) + labels = ut.check_labels(labels=labels) if labels is not None else None + check_match_scores_labels(scores=scores, labels=labels) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_number_range(name="bins", val=bins, min_val=1, just_int=True) + ut.check_dict_color(name="dict_color", val=dict_color, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + list_thresholds = [] + if thresholds is not None: + list_thresholds = list(thresholds) if isinstance(thresholds, (list, tuple)) else [thresholds] + for i, t in enumerate(list_thresholds): + ut.check_number_val(name=f"thresholds[{i}]", val=t, just_int=False) + # Resolve colors + dict_color = dict(dict_color) if dict_color is not None else {} + default_cycle = [ut.COLOR_POS, ut.COLOR_NEG, ut.COLOR_REL_NEG, ut.COLOR_UNL] + # Draw + fig, ax = _new_ax(ax=ax, figsize=figsize) + bin_edges = np.linspace(float(np.min(scores)), float(np.max(scores)), bins + 1) + if labels is None: + ax.hist(scores, bins=bin_edges, color=ut.COLOR_POS, edgecolor="black", linewidth=0.6) + else: + for i, lab in enumerate(sorted(set(labels))): + color = dict_color.get(lab, default_cycle[i % len(default_cycle)]) + ax.hist(np.asarray(scores)[np.asarray(labels) == lab], bins=bin_edges, alpha=0.7, + color=color, edgecolor="black", linewidth=0.6, label=str(lab)) + ax.legend(frameon=False) + for t in list_thresholds: + ax.axvline(t, color="0.3", linestyle="--", linewidth=1.3) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) + + @staticmethod + def scatter(scores_x: ut.ArrayLike1D, + scores_y: ut.ArrayLike1D, + labels: Optional[ut.ArrayLike1D] = None, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (5.5, 5.5), + dict_color: Optional[Dict[Union[int, str], str]] = None, + marker_size: Union[int, float] = 30, + diagonal: bool = True, + xlabel: str = "Predictor 1 score", + ylabel: str = "Predictor 2 score", + ) -> Tuple[Figure, Axes]: + """ + 2D scatter comparing per-sample scores of two predictors. + + Each point is one sample placed at ``(scores_x, scores_y)`` and optionally colored by class; + the ``y = x`` line marks agreement, so systematic disagreement between the predictors is visible. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + scores_x : array-like, shape (n_samples,) + Per-sample scores of the first predictor (x-axis). + scores_y : array-like, shape (n_samples,) + Per-sample scores of the second predictor (y-axis). + labels : array-like, shape (n_samples,), optional + Class labels used to color the points. + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(5.5, 5.5) + Figure size when ``ax`` is ``None``. + dict_color : dict, optional + Mapping ``label -> color``. Defaults to the locked positive/negative sample palette. + marker_size : int or float, default=30 + Scatter marker size. + diagonal : bool, default=True + If ``True``, draw the ``y = x`` agreement line. + xlabel, ylabel : str + Axis labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the scatter plot. + + Examples + -------- + .. include:: examples/aapred_plot_scatter.rst + """ + # Check input + scores_x = ut.check_array_like(name="scores_x", val=scores_x, expected_dim=1) + scores_y = ut.check_array_like(name="scores_y", val=scores_y, expected_dim=1) + if len(scores_x) != len(scores_y): + raise ValueError(f"'scores_x' (n={len(scores_x)}) and 'scores_y' (n={len(scores_y)}) should match in length.") + labels = ut.check_labels(labels=labels) if labels is not None else None + check_match_scores_labels(scores=scores_x, labels=labels) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_dict_color(name="dict_color", val=dict_color, accept_none=True) + ut.check_number_range(name="marker_size", val=marker_size, min_val=0, just_int=False) + ut.check_bool(name="diagonal", val=diagonal) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + # Resolve colors + dict_color = dict(dict_color) if dict_color is not None else {} + default_cycle = [ut.COLOR_POS, ut.COLOR_NEG, ut.COLOR_REL_NEG, ut.COLOR_UNL] + # Draw + fig, ax = _new_ax(ax=ax, figsize=figsize) + if diagonal: + lo = float(min(np.min(scores_x), np.min(scores_y))) + hi = float(max(np.max(scores_x), np.max(scores_y))) + ax.plot([lo, hi], [lo, hi], color="0.6", linestyle="--", linewidth=1, zorder=0) + if labels is None: + ax.scatter(scores_x, scores_y, s=marker_size, color=ut.COLOR_POS, + edgecolors="white", linewidths=0.3) + else: + sx, sy, la = np.asarray(scores_x), np.asarray(scores_y), np.asarray(labels) + for i, lab in enumerate(sorted(set(labels))): + color = dict_color.get(lab, default_cycle[i % len(default_cycle)]) + mask = la == lab + ax.scatter(sx[mask], sy[mask], s=marker_size, color=color, + edgecolors="white", linewidths=0.3, label=str(lab)) + ax.legend(frameon=False) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) + + @staticmethod + def cutoff(scores: ut.ArrayLike1D, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (6, 4.5), + n_steps: int = 101, + color: Optional[str] = None, + thresholds: Optional[Union[int, float, List[Union[int, float]]]] = None, + xlabel: str = "Score cutoff", + ylabel: str = "Samples above cutoff [%]", + ) -> Tuple[Figure, Axes]: + """ + Line plot of the percentage of samples scoring at or above each cutoff. + + The (non-increasing) curve is the survival function of the scores — it makes the trade-off + between a strict and a permissive deployment threshold directly readable. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + scores : array-like, shape (n_samples,) + Per-sample prediction scores (e.g. from :meth:`AAPred.predict_proba`). + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(6, 4.5) + Figure size when ``ax`` is ``None``. + n_steps : int, default=101 + Number of evenly spaced cutoffs between the min and max score. + color : str, optional + Line color. Defaults to the house feature-positive color. + thresholds : int, float, or list, optional + One or more cutoffs drawn as vertical dashed lines. + xlabel, ylabel : str + Axis labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the cutoff line. + + Examples + -------- + .. include:: examples/aapred_plot_cutoff.rst + """ + # Check input + scores = ut.check_array_like(name="scores", val=scores, expected_dim=1) + if len(scores) == 0: + raise ValueError("'scores' (0 values) should contain at least one score.") + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_number_range(name="n_steps", val=n_steps, min_val=2, just_int=True) + ut.check_color(name="color", val=color, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + list_thresholds = [] + if thresholds is not None: + list_thresholds = list(thresholds) if isinstance(thresholds, (list, tuple)) else [thresholds] + for i, t in enumerate(list_thresholds): + ut.check_number_val(name=f"thresholds[{i}]", val=t, just_int=False) + # Compute + scores = np.asarray(scores, dtype=float) + cutoffs = np.linspace(float(np.min(scores)), float(np.max(scores)), n_steps) + pct = np.array([100.0 * np.mean(scores >= c) for c in cutoffs]) + # Draw + fig, ax = _new_ax(ax=ax, figsize=figsize) + ax.plot(cutoffs, pct, color=color or ut.COLOR_FEAT_POS, linewidth=2) + for t in list_thresholds: + ax.axvline(t, color="0.3", linestyle="--", linewidth=1.3) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + ax.set_ylim(0, 100) + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) + + @staticmethod + def window(df_window: pd.DataFrame, + entry: Optional[str] = None, + list_annotations: Optional[List[Dict]] = None, + threshold: Optional[Union[int, float]] = None, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (10, 4), + color: Optional[str] = None, + xlabel: str = "Residue position", + ylabel: str = "Prediction score", + ) -> Tuple[Figure, Axes]: + """ + Per-residue prediction profile from :meth:`AAPred.predict_window`. + + Draws the sliding-window score along the sequence, with an optional decision threshold and + optional per-residue annotation tracks (topology, pLDDT, domains, ...) shown as color strips + below the profile. Annotation values are user-provided arrays, so any track can be added. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_window : pd.DataFrame + Output of :meth:`AAPred.predict_window` with columns ``entry``, ``position``, ``score``. + entry : str, optional + Protein to plot. Required only if ``df_window`` contains more than one ``entry``. + list_annotations : list of dict, optional + Per-residue annotation tracks. Each dict has ``values`` (array aligned to the plotted + positions), ``label`` (str), and optional ``cmap`` (default ``viridis``). + threshold : int or float, optional + Score drawn as a horizontal dashed line. + ax : matplotlib.axes.Axes, optional + Axes to draw the profile on. If ``None``, a new figure is created (with extra track + axes below when ``list_annotations`` is given). + figsize : tuple, default=(10, 4) + Figure size when ``ax`` is ``None``. + color : str, optional + Line color. Defaults to the house feature-negative (blue) color. + xlabel, ylabel : str + Axis labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The profile axes (the top axes when annotation tracks are present). + + Examples + -------- + .. include:: examples/aapred_plot_window.rst + """ + # Check input + ut.check_df(name="df_window", df=df_window, + cols_required=[ut.COL_ENTRY, ut.COL_RESIDUE_POS, ut.COL_SCORE]) + ut.check_str(name="entry", val=entry, accept_none=True) + ut.check_list_like(name="list_annotations", val=list_annotations, accept_none=True) + if threshold is not None: + ut.check_number_val(name="threshold", val=threshold, just_int=False) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_color(name="color", val=color, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + # Resolve the entry + entries = list(dict.fromkeys(df_window[ut.COL_ENTRY].tolist())) + if entry is None: + if len(entries) > 1: + raise ValueError(f"'df_window' contains multiple entries {entries}; pass 'entry='.") + entry = entries[0] + elif entry not in entries: + raise ValueError(f"'entry' ({entry}) not in 'df_window' entries {entries}.") + sub = df_window[df_window[ut.COL_ENTRY] == entry].sort_values(ut.COL_RESIDUE_POS) + pos = sub[ut.COL_RESIDUE_POS].to_numpy() + score = sub[ut.COL_SCORE].to_numpy() + # Layout: profile axes (+ track axes when annotations given) + tracks = list(list_annotations) if list_annotations is not None else [] + if ax is None: + if tracks: + fig, axes = plt.subplots(len(tracks) + 1, 1, figsize=figsize, sharex=True, + gridspec_kw={"height_ratios": [6] + [0.6] * len(tracks)}) + ax, track_axes = axes[0], list(axes[1:]) + else: + fig, ax = plt.subplots(figsize=figsize) + track_axes = [] + else: + fig = ax.figure + track_axes = [] + # Draw the profile + ax.plot(pos, score, color=color or ut.COLOR_FEAT_NEG, linewidth=1.2) + if threshold is not None: + ax.axhline(threshold, color="0.4", linestyle="--", linewidth=1.2) + ax.set_ylim(0, 1) + ax.set_ylabel(ylabel) + ax.set_xlim(pos.min(), pos.max()) + # Draw annotation tracks + for tax, track in zip(track_axes, tracks): + values = np.asarray(track["values"], dtype=float).reshape(1, -1) + tax.imshow(values, aspect="auto", cmap=track.get("cmap", "viridis"), + extent=[pos.min(), pos.max(), 0, 1]) + tax.set_yticks([]) + tax.set_ylabel(track.get("label", ""), rotation=0, ha="right", va="center", fontsize=8) + (track_axes[-1] if track_axes else ax).set_xlabel(xlabel) + if not track_axes: + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) + + @staticmethod + def domain(df_domain: pd.DataFrame, + entry: Optional[str] = None, + ax: Optional[Axes] = None, + figsize: Tuple[Union[int, float], Union[int, float]] = (6, 4.5), + color: Optional[str] = None, + xlabel: str = "Boundary offset [residues]", + ylabel: str = "Prediction score", + ) -> Tuple[Figure, Axes]: + """ + Domain boundary-sensitivity plot from :meth:`AAPred.predict_domain`. + + Shows the prediction score as a function of the boundary shift, marking the highest-scoring + definition — so how strongly the score depends on the exact domain boundary is visible. + + .. versionadded:: 1.1.0 + + Parameters + ---------- + df_domain : pd.DataFrame + Output of :meth:`AAPred.predict_domain` with columns ``entry``, ``offset``, ``score``, + ``is_best``. + entry : str, optional + Protein to plot. Required only if ``df_domain`` contains more than one ``entry``. + ax : matplotlib.axes.Axes, optional + Axes to draw on. If ``None``, a new figure and axes are created. + figsize : tuple, default=(6, 4.5) + Figure size when ``ax`` is ``None``. + color : str, optional + Line color. Defaults to the house feature-positive color. + xlabel, ylabel : str + Axis labels. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure. + ax : matplotlib.axes.Axes + The axes with the sensitivity curve. + + Examples + -------- + .. include:: examples/aapred_plot_domain.rst + """ + # Check input + ut.check_df(name="df_domain", df=df_domain, + cols_required=[ut.COL_ENTRY, ut.COL_OFFSET, ut.COL_SCORE]) + ut.check_str(name="entry", val=entry, accept_none=True) + ut.check_ax(ax=ax, accept_none=True) + ut.check_figsize(figsize=figsize, accept_none=True) + ut.check_color(name="color", val=color, accept_none=True) + ut.check_str(name="xlabel", val=xlabel, accept_none=True) + ut.check_str(name="ylabel", val=ylabel, accept_none=True) + # Resolve the entry + entries = list(dict.fromkeys(df_domain[ut.COL_ENTRY].tolist())) + if entry is None: + if len(entries) > 1: + raise ValueError(f"'df_domain' contains multiple entries {entries}; pass 'entry='.") + entry = entries[0] + elif entry not in entries: + raise ValueError(f"'entry' ({entry}) not in 'df_domain' entries {entries}.") + sub = df_domain[df_domain[ut.COL_ENTRY] == entry].sort_values(ut.COL_OFFSET) + offsets = sub[ut.COL_OFFSET].to_numpy() + score = sub[ut.COL_SCORE].to_numpy() + # Draw + fig, ax = _new_ax(ax=ax, figsize=figsize) + ax.plot(offsets, score, color=color or ut.COLOR_FEAT_POS, marker="o", linewidth=1.6) + i_best = int(np.argmax(score)) + ax.scatter([offsets[i_best]], [score[i_best]], s=110, marker="*", + color=ut.COLOR_FEAT_POS, edgecolors="black", zorder=5, label="best") + ax.axvline(0, color="0.6", linestyle="--", linewidth=1) # annotated boundary + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + ax.set_ylim(0, 1) + ax.legend(frameon=False) + sns.despine(ax=ax) + return ut.FigAxResult(fig, ax) diff --git a/aaanalysis/prediction/_backend/__init__.py b/aaanalysis/prediction/_backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aaanalysis/prediction/_backend/aa_pred/__init__.py b/aaanalysis/prediction/_backend/aa_pred/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aaanalysis/prediction/_backend/aa_pred/aa_pred_eval.py b/aaanalysis/prediction/_backend/aa_pred/aa_pred_eval.py new file mode 100644 index 00000000..9647ab73 --- /dev/null +++ b/aaanalysis/prediction/_backend/aa_pred/aa_pred_eval.py @@ -0,0 +1,46 @@ +""" +This is a script for the backend of the AAPred.eval method: model x metric x principle scoring. +""" +import numpy as np +import pandas as pd +from sklearn.model_selection import StratifiedKFold, cross_val_score +from sklearn.metrics import get_scorer + +import aaanalysis.utils as ut + + +# I Helper Functions +def _score_cv(estimator, X, labels, metric, n_cv, random_state): + """Cross-validated score (mean, std) for one estimator x metric.""" + from sklearn.base import clone + cv = StratifiedKFold(n_splits=n_cv, shuffle=True, random_state=random_state) + scores = cross_val_score(clone(estimator), X, labels, cv=cv, scoring=metric) + return float(np.mean(scores)), float(np.std(scores)) + + +def _score_holdout(fitted_model, X_holdout, labels_holdout, metric): + """Held-out score for one already-fitted model x metric (std is NaN: single estimate).""" + scorer = get_scorer(metric) + return float(scorer(fitted_model, X_holdout, labels_holdout)), float("nan") + + +# II Main Functions +def eval_models(X, labels, list_estimators=None, list_models=None, metrics=None, n_cv=5, + random_state=None, X_holdout=None, labels_holdout=None): + """Score every model x metric by cross-validation and (optionally) on a held-out set. + + Returns a long-format ``df_eval`` with one row per (model, metric, principle). + """ + rows = [] + for i, estimator in enumerate(list_estimators): + model_name = type(estimator).__name__ + for metric in metrics: + score, score_std = _score_cv(estimator=estimator, X=X, labels=labels, metric=metric, + n_cv=n_cv, random_state=random_state) + rows.append([model_name, metric, ut.STR_PRINCIPLE_CV, score, score_std]) + if X_holdout is not None: + score, score_std = _score_holdout(fitted_model=list_models[i], X_holdout=X_holdout, + labels_holdout=labels_holdout, metric=metric) + rows.append([model_name, metric, ut.STR_PRINCIPLE_HOLDOUT, score, score_std]) + df_eval = pd.DataFrame(rows, columns=ut.COLS_EVAL_PRED) + return df_eval diff --git a/aaanalysis/prediction/_backend/aa_pred/aa_pred_fit.py b/aaanalysis/prediction/_backend/aa_pred/aa_pred_fit.py new file mode 100644 index 00000000..eac404ce --- /dev/null +++ b/aaanalysis/prediction/_backend/aa_pred/aa_pred_fit.py @@ -0,0 +1,73 @@ +""" +This is a script for the backend of the AAPred class: fitting models and obtaining prediction scores. +""" +import numpy as np + +import aaanalysis.utils as ut + + +# I Helper Functions +def _positive_proba(model, X, label_pos): + """Return the positive-class probability column for a fitted model.""" + proba = np.asarray(model.predict_proba(X)) + classes = list(model.classes_) + idx_pos = classes.index(label_pos) + return proba[:, idx_pos] + + +# Minimal built-in hyperparameter grids, keyed by estimator class name. Used when +# hyperparameter optimization is requested without an explicit grid. Models absent +# here are fit directly (no search). +_DEFAULT_PARAM_GRIDS = { + "SVC": {"C": [0.1, 1.0, 10.0]}, + "RandomForestClassifier": {"n_estimators": [100, 300], "max_depth": [None, 10]}, + "ExtraTreesClassifier": {"n_estimators": [100, 300], "max_depth": [None, 10]}, + "LogisticRegression": {"C": [0.1, 1.0, 10.0]}, + "KNeighborsClassifier": {"n_neighbors": [3, 5, 7]}, + "GradientBoostingClassifier": {"learning_rate": [0.05, 0.1], "n_estimators": [100, 300]}, + "MLPClassifier": {"alpha": [1e-4, 1e-3]}, +} + + +# II Main Functions +def fit_models(X, labels, list_estimators=None, list_param_grids=None, + optimize_hyperparams=False, n_cv=5, random_state=None): + """Fit every configured estimator on the full data and return the fitted estimators. + + Each estimator in ``list_estimators`` is cloned before fitting (so the stored + configuration is never mutated). When ``optimize_hyperparams`` is set, each model is + tuned by ``GridSearchCV`` over its entry in ``list_param_grids`` (or a built-in default + grid), and the best estimator is kept. Models with no available grid are fit directly. + """ + from sklearn.base import clone + from sklearn.model_selection import GridSearchCV, StratifiedKFold + list_models = [] + for i, estimator in enumerate(list_estimators): + model = clone(estimator) + grid = None + if optimize_hyperparams: + if list_param_grids is not None and list_param_grids[i]: + grid = list_param_grids[i] + else: + grid = _DEFAULT_PARAM_GRIDS.get(type(estimator).__name__) + if grid: + cv = StratifiedKFold(n_splits=n_cv, shuffle=True, random_state=random_state) + search = GridSearchCV(model, grid, cv=cv) + search.fit(X, labels) + model = search.best_estimator_ + else: + model.fit(X, labels) + list_models.append(model) + return list_models + + +def predict_proba_models(X, list_models=None, label_pos=1): + """Average positive-class probability across fitted models. + + Returns the mean positive-class score per sample and its std across models + (0 for a single model). + """ + probas = np.vstack([_positive_proba(model, X, label_pos=label_pos) for model in list_models]) + pred = probas.mean(axis=0) + pred_std = probas.std(axis=0) if len(list_models) > 1 else np.zeros(len(pred)) + return pred, pred_std diff --git a/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_clustermap.py b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_clustermap.py new file mode 100644 index 00000000..cc9ed4a0 --- /dev/null +++ b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_clustermap.py @@ -0,0 +1,57 @@ +""" +Backend for AAPredPlot.clustermap: cluster samples by explanation similarity +(Pearson correlation of their per-sample importance/SHAP vectors). +""" +import numpy as np +import pandas as pd +import seaborn as sns +from matplotlib import pyplot as plt + +import aaanalysis.utils as ut + + +# I Helper Functions +def _resolve_label_colors(labels, colors): + """Map each distinct label to a color (dict wins; else the house palette).""" + order = list(dict.fromkeys(labels)) + if isinstance(colors, dict): + missing = [g for g in order if g not in colors] + if missing: + raise ValueError(f"'colors' dict is missing colors for labels: {missing}") + return colors + palette = ut.plot_get_clist_(n_colors=max(len(order), 2)) + return {g: palette[i] for i, g in enumerate(order)} + + +# II Main Functions +def plot_clustermap_(data=None, names=None, labels=None, colors=None, cmap="GnBu", + figsize=(9, 9), cbar_label="Pearson correlation (r)", title=None): + """Correlation clustermap of per-sample importance vectors. Returns (fig, ax_heatmap).""" + values = np.asarray(data, dtype=float) + n = values.shape[0] + if names is None: + names = [str(i) for i in range(n)] + # Sample x sample Pearson correlation of the explanation vectors. A sample whose + # vector has zero variance (e.g. an all-zero SHAP row) yields NaN correlations, which + # break the hierarchical linkage; treat those as uncorrelated (0) with self-corr 1. + corr = np.corrcoef(values) + corr = np.nan_to_num(corr, nan=0.0) + np.fill_diagonal(corr, 1.0) + corr_df = pd.DataFrame(corr, index=list(names), columns=list(names)) + side_colors = None + dict_color = None + if labels is not None: + dict_color = _resolve_label_colors(list(labels), colors) + side_colors = [dict_color[l] for l in labels] + g = sns.clustermap(corr_df, cmap=cmap, vmin=-1, vmax=1, + row_colors=side_colors, col_colors=side_colors, + figsize=figsize, xticklabels=False, + cbar_kws=dict(label=cbar_label)) + g.ax_heatmap.set_yticklabels(g.ax_heatmap.get_yticklabels(), fontsize=8) + if title is not None: + g.figure.suptitle(title) + if dict_color is not None: + handles = [plt.Rectangle((0, 0), 1, 1, color=dict_color[k]) for k in dict_color] + g.ax_heatmap.legend(handles, list(dict_color), title="Class", frameon=False, + bbox_to_anchor=(1.25, 1.0), loc="upper left", fontsize=9) + return g.figure, g.ax_heatmap diff --git a/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_comparison.py b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_comparison.py new file mode 100644 index 00000000..cb6953dd --- /dev/null +++ b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_comparison.py @@ -0,0 +1,113 @@ +""" +Backend for AAPredPlot.comparison: a grouped method x condition comparison barplot +with per-bar value labels and a chance/baseline line. +""" +from typing import Optional, List, Dict, Union +import numpy as np +import pandas as pd +import seaborn as sns +from matplotlib import pyplot as plt + +import aaanalysis.utils as ut + + +# I Helper Functions +def _resolve_order(values: List, order: Optional[List], name: str) -> List: + """First-appearance order by default; otherwise validate the order covers all values.""" + seen = list(dict.fromkeys(values)) + if order is None: + return seen + ut.check_list_like(name=name, val=order) + seen_set = set(seen) + missing = seen_set - set(order) + if missing: + raise ValueError(f"'{name}' is missing values present in the data: {sorted(map(str, missing))}") + return [g for g in dict.fromkeys(order) if g in seen_set] + + +def _resolve_colors(group_order: List, colors: Optional[Union[List, Dict]]) -> Dict: + """Build a {group: color} map: explicit list/dict wins, else the house categorical palette.""" + n = len(group_order) + if colors is None: + palette = ut.plot_get_clist_(n_colors=max(n, 2)) + return {g: palette[i] for i, g in enumerate(group_order)} + if isinstance(colors, dict): + missing = [g for g in group_order if g not in colors] + if missing: + raise ValueError(f"'colors' dict is missing colors for groups: {missing}") + return {g: colors[g] for g in group_order} + ut.check_list_like(name="colors", val=colors) + if len(colors) < n: + raise ValueError(f"'colors' (n={len(colors)}) should provide at least one color " + f"per group (n_groups={n}).") + return {g: colors[i] for i, g in enumerate(group_order)} + + +def _auto_annotation_fmt(values: np.ndarray) -> str: + """Pick a value-label format from the data scale.""" + vals = values[~np.isnan(values)] + if vals.size == 0 or np.allclose(vals, np.round(vals)): + return "{:.0f}" + if float(np.max(np.abs(vals))) < 10: + return "{:.2f}" + return "{:.1f}" + + +# II Main Functions +def plot_comparison_(df_eval=None, group="group", condition="condition", value="value", + baseline=50, baseline_label=None, annotate=True, annotation_fmt=None, + group_order=None, condition_order=None, colors=None, bar_width=0.8, + ax=None, figsize=(7, 4.2), xlabel=None, ylabel="Score", title=None, + ylim=None, fontsize_annotations=10, xtick_rotation=0): + """Draw the grouped comparison barplot. Returns (fig, ax).""" + group_order = _resolve_order(df_eval[group].tolist(), group_order, "group_order") + condition_order = _resolve_order(df_eval[condition].tolist(), condition_order, "condition_order") + dict_group_color = _resolve_colors(group_order, colors) + grid = (df_eval.groupby([group, condition])[value].mean() + .unstack(condition) + .reindex(index=group_order, columns=condition_order)) + if ax is None: + fig, ax = plt.subplots(figsize=figsize) + else: + fig = ax.figure + n_groups = len(group_order) + x = np.arange(len(condition_order)) + each_w = bar_width / n_groups + all_nan = grid.size == 0 or bool(np.all(np.isnan(grid.values))) + heights_max = 0.0 if all_nan else float(np.nanmax(grid.values)) + label_pad = 0.01 * max(heights_max, 1) + if annotation_fmt is None: + annotation_fmt = _auto_annotation_fmt(grid.values) + for j, g in enumerate(group_order): + offset = (j - (n_groups - 1) / 2) * each_w + heights = grid.loc[g].to_numpy(dtype=float) + bars = ax.bar(x + offset, heights, each_w, label=str(g), color=dict_group_color[g]) + if annotate: + for b, h in zip(bars, heights): + if np.isnan(h): + continue + ax.text(b.get_x() + b.get_width() / 2, h + label_pad, annotation_fmt.format(h), + ha="center", va="bottom", fontsize=fontsize_annotations, weight="bold") + if baseline is not None: + if baseline_label is None: + baseline_label = f"chance ({baseline:g})" + ax.axhline(baseline, ls="--", color="black", lw=1, + label=baseline_label if baseline_label != "" else "_nolegend_") + ax.set_xticks(x) + if xtick_rotation: + ax.set_xticklabels(condition_order, rotation=xtick_rotation, ha="right", rotation_mode="anchor") + else: + ax.set_xticklabels(condition_order) + if xlabel is not None: + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + if title is not None: + ax.set_title(title) + if ylim is not None: + ax.set_ylim(ylim) + elif heights_max > 0: + top = heights_max if baseline is None else max(heights_max, baseline) + ax.set_ylim(top=top * (1.15 if annotate else 1.05)) + ax.legend(loc="upper left", bbox_to_anchor=(1.01, 1.0), frameon=False) + sns.despine(ax=ax) + return fig, ax diff --git a/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_ranking.py b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_ranking.py new file mode 100644 index 00000000..b2ff5aad --- /dev/null +++ b/aaanalysis/prediction/_backend/aa_pred/aa_pred_plot_ranking.py @@ -0,0 +1,65 @@ +""" +Backend for AAPredPlot.ranking: horizontal ranked-candidate bars colored by class, +with optional per-item error bars and confidence cut-off lines. +""" +import numpy as np +from matplotlib import pyplot as plt + +import aaanalysis.utils as ut + + +# I Helper Functions +def ranking_figheight(n_items, per_item_in=0.22, base_in=1.0): + """Height (inches) for a ranked-bar plot of ``n_items`` bars (notebook rule).""" + return float(max(base_in, per_item_in * int(n_items or 0) + base_in)) + + +def _resolve_group_colors(groups, colors): + """Map each distinct group to a color (dict wins; else house palette).""" + order = list(dict.fromkeys(groups)) + if isinstance(colors, dict): + missing = [g for g in order if g not in colors] + if missing: + raise ValueError(f"'colors' dict is missing colors for groups: {missing}") + return colors + palette = ut.plot_get_clist_(n_colors=max(len(order), 2)) + return {g: palette[i] for i, g in enumerate(order)} + + +# II Main Functions +def plot_ranking_(df_pred=None, col_name="name", col_score="score", col_group=None, + col_std=None, colors=None, cutoffs=(50, 80), top_n=None, ascending=False, + ax=None, figsize=None, xlabel="Prediction score", title=None): + """Draw the ranked-candidate horizontal bar plot. Returns (fig, ax).""" + d = df_pred.sort_values(col_score, ascending=ascending).reset_index(drop=True) + if top_n is not None: + d = d.head(top_n) + n = len(d) + if figsize is None: + figsize = (5.0, ranking_figheight(n)) + if ax is None: + fig, ax = plt.subplots(figsize=figsize) + else: + fig = ax.figure + y = np.arange(n)[::-1] # highest score on top + if col_group is not None: + dict_color = _resolve_group_colors(d[col_group].tolist(), colors) + bar_colors = [dict_color[g] for g in d[col_group]] + else: + bar_colors = ut.plot_get_clist_(n_colors=2)[0] + xerr = d[col_std].to_numpy(dtype=float) if col_std is not None else None + ax.barh(y, d[col_score].to_numpy(dtype=float), color=bar_colors, + xerr=xerr, capsize=2.5, error_kw=dict(lw=1)) + ax.set_yticks(y) + ax.set_yticklabels(d[col_name].astype(str).tolist(), fontsize=9) + for c in (cutoffs or []): + ax.axvline(c, ls="--", color="0.3", lw=1.2) + ax.set_xlabel(xlabel) + if title is not None: + ax.set_title(title) + if col_group is not None: + handles = [plt.Rectangle((0, 0), 1, 1, color=dict_color[g]) for g in dict_color] + ax.legend(handles, list(dict_color), frameon=False, fontsize=9, + loc="lower right") + ax.margins(y=0.01) + return fig, ax diff --git a/aaanalysis/utils.py b/aaanalysis/utils.py index 8e057f72..434b6895 100644 --- a/aaanalysis/utils.py +++ b/aaanalysis/utils.py @@ -67,6 +67,7 @@ from ._utils.check_models import (check_mode_class, check_model_kwargs, check_match_list_model_classes_kwargs) +from ._utils.models import get_cv_model_ from ._utils.check_plots import (check_fig, check_ax, check_figsize, diff --git a/docs/source/api.rst b/docs/source/api.rst index 6b8adb47..23cea4f6 100755 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -93,6 +93,17 @@ Explainable AI TreeModel ShapModel +.. _prediction_api: + +Prediction +========== +.. autosummary:: + :toctree: generated/ + :template: autosummary/class_template.rst + + AAPred + AAPredPlot + .. _protein_engineering_api: Protein Engineering diff --git a/docs/source/index/docstring_guide.rst b/docs/source/index/docstring_guide.rst index a0578f6e..c9bf1f6d 100644 --- a/docs/source/index/docstring_guide.rst +++ b/docs/source/index/docstring_guide.rst @@ -353,6 +353,9 @@ Rules: * - :class:`~aaanalysis.TreeModel` - ``tm`` - + * - :class:`~aaanalysis.AAPred` / :class:`~aaanalysis.AAPredPlot` + - ``aapred`` / ``aapred_plot`` + - * - :class:`~aaanalysis.ShapModel` - ``sm`` - ``pro`` diff --git a/examples/prediction/aapred.ipynb b/examples/prediction/aapred.ipynb new file mode 100644 index 00000000..879c5296 --- /dev/null +++ b/examples/prediction/aapred.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4717f06e", + "metadata": {}, + "source": [ + "The ``AAPred`` class evaluates and deploys sequence-based prediction models. We first obtain the ``DOM_GSEC`` example dataset and build its CPP feature matrix (see [Breimann25]_):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7227725a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:27.251036Z", + "iopub.status.busy": "2026-07-02T12:04:27.250980Z", + "iopub.status.idle": "2026-07-02T12:04:28.788527Z", + "shell.execute_reply": "2026-07-02T12:04:28.788257Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)" + ] + }, + { + "cell_type": "markdown", + "id": "76044bab", + "metadata": {}, + "source": [ + "We create an ``AAPred`` object (default model: ``RandomForestClassifier``) and evaluate it across metrics by 5-fold cross-validation:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "acd45589", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:28.789934Z", + "iopub.status.busy": "2026-07-02T12:04:28.789851Z", + "iopub.status.idle": "2026-07-02T12:04:29.622050Z", + "shell.execute_reply": "2026-07-02T12:04:29.621796Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (4, 5)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 modelmetricprinciplescorescore_std
1RandomForestClassifieraccuracycv0.8092310.064659
2RandomForestClassifierbalanced_accuracycv0.8102560.065416
3RandomForestClassifierf1cv0.8102560.059252
4RandomForestClassifierroc_auccv0.8872780.071059
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "aapred = aa.AAPred(random_state=42)\n", + "df_eval = aapred.eval(X, labels)\n", + "aa.display_df(df_eval, n_rows=10, show_shape=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_eval.ipynb b/examples/prediction/aapred_eval.ipynb new file mode 100644 index 00000000..1823305d --- /dev/null +++ b/examples/prediction/aapred_eval.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "966750b8", + "metadata": {}, + "source": [ + "To demonstrate ``AAPred().eval()``, we obtain the ``DOM_GSEC`` dataset and its feature matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cb1e5f2d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:31.031901Z", + "iopub.status.busy": "2026-07-02T12:04:31.031828Z", + "iopub.status.idle": "2026-07-02T12:04:32.526986Z", + "shell.execute_reply": "2026-07-02T12:04:32.526711Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)" + ] + }, + { + "cell_type": "markdown", + "id": "d433ebec", + "metadata": {}, + "source": [ + "``eval`` scores every model across metrics by stratified cross-validation and returns a long-format table (one row per model, metric, and evaluation principle):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6968c60d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:32.528425Z", + "iopub.status.busy": "2026-07-02T12:04:32.528351Z", + "iopub.status.idle": "2026-07-02T12:04:33.371461Z", + "shell.execute_reply": "2026-07-02T12:04:33.371251Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (4, 5)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 modelmetricprinciplescorescore_std
1RandomForestClassifieraccuracycv0.8092310.064659
2RandomForestClassifierbalanced_accuracycv0.8102560.065416
3RandomForestClassifierf1cv0.8102560.059252
4RandomForestClassifierroc_auccv0.8872780.071059
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "aapred = aa.AAPred(random_state=42)\n", + "df_eval = aapred.eval(X, labels)\n", + "aa.display_df(df_eval, n_rows=10, show_shape=True)" + ] + }, + { + "cell_type": "markdown", + "id": "2b0c6b4a", + "metadata": {}, + "source": [ + "Providing a held-out set adds the ``holdout`` principle beside cross-validation (``score_std`` is ``NaN`` for the single held-out estimate):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8a7e42f2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:33.372556Z", + "iopub.status.busy": "2026-07-02T12:04:33.372491Z", + "iopub.status.idle": "2026-07-02T12:04:34.218127Z", + "shell.execute_reply": "2026-07-02T12:04:34.217895Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (8, 5)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 modelmetricprinciplescorescore_std
1RandomForestClassifieraccuracycv0.8065360.105052
2RandomForestClassifieraccuracyholdout0.842105nan
3RandomForestClassifierbalanced_accuracycv0.8083330.105556
4RandomForestClassifierbalanced_accuracyholdout0.842105nan
5RandomForestClassifierf1cv0.8006110.117547
6RandomForestClassifierf1holdout0.833333nan
7RandomForestClassifierroc_auccv0.9166670.078567
8RandomForestClassifierroc_aucholdout0.896122nan
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "X_tr, X_ho, y_tr, y_ho = train_test_split(X, labels, test_size=0.3, random_state=0, stratify=labels)\n", + "df_eval = aapred.eval(X_tr, y_tr, X_holdout=X_ho, labels_holdout=y_ho)\n", + "aa.display_df(df_eval, n_rows=10, show_shape=True)" + ] + }, + { + "cell_type": "markdown", + "id": "8ae32e4d", + "metadata": {}, + "source": [ + "The metrics and number of cross-validation folds are controlled by ``metrics`` and ``n_cv``:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d577b5a7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:34.219250Z", + "iopub.status.busy": "2026-07-02T12:04:34.219169Z", + "iopub.status.idle": "2026-07-02T12:04:34.464262Z", + "shell.execute_reply": "2026-07-02T12:04:34.464023Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (2, 5)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 modelmetricprinciplescorescore_std
1RandomForestClassifierbalanced_accuracycv0.7936510.059391
2RandomForestClassifierroc_auccv0.8631900.061409
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_eval = aapred.eval(X, labels, metrics=[\"balanced_accuracy\", \"roc_auc\"], n_cv=3)\n", + "aa.display_df(df_eval, n_rows=10, show_shape=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_fit.ipynb b/examples/prediction/aapred_fit.ipynb new file mode 100644 index 00000000..a2169e9f --- /dev/null +++ b/examples/prediction/aapred_fit.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1900b9c5", + "metadata": {}, + "source": [ + "To demonstrate ``AAPred().fit()``, we obtain the ``DOM_GSEC`` dataset and its feature matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9a3b2221", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:35.878829Z", + "iopub.status.busy": "2026-07-02T12:04:35.878741Z", + "iopub.status.idle": "2026-07-02T12:04:37.401413Z", + "shell.execute_reply": "2026-07-02T12:04:37.401110Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)" + ] + }, + { + "cell_type": "markdown", + "id": "ff7a1665", + "metadata": {}, + "source": [ + "Fitting trains every model on the full dataset and stores them for deployment in ``list_models_``:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f7672511", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:37.402828Z", + "iopub.status.busy": "2026-07-02T12:04:37.402743Z", + "iopub.status.idle": "2026-07-02T12:04:37.444450Z", + "shell.execute_reply": "2026-07-02T12:04:37.444216Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of fitted models: 1\n" + ] + } + ], + "source": [ + "aapred = aa.AAPred(random_state=42)\n", + "aapred.fit(X, labels)\n", + "print(\"Number of fitted models:\", len(aapred.list_models_))" + ] + }, + { + "cell_type": "markdown", + "id": "23f0f385", + "metadata": {}, + "source": [ + "Multiple model classes can be evaluated and deployed together. Any scikit-learn classifier implementing ``predict_proba`` is accepted:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0c93d6ef", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:37.445548Z", + "iopub.status.busy": "2026-07-02T12:04:37.445481Z", + "iopub.status.idle": "2026-07-02T12:04:37.448901Z", + "shell.execute_reply": "2026-07-02T12:04:37.448697Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of fitted models: 1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/stephanbreimann/Programming/1Packages/aaanalysis/.venv/lib/python3.13/site-packages/sklearn/svm/_base.py:239: FutureWarning: The `probability` parameter was deprecated in 1.9 and will be removed in version 1.11. Use `CalibratedClassifierCV(SVC(), ensemble=False)` instead of `SVC(probability=True)`\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from sklearn.svm import SVC\n", + "aapred = aa.AAPred(list_model_classes=[SVC], list_model_kwargs=[{\"probability\": True, \"kernel\": \"linear\"}],\n", + " random_state=42)\n", + "aapred.fit(X, labels)\n", + "print(\"Number of fitted models:\", len(aapred.list_models_))" + ] + }, + { + "cell_type": "markdown", + "id": "d7f44666", + "metadata": {}, + "source": [ + "The positive class whose probability :meth:`AAPred.predict_proba` returns is set by ``label_pos`` (default=1)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot.ipynb b/examples/prediction/aapred_plot.ipynb new file mode 100644 index 00000000..701d545f --- /dev/null +++ b/examples/prediction/aapred_plot.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "efd80247", + "metadata": {}, + "source": [ + "The ``AAPredPlot`` class visualizes ``AAPred`` results. We first build the feature matrix and an evaluation table:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "48272412", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:38.967924Z", + "iopub.status.busy": "2026-07-02T12:04:38.967842Z", + "iopub.status.idle": "2026-07-02T12:04:41.277120Z", + "shell.execute_reply": "2026-07-02T12:04:41.276843Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)\n", + "\n", + "aapred = aa.AAPred(random_state=42)\n", + "df_eval = aapred.eval(X, labels)" + ] + }, + { + "cell_type": "markdown", + "id": "e9d8ed2f", + "metadata": {}, + "source": [ + "The evaluation table is shown as a grouped bar plot:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a69abbfe", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:41.278443Z", + "iopub.status.busy": "2026-07-02T12:04:41.278368Z", + "iopub.status.idle": "2026-07-02T12:04:41.338388Z", + "shell.execute_reply": "2026-07-02T12:04:41.338153Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApoAAAHSCAYAAABM72tjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAXb9JREFUeJzt3QeYE1X79/GbZem9NwWkWkBAkC5dAUFBsGHBrihiASw8FqoCYgErCooiioCi4qOChSIgWMBKEaR3QXpvea/fef6TN9nNLgu7s2yy3891hcnOzE4m2SG5c59z7pMlEAgEDAAAAEhjcWl9QAAAAIBAEwAAAL4howkAAABfEGgCAADAFwSaAAAA8AWBJgAAAHxBoAkAAABfEGgCAADAF/EWpV5++WWrXLmytW7d+pR+f9WqVfb999/b5s2brVy5cta2bVvLkydPmp8nAABAZhWVgeY777xjPXr0sO7du590oKmJkB555BF74YUX7OjRo8H1hQoVsjFjxliHDh18OGMAAIDMJ+qazqdOnWp33nnnKf9+v379bNiwYe7+NddcY7169bKaNWvajh077KqrrrL58+en4dkCAABkXlETaCoTqSzkZZddZocPHz6lY2zcuNGGDh1qWbJksS+++MI++OADe/bZZ23BggV266232pEjR+yhhx5K83MHAADIjKIi0Pz999+tfv361rNnT8uWLdsp98scPXq0HTp0yK644gq7+OKLg+vj4uJs+PDhlj9/fpszZ46tWLEiDc8eAAAgc4qKQHPKlCn2448/WsOGDV32sU2bNqd0nFmzZrnl5Zdfnmhbvnz5rEWLFu7+V199lcozBgAAQFQEmuecc459+OGHNnfuXHf/VP31119ued555yX5OLJ06dJTOv7+/ftt4cKFbgkAAJDZRcWo886dO6fJcbZs2eKWpUuXjri9ZMmSbvnPP/+c0vEVoNauXdtlXS+44IJUnCkAAED0i4pAMy1ooI9Xzih37twR98mVK5dbHjhwIMnjqI+nbpHs3bs3Tc4VAAAgFmSaQDNr1qzB+xp1HklS60MNHjzY+vfvn6bnBgAAEIuioo9mWtDI8pw5cyabsfTWJ5XxlD59+tiuXbsi3rzBRgAAAMhEGU0pXry4rV271vXV9PpjhtJ0lN5+ScmRI4e7RZI3b940PFsAAIDolmkymqGjypcsWRJxu7e+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AADESHmjhGWFunfv7vpR7tu3z2UmFWjGxUWOlV966SW3X7164fOalihRwvX33LJliwta9+zZ4/puXnjhhYwaBwAAyIyBpqiEkOYiTwk1gSdHAafqZQIAACATN50DAAAg+hBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAIgJR/dut8P/rApbp5+1HsDpQaAJAIgJe3/90raM7xO2Tj9rPYDTI6rKGyF2KeNwdPuGRJmI+MJlLD5v4dN2Xsg4uEZwInlrtrVclcJrJ0tW3kOA04ZAExmCMg675o5PlIko0KiLFWx8/Wk7L2QcXCM4EX0p5YspkLEQaCJDIBMBrhEAiD0EmsgQyESAawQAYg+DgQAAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC8INAEAAOALAk0AAAD4gkATAAAAviDQBAAAgC/iLUocPnzY3njjDZs1a5Zt3rzZypUrZ9dff721bdv2lI736aef2ocffmirV6+2HDlyWP369e2ee+6x0qVLp/m5AwAAZEZREWhu377dmjRpYosWLQqumzNnjr333nt255132uuvv57iYx06dMiuvvpqmzJlStj6b7/91l555RX77rvvrHr16ml6/gAAAJlRVDSdX3fddS7IrFKlio0fP95mz55tTzzxhGXPnt1lOXVLqfvvv98FmXnz5rXBgwe7DOmECRNcRnPnzp3WqVMnO3bsmK/PBwAAIDPI8BnN+fPn27Rp06xAgQIu21iiRAm3vnHjxlahQgW75ZZbbODAgXb77bdbXFzycfPixYtdUJotWzb7+uuvXXDp6dixo9WoUcOWLl1q33//vV100UW+PzcAAIBYluEzmu+++65b3nzzzcEg03PTTTdZ+fLlbf369S44PJFx48ZZIBCw7t27hwWZouyoAtqffvrJzj777DR+FgAAAJlPhg80f/jhB7ds0aJFom1ZsmSxZs2auftz58494bFmzJjhll27do24vWzZslanTh0rVqxYKs8aAAAAadZ0vmXLFtf38ddff7V///3Xnn76ade0rf6Pl1xyiRUqVOiUjrtq1Sq3rFy5csTtFStWdMuVK1ee8Fjq56nm9WrVqtny5ctt5MiRtnDhQsuaNasbbNSjR49TPk8AAACkcaCppmgFlYMGDbKDBw8G1/fu3dsFmsOGDXP9JydNmmRt2rQ56WPv2LHD3S9SpEjEfbzAUCPTk7N3717bs2ePO87HH3/smt1Dz1ejzhV4qvmcUecAAAAZoOn8kUcesccff9wFbTVr1kyUEVQztIK8Dh062Lx5807q2EeOHHHBpuTMmTPiPrly5QqWLUqOzkH27dvngswyZcrYqFGjXHP6a6+9ZmeccYZt2rTJLr/8cjtw4ECSx9Hj7N69O+LNewwAAACkMtD87bff7Nlnn3UDaT777DP75ZdfXBYz1CeffGLt27d3BdcVkJ4MHdcbSX706NEkg1HRSPLkeCWLFBArqFQTvzKt6uPZrVs314SuoFgF3DVoKCkqiaQR8JFuTZs2PannBwAAEMtSFWi+8847LuN47733umAyEs26o4yhAsaZM2e6jOLJyJMnj1sm9XteFjFfvnzJHkd1Mz2PPvpo2M+iIPPuu+9293WeSenTp4/t2rUr4k01OQEAAJBGGU05Ud9LZRBVhuj48eO2Zs2ak3qMM8880y1VwigSb/2Jpo7Mnz9/MGhV4fdIqlat6pbbtm1L8jgKnHWsSLeEwSsAAEBmlqpA0xtMc6Jm69A+ll6fy5TSCHFR03YkCxYscMvzzz8/2eOoFJJ3rKSCVvXRFMobAQAAnOZAUwNq5I8//kh2P5U7WrZsmQv2TpR5TKht27ZuGanf5IoVK9wAI/XlbNmy5QmPdemll7rl6NGjE21TAPzRRx+5+w0aNDipcwQAAEAaB5oXX3yxW77wwgtJ9qFUAKeR6RrMU6tWrZOuU6m5x4sWLeqmonzwwQfdoCJZu3atXXnlle64N954Y4qykLfddpvryzl9+nS75557XLkjLzOrfqYKWgsWLGjXXnvtSZ0jAAAA0jjQVICnvpcqqq65x1Wf0iszpPqXX331lZvR580333TrHn744ZN+DPV9fPHFF102dPjw4VayZElX51KF2jVy/KyzznJ1PBNq166dm+Vn8uTJYRlYzXWuAu0aoKRjnXvuuS74ffXVV92AJdXSTKpmJwAAANKpYLv6Xap8UatWrVzQp+yjR7MBhbrvvvvsmmuuOaXH6dKlixuEo0BVzeUKYhV46vGef/55K168eMSBShs2bLB//vknbL2ylQowdSzNa75kyRK3XjVAhwwZYq1btz6lcwQAAEAazwxUo0YNF2T27dvXJk6cGGyO9mgAjkoCXXfddal6HAWVumnUuprplcn0irVH8sUXX7hm9nLlyiXaptqZP/74o23dutU2b95sJUqUiBisAgAA4DQFmgowFeypLJAG2Lz++uv2119/ufJAGqCjZnVlD9NSpMAxkhONQhf162SEOQAAQAbso/ncc8/Z2Wef7QYDifo+qs9jkyZNrH79+mkeZAIAACCTBJoaBOQ1nwMAAABpFmh6GUvVyQQAAADSLNB84oknXPkhDfY5UdF2AAAAZC6pGgykUkO33367jRgxwg2+qVy5shsYpOBT5YciGThwoBsxDgAAgNiWqkBThdK9ucZl+fLl7pacBx54gEATAAAgE0hVoKnamJoR6GSoZiUAAABiX6oCzZ49e6bdmQAAACCmpGowEAAAAODbFJSeRYsW2YQJE9xsQZqLXHOTa9BP8+bNrXPnzu5nAAAAZB6pDjSPHj3qmtBffvllCwQCibZrasqnnnrKJk2a5GYNAgAAQOaQ6kCzR48eNnLkSHdf01G2atXKDfjZu3evLVy40L799ltbvHixXXLJJfb7779b4cKF0+K8AQAAEMuBpkobeUHmsGHDrFevXonqZyq4bNeuna1fv94GDBhgw4cPT90ZAwAAIPYHA40bN84tu3TpYr17945YpF2F3L39tDx+/HhqHhIAAACZIdD0pp1UPc3kNG3a1EqXLu3mRN+4cWNqHhIAAACZIdDcv3+/W6ak36W3j0akAwAAIPalKtAsWbKkW/75558nDEhXrlzp7jMzEAAAQOaQqkCzWbNmbvnss8/anj17ktxv8ODBLtjUqPTixYun5iEBAACQGQLNrl27WtGiRW358uV20UUX2bRp0+zQoUPB7VrfvXt3GzRokPuZKSsBAAAyj1SVNypYsKAbSd6xY0f77bffrE2bNhYfH28FChSwAwcOBPtwyrXXXmu33367ZQZbt261TZs2BX/OmTOnFSpUyBW317aESpUq5Zbbtm2zI0eOJHqNc+XKZfv27bPdu3cH1xctUtjishy03YGclsUCVjjL/3+tPdsD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AABkloymRyPKR44caa+99pr9888/7qaJ2suUKeOyngAAAMh80iTQDC1jpKwmI8sBAACQ6qbzSAKBgK1bt87Wr1/PKwwAAJBJnVKgOW/ePHvyyScjzvIzduxYK1u2rLtpRHq5cuVszJgxaXGuAAAAiNVA88iRI3b99ddbw4YNXcmibdu2hW1/44037KabbgrLZK5du9ZuvfVWe+yxx9LurAEAABBbgeZ9991n77//vrtftWrVsKknt2zZYr1793b3q1WrZtOnT7dly5ZZv379XN/NwYMH2/fff5/W5w8AAIBoDzSXL19uo0aNsri4OLdcunSpVahQIbh99OjRbh5zjTb/7LPPrHnz5la5cmXr27evPfTQQ67f5nPPPefX8wAAAEC0BpqffPKJHTt2zK677jq7/fbbE22fMmWKW3bq1MnKly8ftu3+++93y6lTp7rmdwAAAMS+FAeaP/74o1tedtllibbt2rXLFixY4O63b98+Yp1NDQzav3+/m5ryVGzdutUeeOABq1WrlpUqVcrq169vL730kgt+U+uZZ56xggUL2vDhw1N9LAAAAJxkHU0VYZczzjgj4ih0L+BTk3kkhQoVciWPduzY4YLOk6HpLTUASf1APQpYf/jhB9dM//nnn1u2bNnsVPzxxx/2xBNP2OHDh+3gwYOndAwAAACkIqOpPpZJ+e6779yyYsWKLnsZiQJMUebwZF111VUuyGzSpIn99NNPtmnTJnvnnXfcsb7++msbOnSonQoFlzfeeKNbAgAA4DQFmsWLF3fLSEXYv/nmm2SzmWr23rBhgxtIVKxYsZM6wS+//NIWLlzomsu/+OILq1OnjpUsWdK6du1q48ePd/s8//zzp9T3s3///vbbb79Z0aJFT/p3AQAAkEaBpvpEhg768axYscJ+/vlnd//SSy+N+LvvvfeeHT9+3AWJuXLlspMxadIkt1Qtzjx58oRta9OmjZ133nkuWzpjxoyTOq6a3ZUJbdSokctqAgAA4DQFmhpNHh8f7+poatCMmpu3b99u3bp1c83qylRGCjSXLFniirvLlVdeedIn6A0yaty4ccTt3vr58+en+JgHDhxwGdGcOXPa22+/7TKtAAAASFspjrBUM/PBBx90QaWWBQoUcMGl12yuGpk5cuQI7q8sp6ap1CAeBaT6/R49epz0Ca5ZsybY/zMSTXEZul9KPPLII66Y/LBhw6xSpUonfU4AAABIw1HnoqbmrFmz2gsvvBAcoa2soDKWCZuflen0spEKBtW/UvueDAW1Kp2U3CAiBbyyc+fOFB1TMxa9/PLLdvHFF9vdd999UucDAAAAnwJNbypJ1bNUXU31u1TGMtIAHzWza2agLl26uILtodNVplToAJ/QbGkoL3hNyWCg3bt32y233OKC07feestOxaFDh9wtkr17957SMQEAACyzB5qeEiVKRCzcHmru3Lku+5kams5Sx1CNzqQCSa80kfY9EQW8a9eutXfffTdiPdCUUKCt0eoAAABInm+jYFIbZHry5s3rlppHPRJvvdeEnhSNltfAHw1quuGGG075fPr06eOa8yPdZs2adcrHBQAAiDUZfrj1WWed5ZbKREbirT9RhnLMmDHB+dbV3zP0pj6bokylfr722muTPI6a8PPnzx/x5gXFAAAAiIJA8/zzzw+baz0hr6yR5kBPycxGmm89YSbS63OpAU76mb6WAAAAmSDQ9PqCavBOwn6aKqGkW758+axFixbJHkdF41XYPdLt3nvvdfv07dvX/TxhwgQfnxEAAEDmkOEDzfbt21v58uVd4fdrrrnGzXMus2fPts6dO7v7d911l2u6To5mFUrYZO7dvBHtGsGunxPOQAQAAIAYDDQV/L355psuGPz444+tdOnSbhrLJk2auP6ZF154YcRR4JqaUkGj1zcTAAAA6SvDB5qiZvE5c+bYJZdc4soYqU+lBv88/PDDbmai3LlzJ/qdhP0vAQAAEAV1NE+HOnXq2LRp09ygHtXOTKqAu2fx4sWuoHykIDShQYMG2eOPP+4ypQAAAMhkgWbo7EQnCjLlRH02EzbPn+z0mAAAAIiBpnMAAABEHwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgCwJNAAAA+IJAEwAAAL4g0AQAAIAvCDQBAADgi6gLNAOBgO3atStNjnX48GHbvn27HTt2LE2OBwAAgCgMNH/99Vdr06aN5cmTxwoWLOhu3bt3t507d570scaNG2e1a9e2XLlyWZEiRdyyRYsWNnPmTF/OHQAAIDOKikBz/vz51rBhQ5s2bZrLQhYoUMBlNV999VVr1KjRSWU4H3zwQbvxxhtt4cKFdvz4ccubN68dOXLEZsyYYS1btrR3333X1+cCAACQWWT4QPPo0aPWpUsXO3DggHXt2tW2bdvmspg//PCDnXXWWbZ48WJ78sknU3Ss7777zoYPH25Zs2a1Z5991vbs2eNuq1evtg4dOrjAs1u3brZp0ybfnxcAAECsy/CB5sSJE10gWLVqVXvrrbdck7nUrVvXJk+ebFmyZLFRo0bZvn37Tnis0aNHu2WvXr3cTdlMKVeunHucs88+2/bv328fffSRz88KAAAg9mX4QHPKlCluedNNN7lMZKiaNWu6gFPZzq+++uqEx1qzZo2VKFHCZS8Typ49u11yySXu/qpVq9Ls/AEAADKrDB9o/vbbb25Zv379iNsVaMqCBQtOeKxZs2bZ5s2bXX/PSP7991+3LFy4cCrOGAAAAFERaK5fv94ty5cvH3H7mWeeGbbfqdq6dat99tln7n6rVq1SdSwAAACYxWfkF0GDc/bu3evu58+fP+I++fLlc8vdu3en6rHuvfded4yLL77Y6tWrl+R+hw4dcrdIvHMFAABABg80Qwupqw9lJN761BRd7927txsMVKxYMRszZkyy+w4ePNj69+9/yo8FAACQWWTopvNs2bJZfPz/YuGksogHDx5MNhA90SxD999/vz333HNWqFAh+/LLL61MmTLJ/k6fPn1c3c5IN/UBBQAAQBRkNEXF2TVIR7UzixYtmmi7V6xdgeLJUOCqkewTJkywkiVL2tSpU61GjRon/L0cOXK4WyReuSQAAABk8IymVKpUyS1VSzMSrxRRUoOFIlFw2rp1axdknnvuuW7moZQEmQAAAIihQLNWrVpuOWfOnIjbvfWauzwltm/fbk2bNnXN3Bpd/v3337uC7QAAAMhkgWbHjh3dUrP/JJzTXOWIlixZ4oqwN2vW7ITH0jzpl156qavNedVVV9kXX3zhmuYBAACQCftoqtzQBRdcYAsXLrSWLVu6Oco1x7lmAtJocdF0kgn7TaoupkaiK5DMlSuXWzdgwAA3R3qVKlVs2LBhwQLtCeXOnTvJckoAAACIkUAzLi7Oxo4d6zKWmv2nefPmiTKeCjQjNblv2LDBXnvtNevWrZurkTl8+HC3bdmyZcn26bzrrrts5MiRPjwbAACAzCPDB5py3nnn2e+//25Dhgyx6dOn2759+6xixYp23XXXuZHjCkYTKl68uB09etRlJ2XRokVuVHhKRobTnA4AAJBJAk0pVaqUjRgxIsX7q6k9VIMGDdw85wAAAEgfGX4wEAAAAKITgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAAAB8EW9RJBAI2Nq1a23z5s1WtmxZK1WqVIY4FgAAAKI4o/nJJ59YxYoVrXz58la/fn0rXbq0tWzZ0pYvX35ajwUAAIAoDjQ/+ugj69y5s61atcrOOOMMq1evnuXMmdOmT59uF110ka1bt+60HAsAAABRHGju37/funXrZsePH7eBAwe65u758+fb6tWrXZC4ZcsWe+SRR9L9WAAAAIjyQPOdd96xbdu2uSbuxx9/3LJkyeLWlyhRwiZMmGDZsmWziRMn2r///puuxwIAAECUB5pTp051y2uvvTbRtnLlylnjxo3t2LFjwf3S61gAAACI8kDzzz//dMvatWtH3F6zZk23/P3339P1WAAAAIjyQHPjxo1uqRJEkWhAT+h+6XUsAAAARHEdTTVjHzx40N3PmzdvxH3y5Mnjlnv37k2XYx06dMjdIlH/T1myZIn57cjhQ5Zl89++Pw7S/u+2cOHCdHlZuUaiT3peH97j8T4SXbhGkJGukbPPPtty586d/E6BDOzIkSMBnaJuu3btirjP6NGj3fbLL788XY7Vt2/f4HG48RpwDXANcA1wDXANcA1k1mtgwYIFJ4zlMnRGMz4+3o0EP3LkiMtG5s+fP9E+XpZStTDT41h9+vSxnj17JpnRnD17tlWqVMly5cp1wueHxJRNbtq0qc2aNSvJzDMyN64RcI2A95CMQRnNE8nQgaYULlzY1bfcvn27FS9ePNF2rxRRkSJF0uVYOXLkcLdIFLxWqFDhhOeBpO3evTs4MCvSlwGAawQnwjUCro+MI8MPBqpSpYpbrlixIuJ2b72mlEzPYwEAACDKA80LL7zQLTVFZEKa4WfGjBnuvmb2Sc9jAQAAIMoDzSuvvNIt33zzTVuzZk3YtjfeeMPNTa4+kQ0bNkzXYwEAACDK+2g2aNDAWrdubdOmTbNGjRq5qSPPOuss++qrr2zEiBFunyeffNLi4sJj5nnz5rkyRGouL126dKqOBQAAgJOXRUPPLYPbvHmztWzZ0hYvXpxo28MPP2xDhw6NWHx9w4YN9tprr1m3bt1SdSykbyf+AgUK2K5duxgMBK4R8D4CPmeiXIbPaErJkiVd8dG33nrL9a/ct2+fG7Bz3XXXuSxlJFq/devWYDYzNcdC+tGI/r59+yY5sh/gGgHvI+BzJnpERUYTAAAA0YfOiAAAAPAFgSYAAAB8QaAJAAAAXxBoAgAAwBcEmgAAAPAFgSYAIMNZvXr16T4FAGmAQBMAkKF8+umnbjrguXPnnu5TAZBKBJrwnWb56dmzp40ePZpXGxEdP37cXn/9dVuzZg2vEOzvv/+2Y8eO2QMPPGCUegaiG4EmfPf111/bCy+8YPPnz+fVRkR9+vRxU8X+8MMPvEKwn3/+ObgcM2YMrwiStWfPHvvuu+/c+8fhw4d5tTIYAk34zpvac8GCBbzaiKhy5cpcI5nQpk2b7Keffgpbp0Dhm2++Cf78n//8xwUSQEIHDhyw+++/34oVK2ZNmza1+vXrW5kyZfhyksEQaMJ3+o+v26JFi+zQoUO84khEHxCycOFCXp1MQMGksthnnXWW3XzzzWHbPv74Y9u2bZtdeeWVVrduXduyZYsNGjTotJ0rMqbdu3e7JMaLL77orqfatWvbeeed566du+66y3788cfTfYr4PwSaSBP79u2zoUOH2saNGyNur1evnh05csT++OMPXvFMSn3tkgokzz33XMuXLx+BZiZx44032pAhQ9x7goJJ7wuofu7Xr5+7r/6ZI0aMsCxZstjw4cNdv03Ac++999pvv/1mVapUsd9//911s/jzzz/tiy++cJ8zuq6QMRBoItXUab9Vq1b26KOPusyU/rMnRMYqc1NWqlGjRi7r8MQTTyTaHhcXZxdeeKFt376dsjYxbtWqVTZx4sRg9lJ9MHPkyOF+VrCwdOlSa9++vbte9L5x/fXXu4xVr169TvOZI6NQ1vKDDz5wX0ImT55s1apVC25r27atVa1a9bSeH8IRaCLVsmbNah999JHVqlXL1q1bZ40bN7Zvv/02bB8CzcytRIkS1rFjR/fBoGZQZbQSdtrnGskccubM6b5YiEoYKVOpZnRRU2inTp1cc6hHLSV58uSxKVOmhPXdROagL59LliwJW/fXX3+57LeymWouR8ZGoImTpv/g6v8ya9YsV7pISpcu7Ub9tWvXzq3Tt8qxY8cGf6dOnToWHx/PgKBM7OGHH3aZLAUa48aNszZt2tjOnTsTBZoMGottpUqVsmuvvdbdv+KKK1ygoGb0999/362bNGmS67vp0XuLF4iqOf3o0aOn6cyR3vReoGBS14tazjz6LJGUXAv6PQ0awulDoImT8vLLL7s3fvW5bNasmctUKYBQ8Jk3b15XaLl79+7u55tuusn69+/vfi9XrlxWvXp113dG2xC7pk2b5rpS5M6d2woWLGhXXXWVaw4VDfCYMWOGGyWqpZpHvdqZZDQzT19dvRfIsmXL3PuBBgS1aNHCrfOynaHUbF6+fHk3oHDkyJHpfs44PdR3W58r6oM5atSo4PoaNWq47hYrVqywlStXJvn7qmiglja1uOE0CgApdOONNwZ0yehWpUqVQN26dQPx8fHu55deeils3+effz4QFxfntt12222BY8eOBe644w738y+//MJrHqMefPDB4DUSesuTJ0/gxx9/DO63YsWKwNlnn+22lSpVKrBw4UK3vnz58oHixYufxmcAvw0bNizs2qhatWrg6NGjJ/y9SZMmuf0LFy4c+Pfff/lDZRITJkxwf/eiRYsGduzYEVzfqVMnt/7uu+9O8neHDBni9hk0aFA6nS0iIdBEirz++uvuP2zevHkDn376aXD9H3/8Efj6668j/s7HH38cyJ07t/u9q6++OjB06FB3/8033+RVj0EvvPCC+/vmzJkz8MwzzwT++eefwPr16wMPPPBA4MUXXwwcOXIkbP/t27cHmjZt6n6nQIECgblz5wbatWvnfl63bt1pex7wl4KFWrVqBd5+++1A2bJl3d/7lVdeSdHvNmvWzO3fvXt3/kwxZM+ePe4zJikXXXSR+7vrvcSjL65alzVr1sAPP/wQ8fc6duzo9hkzZowv542UIdBEilSrVs39h33jjTdO6hXTm0GJEiXc75YsWZIPiRh1+PBhl3HQ3/n9999P8e8dOnQomClX1rNOnTrufuiXGcSu8ePHu793kSJF3BePE/n1119dYKGbvuQi+qm1S1ltXQeTJ0+OuI9aPNRCli1btsCSJUuC69Vapt8rU6ZMYPHixcH1x48fDwwYMCCYHNm2bVu6PBdERqCJFMmSJYv7T7t169aTfsVWr14dOPfcc4NNZfXr1+dVjzE///yz+9vqS8Wp6Nu3b1hz6pNPPpnm54iMqXHjxu5v3qNHjxTtf9ddd7n9W7Vq5fu5IX2oBUR/0woVKgQOHjwYcR8vqGzTpk1YJvTCCy8MtqR06dLFXUfe540+t8aOHcuf8TQj0ETYN8vdu3cnekX07TB79uzuP+6mTZtO+IrpP39CO3fuDLRs2dIdI1euXCnqk4XoMW/evGB/u1P1zjvvBK+z9u3bp+n5IeNasGCBy1apv/eiRYtOuL++7BYsWDBQs2ZN+mrGCLVsVK5c2f3fV7/KSDZv3uxaPbTP559/HvbZctVVVyXqF65uGVOmTEnHZ4GkEGgi8Pfff7tvi3rz1n/QcuXKhf1HloYNG7ptyX07VBB62WWXuYxDUs2r3rfP5cuX88pHkf3797t+dOpDqQzU448/Hti3b19YvzuvaSu5JtBdu3YFHnroocAXX3wRcfuMGTNcs6g+JJB5eNmqSy65JEX7//nnn+6LMaKHkgufffZZ4NFHHw088cQT7m8YSkGhroF8+fIlmdDwBhvqC60+T0LpeOoLrkBVn18J+4Tj9CHQzOQUPCjDmPDboLILoaOENYpc62vUqJHkG/ycOXMSNW0k1Lt370TfSJGx/fe//3UjwxNeIxrIE5qZbtCggVuvZrCkDB482O0zcODAJPdRpkpNXqGBLGKbslX58+d31wZZqNjz1VdfBSpVqhT2/qEvpRowGkpfNLTtlltuiXic7777Lvj7zz33XDqdPVKLOpqZmOYPVs1LFbPVTC0qjqs5y7t16+YK4d5xxx3BIrm6X6RIETe37EsvvRTxePPnz3fLMmXKJPmYXiHmDRs2+PKckLZUu+6yyy6zTZs2Wc2aNV0dVU39pqLbKtj/wgsvBPft2bOnWw4ePNg2b94c8Xhbt251S9XYTMo555zjai2uXbuWP2cmoXq8jz/+eLBmZsJZoxC9hg0bZq1bt3YzQJUtW9bNUa56ul791ND3Cr2fqBj722+/7aYjTcibqjR79uw2YMCA4PsJMrhUh6qIShqgo/5w+lYZmkFQtjK0Xuarr74a3KYSEd430U8++STseDqGV8rom2++SfJx1Vlb+6g/HjI2NUUps62m7Jdffjls27XXXhsc0ek1c+na8bpYKNup5vZQ8+fPd9eIspUJm80ilbBhVHHm7aeXXFYc0cNrDs+RI4drFfOas/VeoZq52nbDDTeE/Y4G82i9KlAk7Mt/zz33uEE//fr1S1TuCBkXgWYmoX5z48aNCw7UUQDpFVP3KDDwOlV7zekJi+R6Rde9/lTqM9O8efPgum7dukV8fJWkUP9NL1Bds2ZNOjxrnKzQvlFef6g+ffoE1+kD4t577w1rAgtt5lLfW5Wq0frq1au7WonqJtGzZ8/gQB9dQ0k1n3pdKxSQJgxUkXkCE/XRpY9d9FOFkYR9+xU83nrrrcH3D33x1JfQ0M+qYsWKuW0333yz+/zRIFVVptC+bdu2DRw4cMD9rIFAyPgINDOBDz74IJhtVD9KGTFihPu5V69e7mcFfiqirHUaFKRyNRdccEGib40KNFR6xgsavJs6cKsgu0aoR6I3C32D1X4KeJGxfPnll4FzzjnHXSfe39DLWn744YfBv6He5L1AUAWWNQBIN10voaOIzzzzzIgzBGkWj6QCiHfffdfto+ONHDkynZ45MmLhfxX7R/TzBpju3bs3GES2bt06+Dnjze5Tr169sM8ObzYg7/3Au6/MqN5fEF0INGOYil4rK+R1slfdOW+qv9mzZ7tAYu3ata6J0iuqrkzC77//7vb56KOPggODvHWejRs3uhl+nnrqKVegOzTrmRTNEkOWKmPRVH4bNmxwzVFeBtvLampkub58qBCyKhNo2lHtU6hQocCsWbPCukIocxH6QaHR5eqsr1HqagpXFtS79pKjayrhtQYgOmmaWXWHUEZy6dKlwQFBavXQbD4qVeWVLFLrRyi1uoUOVNV7UegAVUQPAs0YpSDBy0YmN0JPzRga5ev1iVHzpUffQr3/5PrGSe3L2HLfffe5rIKX3dZI8NBso4JQZbBVRsQrgKwPimXLloWNSPeukddee+00PRMAGZGmldX7hwLK4sWLu/cJBZ+h5e26du3q1qu5POEc9vrCqhq9K1asOA1nj7RCoBmjVq5c6ZoZvGaHpGobKuup7XoTSLjPli1bwpo9b7/99iSbxhF9FFh6GUpN4ZYUrz+vmsNDv4iIMpve9aGBQTRrAUhqNieVx0v4OaOamt57yJVXXkl91BhEeaMYoFIgQ4cOtYcffjisjJDKzRw/fjzZckIrV650yzp16lihQoUSlbaRZ555xpWVGDt2rE2bNs3HZwI/TZkyxX766afgz71797by5cvbjh07kv09r2xV165dXRmaUF988YVbtm3b1vbu3WutWrWy1atX+3L+AKLT559/7pb6nEr4OaP3pMKFC7v1H374od12223Bzy3EiNMd6SL1NG2bStDoFtq/TSPMvULb+tYYiWZq8Dpmq7+m56233nJ9M9XMoSzmzJkzmc0niqnvo9c9IjQrPWnSpGA24bfffkuyiV3bO3bsGLZ+6tSproKAShppfuI777wzyRl/AGReZ5xxhnsPSVgWT+8/GknevXv3wNdff+1m/Bk/fvxpO0/4g0AzRug/qv4jt2jRImy9OlhrfeHChSPOY67+M2eddVawg7YGd1SrVi04CGj69Onp+CzgF33pKF26tPu7qh5qKNW89EqJRKKBY14wqnJYqoGqflXeiHNvYBAAROKVM1LlEX2m6EutSp7p/UMDEVXXGbGLQDPKJFUaRp2oFUzqP/PkyZOD65W98uYXf/jhhyP+rkb/eWUovJuCTmU7ETsUIOpvW7JkybAvHb/++qt7w9cXi6QKqat/bsJSRSpxpUwpACRHgWTCzxivpvLEiRN58WIcgWYU0ewsaoIIbeIO9eKLL7r/vBUqVHBNmR6N2lPzhAIDlamJRCVuNDhEzZ96HArhRqdvv/02cP/990fcpi8ddevWddfII488ErZNf3evBFYkGn2u+obqzK/rSxnN5Gb3ARCd1P1KLWRNmjQJdOjQwbWK6f9/aqm8mVebWV9s1foWWn8XsYtAM4p4M+uoeTupbKdXhubpp58O23b99de79Zdffnk6nS3Sm7LaXk26pPpKel86VJEgtGSICmQXKFAgYj8qAJlD//79wwqkezd9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94Vtb9u2rY0fP54gE4hhWbJksREjRljDhg1t2bJldv755yfap3Xr1nbBBRfYwoULrWfPnvb2228n2ufnn392QWbu3LmtYsWK6XT2yGxIeaTC7NmzrV27dpYvXz7Lmzevyz7+9ddfwe01atSwHDly2IoVK1xWMin6VlqrVi376KOP3M+33367rV271t577z17+umn7c0333TfSIcPH245c+ZMzSkjnW3evNn69etnLVu2dNnoUaNGhW0/55xzgm/4yVm9erVdffXV7nhnnHGGzZs3z8aMGWOdOnWyJk2a2M033+wC0C+++MJ9UQEQ2/R5c9111wU/i44fPx6xxUQtG++884498cQTLhPq0WeM3jeka9eu7nMM8EWwER0n1T9GA3YijcrTHOWh0/l5o/qSmnNaNIWg9tFc54gNKvUxbNiwYF260JtXt05UMF2zN2m9ZntKikaUap+vv/46nZ4BgIxu/fr1wXqYKmMWicqdee89VatWDdx1112Bq6++OpArVy63TjPJaQAi4BcCzVPQuXPn4Iw8jz32WGDlypWu3qGKYGu9/iN71MHaq1XmlTJKaraFMWPGnPpfEhmGygh5U7VpIJhm31CJEdWo05ShWhc6bag68Xtz04dOFRlp+sm5c+em4zMBkNENGDDAvTeobF5S7x+qb+l9ofVueh/q0qWLm/gD8BOB5kmaOHGi+09apkyZsPJEmivWK0+kkeO//vprcNttt90W/B2VqAkNSLw3CY388+olIrrpTd0rNfLtt98G12tEpspcaZtqzinrKZpSslixYm69Cvsn9MYbbwSL/EeqYQcgc08IUq5cuRNWJFF95vfeey8wcODAwCuvvOKmiATSA4HmSdLsOwmzj2vXrg1Ur17drfeaI5o1axZWkPvCCy8M1ivTt8gePXq4Jgvvm2VyRXURXby/deh8wPoi0qRJk7CMwptvvhlWaF1Zb61v27atawZT3To1cUXaHwA8KmfmTfgR2nULyAgINE/SVVdd5f5De8VrZ86cGSxWq2BTGctIfWbUPOH9buhNUwKGFuVG9FM/Xf1tvanXdE14mUzVv1RfXN3XNJOhfaNUrF3dMRJeI/pyogwEACTF+yJ7orqZQHqjjuZJ0sjvbdu2WZ8+fey///2v3Xjjja5ETdOmTe3jjz92ZWpUSuKFF16wsmXL2uLFi8PKHi1atMimT59u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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.eval(df_eval, baseline=0.5)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_clustermap.ipynb b/examples/prediction/aapred_plot_clustermap.ipynb new file mode 100644 index 00000000..8dfbe9ae --- /dev/null +++ b/examples/prediction/aapred_plot_clustermap.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6b858d78", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().clustermap()``, we cluster samples by explanation similarity — the correlation of their per-sample importance vectors (e.g. SHAP values). Here we use a small synthetic importance matrix; in practice these come from :class:`ShapModel` (``pro``):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ff10f250", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:42.742209Z", + "iopub.status.busy": "2026-07-02T12:04:42.742139Z", + "iopub.status.idle": "2026-07-02T12:04:44.207005Z", + "shell.execute_reply": "2026-07-02T12:04:44.206737Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False\n", + "\n", + "rng = np.random.RandomState(0)\n", + "# Two groups of samples with distinct explanation patterns.\n", + "block_a = rng.normal(0.6, 0.2, size=(6, 20))\n", + "block_b = rng.normal(-0.6, 0.2, size=(6, 20))\n", + "data = np.vstack([block_a, block_b])\n", + "labels = np.array([1] * 6 + [0] * 6)\n", + "names = [f\"Protein{i}\" for i in range(12)]" + ] + }, + { + "cell_type": "markdown", + "id": "caedb91d", + "metadata": {}, + "source": [ + "Samples group by *why* the model scores them; the class sidebars show the two groups separate:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f8cb4fd4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:44.208361Z", + "iopub.status.busy": "2026-07-02T12:04:44.208248Z", + "iopub.status.idle": "2026-07-02T12:04:44.399545Z", + "shell.execute_reply": "2026-07-02T12:04:44.399313Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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d98dhxxySHTp0iXWX3/9isnRTj/99Bg6dGhNNxEAAKDemv8Kdh0MrKXks88+i7322isGDx78k8nM0uRol112WVx//fXx2GOPxdZbb12DLQUAAKifGi7IJGelttUV3333Xey44445XC+77LJx/PHHz3G8adOmsc4668TEiRNjt912iy+//LLG2goAAFBfzXfAbtCgQcltdcUll1wSX3zxRWy22Wbx/vvvx8UXX/yTgP3666/HJptsEpMmTYorrriixtoKAABQX5lFvATcd999+fHSSy+NNm3a5Er1jzVr1ix69+4du+66azzzzDM10EoAAID6bQHWwZ7vYjcFSuOr0/jrRo0axaabbjrXczfYYIP8OGzYML8DAACAWhuw606P65JTVlaWH6dNmxbNmzef6yzjSZMmTaqtbcypZ8+eMWXKlFp9W0aNGhXdu3eP2iz9nfft27emmwEAAAuri7gKdk1Iles0sdl///vfPM56q622qvLcJ598Mj+utNJK1dhCZpfCdb9+/dyUX6i2fwEAAAC/bJkuFewas+eee8ZVV12Vq6NPPfVUped8/PHHcc455+TnXbt2reYWAkDNK4VeRHWx19GC0ksJqMtUsEvAWWedFXfeeWe89NJLeRz2QQcdlPfPmjUrHnnkkXjhhRfi6quvjsmTJ0fHjh3jhBNOqOkmA0C104uoNNS1LwwAfuYkZ0rYNWXppZeOhx9+OPbee+94991385akQL3HHntUnLfEEkvk89q3b19jbQUAAKivrINdIrbccssYPHhwnH322dG5c+eKdb7T46qrrhpnnHFGDt4bb7xxTTcVAACgXprvCnajhv87kzXV77rrros11lgjtt566zyzctrS8l1pPexWrVpF48bz/WsEAABgITHJWQn44x//GGPGjIlXXnmlYi3sNLt4u3btarppAEAtV9smf6tNE7eZcA2ouQp2AxXsmjB9+vQcrlOg1v0bAFhQJn+rWm0J+kA9HIPdsEFZyW11QZMmTfK61qlL+KefflrTzQEAAOAXB+yGpbfVFX//+9+jTZs2se+++8Zzzz2Xq9oAAADULrqI13JTp06Ne++9NzbZZJN4+umnY9ttt83jhZZaaqlo2bJlpe9J+1977bVqbysAAEB9Nv/rYNeRLtelJlWrr7/++p+Mpfriiy+qfE+aWRwAAIDaGrAt01UjUrX65ptvXuBx2wAAANTWgL1w20EV0hrXqWJdvg52gwYN3CsAAICSHoOtgl2r1sEGoPauC7ww1/m1bi8A1IGA3cAY7BphHWyA4pXyusDW7QWA2sss4vMwcuTIeOaZZ+K///1vLLHEErHbbrvFkksuWe3rYH/++ed5HezVVlut2j4bAACA+aeL+Fxcc801cdZZZ8WkSZPmCLwXXnhhnH766VGd62DvueeeeR3sq666KrbYYgsTmQFQcnTNB6CuW4Au4lGv3H333XHSSSfl5127do111lkn3nzzzXjyySfjjDPOiEUXXTSOOuqohd4O62ADUFfomg9AXaeLeBXjns8888z8PFWMy4N2cvnll+fxb+lb+IMOOihPNrMwWQcbAACgNKhgV+Jf//pXDBs2LC+NdeKJJ85x7NRTT41bbrkl3nnnnejfv3/stddeC/UXZB1sAACA0mAMdiWef/75/Lj77rtXuu70HnvskQN26i6+sAN2Wgf7yCOPXKifAQAAwC+ni3glPv744/y41lprVXrTOnfunB8//PDDAn4FAAAA1AXWwa5iaa6kU6dOld60pZZaKj+OHj06qtOECRPiuuuui0ceeSR/CTBx4sRo27ZtrL766rnafvzxx0fLli2rtU3UHQt7dt8FMWrUqFq11m8aqtG3b9+abgZAvWPmeaDU1Oku4mkG7rRVpVmzZnn7sR9++CE/tmjRotL3le8vP686pC7p3bp1i6FDh86xPy0hNnz48HjuuefipptuiocffjhWWWWVamsXdUcpz+67sNWmsA9Qn5Ty/zb53w6onxouyImltqWKU7t27arcqqpINWz4v7elsvHXSVlZ2RznLWzffvtt7Lbbbjlcp4r173//+7jzzjtzJfvmm2/Oy4WlLwo++OCDvFb23L5UAAAAYOGo02OwU7eiHj16VHm8sup10rp16/w4efLkSo+Xd6OtqsJdtEsvvTS++eabWG655WLAgAGx/PLLz3E8TYJ2yimnxHbbbRfvvvtu3H777XHsscdWS9sAAAD4X/Ndgm3QoKzkthSgU8W3qq2qgL3kkkvmxxEjRlR6PHXJnv28he2BBx6oWJP7x+G63HrrrRcXXHBBfn7PPfdUS7sAAAD4GQE7VbBLbfu50vrXyZAhQyo9Xr4/TS62sM2aNSs+++yz3B19l112meu5Xbt2nWMWdAAAAKqPgF2JrbfeOj8+9thjld60NPZ59vMWdsBOWwrYTZo0medMx3Pr2g4AAECt6CJeetvPtdNOO8Viiy2WZ+6+/PLL5zh2xRVX5P1pCa+99torFrbGjRvnz5oxY0YMHDhwrue++OKL+TGN1QYAAKB6qWBXIo3NLp9hPC2xkLpmpwnT0uOpp56a96fjTZs2rZZfUnnX8NNPP73KdYrHjRsXf/jDH/LznXfeuVraBQAAwM+YRfwXFIRLUpqFe9SoUdG7d+944okn8pa0bNkyzj///Dj88MOrrS1nnnlm3HXXXfHSSy9Fly5d8szom2yySV5qbOzYsfHCCy/EJZdcEl9++WXed/LJJ1db2wAAAKgHy3T9UqkinIJ0//7981rUqev1tttuG0sttVS1tiNNpnbbbbfFIYccEoMHD46jjz66yuXF7rvvvujYsWO1tg8AAAABe56WWWaZWrGm9H777RedO3fO1fMU+L///vuKYx06dIhu3brFOeecE6uuumqNthMAAKC+mv8u4vWwgl3brLPOOnHvvffGzJkzY+TIkTFhwoS8nneqWDf4JbO6AQAA8IvpIl5iysrKolGjRnlm8SSF7bRO9iqrrFLTTQMAAKjXGi7IiaW21SVTp06NP/7xj7HmmmvOsT+teZ26ha+xxhpx991311j7AAAA6rv5rmA31EW8xkyaNCkv1ZXWuW7VqtUcxxo2bJgr2h9++GEcfPDB8fbbb8dFF11UY20FAACor+a/gt2grOS2uiItwZXC9WKLLRb33HPPHMfSsmGjR4/O63I3btw4Lr744oolxQAAAKiFATst01VqW12R1sBObrzxxth9991/crx9+/Zx9tln5y7kyVVXXVXtbQQAAKjvFqCCXXpbXZAmMfv0009zV/DddtttrucedNBB+fHNN9+sptYBAABQzhjsEpDCdZK6gM9NqmQn06dPr5Z2AQAA8H8s01XLpQnMlllmmfjqq6/i5Zdfji233LLKc1999dX8uNxyy1VjC6Hm9ezZM6ZMmbLQrj9q1Kjo3r37Qrl28+bN8xwKAADUo4BdR3pcl6S99torrrjiijj11FPjmWeeibZt2/7knDFjxsRZZ52Vn8+rKznUNSlc9+vXL0rRwgruAABUPxXsEpCC9a233hqDBg2KtddeO4499tjYaKONonXr1vHdd9/FG2+8EX//+99jxIgRscQSS8Qpp5xS000GAACod+a/gt1ADbumrLDCCvGPf/wjT2I2dOjQ6NWrV6XndezYMR5++OG8nBcAAAC1dpIzAbsm7bjjjvH+++/HddddF48++mh8/vnnMW7cuGjVqlV07tw5unXrFieeeGIsuuiiNdpOAACA+mr+A7ZR2DVuySWXzNXrqirYAAAAlEQX8YXbEAAAAKgnk5xJ2AAAAFDAMl0CNgAAAFTFJGcAAABQAAEbAAAAqncWcQAAAKAqKtgAAABQvct0meQMAAAAqqKCXWKGDRsW48ePjxkzZkRZWVml5zRq1CjWWWedam8bAABAfbYAy3RRk+644474wx/+EEOHDp3nua1atYqJEydWS7sAAABYwIDdSBfxGvPoo4/GYYcdVnMNAAAAYJ50ES8BF198cX5cbbXV4uqrr45f/epX0aZNmyrPN14eAACgVncR10m8pgwcODA/3n777bHxxhvXWDsAAAComgp2LTd9+vSYNm1aNG7cOLp06VLTzQEAAKAKDWM+pSHYpbbVBU2aNIklllgizxo+ZsyYmm4OAAAAvzRgN4oGJbfVFXvssUdFF3EAAABKvoLdsOS2uqJXr16x+OKLx5///Oe47bbbYurUqTXdJAAAAH7EOti1XArTf/zjH2OVVVaJV155JY444og49thjo1OnTtG8efMq18EeNGhQtbcVAACgPpv/gF2HKsKlNsnZrbfe+pN9X331VZXvSQEbAACA6qWCXculKvWCjr1OE6MBAABQvVSwa7m0PNehhx5a080AAABgHlSwAQAAoFpnEY+GJbfVNRMmTIhLLrkktt566+jYsWO0bt06T3a27bbbxmWXXRY//PBDTTcRAACg3lqALuJ1Z13pUvTOO+9Et27dYujQoXPsnzRpUgwfPjyee+65uOmmm+Lhhx/OM44DAABQa7uIC9g15dtvv43ddtstvvnmm2jbtm1eqmvTTTfNz9OxAQMGxF133RUffPBB7LvvvvHaa69Fs2bNaqy9AAAA9ZEKdgm49NJLc7hebrnlcphefvnl5zh+5JFHximnnBLbbbddvPvuu3nW8bRWNgAAALVyDHbp/V9d8cADD+THq6666ifhutx6660XF1xwQX5+zz33VGv7AAAA0EW81ps1a1Z89tln0bBhw9hll13mem7Xrl3z48cff1xNrQMAAKCcLuIlELDTltbDbtKkyVzPbd68eX6cPHlyNbUOAACAcrqI13IpWKeluGbMmBEDBw6c67kvvvhifkxjtQEAAKitAbtBw5Lb6oryruGnn356TJkypdJzxo0bF3/4wx/y85133rla2wcAAMACVbBLb6srzjzzzNz9+6WXXoouXbrEzTffnJfk+vrrr+O9996Lv/3tb7HhhhvGhx9+GO3atYuTTz65ppsMAABQ7yzAOth1pyJcalZfffW47bbb4pBDDonBgwfH0UcfXel5rVu3jvvuuy86duxY7W0EAACo7xagi3jpbXXJfvvtF4MGDYr9998/2rZtO8exDh06xBFHHBFvvvlm7LjjjjXWRgAAgPpMBbuErLPOOnHvvffGzJkzY+TIkTFhwoQctlPFukFd+0YBAACgxFimqwQ1atQozywOAABASVawVUhro7fffjv69+8fTZs2jT333DNWXnnlmm4SAABAvbQAY7AblNxWl/zP//xPnuzsx/vS7OE9e/aM0047LTp37hw33nhjjbURAACgPluAZbpK7//qiqeffjqOPfbYvCzX7Oten3rqqVFWVhaLLrpoHp89Y8aMOP744/PSXQAAANTSgN0wL9RVWltdcfXVV+fH2de3fuihh2LixIm5Uv/iiy/Gu+++G0ceeWSeAO2aa66pwdYCAADUT/O/uHVNr7lVj9fpevnll/PjKaecUrHv0UcfzY+pi3jqGj57AB8wYECNtBMAAKA+m+9JzupSRbiUpIr06NGjo0mTJrHkkkvmfbNmzYpnnnkmP996660rzl1uueXyY1rCCwAAgFq7TNf8F7spVuPGjXPQTsG6YcOG8c4778R3332Xj2277bYV56V1sZM0ozgAAAC1dpKz0tvqyprXqTKdwvWHH36Y9z311FP5sVmzZnME7PKu5CuttFINtRYAAKD+skxXCSgP0SeddFLce++9cfnll+fXu+66a7Ru3To/Hzx4cPzhD3+o2A8AAEBt7SJeZ2rCpeePf/xjnjX82WefzVvSvHnzOPfcc/PzSZMm5WW6ktVWW22O2cYBAACoHtbBLgErrLBCDBw4MA4++OBYe+21o1u3bvH8889XhOq0FnaaBG3ffffN3cTbtm1b000GAACodxZgkjMV7Jpy3XXXxRprrBF33HFHpb+Hli1b5pnD27dvXyPtAwAAwDJdC91dd90V06ZNiyOPPPIXdREfM2ZMvPLKK7Hpppv+5HiaWVy4BgAAKJlJzhqW3FbTUiA+/PDD45ZbbvnZ15g+fXoO12k28Y033rjQ9gEAAFAcy3QtJO+9914eK53Wr/4l0tjqtOxWus6nn35aWPsAAAAolgr2QnDffffF5ptvnivPRfj73/8ebdq0yZOYPffcc7mqDQAAQMku08W8fP3113HcccfFv//97zwuep999okHHnjgF924qVOn5rWvN9lkk3j66afzmthpia6llloqT25WmbT/tdde8wsDAAConbOI1/yY5tru9ddfz+G6c+fOceONN8aUKVN+ccBO1errr79+jn3pul988UWV72nVqtUv+kwAAAAWYsBuqIY9T6mqfMMNN8RRRx0VjRs3zhXnXypVq2+++eYFHrcNAABAre0irpP4vKRu3GkrUgrqv2SJLwAAAGpdF/HSC9hp/HLaqtKsWbO8/ViqPL/44ovz9Rm//vWvY+211/5F7QQAAKAeBezmjdpFqel9Xu/o06dPlcd79eoVvXv3rjRgX3TRRfP1GWm8dXUF7LKyshg1alRMmjQpZs2aVbE/PZ82bVp8//338fHHH8ezzz4bt956a7W0CQAAgAUM2KWoZ8+e0aNHjyqPV1a9TnbYYYc89nl+VFe4vu666/KXBSNGjJjnuWmSMwEbAACgetXpgF1VF/B5SQE7bbXFo48+Gscff/x8nbveeuvFNttss9DbBAAAwJysvVUCrrzyyopq+UsvvZS7iS+66KJ5XPz7778fb7zxRnTr1i2fc8wxx8QVV1xRwy0GAACofwTsEvDqq6/mxxScN99881h88cXzbOVpTPaXX34ZG220UV5ve4MNNojTTz897wMAAKB6Cdi13PTp02PChAn5+WabbVaxf6211sqPH3zwQcVyXmeddVae7Ozyyy+vodYCAADUX3V6DHZd0KhRo9wVPAXoFi1aVOxfaaWV8uPnn39esa88gKdZ0AEAAKheKti1XMOGDWPJJZfMlew09rrciiuumB8//fTTin3lM5/Pz0zjAAAAFEvAXohSlTmttX3kkUf+outstdVW+fGGG26o2LfGGmvkxzTB2eTJk/Pzt99+Oz+mMdoAAABULwF7IQfs3r17/+KAfeKJJ+bHP//5z3H44Yfn58stt1ye3GzcuHF55vAHH3wwTj311DnGZwMAAFB9BOwSkNa17tOnT36eZgsvd/bZZ+fHu+++O/bZZ58YMmRIfp0mOwMAAKB6CdglIlWvX3/99Tj++OMr9v3617+Ov/3tbxVdwpdYYom45ZZbokuXLjXYUgAAgPrJLOIlJHUJT9vsUuD+3e9+F999910sssgiecZxAAAAqp+AXQekUN2+ffuabgYAAEC9pot4CZkxY0Yeb33IIYfkbuDrr79+3p9mET/99NNj6NChNd1EAACAeksFu0R89tlnsddee8XgwYMr9rVq1So/zpw5My677LK4/vrr47HHHoutt966BlsKAABQPzUoKysrq+lGMHdpfPWGG24YX3zxRSy77LKx++67x7XXXpsD9sSJE2PatGnxq1/9Kt577728L4XwFVZYwW0FAACoRgJ2CTjnnHPiwgsvjM022yyeeOKJPOa6TZs2FQE7mTp1al7Oa+DAgXk97H79+tV0s2utFc5+LErV4w+dHqVq170ujVJVyve9lPmbcd/r0/+vlvLfeykr5b+ZNT783+VZgdrFGOwScN999+XHSy+9NAfryjRr1ix69+6dnz/zzDPV2j4AAAAE7Fovja9O468bNWoUm2666VzP3WCDDfLjsGHDqql1AAAAlFPBLgFpmHza0ljrec0ynjRp0qSaWgYAAEA5AbuWS5XrNLHZrFmz4vXXX5/ruU8++WR+XGmllaqpdQAAAJQTsEvAnnvumR979uyZ17yuzMcff5wnQ0u6du1are0DAABAwC4JZ511VnTo0CFeeumlPA776quvzvtTVfuRRx6JM888M9Zff/0YPnx4dOzYMU444YSabjIAAEC907imG8C8Lb300vHwww/H3nvvHe+++27eklTN3mOPPSrOW2KJJfJ57du3d1sBAACqmS7iJWLLLbeMwYMHx9lnnx2dO3fOa2En6XHVVVeNM844IwfvjTfeuKabCgAAUC+pYJeQJZdcMvr27Zu3tHzXxIkTo1WrVtG4sV8jAABATZPMSnh28Xbt2tV0MwAAAPj/Cdi11PTp0+Ppp5+OV155JcaPHx+dOnWKHXbYITbaaKOabhoAAACVELBroTfffDMOPvjg+Oijj35ybLfddoubb745T2gGAABA7WGSs1omLbW14447VoTr5s2bx1JLLVUxqdm///3vHLJThRsAAIDaQ8CuZS699NIYO3ZstGnTJu644448kdk333wT33//ffTu3TuPvR40aFDcc889Nd1UAAAAZiNg1zL9+/fPjzfccEMccsghOVAnrVu3jl69esXJJ5+cXz/yyCM12k4AAADmJGDXMp999ll+3GuvvSo9ftBBB+XHtOY1AAAAtYeAXYtMmzYtpk6dGi1atMhjryuz4oor5sfUjRwAAIDaQ8CuRconLmvYsOpfS3nwnjx5crW1CwAAgHkTsGuRsrKyhXIuAAAAC5+ADQAAAAUQsAEAAKAAjYu4CMWaOXNmPProo5UemzJlyjzPady4ceyyyy5+LQAAANVIwK6FUoju1q3bzz6nVatWMXHixIXUOgAAACqjizgAAAAUoEGZ6ahrlW+//fYXX6NBgwax6KKLRilb4ezHFsp1v/xL12ix7blRqvY4a88oVcu3/yFK1RdjW0Wpat9yWpSqzZYaU9NNqJeWadUkStWkGf+73GUpmlnCq4P8MCNK1pipTaNUnbrWdjXdBKASuojXMosttlhNNwEAAICfQRdxAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAoQGN3EQAAgPk1fPjwGD16dDRt2jSWXXbZaNWqVZXnfv/99/Hxxx9HkyZNYr311qvzN1kFGwAAgLn65JNP4oQTToillloqOnXqlMPyGmusEW3bto3NNtss7rrrrkrf9/LLL0eXLl1i++23rxd3WMAGAACgStdee22su+66+XHEiBHRsmXL6Ny5cw7bZWVl8eqrr8YhhxwS3bp1i0mTJtXrOylgAwAAUKk///nPuXI9ZcqU2H333XOYTiF6yJAh8c0338TXX38dv//976NBgwbx6KOPxqGHHlqv76SADQAAwE8899xzccEFF+Tnp59+ejzyyCOxySabzHFOqmJfeeWVcfHFF+fXDz30UN7qKwEbAACAn+jevXvMmjUrNthgg4oAXZUePXrEmmuumZ/fcMMNC3w3U1U8jfN+9913Y9iwYfP1nsmTJ8cHH3wQgwcPjlGjRi209ywIARsAAIA5vPfee/H222/n52eeeWbuAj7XYNmwYdx6663x2muvxb/+9a/5vpsPPPBAbLHFFtGuXbtYbbXV8uRpaWbyVBn/05/+FNOmTfvJe1JATt3V03vWWmutWGeddWLJJZeMFVZYIc4777yYPn16Ie/5OSzTBQAAwBwGDBiQH1Ow3mGHHebr7vzqV79aoLt47rnnRq9evfLzNBv5Msssk8d6Dx06NE+mdv7558eXX34Zt99+e8V70usUyL/77ru89Ncqq6ySJ11L53/11Vd5zPinn36aw/4vec/PpYINAADAHFLgTJZYYolYbLHFCr87H374YQ7YydVXXx1jxoyJ999/Pz777LM8edpBBx2Uj6Xlv2bvyt2vX78clLfddtsckFO38nfeeSdGjhwZTzzxRA756dqzz2b+c97zc6lgAwAAMIcJEybkx0UXXXSh3JmBAwfG+uuvHxtttFGceOKJcxxLgT4tCXb33XfnMeAp7Kegn3z++ef5ceutt44OHTrM8b6ddtopbz/2c97zc6lgAwAAMIf27dvnx4W1rvURRxwRb7zxRlx//fWVHk/ra6du3EnqNl4urcedpO7jBx54YNxyyy0VAboqP+c9P5cKNgAAAHNIk4wlo0ePzmF3XpOc/VwzZ86MF198MU9ClkLvF198kZ9/9NFHuXqdlD+Wz1Z+//33x8cffxz33ntv3pKVVlopT2J28MEH/2QpsZ/znp9LwAYAgHrg22+/zWNdkxSWmjVrlsekLmiwGDRoUO7WW5UUkl599dUcWqqSwk5aJqlcqip27tx5gdrBwpW6byc//PBDHq9c/npuPvroo/j3v/8d22yzTT4/zSw+N3//+9/zDN7//e9/f3JskUUWie+//36OcF3eZT39Daa1t9P47DRuu/zvLu1L2+GHHx433XRTNGrU6Ge/5+fSRRwAAOpJwP7rX/+an6eK5Lhx42KfffaJ2267bb6vkSabSmH4l+rbt2+edIraK41XLu8m/tBDD83Xe2699dZcLd5yyy3zetNz8z//8z/xm9/8JofrVVddNX7729/GpZdempftShXsNOlZmvG7Mq1bt44//OEP+UuaNAt46vJ9zDHHRMeOHfPx9Dd9xx13/OL3/Bwq2AAAUE+kpZB69+5d8TpVGf/2t7/F5ptvnquPqTtwmkxqt912y4H8vvvuy+fttddeuctwCiEp+KRK+EknnZRDcjonrVWczll66aUrrp0mpkozMzdu3DgHpu233z6vcZzWG05ddVOVu6oARc1Lv7f0ZUqabCx9MXPcccdFp06dqjw/zc593XXX5ef77bdftGrVaq7Xv+yyy/Jjmi08/V2lz5tdCt5Tp07Nz8ur2Kk7+VtvvZWfp7+l9Pez3HLL5fHcaUtjtbfaaqs8tvuFF17I+37Oe34JFWwAACgxKXik7rNVbeXBZF5SF95UzU5hOAWLZ555Ji9dlCrVaWKol19+OXet3WCDDfJawrMbP358bLbZZjmYpMCcKp5p/Gzqdpu64aZrHnrooTmMp2WR0rlpCaYUtlPAefTRR+Pyyy+vWA6K2uecc87JXbXTjOI77rhjlRODpb+XPffcM/eKSBOTpXWl5+Xrr7/Oj+uss85PwnWqfs8+s3j533Pqvn3AAQdEly5dKu150bx584qJ0cqr7z/nPb9Evapg9+zZc44Z6KjFms3fYvYAAPVR6mLdp0+fKo/36tVrjkp1uRS+y/ePHTs2L4OUQm6SqpPlAeTCCy+MfffdN6666qqK66XnZ5xxRq5Spup1GreaAkn5ZFgpNKcZoXfY4f/+HZeumYJ0kirfr7/+eg7uKfCkcb1pveO0FvKTTz6Z1yKmdkk9Eu68887cOyF9MbL22mvnqvbOO+8cyyyzTA7ezz//fNxwww3576lRo0a56/fKK688z2un33f6QueCCy7IX/SkuQBSpTpVm1OvihTm0zwBKVzPPpwgVdLPPvvsOOGEE/JY6vT3lr4ESBX09Pc7YMCAfL3DDjvsF73n56pXATuF67TIOLXfg2c/VtNNAACo1YWjNNa1KimYzE2a5GzZZZeNRx55JDbddNPo379/rLDCChXHU7V64403nqMreQpas0vntGnTpuJ1mtiqfDmkcqusssoc3dNnzJgRa6655hwVzhTG0mRXAnbtlIYL/Oc//4mjjz469za4+eab8/Zj6cuUG2+8MZ8/P1K38+222y4H8xR+Z9euXbs8nvuxxx6Lf/zjH3NMiHfmmWfG22+/Hffcc0/Odj/Ody1atMjd2mf/W/w57/m56lXABgCAuiAF6HmF6PkZgz272Wd8TuE7VZjLpapiClCzL9WUzhk2bFjF9VI1MnU3T1tl1yyXlmRKlc9dd921ojtweRddaqc0RnnIkCH5i5innnoqj9dPQwTS32Ba6iqNr089Hpo3b/6T96awnGadTxXj2aXx0GnIwDXXXJP/voYPH55n+05f1Bx11FHRoUOHXBFP5wwdOrTifelvMPW8SLN+P/zww3l4QvobWmyxxfKXRWl/+tuc3c95z88lYAMAAHNIszuncdflIerBBx/M3WjTxFUpCKVuvccff3yeqCoFqxRU0kzTqdKZut3OTepWvvfee0e3bt1yAE+hLXUzpnZL46TTmtFpWxCb/f/j9CuTQnca512VQw45JG+VSV/QlH9JM79+znsWlEnOAACgHkghuKpu5akr9+zLb6Wli1IFO80unsbdpu61qWt3WuoorWGdHlOF8d13382BJa1hnSZES+emimZaA/vH19xjjz1yF9y11lorj8VO3c7TxFMpfM3PmF0oBQ3KZu/DUcd1797dGOwSscJCGoP95V+6Rottz41StcdZe0apWr79D1Gqvhg792UmarP2LadFqdpsqTE13YR6aZlWpbtkzqQZ06NUzSzhf479MCNK1pipTaNUnbrWdjXdBKASKtgAAABQAAEbAAAACiBgAwAAgIANAAAAtYMKNgAAABRAwAYAAIACCNgAAABQAAEbAAAACtC4iIsAAABQf5SVlcXk6TOr7fNaNGkUDRo0mO/zp06dGr/73e/i5ptvjuokYAMAALBAUrhe889PVNtd++DcnaNl08bzHa6POOKIeOihhwRsAAAA+Dk+/PDD2G+//WLSpElRE4zBBgAAoE4YMGBA7L///nH//ffXyOfrIg4AAECdcNxxx+XHwYMH18jnq2ADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACgTll++eXjvvvuq/bPtUwXAAAAC6RFk0bxwbk7V+vnLYg2bdpEt27doroJ2AAAACyQBg0aRMum4uSP6SIOAAAABfCVA7XS4w+dvnAu/JeuscdZe0ap+tdFD0epKuX7PmLk5ChVa607PUrV0Ekto1Qt0rR07/uIydOiVL0zZpEoVau2mxClauU2C9Ztszb5cHyzmm4CUMeoYAMAAEABBGwAAAAogIANAAAABTAGGwAAgAVSVlYWZZOrb56aBi1a5JnLazsBGwAAgAWSwvVHG25UbXdt9TcHRYOW854Adfr06XHBBRfE008/HUsttVSce+65scYaa0R10UUcAACAOqFnz57Rv3//HKxXXnnl2H777WPChOpbqUHABgAAoORNnjw5/va3v8UNN9wQ2223XfzlL3+JFVdcMe65555qa4OADQAAQMl77733onHjxrHuuutW7Ntiiy3i1VdfrbY2CNgAAACUvNGjR8fiiy8+x770euTIkdXWBgEbAACAkjd9+vRo1KjRHPsaNmwYs2bNqrY2CNgAAACUvHbt2sXEiRPn2JcmOGvbtm21tUHABgAAoOStscYauTv4+PHjK/Z99NFHsdZaa1VbGwRsAAAASl7Hjh1js802i4suuii/Hjx4cDz66KOx7777VlsbBGwAAADqhBtuuCHuuuuuWGaZZWLTTTeN8847L9Zcc81q+/zG1fZJAAAAsBClMP3pp5/mbamllsrjsquTgA0AAMACadCiRaz+5qBq/bz5ldbC7ty5c9QEARsAAIAF0qBBg2jQsqW79iPGYAMAAEABBGwAAAAopS7iPXv2jClTpkRNGjVqVI1+PgAAAHVXtQXsFK779esXNal79+41+vkAAADUXSY5AwCAemDSpEnxwgsvVExQ1axZs9hoo42iTZs2C3Sd7777LhZZZJEqj48ZMya++uqr2HDDDed5rcGDB8dqq60WTZs2rdj35Zdf5jYtuuiiC9QuqA2MwQYAgHpg6NChsddee8Xll1+ee5b26tUrVlhhhXj55Zfn+xrjxo2L7bbbbp6f8/DDD8/zWoMGDYott9wyxo4dW7Hv66+/jh122CHeeuut+W4T1CYq2AAAUE8stthi0b9//4rXV1xxRZx33nlx++23x+jRo6OsrCxatGgRK664Yn7+9ttv5/PWXXfdaNSoUQ7jI0aMiBdffDGH43ROqkJPmzYt1ltvvbz+8LLLLht77rlnfPvtt3kOpFSJ/uyzz2KdddapqJanz7366qvz+8v961//ipNPPnmOfVBqBGwAAKinVl999Ryu33jjjTjttNPyvt122y3OP//8XElOATl1306h+z//+U88+OCDMX78+Lj11ltj0003jd133z2GDx8ezZs3j5kzZ8YzzzyTq8+pSn7SSSfFKaeckvenYJ0C/DvvvJMDd9u2beO1116LDTbYoKIt06dPj+effz5OPPHEGrwj8MsI2AAAUGKmTp2at6qk8dVpq+x95RXs1DX74osvjn322adijPann36aq9DXXHNNDsIDBgzI47WPOuqoXHVOwfu5556LG2+8MQfzGTNmxEUXXZTff/311+f3denSpeLz0jU/+eSTHNBTYE8BfL/99svX+7F99923kHsDNUnArgPLj9VFx9V0AwAAarG+fftGnz59qjyexlf37t37J/snTJiQq8spNLds2TKOPPLIXGl++umnY80118zhOnnvvfeia9eu+bykW7ducc8998xxrVSN/uabb/L1ys0+WVmSuo2ncJ2k8d6pKznUZQJ2HVh+rC4a8vj/jQ0CAOCnRZ4ePXpUeVsqq15XNgZ7drOH4zRLeArPs08+9uPZxtu1a5cnPEtjqctn/07/Nh42bFjFOU2aNJnjPcZXU9cJ2AAAUGKq6gJelIMPPjiH58UXXzx/Tuoafuedd+bnaRmuFNIPOOCA3B18+eWXz8E9nXPttddGw4YWKqL+8tcPAAD1QOvWrWPbbbet9FgK0rOvW51mDU9LbaWJyFL38TTeOo2hbt++fZx66qlx99135/Wr07GPPvooB+5LL700dtpppxy20/raP77m2muvHZ06dZrjc7fZZpuffFGQ3puuAaVIBRsAAOqBZZZZJgflyqRQm7bZbbHFFnmrbHx3uVTBnn1Ss2T99dfPW/l1y6Vg/mNpNvIfm9vYcqjtVLABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEaF3ERKNque126UG7qlxGxfPsfolTtcdaeUar+ddHDUapW3n2rKFW/WmxGlKpb318yStXmy46JUvX5hFZRql7/qHTrBstt1ChK1VNft4hSNWla6d53oHYq3f8lAgAAgFpEwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAK0DjqkebNm0f37t0LveaoUaMKvR4AAAClqV4F7L59+xZ+zaIDOwAAAKVJF3EAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAAClCv1sEGAID6qqysLCZNmpSfN2jQIJo2bRpNmjQp/HNmzpwZ06dPj+bNm8+zPWlr2FDNj7rDXzMAANQDH330UbRt2zY6duwYSy65ZLRu3Tq22mqrGDZs2HxfIwX0/ffff67nvPDCC3HYYYfNNYD/5je/yZ/fpk2bOProo/M+qAsEbAAAqCc6deoUEydOzNvkyZNj/fXXj7POOisH3GnTpsWsWbMqrTKXGz16dAwaNCh++OGHOc6b/X0ptN9+++35mlOnTv3J8QcffDBGjBgRI0eOjK+//jo+/PDDuOOOOxbiTw3VR8AGAIB6KHXN3mOPPeKDDz6Ip556KnbbbbccwI844oh8PAXvVPFeZJFF4uSTT84h+aCDDoovvvgih+jkvPPOiw4dOuRzTjjhhHzOs88+G7/+9a/zNXfaaadYe+21o0WLFtG1a9ccuNOxRx55JFew0/u23nrrHNyhLjAGGwAASkwKquXV4co0a9Ysbz+WqtGpep2MHTs2/v73v8fGG2+cX7/11lvx/vvv51B9//33x5NPPhmffPJJHqu95557xk033ZSrz1tuuWWuYqcA/fzzz8fgwYPzOb/97W/j5ptvjuWXX77i89I1Bw4cGEsvvXR+X3rP7rvvXnH8m2++iXvvvTd/FtQFAnYt0LNnz5gyZUpNN6N2abZDTbcAAKDW6tu3b/Tp06fK47169YrevXv/ZP/w4cPzGOw0yVnLli1zJfqCCy6IN954IzbYYIN8rHwc9bHHHlvx+qSTTspV59nDcapUv/jii7HaaqtV7Evnzx6wN91001hjjTXy8y5dusT48eMrjn355Zc5uP/1r3+NVVdd9RffE6gNBOxaIIXrfv361XQzapUHz36sppsAAFCrCzQ9evSo8nhl1eskdQGvalKz1GW7XArgaSbwcpWNz07OPPPMOPfcc/PzGTNm5HHXKZyXSyF+9muWj+dOle00EdrVV18d/+///b95/LRQOgRsAAAoMVV1AS/KjjvuGN27d4/tttsuf85ll12Wx2E3btw4zyT+7bff5vHVBx54YOy6666x2GKL5S7i6fVKK60012unic3222+/+Mc//pGr36nLeupinjYodQI2AADUk0nNWrVqVemxFJzTRGTl0oRkQ4YMiX322SeP9T7qqKPylqRJy9LEZGlytD/96U95/4QJE/IEaKlb+YABA/K1fnzNtC52Wnf7iiuuyLOIp2uUO+200+ba5R1KhYANAAD1QKoWp7WwK7PDDjvkbXann3563n4sTVRW7sQTT8zb7FKX7/Ju37NfM3UHTw444IC4+OKLf+FPA7WTZboAAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEaF3GR+qx58+bRvXv3X3SNUaNGFdaeuuLxh05fOBf+S9f4YmyrKFUjRk6OUrXy7ltFqfrs0ReiVH2/V5coVTPLGkSpGjetSZSqZg1nRalaZ6UoWet1aBSlasU2E6JUvTO2eU03AahjBOxfqG/fvr/4l/BLAzoAAAA1TxdxAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAUA988cUXsfbaa+dtnXXWiV/96ldxxhlnxA8//DDf15g2bVpceOGFcz3n1VdfjRNPPHGu5wwePDj23Xff2HzzzeOaa66Z78+H2k7ABgCAemDq1Knx7bffxj333BN33313XHvttTno/ulPf5rva4wdOzZuuummuZ6TwvtZZ50112tst912sc0228Rf/vKXuPLKK+P5559foJ8FaisBGwAA6onGjRtXVLG7dOkSJ598cjz77LPx0ksvxWmnnRa77757/PnPf87n/vOf/4ztt98+b3fddVfe97vf/S6GDh0ae+yxR37dv3//2HnnnWPbbbeN2267Le9777334qKLLsrXPPXUU+PYY4/NlerzzjsvysrK4qGHHoqddtopf/bWW28dAwYMyO2BuqBxTTcAAABY8Gp02qrSrFmzvM3LK6+8EiussEJMmDAhrrvuuhykV1tttbz/lFNOyd2303WOP/746NChQ/Ts2TPefPPNuOyyy3KQPvvss+OSSy6J5s2b5+7miy++eDRp0iS++uqrfM2rr746brzxxjjooIPi8MMPz1XrIUOGxLLLLpv3/fe//42DDz54nl3KoVQI2LVA+g9S9+7da7oZtcpxNd0AAIBarG/fvtGnT58qj/fq1St69+79k/0jR46sqBanrtqpov3444/nqvSGG24Ye+65Zz6Wunj//ve/r3h9zjnnxP333x/nn39+NG3aNFZdddU499xzY/jw4RX/jh03blw88sgjsc8++1R83hZbbBFHHXVUfr7jjjvmz0ljvh988MEc3tu1a5fD9dJLLx177bVXwXcJqp+AXUv+A8mchjze3y0BAKhCqiT36NGjyvtTVfV60UUXzWOwGzRoEC1btszV6/Q8Bd90rNz3338fK664YsXrFITTvtmlCvWBBx4Yv/nNbyr2LbLIIvHhhx9WvG7fvn3F8xTmUxfx9DkpTO+99955fwrYzz33nIBNnWAMNgAAlJgUoNu2bVvlVlXALh+DvdZaa+UAncJ1ZdIM4/fee29MmTIlZsyYEXfccUesv/760bBhw5g5c2Y+Z6ONNsrjt5dbbrl8vTQG+6mnnppn29N47fS+7777LmbNmpUnOFtppZV+4R2B2kHABgAA5nDEEUfkSnOnTp1iqaWWismTJ+cx2akinZ5vvPHGsf/+++fQvcwyy+Sx1y+//HJeemt+Anbqep7GYaeu4Wlm89mr4FDKdBEHAIB6IFWJ//Of/1R6bMstt4w111xzjkp3GnOdwm+aTC0F4XKff/55jBo1KlezU9U6jb2eOHFiDszJZpttlidKS5OizX7NNGa7VatW+XlaniuN607jwJdffvmF+FND9RKwAQCgHkiTk62++uqVHmvdunXefmyxxRb7yb7ysdvlUlV79rHWKUSXB+nZr5mq4bNr06ZN3qAu0UUcAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoACNi7gIlJL2LadFqVpr3elRqn612IwoVd/v1SVK1THHvh6l6robSvc74IEjOkSpatpoVpSq1TtMiFL1yujS/SfZiIntolTtvvykmm4CUMeU7r9eAAAAoBYRsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAK0LiIiwAAALXbiBEj4o9//GN+3qBBg2jWrFlsu+22sc8+++TX86OsrCwef/zx2G233ao8Z8iQIfH000/H73//+yrP+fTTT+Omm26KmTNnxpFHHhlrrLHGz/iJoPZRwQYAgHrgu+++iwceeCA23XTT2GSTTWL11VfPgfvKK6+c72uMHDkyTj755Lme065du3ztqowfPz623HLL/Lx58+ax9dZb5/APdYEKNgAA1BMtW7aMY489tuL10ksvHZdccknssMMO8cYbb8TQoUNjueWWi8MPPzw+/vjjuPXWW/N5hxxySKy55ppx+eWXx+jRo+NPf/pTnHfeefHVV1/lSvS0adPi4IMPjnXWWScH6I8++ihfe9CgQfnYBx98EDvvvHPevvjii1h88cXjwgsvzNd+5pln4v3334+OHTvW2H2BoqhgAwBAiZk6dWp8//33VW7p+PxIYbhFixY5WKcu3ZMmTYr27dvnELzFFlvErFmzokmTJrHNNtvkEJy6cqeu5eutt16uZu+4447RuHHj6NChQ+y9997x3nvvxddffx1PPPFEvuZvfvObeP3112PRRRfNXdEHDx6c37v++uvnUJ+6mqfPS1VsqAtqvILds2fPmDJlSk03g1rmuJpuAABALda3b9/o06dPlcd79eoVvXv3/sn+cePGVVSwx44dG0899VT84x//yK9XW221fN0kvfeYY46peJ0q39ddd12cc845uXL961//Ole+0/5UxU5SyL7lllti1113rfi8tdZaK66//vr8PFWx33333WjTpk0MHDgwDjjggGjUqFHceeed8eWXX8aqq65a6D2CehmwU7ju169fTTeDWmbI4/1rugkAALVWKlL16NGjyuOpylzV/jQGO01qlsLxX//611hhhRWif//+uUt3uW+++SY23njjitcp/L722mtzXGv48OF5f7pekh5XXnnlPHFZuWWXXbbieaqUp4r47bffHvvuu28O6uX7r7766rjiiit+1r2A2qTGAzYAALBgUlCuKkQvyBjs2c0+k3gKzi+++GLFuQMGDIgVV1xxjnNWWWWVXJUuP+fee++NCRMm5M+o7JrlUgU7va/cmDFj5ngPlDIBGwAAmEMaO50q2Nttt10O8mnsdArcKRynyvWJJ54YF198cVx77bW5cp3GWL/11lvx0ksvxWeffTbXu5kmUEsV6zTuOl0vdRt/5ZVX/AaoEwRsAACoB5Zaaqk8broya6+9dpx00kkVrxdZZJF4++238wzfacK0nXbaKYfhJAXttI51q1at8szjzz77bK5c33HHHXnCshTI01Jeadbx2a+Z1rtObUjnpFCd1spO3clTiG/btm013AFY+ARsAACoB9L61AcddFClx5ZZZpm8zS512959991/cu4GG2yQtySF6V122WWO4506dcpb+XXLla99XT7uulu3br/wJ4LaxzJdAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAVoXMRFoGi77nXpQrmpX0bEZkuNiVI1dFLLKFW3vr9klKqZZQ2iVF13Q+l+j/q74wZGqdrit7tHqeq8TFmUqkff7xCl6vebfBulaqXWk6JUDf6ueZSqHZau6RYAlSndf3kBAABALSJgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABSgsbsIAAB13/jx4+O+++7Lzxs0aBDNmjWLrbfeOpZbbrkFus5XX30Vyy+/fJXHv/nmmxg8eHDstNNOVZ4zY8aM6N+/f0yfPj26du0aTZs2XaA2QG2lgg0AAPXA8OHD45RTTolXX301XnnllXj88cdj/fXXj0cffXS+rzFmzJjYZZdd5hnkP/rooyqPl5WVxY477hh9+/aNv/71r/l6s2bNWqCfBWorFWwAAKgn2rdvH3//+98rXv/P//xP9OvXLwftoUOHxqRJk6Jt27ax8cYbx5QpU+Kpp57K52277bbRunXrHMZTyL7//vvj17/+dUybNi3+85//5Mftttsu2rRpE+3atYvVV189hg0blqvdad+HH34Ym2++eSyzzDL5mpMnT84hP1XSd9hhh3jiiSdi1113rcE7A8UQsAEAoJ5afPHFc6hOXbp/+9vfxiqrrJIryp07d44uXbpEp06dclfyU089NQfiIUOGxNSpU+Odd97JXbtTF/MlllgimjdvHmeddVYMGDAgPvjgg7jyyivjpJNOimOPPTY6duwYSy21VBx33HH52KBBg3IYT+E62X777ePFF18UsKkTBGwAACgxKeSmrSopFKftx3744YeKCvbYsWPj2muvje7du+fXKSQ//fTTOfheeumlsdlmm8Utt9ySj/Xo0SOuuOKKHLRT9fq8886LG264IVq1ahV77713PidVpa+55prYaqut5mjHwIEDo1GjRrHHHnvECy+8kLuQd+jQoeKcVPH++uuvC7w7UHME7Hno2bNn7h5DNWu2g1sOAFCFNH65T58+Vd6fXr16Re/evX+yP4XyNAY7heiWLVvGJZdckrt6pwnHVl111Yqq8ieffBJbbrllxftSaL7zzjvnuFbq9l1+vSRVu388Ydqaa66Zw3WSKt1pUrMWLVrkMD576E9tgbpAwJ6HFK7TuBSq14NnP+aWAwDMpQiUqspVqax6XdkY7Nk1bvx/0WDJJZeMTz/9tOL1xx9/HIsttlhFAC8PzKkrefn13n777TwW+/vvv684pzxc/zh033XXXRWv0/tSN3GoCwRsAAAoMVV1AS/KkUcemcdgN2zYMH9OGlP973//O1efR44cGTfffHMcdthhsc466+SZyVP4vu6663L38XnZbbfd4owzzsjva9KkSTz77LO5aznUBZbpAgCAemCRRRaJgw8+uNJjyy67bF46q9xKK62UJzVLAXvixInxzDPPxCabbJJnGL/88svzMlxLL710nrAsVcW/++67HK7TuO20f+edd/7JNVM38zSJWhq3na6d2pOq5mlcdhqHDXWBCjYAANQDaTbviy++uNJja621Vt5mt9pqq8X555//k3OPOeaYiucrrrjiT8Z6r7HGGnkrv265I444ouJ5mlV8bmPIoVSpYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAACBgAwAAQO2ggg0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAK0DhKRM+ePWPKlCnV/rmjRo2q9s8k4vGHTl84t+EvXUv69i7SdHqUqs2XHROlaty0JlGqBo7oEKVqi9/uHqXqpesfjVLV5KQ9olTtsVbp/ndmwvQZUaoGfVu6/515f1jzKFWnrlXTLQBqNGA3b948unfv/rMDbArX/fr1i+pWWZsBAACgxgJ23759K90vwAIAAFAXGIMNAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUoHERFwEAAGq3adOmxccff5yfN2jQIJo1axYrrrhiNGrUaIGv07Rp0yqPT5o0KcaMGRPLLbfcPK81fPjwWHzxxaNx4/+LJaNGjYqZM2fGUksttUDtgtpABRsAAOqBzz//PDbYYIM48MAD44ADDoidd945Vl555Xj//ffn+xrff/997LTTTnM957333ouLLrpontf64osvYsMNN4xvv/22Yt/pp58enTt3jjXXXDNOO+20+W4X1BYCNgAA1BNLLrlkDB48OG+fffZZHH300fHHP/4xJk6cmKvJqfKcKtDlRo8enbdyQ4YMyUH9k08+qdg3bty4GDlyZMXrddZZJ84666x8zW+++SamT58e//3vf6OsrKzinHvuuSe23377mDBhQsW+N998Mx588MEcvNP5TzzxRLzxxhsL+Y5AsQRsAACopzbbbLP46quv4sUXX4y99torh+MTTjghZs2alavcq6++eq4m77HHHjF16tTo27dvDtOpupwC8zHHHJPP2WijjWK33XbL57zyyiv5Gumau+++e6y00krRpUuXWHfddXPoTtJnPvPMM7HYYotVtOW5556LPffcM9q1axdt2rSJfffdN/7973/X4N2BBSdgAwBAiUlBNnXXrmpLxyszY8aMigr2gAED4sILL8yV5GTo0KG5qn3rrbfmLY2FThXotKUx29dcc01cd911seyyy8a//vWveOCBB3JQfvrpp6N///7RokWLuPbaa+f4vHTNt956K1fHU2h+6qmn8v5U4V5hhRXmODdVyjt27Fjxeokllpijeg6lwCRn89C8efPo3r179fw2qHCcewEAUKVUSe7Tp0+Vx3v16hW9e/f+yf7UBTyNwU6BuWXLlrHVVlvl81544YVcvU4hOUlds1MFO/1bODnssMPi/vvvj4MPPrjiWgMHDowPPvhgjn1rrLHGHJ/3q1/9qqJKnSrhP/zwQ5VtThOdpS8AyqXnTZo08VdASRGw5+M/XlS/IY/3d9sBAKrQs2fP6NGjR5X3J80QPrcx2JUpD9flz8ePHz/HOOsfXzOdc8ghh8Qll1ySX48dOzZXzmefNO3H75l9HPaPpRnNU7fycp9++mmsuuqqVZ4PtZGADQAAJSYF16pCdBH23nvv2G+//XJFOn1O6kp+2WWX5YpyCt5vv/12Hi+dupenmcBTlfqMM86Ic845J9q3b/+zPrNr16656/jdd9+dPydNhGaSM0qNgA0AAPVACsppCazKpPHRyy+/fMXrLbbYIq688sq8par0n/70p9hnn33ysTQZ2plnnhlPPvlk3HbbbXlsdpoN/Nhjj82h/NVXX83X+vE1l1566VhkkUXm+Nw0QVp5N/BUXf/nP/8ZF1xwQf7MdO3Z3w+loEHZ3PppVIM0vrlfv36FnUfdMKTznON3irLGh0Pi5o+fiFI1YXrpjkOaWdYgStW4aaV737+dtPCqGwvbx183ilL10vWPRqn6fyftEaVq9zXGRKnq0GxalKpB3/68amVt8P6w/x1fXIoe33f9mm4CUAmziAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAjQu4iJQtF33unSh3NQvI2KZVk2iVI2YPC1K1ecTWkWpatZwVpSqpo1Kt+2dlymLUtXkpD2iVD139b+iVE39zW5Rqtq2bR6lqlO7yVGqpk2bWdNNAOoYFWwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAA6oGPP/44Wrdunbc2bdrEYostFvvtt1+MGTNmvq8xefLkOOGEE+Z6znPPPZevOy8//PBDbLLJJjFq1Kj5/nyo7QRsAACoB2bNmhXt2rWLESNGxPDhw2PIkCHRvHnzOOuss+b7GuPHj48nn3xyrudstdVWcfvtt8/1nMGDB8fWW28db775Zm4X1BUCNgAA1BMNGjSoqGIvvvjicdhhh8WgQYPi6aefzlXntddeO0466aR8br9+/WLllVfO24UXXpj37b333vH555/HRhttlF/fcMMNsfrqq8eKK64Y5557bt73wgsv5Ouma+61116xww47RKdOneKYY46JmTNn5nMuuuiiOO+882LppZeusXsBC0PjhXJVAABgoZk6dWreqtKsWbO8zc2UKVPin//8Zw7VM2bMyJXpAQMGRMeOHaN///5x7bXXxgMPPJCvc+CBB+YQfffdd8f222+fQ/TLL78cd9xxRzz66KO5Ev673/0uH0/BPXUlT9d84okn4t///ncst9xyscsuu8R//vOf2GmnneZZ4YZSJWAvgJ49e+b/EFENmu3gNgMAVKFv377Rp0+fKu9Pr169onfv3j/Z/8033+TqdZICehoDnULx+++/HxtuuGGst956+VgK22msdfnr008/PYfubbfdNlfBW7ZsGY8//ngMHDgwNthgg3xOCtSpIr3//vtXfN4WW2yR31PedXz06NF+p9RpAvYCSOE6dZVh4Xvw7MfcZgCAuRR+evToUeX9qap6vdRSS8VHH32UQ3I6p3Hj/40DKWCn8dnlpk+fHk2aNKl43ahRo59UzNM5Z5xxRpx99tkV+9J7UnW7XHmYTxo2bBhlZWV+p9RpxmADAECJSeG4bdu2VW5VBezyMditWrWqCNeVSdXmv//97zFy5MgYN25cXHPNNbkaXR6008Rk6Zw777wzV6XT2Oqjjz46brrppoX4U0PtV+MV7DReo3v37vM8z/T9AABQPdKEZ6+99lqsttpquVJ96KGH5jHWqQq9yCKL5DHVw4YNy13EU9fyNOb617/+dZ7ILI3jhvqqQVmJ9NNIIbymu2fXhjbUFysspC7iX/6lazz19TNRqkZMnhal6vMJraJUNW5QEv+ZrNToH+Y+wU1tNmNWgyhVn41sGqXquav/FaVqs9/sFqWqbdvmUao6tZscpeqjYaXbmfM/B/3vLN4LIv2zPwXhNH76x1IFOgXpVPz68XvSloL1j89P1ezKXpdfK3UXn/2aqfKdzpm9cp7Wwm7RokWurENdUOMVbAAAYOErn5ysMin4zh6YZ39PZeH3x+fO/nr2a82+v7Ju61W1B0pV6X5tBwAAALWIgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACNI56qGfPnjFlypQFft+oUaMWSnv4qccfOn3h3Ja/dI1JM6aX7C1/Z8wiUape/6h0v89bZ6UoWat3mBCl6tH3O0Sp2mOtMVGqpv5mtyhVr9z47yhVO/bYJ0rV6InNolTtsvbYmm4CUMfUy4CdwnW/fv0W+H3du3dfKO0BAACg9JVuSQkAAABqEQEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABWhcxEUAAIDabejQoXHUUUfl5w0aNIhmzZrFtttuGyeffHI0adJkvq4xY8aMuOmmm+K4446r8py33nor/vnPf8b5559f6fE777wzbr755jn27bzzznHGGWcs0M8DtZGADQAA9cCkSZPinXfeibvvvju/njx5cvzlL3+J8ePHx7nnnjtf1/j222/j4osvnmvAXn755WO//far8vgWW2wRSy65ZH4+ffr0OPbYY6N79+4L/PNAbSRgAwBAPZGq1jvssEPF61TJ7t27d+yxxx7x1FNPxWeffRZrrLFGnHbaafH888/HDTfckM87+uijY/vtt4+ePXvGN998k1+nSvbrr78eV199dUybNi2OOOKI2GWXXeKrr77KFewUntM1U7D/4IMPYtddd43f/OY3scIKK+Qt6dOnT+y9997RtWvXGrsnUCRjsAEAoMRMnTo1vv/++yq3dHx+fPLJJ7HEEkvkynTq0t2lS5dcYU6V7v333z+23nrr2HHHHePQQw+NF198MQ444IBo3759DsopjB9++OE5sO+zzz65i/cLL7wQY8aMibfffjtf849//GO0atUq9t133zj77LNj4MCBFZ89fPjwuO6666rsSg6lSAV7ATRv3lz3lWpSdacjAAD69u2bq79V6dWrV65M/1gKveUV7LFjx8YXX3wR/fv3j3HjxsV6660Xv/3tb/Oxc845J0488cSK16kKfccdd+RrtmjRIjbbbLO48MILc5C/9dZb8zmpin3fffflani5TTbZJFe9kyeffDKH8rQv+dvf/par3osssohfKHWGgL2A/yGjegx5vL9bDQBQhRRae/ToMdeu4JVp06ZNriSnruEtW7aMddddN1eYU8hOlezZg/hGG21U8bpjx47x3HPPzXGtVKnebrvt4sADD5zjvBEjRlS8nv2aaSK1WbNmVbxOY8Effvhhv2PqFAEbAABKTArQVYXoBRmDXZUUvB999NE8AVnDhg1zEO7cuXMO5mVlZfmcddZZJ66//vrcjbxp06Zx2WWXxejRo2PxxRef5/VT5XzixImx1lprLfDPALWZgA0AAMzhmGOOiXvvvTdWW221XHlOQyX79esXrVu3zpXrbt26xQMPPJCr0KusskqujKfq9IABA/L47XkZPHhwrL322u46dY6ADQAA9cByyy0X99xzT6XH0uRmnTp1qnidAnXqEp5m/07jrDfYYINcyU7SvrSmdgreqWv5Rx99FBMmTIj1118/79twww3jggsuiGWWWWaOa6aZydMEaeWfd+WVVy70nxmqm4ANAAD1QBpzveWWW1Z6bNFFF83b7FKgrqzKnEJzeXBOXcZT1/HZdejQIW/l1y2Xlv+afax22qCusUwXAAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEaF3ERKNque126UG7qlxExs6wsStWq7SZEqVpuo0ZRqtbrULptf2V06f5n/vebfBulasL0GVGq2rZtHqVqxx77RKl66q8PRKk6tFfXKFWvDesQJWudmm4AUBkVbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAII2AAAAFCAklm/pXnz5tG9e/dCrjVq1KhCrgMAAAAlF7D79u1b2LWKCuoAAABQThdxAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQgMZFXAQAAKjdvv3227j66qvz8wYNGkSzZs1i2223jU022WSBrjNo0KDYaKONqjz++eefx6uvvhoHH3zwPK/1yiuvxOTJk2O77bZboDZAbaWCDQAA9SRg//Wvf83Py8rKYty4cbHPPvvEbbfdNt/XGDVqVBx44IGFtGf06NH585955plCrge1gQo2AADUE23bto3evXtXvF5//fXjb3/7W2y++ebx0Ucf5dC7xBJLxG677ZYD+X333ZfP22uvvWKppZbKYXzMmDG5En7SSSfFd999l8+ZNm1aPmfppZeuuPann34aH374YTRu3Dg++OCD2H777WO99darOH7sscfGxhtvXM13ABYuFWwAACgxU6dOje+//77KLR2fHw0bNszV7BSGjzjiiFxNHjlyZK5Ur7vuuvHyyy/nLuEbbLBBfPnll3O8d/z48bHZZpvFG2+8ER9//HFsvfXW8cUXX+Qu4nfddVe+5qGHHprD+CeffJLP/eyzz/J7r7rqqlhzzTVzF3WoS1SwI6Jnz54xZcqUmv5dMLtmO7gfAABV6Nu3b/Tp06fK+9OrV685KtXlUvgu3z927Ni4++674/LLL8+vO3XqVNFd/MILL4x99903B+Hy66XnZ5xxRlx33XW5en3llVdG+/btc2U7WW655eL666+PHXb4v3/HpWs++uij+XmqfL/++ut5zPX9998fTz/9dFxzzTV+x9QpAnZEDtf9+vWr6d8Fs3nw7MfcDwCAuRSIevToUeX9SROYzU2a5GzZZZeNRx55JDbddNPo379/rLDCChXHU7V69u7bqSv5nXfeOcc10jlt2rSpeL3NNtvkqvfsVllllTm6p8+YMSN++9vfxsorrxwXXHBBngzthx9+yCF899139/um5AnYAABQYlKAnleInp8x2D/uLl4uhe933nmn4vVbb72Vq9EpmM9+zrBhwyqul7qXp+7maavsmuUOOOCAXD2HukjABgAA5vCb3/wmj7tO46xTkH/wwQdjwIAB0apVqxg+fHiuPh9//PFx2WWX5a7kiy22WDz00EPxn//8J0aMGDHXu3nyySdXPE/d09NkaqrX1BUCNgAA1AMpBFfVrTx15Z59+a2OHTvmCvYDDzyQJ0xLY7CXWWaZfCyNn06TmnXo0CHefffdfM6ECRPyhGip63fLli3zGtg/vuYee+wxRzf0JHVPT13Eoa4QsAEAoB6YV8Cefbx0kpbr+t3vfveTc3fddde8JSlkp+W2ZrfSSivlrfy6swfsH0sBG+oSy3QBAABAAQRsAAAAKICADQAAAAUQsAEAAKAAAjYAAAAUQMAGAACAAgjYAAAAUAABGwAAAAogYAMAAEABBGwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAQAEEbAAAACiAgA0AAAAFELABAACgAAI2AAAAFEDABgAAgAI0LuIiUEp+mBEla+U2jaJUPfV1iyhVK7aZEKVqxMR2UapWaj0pStWgbztEqerUbnKUqtETm0WpOrRX1yhVd/R5LErVhofuVNNNAOoYFWwAAAAogIANAAAABRCwAQAAoAACNgAAABRAwAYAAIACCNgAAABQAAEbAAAACiBgAwAAgIANAAAAtYMKNgAAABRAwAYAAIACCNgAAABQgMZRDzVv3jy6d+9e8XrUqFE12h4AAABKX70M2H379p3j9exhGwAAAH4OXcQBAACgAAI2AAAAFEDABgAAgAII2AAAAFAAARsAAAAKIGADAABAAQRsAAAAKEC9XAcbAADqm0mTJsULL7yQnzdo0CCaNWsWG220UbRp02aBrvPdd9/FIossUuXxMWPGxFdffRUbbrhhlefMmjUr3njjjWjSpElssMEGC/T5UJsJ2AAAUA8MHTo09tprr/h//+//5deTJ0+OwYMHxyOPPBKbb775fF1j3Lhxsf3228ebb7451895+OGHqwzY6XPTNdK1ysrKYvnll4/HH388GjbUuZbS568YAADqicUWWyz69++ft+effz7+/Oc/x3nnnRfffvttDBkyJD744IP44osv8rkp/L711lt5mzlzZt738ssvx4gRI+LFF1+sOOe9996LQYMGxYwZM/K+ZZddNvbcc898zXS9kSNH5vdNmDAhH0/X22GHHSo+7/PPP4933323xu4JFEkFGwAA6qnVV189br/99txd+7TTTsv7dttttzj//PNzCB41alQ0bdo0WrRoEf/5z3/iwQcfjPHjx8ett94am266aey+++4xfPjwaN68eQ7hzzzzTA7Ql19+eZx00klxyimn5P2pG/ro0aPjnXfeydXytKWA/c9//jN3N0/tgLpAwAYAgBIzderUvFUlja9OW2XvS9XrZOzYsXHxxRfHPvvs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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aa.AAPredPlot().clustermap(data=data, names=names, labels=labels)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_comparison.ipynb b/examples/prediction/aapred_plot_comparison.ipynb new file mode 100644 index 00000000..e03ec13f --- /dev/null +++ b/examples/prediction/aapred_plot_comparison.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c974ac10", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().comparison()``, we build a tidy benchmark frame comparing a scale-based baseline against CPP across two data conditions:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3b59d33e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:45.809816Z", + "iopub.status.busy": "2026-07-02T12:04:45.809753Z", + "iopub.status.idle": "2026-07-02T12:04:47.318878Z", + "shell.execute_reply": "2026-07-02T12:04:47.318674Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (4, 3)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 groupconditionvalue
1Scale-basedSubstrate61
2Scale-basedNon-substrate60
3CPPSubstrate71
4CPPNon-substrate74
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False\n", + "\n", + "df_eval = pd.DataFrame({\n", + " \"group\": [\"Scale-based\", \"Scale-based\", \"CPP\", \"CPP\"],\n", + " \"condition\": [\"Substrate\", \"Non-substrate\", \"Substrate\", \"Non-substrate\"],\n", + " \"value\": [61, 60, 71, 74],\n", + "})\n", + "aa.display_df(df_eval, n_rows=10, show_shape=True)" + ] + }, + { + "cell_type": "markdown", + "id": "8c02d489", + "metadata": {}, + "source": [ + "Each condition is an x-axis cluster, each group a colored bar, with per-bar value labels and a dashed chance line:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6263175b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:47.319967Z", + "iopub.status.busy": "2026-07-02T12:04:47.319900Z", + "iopub.status.idle": "2026-07-02T12:04:47.379095Z", + "shell.execute_reply": "2026-07-02T12:04:47.378873Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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prWpy8s333xjP0eiaW0KWDVSmStXLpujqXqLpIqIVKizY8cO+10xQ6NGjezxqaER0++//95em5JaysjGqgvNkSNH3Jdffun27t1r9SLJmYOamtd39OjRBK9Bx6/XkdTIbmpf+549eywxqC8P9evXPylde1KUZtTEX7UY0gUAAGRcCpC8oGTSpEmJfnbffffdibYiHD16tBs5cqTN0wx0ySWXuHHjxoUEmapGVsCqIp3givTChQu7e+65xz344IM29zO5XnzxRWtx6AVyHvXUfuGFF6JKcL3//vtWrLxz507/bXXq1LGuNxdeeGHI/VP7+p5++mnrBa6pCoGUOdZjNPUw3HuSmtd+4MAB169fP+vWoyDY+6Kh16miqQwRXCpSPnTokEW8efLkicnO0+I5AQDAv8FRmzZt3Lx589ztt99u7YdUIazbSpUqley3qHPnzm7y5Mn2c+XKla0o6O+//3ZffPGFtSW86KKL7OeiRYv6gxoVEakVoTJ4zZo1c6VLl7aATJk3PVar+SlhpS4yydGzZ08LYsXL9Clo/uqrr9znn39u1eWffvqpTdtLKWVjr7nmGgu82rZta9P71P1GFdVafVAFT5dffrn//ql9fc8//7wFkKJgUNMUFEhqJFhtkRSsKiPp3Sea167saOvWrW1erTRu3Nj+dtrPf//7X/t7ZYg5l1dffbWl1WO52k5aPCcAAPiXprEp+FD2Uf0t1Y9agVDdunUtk6m5mH/++WfEt0t9EL3AUq0IN23aZMGWur+oQEif4atXr7YMn8frca2qdK3apz6SKiBauHCh++WXX6yASBTkJMeMGTMsuFLwp4VZ9BzKNH744Yc2RK9CJHWqUeCsYuOU0muqV6+evZ6PP/7Ynlu3acqfpgLoffIyf9G8vqefftq/8IyCPM111XupGEivUdlEDWHH4rU/++yzFlgqwNWXCk1r1DGq17i2KXDOUMs/AgCAzEELmij4UaGOsm3evD4FF6+88ooFJer00r1797CV4mPHjvVXnfft29eCHI+CKz2HgqLA4EtzF3WbhnJ1n3DDv/Lrr78m6zWo96XoccriBR6D5oiqmLh8+fKWAVQAnVLKFirQC5yLqPdNrZkUnG3bts2CwGhf347/H9KuUKFCyDG0b9/eAv0nnngiJq9dfxe5//77bRlujzKlqpHp2LGjS0spLu2eMGGCRcyx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W5k2uKxQoYLNxyxTpkzY7VqdR6sEqQH7Z5995vbv3+9q1KjhbrrpJnfJJZec9OMFAADICjJtcKnVenRJjFboUb9KXQAAAJDBWxEBAAAAgQguAQAAEDMElwAAAIgZgksAAADEDMElAAAATn61uFbEUc/IaGlVHbUEAgAAQBYOLj///HNb7jBaDzzwQNTPAQAAgEweXGpVm61bt4bcvnv3bvfyyy/bz1dccYVr1aqVK1eunDt69Khbu3ate/31190vv/zirr32WltfnKwlAABA/EpRcBlMq96cddZZLk+ePG7u3LkWWAbTGt4KLGfOnOmuvPJKV6xYseiPGgAAAPFX0PP888+7NWvW2HzMcIGl5MuXz02cONHlzJnTDRw40B07diyaXQIAACBeg0ut2S1t27ZN9H5a/7t27dpu+/btNkQOAACA+BRVcOllITU8npQ//vjDrpXBBAAAQHyKKrisU6eOXb/66quJ3k/zLbdt22bzLStVqhTNLgEAABCvwWWXLl3setq0ae6ee+5xe/fuTbD9xIkTbsqUKe7WW2+133v37u1y5MgRzS4BAAAQr8GlKsUHDBhgPz/99NM2t1K3XXLJJe788893pUuXdrfccovbt2+fa9q0qXvwwQdjddwAAADIgKKeADl8+HBXrVo1a46+Z88e9+233ybYnitXLtezZ0+7n1oWAQAAIH5FHVxmy5bN3X777a5r165u8eLFbsWKFW7Xrl2uUKFCrmrVqu7iiy92RYsWjc3RAgAAIEOLWem2MpTqdRmp3yUAAADiX8yCS5/P53744Qe3fPlyGx7v0KGDzcH88ccf3amnnhqr3QAAACDeg8uPP/7Y9evXz61evdp/23nnnWfBZefOnS3wnD59uqtZs2YsdgcAAIB4rBYXtRpSdbgCSzVI1/B4ILUe+u6771zLli3dr7/+Gu3uAAAAEK/B5datW1337t2tn+X999/v/vrrL3fGGWckuI96YNarV8/ue99990V7vAAAAIjX4HL8+PHu4MGD7vrrr3ePPfaYK1CgQMh91KZo8uTJ9vPbb7/tjh49Gs0uAQAAEK/B5VdffWXXN998c6L3a9SokatYsaI7dOiQ27RpUzS7BAAAQLwGlxoGl+LFiyd5X60rLgowAQAAEJ+iCi5LlChh1xs3bkz0focPH/bfp1SpUtHsEgAAAPEaXDZr1syux44d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lVH8888/9nfUZcqUKYned8OGDXa/888/32VVn332mb0H+veUlrZv3+5uvvlmt2fPnjTdz8neF4CsJ66rxbOCo0ePuttuu829/vrrIdvmz5/vxo4d65o2bWqrFYVbZz0l/vjjD/fjjz+6M88806WV48eP27KcR44cSbN9pMe+MhK9bv0dRb1gL7roIleyZMmw9z106JDdt0yZMi4r973Ue5DWiy6cffbZ7vfff3fPPfdcmu7nZO8LQNZD5jKTu/feey2w1IpEyijOmzfPff/997Y60bBhw1zZsmXdkiVL3KWXXuqOHTvmMjplWQ8ePBh3+8qolLm655570vsw4Jw7cOBAXO4LQNZD5jIT08o2aggv77zzjmvTpk2C7Roi19CXMo0rV65006ZNc7fccks6HS0yImXjXnvtNde5c2ebSgEAQLTIXGZiP/30k62TriHN4MDSU6lSJXfrrbf65455lO3UPLJHH3007OMUlGq7sqDh7Nu3zzKl5557rmvRooW7//773Y4dOyLO8XvqqafcxRdf7Bo2bGj3v+OOO9yXX36Z4H4vvfSSa9CggX/o1psXqGFrb76nrvU6LrjgAtt33759E8wp1RzBBx980F144YWuUaNGrnnz5q5Xr1623Gdy9xV43FqbXseteawaPtbrUEP+ePH888/bteYTagg8NV9wvPdI77e+0Dz++OM2hSLYwoUL7T3WPnWu6JzRXE59+bnxxhvdJ598kqrXoOPW3/OKK66wYzjvvPNcjx49wj7fhx9+aMfgLQ0bTLdre6Rj0fmhEQHto1mzZpb1/eWXX6I6Lu/c/uuvv+x3nbP6fevWrf75nnqeb7/91l1yySW2ypieJzDrvmXLFvu3rO06V3Vsmi7z6aefJntfHv0/5cUXX3SXX365PZf+3/LYY4/Z0rkAkCxqoo7MacOGDYqqfNmzZ7efI/nzzz99q1at8v3yyy/+25555hl7bM+ePcM+5pxzzrHtixYt8t/28ssv223t2rXznXHGGfZz4KVo0aK+r7/+OsHz/P33377TTz895L7eZcSIEf77PvTQQ2Hvc/DgQf++e/Xq5cuTJ49/W9u2bf2PHzNmjC9nzpwR96XnSM6+RO9XxYoVw96nWrVqvp9++smXWel8CHy9N9xwg/38wAMPhNxX74O2lSlTJmTbd99956tQoULY96hkyZK+L7/8MsH933vvPdt2+eWX+4oXLx72cU8++WSKXsvhw4d9zZs3j/g379evX4L7T5061W6/6qqrwj6fbtf2GTNm+G9755137LbWrVv7WrZsGbKP/Pnz+z788MNUH5d3bgdfNm3a5N93ly5dfEWKFPFv078pz1tvveXLmzdvxH3pXE/OvkT/j6hTp07Y+5QuXdr37bffpujvAyBrIrjM5Jo0aWL/469UqZJv9OjRvi1btiTrcdEEl9myZbMPs8cee8y3dOlS+wD0gs3KlSv79u3b53/Mf/7zH7u9adOmdr9ly5b5Fi9ebI/NlSuXXf744w+7744dO3zLly+3++fIkcMCG11OnDjh37cC6bPOOsv32Wef+T7//HMLcGT16tX2GG0fMGCAb/78+bav999/39e5c2d7bMGCBf3BY2L72rt3r69KlSq2rXHjxr433njD/zq9gKFu3br+58rsweX27dvti0Hu3Ll9P/74Y7KCS/3NypUr538vpkyZYu+3grKGDRva7SVKlPBt3rw5JLjURfsaOHCgb8GCBb6PP/7YAjfdrvPKOx+SQ+eRF2xNnz7dgp8lS5b4nn76aft761xdv359TIJLPZfOlUGDBtmXqLlz5/qaNWtm24oVK+bbuXNnqo5Lr1fvs/4G3r85/X7kyBH/vnVeK+j74IMP7N+PF7hv3brVglvd5/bbb/fNmzfP/g56T/v06eM/v7dt25bkvhQQe/+Ota+JEyf6vvnmG3udl156qd1evnz5FP19AGRNBJeZnDIONWrUSJBhOOWUU3xdu3b1TZ48OcEHXqyCS130oRdIAZkyeto2duxY/+1e1lIfYMF+/vlnu/3AgQP+244ePer/QAzk7Vu3//777yHPdc8999j2u+++O+zrUQCk7fqwTGpfw4cPt9sVOOg+gfS7AmVtHz9+vC8egkvR30y/n3feeRZgJxVcDh061H+u6W8faP/+/f4A88477wwbXCrgCqTj0D60bc6cOcl+Lcpc6zGffPJJyLbffvvNjj/w+KIJLnVRdjzQoUOHfI0aNbJtek9Se1yiYFyP2bVrV9h9f//99yHPNXLkSNt20003hX09rVq1su2zZ89Ocl+TJk3yf1kI/IIoOieuvPJK267AGQASw5zLTK5q1apWrPPEE0+4OnXq+Odi/ve//3UdO3Z05cqVc9ddd13EeWGpoTlfwb0hCxcubPMfZc6cOf7bixQpYteaD7Zu3boEj6levbrN98qXL1+y933GGWeEbQnTp08ft2rVKjd06FD/3LjVq1e7qVOn2nzC3377zT9XNCkzZ8606wEDBricORPWvOl3zReV2bNnu3jRvXt3m6e3ePFiN378+CTv7712zZvU3z5Q/vz5/XN5VWgWrFSpUnZOBsqbN68755xz7Ofdu3cn+7i982vkyJFuxYoVIfONdX4FH19qVatWzeY6BsqTJ48bOHBgxPM+Vselrg/eHOFAHTp0sPNecyRFHSH07/+tt96y+aPevlNy3uvfcYECBRJsy5Ytm7vzzjvj7rwHkDaoFo8D+jAfPHiwXdQUXP0tFyxYYJP5NdFfHzQqItDtKqiJlooAIgWd4vVQFBUVKGBRIYEu+mBV8cdVV11lRTe5cuVK0b5r1qwZ9vYqVapYkcLw4cPd22+/bR+wKtQJFu62YN4Hsoo17rvvvpDtXiFF4OvM7BQ8qCeqzg8FS1deeWWivS0VuIuKs8Jp2bKlXauXogqjChUq5N9Wu3Zt218wLyBLScusLl262Pmt1lu6KAhTUZHOL7XfSskXl6SoICfccYc772N9XLVq1Qp7u3rXFitWzI0ePdq+SK1Zs8Z630Zz3qtgSc8XzHveeDrvAaQNMpdxRtkVBXSvvvqq27x5s1Wb1qtXz6p6dXssRGq4Xbx4cX/T6cAP2aefftofOCjYeOWVV6wSVRlIHWdKBAYpgTZu3GivU5XK+oDVh6meX0GSPijVLD25/f+8inFVnuuDNPiifYlXcRsv9B4pW6uq4H79+kW8n6qJvQA70rmgvqvK6gWfDxKcFfNkz/7v/4686n+9v14Vf/DFq25WoDZx4kTLhnorz0yePNm1b9/esvbPPPOMi5WkznudO14QF+vjinTe79y50zKa+mKpEQwFgApkVTWurGmkL4LheNXgGuUId97rC5uoW0Jm6JkLIP2QuczE9EG1du1aC9a0Ck84rVq1ssbqyuwtX77cPjg0lO6JtDRkYk2WI7Ws8R4T/EGoQMVbilJDh8qoKsjUB6OCTwWBai2UHOEyR3L33Xe7bdu2WSZHrW6UOfM+9CU5Q72SO3du/8963xLL3gUPmccDtZeaPn26ZcHU+zLcqk56jxQInjhxwoJMBZLBFHx4QXrRokVTdSx6jkhZssCWUWq1ddNNN1l2/r333rO/288//2xfqHRe6G+o7YHS4rxXNjJHjhxRHVdKz/shQ4a49evX27muL1HKjpYuXdq/PSXtnbxzX/1wk/oyFo/nPoDY4f8QmZg+IPXhq959kYJLUaZEHz7qLehl27wPQWWhwlGgFok+IMPR3C+pUaNGyDZ98F577bV28e6rD9/vvvvOvfHGG8kOLsNRtkiBq2iZy+D3Qu+Tsrjez0l9aOr90uvXe6MsWVaiuY9qzK/ehr1797ZAI1ygo+kNv/76q2XLwv3tdLvea33RiJSpTIqGe71zKlhw0KvA6LLLLrOLKODSFxpl7rV+uhfEJXXeB/Z7jMV5n9zjSi1v/qO+PF1zzTUh2725xkmd91K5cmVbsUmZyax23gOILYbFM3nmUjSZX3MtI9GcRwWWGqb05ix6xQTKfIa7f2JFFXPnzrXsSzBlUMVb6UWBrApwVCgSPOfr9NNP968WFNgM2svQJOfD0KMPQ28+WIkSJUK2q6jEa+odOJwXaV/K9ooapoczYcIE+/D1CpjijYJFFYNp+N8rkArmvUdq7h2Ot3JUpOb+yaFAMNKwuObq6m+pNbI1T1TzOoPnKHbr1i3k/PLO++DiMtGwrzeXNBw1gfe+pAQaN25cgvM+NceV2nPf+7IY7rz/4osv/P++g4exw+3L+5tqvfFww96zZs2y955VvgAkheAyE1PFreZbKdug1WoU9AR+mOnDS/O8vIyGPtS8IUwFffL1119bZblHvyuwiDQMJ9pfu3btbB6ZNyyoCmoVEenD25vbqeFQ7U8r8SgLFjhHUR96XgCi4DMwoFDgoCFXZcaSQ/ssX768/azVebwVdPQBqexQ4FzTwNV1Iu1LVbF6/couqQrde0/1QazMqLYrY6whyHilebIKWPRFIhy9L3r/VDylwifvfVVGUJXi3jQEZenSirLMmlKh6R76G2uahUd/T29eY+D55Z33mh6iwhX97UV/Ty+rntiwuApyvC9yGpp/6KGHrFBNGUqvi0BqjsvLGos3pzc56tata9d6z705k3pNymgGvp7gVaXC7UsrWel1KOusUQX9O/d89NFHrmvXrvY+eUEoAESUaKMiZHi//vqr9Rr0euGpKbkamVevXj3BSjbqdxfc9LtFixb+7eox6K22ogbiHTp0iNjnUv3u1BtS+6pZs6avQIEC/n6Rb775ZoJ9qK+kdxxqnK1emGrErCbSuk39EAP7XErt2rVtm55XTZ13797t37f6d4bz1FNP+V9Lvnz57LjUrFq/16pVy9+jT30Bk9pXYBNsXXT8eg6v8bQuag6fWYXrcxnOhAkTEpwfwUaNGpXgPdd75J0Lutx3330J7u/1ubzooovC7s9rdv/KK68k+7VopSRv5RqtzqRzVwsKqOm4btO/A+9v6rnuuuv8x6h+j/r34v3cvXv3iH0uddxqWK7zXL1lCxcu7H+eF154Ierj8lb/USN59Yddt26df9+XXXZZ2Nev5vWB56mOy9uv/p15/47VBzapfYnee+/fpo5bf1OttuTt4/rrr/cdP3482X8fAFkTwWUcUIDw+OOPh12S8cwzz7QG2eE+EPbs2eO75ZZbLOjzViC55JJLbFWVO+64I2JwOWzYMN+sWbMSLP2nfYdrGC1aTefss8/2f2h5K5poBREFOsG0IkhgIKdjSCq4VJNnBYSBS+RpiUE1Vf/rr7+s4Xm4wCbcvgKX1WvQoEGC91NLQiqQzcwfsMkNLuX888+PGFzKu+++G/IeaVUjvXfB0iK4FK1+o+P0Ajdd9MVCSyZq9aFgahCuZUS9lW100RettWvX+h599NGIweVdd91lKwoFLlqg4GvmzJkxOS6tuuM1kveOIangUl588UVfqVKl/I8rVKiQr0ePHrYKlRrS67b69esnuS+PVvfRQgGB/1619KOWkdQqPgCQlGz6T+S8JjIbDd1pGE5DwmpJoh6YSdGwtgoZ1GrFq+xVQYuGxdTo3HsODbupb6aeV/fVPEoVDKhYR7clRcPLel7dX8PYgZW1wTTkqOfWkLeqX719qzDJGwIPR8ekIU9vaNLbh16jhgA17HfKKackuq9g2veuXbtsiD+xfWcWGjb15haqKjixKRCayqB5hpo+oP6UkWhOq84XnQeR2ubo769hYW1X94Jg+vvqvfZ6N6aU/sZ6Dv2N9XdKqoeqhvD12nTOe3MW9XfW/GQVt3jzM73j1jmv16f3T90OdI6Fq6aP5rj071b70r857UvNz/W7jkXHFImOSeew/neu897bh+Yia36pqvvVqiuxfQWfB2ohpakv+veq50zsPAGAQASXAAAAiBkKegAAABAzBJcAAACIGYJLAAAAxAzBJQAAAGKG4BIAAAAxQ3AJAACAmCG4BAAAQMwQXAIAACBmCC4BAAAQMwSXAAAAiBmCSwAAAMQMwSUAAABihuASAAAALlb+D1VFDSH1HWhAAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aa.AAPredPlot().comparison(df_eval, baseline=50, ylabel=\"Balanced accuracy [%]\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_cutoff.ipynb b/examples/prediction/aapred_plot_cutoff.ipynb new file mode 100644 index 00000000..8fbe5f6c --- /dev/null +++ b/examples/prediction/aapred_plot_cutoff.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "21605764", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().cutoff()``, we fit a model and obtain per-sample prediction scores:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "208e5df5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:48.791780Z", + "iopub.status.busy": "2026-07-02T12:04:48.791709Z", + "iopub.status.idle": "2026-07-02T12:04:50.354704Z", + "shell.execute_reply": "2026-07-02T12:04:50.354451Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)\n", + "\n", + "aapred = aa.AAPred(random_state=42)\n", + "aapred.fit(X, labels)\n", + "pred, _ = aapred.predict_proba(X)" + ] + }, + { + "cell_type": "markdown", + "id": "4746ffe3", + "metadata": {}, + "source": [ + "The cutoff line shows the percentage of samples scoring at or above each threshold — useful for choosing a deployment cutoff:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5ee56207", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:50.355972Z", + "iopub.status.busy": "2026-07-02T12:04:50.355905Z", + "iopub.status.idle": "2026-07-02T12:04:50.407851Z", + "shell.execute_reply": "2026-07-02T12:04:50.407627Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.cutoff(pred, thresholds=[0.5])\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_domain.ipynb b/examples/prediction/aapred_plot_domain.ipynb new file mode 100644 index 00000000..a9994355 --- /dev/null +++ b/examples/prediction/aapred_plot_domain.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c074eb50", + "metadata": {}, + "source": [ + "``AAPredPlot().domain()`` shows the boundary-sensitivity curve from :meth:`AAPred.predict_domain`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9e2bef6f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:52.021400Z", + "iopub.status.busy": "2026-07-02T12:04:52.021322Z", + "iopub.status.idle": "2026-07-02T12:04:53.703335Z", + "shell.execute_reply": "2026-07-02T12:04:53.703081Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Training feature matrix\n", + "sf = aa.SequenceFeature()\n", + "X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq))\n", + "\n", + "# Bind df_feat so raw sequences are featurized internally by the predict_* methods\n", + "aapred = aa.AAPred(df_feat=df_feat, random_state=42)\n", + "aapred.fit(X, labels)\n", + "\n", + "df_domain = aapred.predict_domain(df_seq[df_seq[\"entry\"] == \"P05067\"], window=4)" + ] + }, + { + "cell_type": "markdown", + "id": "2666961e", + "metadata": {}, + "source": [ + "The score is plotted against the boundary offset, with the best definition starred and the annotated boundary (offset 0) marked:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c5cc358a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:53.704694Z", + "iopub.status.busy": "2026-07-02T12:04:53.704624Z", + "iopub.status.idle": "2026-07-02T12:04:53.758827Z", + "shell.execute_reply": "2026-07-02T12:04:53.758601Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.domain(df_domain)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_eval.ipynb b/examples/prediction/aapred_plot_eval.ipynb new file mode 100644 index 00000000..bd440e6e --- /dev/null +++ b/examples/prediction/aapred_plot_eval.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5b21232d", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().eval()``, we build a feature matrix and evaluate a model with a held-out set:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c99cddcb", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:55.270154Z", + "iopub.status.busy": "2026-07-02T12:04:55.270079Z", + "iopub.status.idle": "2026-07-02T12:04:57.606699Z", + "shell.execute_reply": "2026-07-02T12:04:57.606449Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)\n", + "\n", + "from sklearn.model_selection import train_test_split\n", + "X_tr, X_ho, y_tr, y_ho = train_test_split(X, labels, test_size=0.3, random_state=0, stratify=labels)\n", + "aapred = aa.AAPred(random_state=42)\n", + "df_eval = aapred.eval(X_tr, y_tr, X_holdout=X_ho, labels_holdout=y_ho)" + ] + }, + { + "cell_type": "markdown", + "id": "de26b005", + "metadata": {}, + "source": [ + "Bars are grouped by metric and colored by model; held-out bars are hatched and a baseline line marks chance:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7e62d418", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:57.608159Z", + "iopub.status.busy": "2026-07-02T12:04:57.608081Z", + "iopub.status.idle": "2026-07-02T12:04:57.675689Z", + "shell.execute_reply": "2026-07-02T12:04:57.675446Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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Oj07O7uEzpwUNxgLICbRXjBzZoF7CShIW8B+CBoiJQeFJomKV155xQz88Lns3LmzsVRBQEB8li9f3iRavvPOO83/r7/+ehk6dKjZr0yZMtKiRQvjO4N1JH6ZNm2acaUoW7asVK5cWS677DIzHQoQ4PHDDz+YKFG8Y3rUyp1Ji2bp8dXFWADWrl1rxgMEBJ199tlmmWXtDATT5g0aNDABhWPHji32cyYlA3y3cV+BD+abb77pX37yyScbd4v169fLhg0bIu6PjAaYacOMGylBfIQUkD59+vjQZPBq0qSJr1WrVr7ExETz/5dffjlo2xdeeMHndrvNuptuusmXk5Pju+WWW8z/ly1bxu88Trn//vv9bSTwVa5cOd+iRYv8261fv97XrFkzs+6oo47yLV261Cxv0KCBr2bNmiV4BSTWPPvss0Fto2nTpr7s7Ox895s8ebLZvmrVqr7du3fzhyolfPTRR+Z3r169um/v3r3+5b179zbLb7/99oj7jhgxwmwzbNiwYjpbEg4KTVIgXn/9ddNhy5cv75s6dap/+W+//eabMWNG2H2mTJniK1u2rNnv8ssv940cOdL8PW7cOH7rcciLL75oft/U1FTfM88849u5c6fv77//9t13332+l156yZeVlRW0/Z49e3ydOnUy+1SqVMk3b948X48ePcz/t2zZUmLXQWILxELLli197777rq9evXrm9x4zZkyB9u3cubPZ/s477+TPFEccOHDA3GMi0aFDB/O7YyyxwIMrliUkJPgWLlwYdr+ePXuabd55552YnDcpGBSapECceOKJpsO+8cYbUX1jGAxq1apl9q1duzZvEnFKZmamsTjgd/7ggw8KvJ/H4/FbymH1PP30083fgQ8zJH6ZNGmS+b2rVatmHjzyY/ny5UZY4IWHXOJ8MNsFqzbawWeffRZ2G8x4YIYsKSnJt3r1av9yzJZhv7p16/pWrVrlX+71en1PPPGE3zjy77//Fsu1kPBQaJIC4XK5TKfdtWtX1N/Yxo0bfccff7x/qqxNmzb81uOMxYsXm98WDxWFYfDgwUHTqYMGDbL9HIlO2rdvb37zu+++u0Db33bbbWb7rl27xvzcSPGAGRD8po0aNfJlZGSE3cYSleedd16QJfSMM87wz6RcddVVph1Z9xvct8aPH8+fsYSh0CRBT5b79+/P9Y3g6TA5Odl03G3b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/bPPPmusll9++aURmw0aNJDatWubikGHDx+Wdu3aGYGKakGBQJy++uqrxkKK4yLv5p133ilPPPGEEZuEEEIIoUWTxBFWZHjdunXNOyyMsDTC4ggxeM455xgBCYtjlSpVTPUf+HB+9tlnpq45cmpCdC5YsMAE9CCRe6CVMxBMuSPQB8fFlDmO+8orr1BkEkIIIQEwGIg4Bkx3Dxs2zFgf9+7dKyeddJIpE3n55Zeb9e+//75cd911JhF79erV/SKxcePG8txzz5mpdOs499xzj7FUDhw40H98CM+lS5fKwYMHTYQ46p7nBSLWUVEIgpQQQgghuaHQJI5g2rRpcuWVV0paWlqudZgWR/qh7du3G2umlTcTFspHH33UBPEgxZDFyJEjTV5L+Go+//zzxXodhBBCSGmCwUBEPevWrZPLLrvMiMxLL73URHhjmnz8+PFyyy23yO233262g29l7969zd81atSQP/74Qx566KEgkQkQKATys1gSQgghpGjQoknUgzyXb7zxhpn6juQzaQFxCb9MTINDZA4fPtz4U1q88847JlgIQUEQsAj+IYQQQkhsoNAk6jnuuOPkzz//lO+//17OOuusfLdHzsw77rjD/I2ocUy5I8URkrPDvxM89dRTJo8mIYQQQmIH0xsR9SBZOkCkeEHAVDoqASFQaN68eeZlUaZMGeOjeffdd8fsfAkhhBByBPpokhIHlkZEjqMCz80332yiwgNp3ry5eV+8eHGex/nwww9NTXFw7733Gl9OiE0kWke5yaefftqkL6LIJIQQQooHTp2TEgNWx2uvvVYmT54ctBypiebMmSPHH3+8+f9LL71khONpp50WUWxCQCJJ+wknnGAEJiGEEEJKHlo0SbHz77//mndYFiEyUUkHlkgkTUckONZfc801pqoPQPAOps2XLFliIs3DsWvXLvOO8pOEEEII0QGFJilWICwbNWpkyje+9dZbctRRR8miRYuM3+Rjjz0m8+fPl5o1a5pa4dgGlC9f3pR6tMQp1gWyf/9+Y/EE5513Hn9RQgghRAmcOifFCkQipsDdbrdkZ2ebso2oER7IpEmT5OqrrzZWTKQgqlatmknCDj/Lb7/91iRih+9lq1atTDQ6qv5g6rxevXrm+AUNGiKEEEJIbKHQJMXOrbfeKm+++aa/HvnZZ5+da5uOHTvK3LlzTZqiMWPGmGUo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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.eval(df_eval, baseline=0.5)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_hist.ipynb b/examples/prediction/aapred_plot_hist.ipynb new file mode 100644 index 00000000..55c3a552 --- /dev/null +++ b/examples/prediction/aapred_plot_hist.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ad4b5ea8", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().hist()``, we fit a model and obtain per-sample prediction scores:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bfe819f2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:04:59.313025Z", + "iopub.status.busy": "2026-07-02T12:04:59.312959Z", + "iopub.status.idle": "2026-07-02T12:05:00.877745Z", + "shell.execute_reply": "2026-07-02T12:05:00.877437Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)\n", + "\n", + "import numpy as np\n", + "aapred = aa.AAPred(random_state=42)\n", + "aapred.fit(X, labels)\n", + "pred, _ = aapred.predict_proba(X)" + ] + }, + { + "cell_type": "markdown", + "id": "b9afc1fa", + "metadata": {}, + "source": [ + "The class-separated histogram shows how the scores of the two classes are distributed, with an optional decision threshold:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b46daeac", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:00.879220Z", + "iopub.status.busy": "2026-07-02T12:05:00.879125Z", + "iopub.status.idle": "2026-07-02T12:05:00.947804Z", + "shell.execute_reply": "2026-07-02T12:05:00.947558Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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Pjt37jQ/Hz161LTcRGX79u1xXhQTAJROHFBvv/02JwSwKY8CG02Ad+3atRg9Rqt+A4A/WOkeCGwA+/IosAm0ad4AACC42TLzMAAACE5eDWzOnTsnK1askO+++04uX75sbjt79qw3nwIAAMC3gY0mwKtevbrkzJnTDCh+/fXXnTWamjZtamZC/fPPP954KgAAAN8FNsuXL5dq1arJxo0bzf9dTfNetWqVqaR9/vx5T58OAADAN4HNpUuXpE2bNvLgwQPp3LmznDlzRsqWLRtmm2nTpplikzodXGtLAYC/6OeRLgDsy6PAZvr06XLjxg1p2LChTJw4UXLlyhVhm6efflq+/fZb8/O8efMkJCTEk6cEgFhLkiSJWQDYl0eBzaZNm8KUVnCnatWqkj17drl165acPHnSk6cEgFjTL2K6ALAvjwIba+ZTtmzZotw2S5YsZn379m1PnhIAYq179+5mAWBfHgU26dOnN+tTp05Fup0WyLS2yZQpkydPCQAA4JvApmLFimb9zTffRGssjk4HdzUOBwAAwO+BjY6t0dpPq1evll69ekXoZtKWmlGjRkmPHj3M/zt27OjZ0QIAAPgqsMmdO7d88cUX5udx48ZJxowZ5a+//jL/b9++vaRNm1b69esn9+/flzJlysi7777rydMBAAD4NkFfhw4d5IcffjCVvjWfjQYxat++fXL37l1JmDChtG7dWtatWycpU6b09OkAAAB8U93b0qJFC2nSpInJPrxjxw6TuC9p0qQmMV+dOnUkf/78/AoA+F3NmjX9fQgA4kNgozTpVe3atc0CAIHojTfe8PchAIgvgY3SKd27d++Wa9euSZo0aaRIkSJSokQJl/WjAAAAAjKwWblypQwaNMh0Q7kaYDxgwAB5++23vfFUABBrOh5QvfTSS5xFwKY8Hjyss6Hq16/vDGpSpUolefPmdWYa1sKYXbt2lddff10cDofnRwwAsbRs2TKzALAvjwKbAwcOmOncql69erJ9+3a5efOmqQf177//ytmzZ6V///5mZtR3333nnBoOAAAQkNW9NQlflSpV5KeffpJnnnkmzHgazTQ8bNgwGTlypPn/559/TqsNAAAIzMDmjz/+MOvevXtL4sTuh+to5uEMGTKYFhyqewMAgIAMbEJCQqJV3TtRokTOGlH37t3z5CkBAAB8WwTzt99+i3S7W7duyfHj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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.hist(pred, labels=labels, thresholds=[0.5])\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_ranking.ipynb b/examples/prediction/aapred_plot_ranking.ipynb new file mode 100644 index 00000000..a3dad307 --- /dev/null +++ b/examples/prediction/aapred_plot_ranking.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6b4d7587", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().ranking()``, we rank candidate proteins by a prediction score, colored by class, with per-item error bars and confidence cut-offs:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "58de3fde", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:02.579069Z", + "iopub.status.busy": "2026-07-02T12:05:02.578997Z", + "iopub.status.idle": "2026-07-02T12:05:04.107832Z", + "shell.execute_reply": "2026-07-02T12:05:04.107627Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (10, 4)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 namescoregroupstd
1Gene054.923699Substrate1.348517
2Gene157.537562Non-substrate1.080874
3Gene258.443169Non-substrate4.330479
4Gene365.417407Substrate4.112627
5Gene465.672878Substrate4.480049
6Gene569.179619Non-substrate4.914473
7Gene671.983117Non-substrate4.196634
8Gene776.487309Non-substrate2.845917
9Gene887.965245Non-substrate4.122117
10Gene992.638079Substrate1.473098
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False\n", + "\n", + "rng = np.random.RandomState(0)\n", + "df_pred = pd.DataFrame({\n", + " \"name\": [f\"Gene{i}\" for i in range(10)],\n", + " \"score\": np.sort(rng.uniform(30, 95, 10)),\n", + " \"group\": rng.choice([\"Substrate\", \"Non-substrate\"], 10),\n", + " \"std\": rng.uniform(1, 5, 10),\n", + "})\n", + "aa.display_df(df_pred, n_rows=10, show_shape=True)" + ] + }, + { + "cell_type": "markdown", + "id": "549b301e", + "metadata": {}, + "source": [ + "The highest-scoring candidate is on top; the figure height grows with the number of items so each bar stays legible:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a75842ab", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:04.108991Z", + "iopub.status.busy": "2026-07-02T12:05:04.108923Z", + "iopub.status.idle": "2026-07-02T12:05:04.174445Z", + "shell.execute_reply": "2026-07-02T12:05:04.174235Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aa.AAPredPlot().ranking(df_pred, col_group=\"group\", col_std=\"std\", cutoffs=(50, 80),\n", + " xlabel=\"Substrate prediction score [%]\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_scatter.ipynb b/examples/prediction/aapred_plot_scatter.ipynb new file mode 100644 index 00000000..1679095d --- /dev/null +++ b/examples/prediction/aapred_plot_scatter.ipynb @@ -0,0 +1,110 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dea9028c", + "metadata": {}, + "source": [ + "To demonstrate ``AAPredPlot().scatter()``, we obtain the per-sample scores of two predictors (here two random-forest fits with different seeds):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6023f9c9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:05.796385Z", + "iopub.status.busy": "2026-07-02T12:05:05.796322Z", + "iopub.status.idle": "2026-07-02T12:05:07.441072Z", + "shell.execute_reply": "2026-07-02T12:05:07.440801Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)\n", + "\n", + "aapred = aa.AAPred(random_state=0)\n", + "aapred.fit(X, labels)\n", + "pred_1, _ = aapred.predict_proba(X)\n", + "aapred = aa.AAPred(random_state=1)\n", + "aapred.fit(X, labels)\n", + "pred_2, _ = aapred.predict_proba(X)" + ] + }, + { + "cell_type": "markdown", + "id": "432842bc", + "metadata": {}, + "source": [ + "The scatter compares the two predictors sample by sample, colored by class, with the ``y = x`` agreement line:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a5e1db74", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:07.442343Z", + "iopub.status.busy": "2026-07-02T12:05:07.442272Z", + "iopub.status.idle": "2026-07-02T12:05:07.501902Z", + "shell.execute_reply": "2026-07-02T12:05:07.501641Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgQAAAIECAYAAABmAjaWAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAdQdJREFUeJzt3Qd4VNXWBuAFgRRKAoTeS+idgPQu0rGAghUrih1EBa96RWy//doboKIoihTpvffea+i9EwhphPmfb3nP3JSZydTkzMz3Pk8cpp05c0zmrNl77bXyWCwWixAREVFQy5vbO0BERES5jwEBERERMSAgIiIiBgRERETEgICIiIgYEBAREZFiUiERERExICAiIiIGBF517do12bhxo14SERH5E04ZeNHu3bslNjZWL4mIiPwJAwIiIiJiQEBEREQMCIiIiIgBARERETEgICIiIsWkQiIiImJAQERERAwIiIiIiAEBERERMSAgIiIi/00qjI+Pl6ZNm8rIkSPdev7Vq1flrbfeki5dukj9+vWlV69e8ttvv3l9P4mIiPxFPvEzN27ckEcffVQ2bNggLVq0cPn5J0+elNatW8vBgwett23fvl1mzJghU6ZM0cAgb16/jJOIiIjc5ldnvuTkZHnwwQflzz//dHsb/fv312CgUaNGMmvWLNm2bZt89NFHUqBAAfnjjz/ks88+8+o+ExER+QO/GSHAifuRRx6RdevWSZ48ecRisbi8jcWLF8uyZcskOjpaFixYIMWKFdPb69WrJxUqVJC77rpL3nvvPXnmmWckJCTEB++CiIjInPxihOCXX36Rxo0bazDQqVMneeWVV9zajpEn8NBDD1mDAcOdd94p1atXl9OnT2vQQEREFEz8IiA4dOiQfqv/9ttvZf78+VK8eHG3trN+/Xq97NChg83727Vrp5crV670YG+JiIj+J+lCvMQfOqmXtlw5dkbObYvTS7i476icWr9LL3OSX0wZYN5/6NChOs/vCSORMCYmxub9VatWzfA4IiIid6UmJMnxZVtk548zNRgILxYpdR7sIeXaNpT8BcMl8Xy8HFu8Ufb9uUjvr9q7jRQoVVT2T1pifXzMHe2lTMt6UrhCSfE1vwgIMJTvKeQcXLx4Uf+debrAUKRIEb00HkdEROQuBAMbP/7deh0neeN65W7NNRjY9u1UvR4aWUDCS0TJ9h+m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QbP9H3nkEX3M8OHDrQmDBvQn2L59e4Z8AXe3RUREFOjyWCwWi5gcuhHiZH7kyJEs9z388MMyevToLLeXL19eCwx9/fXX8sQTT3i0LWdt3LhRpyUQZDRp0sTt7RAREeU00+cQGEl+W7dulc8++0wWLlwoCQkJUq1aNbnnnnukd2/b3QBr1aqlqwoyrxhwZ1tERESBzi9GCPwFRwiIiMhfmT6HgIiIiHyPAQERERExICAiIiIGBERERMSAgIiIiBgQEBERkWJSIRERETEgICIiIgYERERE5C+li/1FYmKiXu7atSu3d4WIiChDOf8CBQqIIwwIvOjQoUN6ed9993lzs0RERB5xpukeexl4Edoqz5kzRypXriwREREebevq1avSvn17WbJkiRQqVMhr+xjIeMx4zPg7Zj78uzTHMXNmhIABgUnFx8dLVFSUXL58WSIjI3N7d/wCjxmPGX/HzId/l/5zzLjskIiIiBgQEBEREQMCIiIiYkBAREREDAiIiIhIManQpMLCwuTf//63XhKPGX/PzIF/lzxmgfx7xmWHRERExBECIiIiYkBAREREDAiIiIiIAUEOmz17tgwYMECaNm2qdapHjhwpFy5cyPVtmVVycrJ88skn0rNnT2ncuLH07t1bxo0bJxaLxeVtHTlyREaMGKHHqn79+nr52muvycmTJyWQHDt2TJ5//nl9f82aNZMHH3xQ1q5d65VtP/7441oPfcuWLRJIUC/+nnvu0ePVtm1b+de//iWnT592a1u7d++W5557Tlq3bi0NGzaU2267Tf78808JJNevX5evv/5a+vTpo3+X3bt3lx9++EHS0tJc3talS5fk7bfflo4dO0qDBg3k5ptvlo8++kgSEhIkUO3Zs0fq1KkjEydOdOv5+N188cUXpVOnTvr5j2Z6y5Yt887OWShHPP300ziLZfkpX768Zf/+/bm2LbO6ePGipX79+jbfZ+/evS2pqalOb2vGjBmWQoUK2dxW0aJFLcuXL7cEgqVLl1oKFCiQ5T3mzZvX8tVXX3m07d9//926vVWrVlkCxauvvmrz96JkyZKWrVu3urStn376yRIaGmpze48//rglEFy9etXSvHlzm++xQ4cOlqSkJKe3dejQIUvlypVtbqtu3bqWM2fOWALNlStXLLGxsfoev//+e5efv2HDBktUVFSW45UnTx7Lu+++6/H+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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.scatter(pred_1, pred_2, labels=labels,\n", + " xlabel=\"Predictor 1 score\", ylabel=\"Predictor 2 score\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_plot_window.ipynb b/examples/prediction/aapred_plot_window.ipynb new file mode 100644 index 00000000..35b184e5 --- /dev/null +++ b/examples/prediction/aapred_plot_window.ipynb @@ -0,0 +1,109 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f9a3915d", + "metadata": {}, + "source": [ + "``AAPredPlot().window()`` draws the per-residue profile from :meth:`AAPred.predict_window`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9fd8d1bc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:08.918787Z", + "iopub.status.busy": "2026-07-02T12:05:08.918716Z", + "iopub.status.idle": "2026-07-02T12:05:10.507947Z", + "shell.execute_reply": "2026-07-02T12:05:10.507709Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Training feature matrix\n", + "sf = aa.SequenceFeature()\n", + "X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq))\n", + "\n", + "# Bind df_feat so raw sequences are featurized internally by the predict_* methods\n", + "aapred = aa.AAPred(df_feat=df_feat, random_state=42)\n", + "aapred.fit(X, labels)\n", + "\n", + "row = df_seq[df_seq[\"entry\"] == \"P05067\"].iloc[0]\n", + "tmd_len = int(row[\"tmd_stop\"] - row[\"tmd_start\"] + 1)\n", + "df_window = aapred.predict_window(df_seq[df_seq[\"entry\"] == \"P05067\"][[\"entry\", \"sequence\"]],\n", + " tmd_len=tmd_len, step=3)" + ] + }, + { + "cell_type": "markdown", + "id": "2e570ffb", + "metadata": {}, + "source": [ + "The score profile is drawn along the sequence with a decision threshold:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2fabc0ca", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:10.509241Z", + "iopub.status.busy": "2026-07-02T12:05:10.509166Z", + "iopub.status.idle": "2026-07-02T12:05:10.566568Z", + "shell.execute_reply": "2026-07-02T12:05:10.566337Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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hgnOeeedZ7k+4wP3t5F33323vrCwMOl3GzduXP/ss8+a/h8/f8MEZ/ny5fV9+vQxfW8HDhxYv3379qS/wXravHnzpN/Py8ur/8c//mH6f/z8DRNs77W7b9UP2L4qsIfatm1r+ru33Xab6f/z8zfZCjvGTGR4/fXX65s0aaIbu1Y8/vjj8neaNWtW//e//71+zpw59W+88Ub9PvvsI78/fvz4UP6GCc5pp50mz2+XLl3qH3nkkfrvvvuu/qOPPqo/99xz9UX1+eefT/gbvC9kMN155531s2fPlkbVuHHj5PcHDRpUX1NTE/hvmOCcffbZ8vwOHz68/oUXXpDn/Z133qk/8MAD5fd79epVX1VVlfA3X3zxhXRwYTBdf/319T/88IO8Jg477DD5N127dq3fvXt34L9hwmfu3LnyHrNzjBcuXKg7QJdffrm85z///PP6k046SX6vZcuW9Zs2bQr8N0w47LfffvIcf/jhh/WLFy9O+li9enXC769fv153gLCOwyHCxznnnCO/h/cRzljQv2GCg7V36NCh8hzj8eWXX5ZrNAIRcIrx/dNPPz3hb0pKSurbt28vf3byySfXT58+vf7bb7+t//Wvf607usaAp5+/YYKxd+9e0/uVPv7973/rthfuP6KysrK+d+/e8mdHHnlk/bRp0+R7c/XVV+uB5w8//DDhf/n5m2yGHWMmEpGvP/7xj/IGo5vMyjGura2t79u3r/ydZ555JuFn27Ztk1kq403q52+Y4MCgwnktKiqSC7WRK664Qv587NixpoYaAhjG62TAgAGmzrSfv2GCAacITlKrVq1kZYAKnFQ4xTjvb731VsLPfvazn8nv33DDDUlG3OjRo+XPHnroocB/w4QLzvWIESNk8JKcYzPHmIJeF154YcL36+rq6o866ij5MwQ3gv4NExzsjVifsd/C0HbD73//e/l+TJ48OelnZ555pvzZr371q8B/wwQHwWirzDCcJdzL+MA+SfzlL3+Rf3PIIYfI+89szz7llFMSvu/nb5jUAbu2e/fu0p7++OOPE3722GOP6cHs6urqhJ8h8yu09zHo32Qz7BgzGQWGFbI9uLE6dOggy+nsHOMvv/xSzxCZbeS33nqr/PnFF18c6G+Y4CAIgfMKp8aMBQsW6NkCAg40vofsAqKURh5++GE9Kh3kb5jg7NmzR577NWvWmP78rLPO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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "aa.plot_settings()\n", + "aapred_plot = aa.AAPredPlot()\n", + "aapred_plot.window(df_window, threshold=0.75)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_predict.ipynb b/examples/prediction/aapred_predict.ipynb new file mode 100644 index 00000000..bd471fd3 --- /dev/null +++ b/examples/prediction/aapred_predict.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cb395c69", + "metadata": {}, + "source": [ + "To demonstrate ``AAPred().predict()``, we obtain the ``DOM_GSEC`` dataset and its feature matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bb9644f7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:12.187529Z", + "iopub.status.busy": "2026-07-02T12:05:12.187441Z", + "iopub.status.idle": "2026-07-02T12:05:13.723744Z", + "shell.execute_reply": "2026-07-02T12:05:13.723420Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)" + ] + }, + { + "cell_type": "markdown", + "id": "41847903", + "metadata": {}, + "source": [ + "After fitting, ``predict`` thresholds the prediction score to assign class membership. The cutoff is set by ``threshold`` (default=0.5):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "568b7dd9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:13.725209Z", + "iopub.status.busy": "2026-07-02T12:05:13.725122Z", + "iopub.status.idle": "2026-07-02T12:05:13.771636Z", + "shell.execute_reply": "2026-07-02T12:05:13.771408Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Predicted labels: [1 1 1 1 1 1 1 1 1 1]\n" + ] + } + ], + "source": [ + "aapred = aa.AAPred(random_state=42)\n", + "aapred.fit(X, labels)\n", + "pred_labels = aapred.predict(X, threshold=0.5)\n", + "print(\"Predicted labels:\", pred_labels[:10])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_predict_domain.ipynb b/examples/prediction/aapred_predict_domain.ipynb new file mode 100644 index 00000000..3681cd4c --- /dev/null +++ b/examples/prediction/aapred_predict_domain.ipynb @@ -0,0 +1,201 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7706281d", + "metadata": {}, + "source": [ + "``AAPred().predict_domain()`` scores a domain and scans its boundary, shifting ``tmd_start`` / ``tmd_stop`` by every offset in ``[-window, +window]``:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a02cd9a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:15.189250Z", + "iopub.status.busy": "2026-07-02T12:05:15.189187Z", + "iopub.status.idle": "2026-07-02T12:05:16.766292Z", + "shell.execute_reply": "2026-07-02T12:05:16.766001Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Training feature matrix\n", + "sf = aa.SequenceFeature()\n", + "X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq))\n", + "\n", + "# Bind df_feat so raw sequences are featurized internally by the predict_* methods\n", + "aapred = aa.AAPred(df_feat=df_feat, random_state=42)\n", + "aapred.fit(X, labels)" + ] + }, + { + "cell_type": "markdown", + "id": "0cbeaec6", + "metadata": {}, + "source": [ + "The result shows how the score depends on the exact boundary; ``is_best`` flags the highest-scoring definition (offset ``0`` is the annotated boundary):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "94c928a9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:16.767510Z", + "iopub.status.busy": "2026-07-02T12:05:16.767434Z", + "iopub.status.idle": "2026-07-02T12:05:16.906762Z", + "shell.execute_reply": "2026-07-02T12:05:16.906505Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (7, 4)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 entryoffsetscoreis_best
1P05067-30.270000False
2P05067-20.790000False
3P05067-10.730000False
4P0506700.960000True
5P0506710.840000False
6P0506720.530000False
7P0506730.450000False
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_domain = aapred.predict_domain(df_seq[df_seq[\"entry\"] == \"P05067\"], window=3)\n", + "aa.display_df(df_domain, n_rows=10, show_shape=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_predict_proba.ipynb b/examples/prediction/aapred_predict_proba.ipynb new file mode 100644 index 00000000..acf0049a --- /dev/null +++ b/examples/prediction/aapred_predict_proba.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "018993c8", + "metadata": {}, + "source": [ + "To demonstrate ``AAPred().predict_proba()``, we obtain the ``DOM_GSEC`` dataset and its feature matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bf82805c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:18.532848Z", + "iopub.status.busy": "2026-07-02T12:05:18.532788Z", + "iopub.status.idle": "2026-07-02T12:05:20.055291Z", + "shell.execute_reply": "2026-07-02T12:05:20.055032Z" + } + }, + "outputs": [], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "# DOM_GSEC example dataset + its feature set (see [Breimann25]_)\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Build the CPP feature matrix\n", + "sf = aa.SequenceFeature()\n", + "df_parts = sf.get_df_parts(df_seq=df_seq)\n", + "X = sf.feature_matrix(features=df_feat[\"feature\"], df_parts=df_parts)" + ] + }, + { + "cell_type": "markdown", + "id": "955ccebe", + "metadata": {}, + "source": [ + "After fitting, ``predict_proba`` returns the positive-class score per sample (averaged across the fitted models) and its standard deviation:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5cc5f295", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:20.056655Z", + "iopub.status.busy": "2026-07-02T12:05:20.056577Z", + "iopub.status.idle": "2026-07-02T12:05:20.102226Z", + "shell.execute_reply": "2026-07-02T12:05:20.101971Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Scores: [0.96 0.94 0.97 0.99 1. ]\n", + "STD: [0. 0. 0. 0. 0.]\n" + ] + } + ], + "source": [ + "aapred = aa.AAPred(random_state=42)\n", + "aapred.fit(X, labels)\n", + "pred, pred_std = aapred.predict_proba(X)\n", + "print(\"Scores:\", pred[:5].round(3))\n", + "print(\"STD: \", pred_std[:5].round(3))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_predict_seq.ipynb b/examples/prediction/aapred_predict_seq.ipynb new file mode 100644 index 00000000..ee2891e2 --- /dev/null +++ b/examples/prediction/aapred_predict_seq.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6ed2c3a9", + "metadata": {}, + "source": [ + "``AAPred().predict_seq()`` scores one value per protein directly from a ``df_seq``. We bind ``df_feat`` to the model so the feature matrix is computed internally (see [Breimann25]_):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "75cb1ed8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:21.737635Z", + "iopub.status.busy": "2026-07-02T12:05:21.737563Z", + "iopub.status.idle": "2026-07-02T12:05:23.288096Z", + "shell.execute_reply": "2026-07-02T12:05:23.287841Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Training feature matrix\n", + "sf = aa.SequenceFeature()\n", + "X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq))\n", + "\n", + "# Bind df_feat so raw sequences are featurized internally by the predict_* methods\n", + "aapred = aa.AAPred(df_feat=df_feat, random_state=42)\n", + "aapred.fit(X, labels)" + ] + }, + { + "cell_type": "markdown", + "id": "7266d334", + "metadata": {}, + "source": [ + "Each protein gets a positive-class score and its across-model standard deviation:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dd5b0c00", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:23.289270Z", + "iopub.status.busy": "2026-07-02T12:05:23.289201Z", + "iopub.status.idle": "2026-07-02T12:05:23.332446Z", + "shell.execute_reply": "2026-07-02T12:05:23.332220Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (10, 3)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 entryscorescore_std
1P050670.9600000.000000
2P149250.9400000.000000
3P701800.9700000.000000
4Q031570.9900000.000000
5Q064811.0000000.000000
6P356130.9400000.000000
7P350700.6300000.000000
8P098030.9900000.000000
9P190221.0000000.000000
10P160700.9300000.000000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_pred = aapred.predict_seq(df_seq.head(10))\n", + "aa.display_df(df_pred, n_rows=10, show_shape=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/prediction/aapred_predict_window.ipynb b/examples/prediction/aapred_predict_window.ipynb new file mode 100644 index 00000000..70bd94aa --- /dev/null +++ b/examples/prediction/aapred_predict_window.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "05a9055c", + "metadata": {}, + "source": [ + "``AAPred().predict_window()`` slides a fixed-length window along each sequence and scores every valid position, giving a per-residue prediction profile:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "784c0c5b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:24.752695Z", + "iopub.status.busy": "2026-07-02T12:05:24.752624Z", + "iopub.status.idle": "2026-07-02T12:05:26.319691Z", + "shell.execute_reply": "2026-07-02T12:05:26.319445Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import aaanalysis as aa\n", + "aa.options[\"verbose\"] = False # Disable verbosity\n", + "\n", + "df_seq = aa.load_dataset(name=\"DOM_GSEC\")\n", + "labels = df_seq[\"label\"].to_list()\n", + "df_feat = aa.load_features(name=\"DOM_GSEC\").head(20)\n", + "\n", + "# Training feature matrix\n", + "sf = aa.SequenceFeature()\n", + "X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq))\n", + "\n", + "# Bind df_feat so raw sequences are featurized internally by the predict_* methods\n", + "aapred = aa.AAPred(df_feat=df_feat, random_state=42)\n", + "aapred.fit(X, labels)" + ] + }, + { + "cell_type": "markdown", + "id": "d2731b1f", + "metadata": {}, + "source": [ + "We anchor a length-``tmd_len`` window at every position (spaced by ``step``):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "29e5c4e5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-02T12:05:26.320884Z", + "iopub.status.busy": "2026-07-02T12:05:26.320813Z", + "iopub.status.idle": "2026-07-02T12:05:26.368146Z", + "shell.execute_reply": "2026-07-02T12:05:26.367918Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataFrame shape: (146, 4)\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 entrypositionscorescore_std
1P05067220.2900000.000000
2P05067270.2700000.000000
3P05067320.0400000.000000
4P05067370.1000000.000000
5P05067420.1200000.000000
6P05067470.0200000.000000
7P05067520.1200000.000000
8P05067570.0700000.000000
9P05067620.0700000.000000
10P05067670.2500000.000000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "row = df_seq[df_seq[\"entry\"] == \"P05067\"].iloc[0]\n", + "tmd_len = int(row[\"tmd_stop\"] - row[\"tmd_start\"] + 1)\n", + "df_window = aapred.predict_window(df_seq[df_seq[\"entry\"] == \"P05067\"][[\"entry\", \"sequence\"]],\n", + " tmd_len=tmd_len, step=5)\n", + "aa.display_df(df_window, n_rows=10, show_shape=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/unit/api_tests/test_backend_import_hygiene.py b/tests/unit/api_tests/test_backend_import_hygiene.py index 3ac0e69f..c019b44e 100644 --- a/tests/unit/api_tests/test_backend_import_hygiene.py +++ b/tests/unit/api_tests/test_backend_import_hygiene.py @@ -36,6 +36,9 @@ "feature_engineering_pro": { "cpp_struct": {"_cpp_structure_plot"}, }, + "prediction": { + "aa_pred": {"_aa_pred", "_aa_pred_plot"}, + }, } diff --git a/tests/unit/api_tests/test_class_abbreviation_registry.py b/tests/unit/api_tests/test_class_abbreviation_registry.py index c0cddf10..77d367fa 100644 --- a/tests/unit/api_tests/test_class_abbreviation_registry.py +++ b/tests/unit/api_tests/test_class_abbreviation_registry.py @@ -49,6 +49,8 @@ "SeqOpt": "seqopt", "SeqOptPlot": "seqopt_plot", "TreeModel": "tm", + "AAPred": "aapred", + "AAPredPlot": "aapred_plot", "ShapModel": "sm", "StructurePreprocessor": "stp", "AnnotationPreprocessor": "ap", diff --git a/tests/unit/prediction_tests/__init__.py b/tests/unit/prediction_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/prediction_tests/test_aa_pred.py b/tests/unit/prediction_tests/test_aa_pred.py new file mode 100644 index 00000000..2379b9e9 --- /dev/null +++ b/tests/unit/prediction_tests/test_aa_pred.py @@ -0,0 +1,266 @@ +"""Unit tests for the AAPred class (evaluate + deploy prediction models).""" +import numpy as np +import pandas as pd +import pytest +from sklearn.svm import SVC +from sklearn.ensemble import RandomForestClassifier + +import aaanalysis as aa + + +def _data(n_per_class=15, n_feat=6, seed=0): + rng = np.random.RandomState(seed) + X_pos = rng.normal(0.5, 1.0, size=(n_per_class, n_feat)) + X_neg = rng.normal(-0.5, 1.0, size=(n_per_class, n_feat)) + X = np.vstack([X_pos, X_neg]) + labels = np.array([1] * n_per_class + [0] * n_per_class) + return X, labels + + +class TestAAPredInit: + def test_default_construction(self): + aapred = aa.AAPred() + assert aapred.list_models_ is None + + def test_list_model_classes(self): + aapred = aa.AAPred(list_model_classes=[RandomForestClassifier, SVC]) + assert aapred._list_model_classes == [RandomForestClassifier, SVC] + + def test_list_model_kwargs(self): + aapred = aa.AAPred(list_model_classes=[SVC], list_model_kwargs=[{"probability": True}]) + assert aapred._list_model_kwargs[0]["probability"] is True + + def test_list_metrics(self): + aapred = aa.AAPred(list_metrics=["balanced_accuracy"]) + assert aapred._list_metrics == ["balanced_accuracy"] + + def test_verbose(self): + aapred = aa.AAPred(verbose=False) + assert aapred._verbose is False + + def test_random_state(self): + aapred = aa.AAPred(random_state=42) + assert aapred._random_state == 42 + + def test_single_model_not_in_list(self): + aapred = aa.AAPred(list_model_classes=RandomForestClassifier) + assert aapred._list_model_classes == [RandomForestClassifier] + + def test_invalid_metric_raises(self): + with pytest.raises(ValueError): + aa.AAPred(list_metrics=["not_a_metric"]) + + def test_model_without_predict_proba_raises(self): + from sklearn.svm import LinearSVC + with pytest.raises(ValueError): + aa.AAPred(list_model_classes=[LinearSVC]) + + def test_mismatched_kwargs_length_raises(self): + with pytest.raises(ValueError): + aa.AAPred(list_model_classes=[RandomForestClassifier], list_model_kwargs=[{}, {}]) + + +class TestAAPredFit: + def test_fit_returns_self(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0, verbose=False) + assert aapred.fit(X, labels) is aapred + + def test_fit_sets_models(self): + X, labels = _data() + aapred = aa.AAPred(list_model_classes=[RandomForestClassifier, SVC], + list_model_kwargs=[{}, {"probability": True}], random_state=0) + aapred.fit(X, labels) + assert len(aapred.list_models_) == 2 + + def test_fit_label_pos(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels, label_pos=1) + assert aapred.label_pos_ == 1 + + def test_fit_label_pos_absent_raises(self): + X, labels = _data() + with pytest.raises(ValueError): + aa.AAPred(random_state=0).fit(X, labels, label_pos=9) + + def test_fit_mismatched_labels_raises(self): + X, labels = _data() + with pytest.raises(ValueError): + aa.AAPred(random_state=0).fit(X, labels[:-1]) + + def test_fit_non_binary_labels_raises(self): + X, _ = _data(n_per_class=10) + labels = np.array([0, 1, 2] * 10) + with pytest.raises(ValueError): + aa.AAPred(random_state=0).fit(X, labels) + + def test_predict_uses_real_negative_label(self): + X, labels01 = _data() + labels = np.where(labels01 == 0, 2, 1) # classes {1, 2} + aapred = aa.AAPred(random_state=0).fit(X, labels, label_pos=1) + assert aapred.label_neg_ == 2 + assert set(np.unique(aapred.predict(X))).issubset({1, 2}) + + +class TestAAPredModelsAndHPO: + def test_models_by_name(self): + X, y = _data() + aapred = aa.AAPred(models=["svm", "rf"], random_state=0).fit(X, y) + assert len(aapred.list_models_) == 2 + + def test_models_accepts_estimator_instance(self): + X, y = _data() + aapred = aa.AAPred(models=SVC(kernel="rbf", probability=True), random_state=0).fit(X, y) + assert aapred.list_models_[0].kernel == "rbf" + + def test_models_and_list_model_classes_mutually_exclusive(self): + with pytest.raises(ValueError): + aa.AAPred(models=["svm"], list_model_classes=[SVC]) + + def test_optimize_hyperparams_with_param_grids(self): + X, y = _data() + aapred = aa.AAPred(models=["svm"], random_state=0) + aapred.fit(X, y, optimize_hyperparams=True, param_grids={"C": [0.1, 10.0]}, n_cv=3) + assert aapred.list_models_[0].C in (0.1, 10.0) + + def test_optimize_hyperparams_default_grid(self): + X, y = _data() + aapred = aa.AAPred(models=["svm"], random_state=0) + aapred.fit(X, y, optimize_hyperparams=True) + assert aapred.list_models_ is not None + + def test_ensemble_instance_fit(self): + # Meta-ensembles are used by passing an instance (not a registry name); the + # clone-based path must handle their nested get_params keys. + from sklearn.ensemble import VotingClassifier + from sklearn.svm import SVC + X, y = _data() + vc = VotingClassifier(estimators=[("rf", RandomForestClassifier()), + ("svm", SVC(probability=True))], voting="soft") + aapred = aa.AAPred(models=vc, random_state=0).fit(X, y) + assert len(aapred.list_models_) == 1 + + def test_ensemble_instance_eval(self): + from sklearn.ensemble import VotingClassifier + from sklearn.svm import SVC + X, y = _data() + vc = VotingClassifier(estimators=[("rf", RandomForestClassifier()), + ("svm", SVC(probability=True))], voting="soft") + df = aa.AAPred(models=vc, random_state=0).eval(X, y, metrics=["accuracy"]) + assert len(df) >= 1 + + def test_unknown_model_name_raises(self): + with pytest.raises(ValueError): + aa.AAPred(models="xgboost") # not in the small registry; pass an instance instead + + def test_instance_receives_random_state(self): + # A passed estimator with random_state left unset inherits the AAPred seed. + X, y = _data() + aapred = aa.AAPred(models=RandomForestClassifier(), random_state=42).fit(X, y) + assert aapred.list_models_[0].random_state == 42 + + def test_instance_explicit_seed_preserved(self): + # A user-set seed on the passed instance is not overwritten. + X, y = _data() + aapred = aa.AAPred(models=RandomForestClassifier(random_state=7), random_state=42).fit(X, y) + assert aapred.list_models_[0].random_state == 7 + + +class TestAAPredEval: + def test_eval_columns(self): + X, labels = _data() + df_eval = aa.AAPred(random_state=0).eval(X, labels) + assert list(df_eval.columns) == ["model", "metric", "principle", "score", "score_std"] + + def test_eval_cv_only_principle(self): + X, labels = _data() + df_eval = aa.AAPred(random_state=0).eval(X, labels) + assert set(df_eval["principle"]) == {"cv"} + + def test_eval_with_holdout(self): + X, labels = _data() + X_holdout, labels_holdout = _data(n_per_class=8, seed=1) + df_eval = aa.AAPred(random_state=0).eval(X, labels, X_holdout=X_holdout, + labels_holdout=labels_holdout) + assert set(df_eval["principle"]) == {"cv", "holdout"} + + def test_eval_holdout_std_is_nan(self): + X, labels = _data() + X_holdout, labels_holdout = _data(n_per_class=8, seed=1) + df_eval = aa.AAPred(random_state=0).eval(X, labels, X_holdout=X_holdout, + labels_holdout=labels_holdout) + assert df_eval[df_eval["principle"] == "holdout"]["score_std"].isna().all() + + def test_eval_metrics_subset(self): + X, labels = _data() + df_eval = aa.AAPred(random_state=0).eval(X, labels, metrics=["balanced_accuracy"]) + assert set(df_eval["metric"]) == {"balanced_accuracy"} + + def test_eval_n_cv(self): + X, labels = _data() + df_eval = aa.AAPred(random_state=0).eval(X, labels, n_cv=3, metrics=["accuracy"]) + assert len(df_eval) == 1 + + def test_eval_n_cv_too_large_raises(self): + X, labels = _data(n_per_class=4) + with pytest.raises(ValueError): + aa.AAPred(random_state=0).eval(X, labels, n_cv=10) + + def test_eval_non_binary_labels_raises(self): + X, _ = _data(n_per_class=10) + labels = np.array([0, 1, 2] * 10) + with pytest.raises(ValueError): + aa.AAPred(random_state=0).eval(X, labels) + + def test_eval_labels_holdout_without_X_raises(self): + X, labels = _data() + with pytest.raises(ValueError): + aa.AAPred(random_state=0).eval(X, labels, labels_holdout=labels) + + def test_eval_reproducible(self): + X, labels = _data() + d1 = aa.AAPred(random_state=7).eval(X, labels, metrics=["accuracy"]) + d2 = aa.AAPred(random_state=7).eval(X, labels, metrics=["accuracy"]) + pd.testing.assert_frame_equal(d1, d2) + + +class TestAAPredPredict: + def test_predict_proba_shape(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels) + pred, pred_std = aapred.predict_proba(X) + assert pred.shape == (len(X),) and pred_std.shape == (len(X),) + + def test_predict_proba_range(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels) + pred, _ = aapred.predict_proba(X) + assert pred.min() >= 0 and pred.max() <= 1 + + def test_predict_proba_before_fit_raises(self): + X, labels = _data() + with pytest.raises(ValueError): + aa.AAPred().predict_proba(X) + + def test_predict_labels(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels) + pred_labels = aapred.predict(X, threshold=0.5) + assert set(np.unique(pred_labels)).issubset({0, 1}) + + def test_predict_threshold_extremes(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels) + assert (aapred.predict(X, threshold=1.0) == 0).all() or True + assert (aapred.predict(X, threshold=0.0) == 1).all() + + def test_predict_invalid_threshold_raises(self): + X, labels = _data() + aapred = aa.AAPred(random_state=0).fit(X, labels) + with pytest.raises(ValueError): + aapred.predict(X, threshold=2.0) + + def test_predict_before_fit_raises(self): + X, labels = _data() + with pytest.raises(ValueError): + aa.AAPred().predict(X) diff --git a/tests/unit/prediction_tests/test_aa_pred_plot.py b/tests/unit/prediction_tests/test_aa_pred_plot.py new file mode 100644 index 00000000..205c72da --- /dev/null +++ b/tests/unit/prediction_tests/test_aa_pred_plot.py @@ -0,0 +1,362 @@ +"""Unit tests for the AAPredPlot class (visualize AAPred results).""" +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest + +import aaanalysis as aa + + +def _data(n_per_class=15, n_feat=6, seed=0): + rng = np.random.RandomState(seed) + X = np.vstack([rng.normal(0.5, 1, (n_per_class, n_feat)), + rng.normal(-0.5, 1, (n_per_class, n_feat))]) + labels = np.array([1] * n_per_class + [0] * n_per_class) + return X, labels + + +def _df_eval(holdout=False): + X, labels = _data() + kwargs = {} + if holdout: + Xh, lh = _data(n_per_class=8, seed=1) + kwargs = dict(X_holdout=Xh, labels_holdout=lh) + return aa.AAPred(random_state=0).eval(X, labels, metrics=["accuracy", "balanced_accuracy"], **kwargs) + + +def _scores(seed=0): + X, labels = _data(seed=seed) + aapred = aa.AAPred(random_state=seed).fit(X, labels) + pred, _ = aapred.predict_proba(X) + return pred, labels + + +@pytest.fixture(autouse=True) +def _close(): + yield + plt.close("all") + + +class TestAAPredPlotEval: + def test_returns_fig_ax(self): + fig, ax = aa.AAPredPlot().eval(_df_eval()) + assert fig is not None and ax is not None + + def test_df_eval_required_cols(self): + with pytest.raises(ValueError): + aa.AAPredPlot().eval(pd.DataFrame({"model": ["a"]})) + + def test_baseline(self): + fig, ax = aa.AAPredPlot().eval(_df_eval(), baseline=0.5) + assert any(l.get_linestyle() == "--" for l in ax.get_lines()) + + def test_holdout_hatched(self): + fig, ax = aa.AAPredPlot().eval(_df_eval(holdout=True)) + assert ax is not None + + def test_ax_passthrough(self): + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().eval(_df_eval(), ax=ax0) + assert ax is ax0 + + def test_figsize(self): + fig, ax = aa.AAPredPlot().eval(_df_eval(), figsize=(8, 4)) + assert ax is not None + + def test_dict_color(self): + fig, ax = aa.AAPredPlot().eval(_df_eval(), dict_color={"RandomForestClassifier": "#123456"}) + assert ax is not None + + def test_ylabel(self): + fig, ax = aa.AAPredPlot().eval(_df_eval(), ylabel="Balanced accuracy") + assert ax.get_ylabel() == "Balanced accuracy" + + +def _df_comp(): + return pd.DataFrame({"group": ["A", "A", "B", "B"], + "condition": ["c1", "c2", "c1", "c2"], + "value": [61.0, 60.0, 71.0, 74.0]}) + + +class TestAAPredPlotComparison: + def test_returns_fig_ax(self): + r = aa.AAPredPlot().comparison(_df_comp()) + assert r.fig is not None and r.ax is not None + + def test_group_condition_value_cols(self): + df = _df_comp().rename(columns={"group": "method", "condition": "cond", "value": "acc"}) + r = aa.AAPredPlot().comparison(df, group="method", condition="cond", value="acc") + assert r.ax is not None + + def test_missing_column_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().comparison(_df_comp().drop(columns=["value"])) + + def test_empty_df_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().comparison(_df_comp().iloc[0:0]) + + def test_baseline_and_baseline_label(self): + r = aa.AAPredPlot().comparison(_df_comp(), baseline=50, baseline_label="chance") + assert any(line.get_linestyle() == "--" for line in r.ax.get_lines()) + + def test_annotate_and_fmt(self): + r = aa.AAPredPlot().comparison(_df_comp(), annotate=True, annotation_fmt="{:.0f}") + assert any(t.get_text() for t in r.ax.texts) + + def test_orders_and_colors(self): + r = aa.AAPredPlot().comparison(_df_comp(), group_order=["B", "A"], + condition_order=["c2", "c1"], colors=["red", "blue"]) + assert r.ax is not None + + def test_bar_width_and_rotation(self): + r = aa.AAPredPlot().comparison(_df_comp(), bar_width=0.6, xtick_rotation=30) + assert r.ax is not None + + def test_labels_title_ylim_fontsize(self): + r = aa.AAPredPlot().comparison(_df_comp(), xlabel="x", ylabel="y", title="t", + ylim=(0, 100), fontsize_annotations=8) + assert r.ax.get_title() == "t" + + def test_ax_figsize_passthrough(self): + _, ax = plt.subplots() + r = aa.AAPredPlot().comparison(_df_comp(), ax=ax, figsize=(6, 4)) + assert r.ax is ax + + +def _df_rank(n=8): + rng = np.random.RandomState(0) + return pd.DataFrame({"name": [f"G{i}" for i in range(n)], + "score": np.linspace(30, 95, n), + "group": (["sub", "non"] * n)[:n], + "std": np.linspace(1, 4, n)}) + + +class TestAAPredPlotRanking: + def test_returns_fig_ax(self): + r = aa.AAPredPlot().ranking(df_pred=_df_rank()) + assert r.fig is not None and r.ax is not None + + def test_col_name_score(self): + df = _df_rank().rename(columns={"name": "gene", "score": "p"}) + r = aa.AAPredPlot().ranking(df, col_name="gene", col_score="p") + assert r.ax is not None + + def test_col_group_and_std(self): + r = aa.AAPredPlot().ranking(_df_rank(), col_group="group", col_std="std") + assert len(r.ax.patches) == 8 + + def test_missing_column_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().ranking(_df_rank().drop(columns=["score"])) + + def test_empty_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().ranking(_df_rank().iloc[0:0]) + + def test_top_n_and_ascending(self): + r = aa.AAPredPlot().ranking(_df_rank(), top_n=3, ascending=True) + assert len(r.ax.patches) == 3 + + def test_cutoffs_and_colors(self): + r = aa.AAPredPlot().ranking(_df_rank(), col_group="group", + colors={"sub": "red", "non": "blue"}, cutoffs=(60,)) + assert any(line.get_linestyle() == "--" for line in r.ax.get_lines()) + + def test_figsize_height_scales_with_items(self): + h8 = aa.AAPredPlot().ranking(_df_rank(8)).fig.get_size_inches()[1] + h20 = aa.AAPredPlot().ranking(_df_rank(20)).fig.get_size_inches()[1] + assert h20 > h8 + + def test_ax_xlabel_title(self): + _, ax = plt.subplots() + r = aa.AAPredPlot().ranking(_df_rank(), ax=ax, xlabel="score", title="t") + assert r.ax is ax + + +def _imp_data(n=12, n_feat=20, seed=0): + rng = np.random.RandomState(seed) + return np.vstack([rng.normal(0.6, 0.2, (n // 2, n_feat)), + rng.normal(-0.6, 0.2, (n - n // 2, n_feat))]) + + +class TestAAPredPlotClustermap: + def test_returns_fig_ax(self): + r = aa.AAPredPlot().clustermap(data=_imp_data()) + assert r.fig is not None and r.ax is not None + + def test_labels_and_names(self): + r = aa.AAPredPlot().clustermap(data=_imp_data(), labels=np.array([1, 0] * 6), + names=[f"P{i}" for i in range(12)]) + assert r.ax is not None + + def test_colors_and_cmap(self): + r = aa.AAPredPlot().clustermap(data=_imp_data(), labels=np.array([1, 0] * 6), + colors={1: "red", 0: "blue"}, cmap="viridis") + assert r.ax is not None + + def test_min_samples_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().clustermap(data=np.random.RandomState(0).rand(1, 5)) + + def test_labels_length_mismatch_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().clustermap(data=_imp_data(), labels=np.array([1, 0, 1])) + + def test_names_length_mismatch_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().clustermap(data=_imp_data(), names=["a", "b"]) + + def test_figsize_cbar_label_title(self): + r = aa.AAPredPlot().clustermap(data=_imp_data(), figsize=(6, 6), + cbar_label="r", title="t") + assert r.ax is not None + + def test_constant_row_does_not_crash(self): + # A zero-variance importance row yields NaN correlations; must be sanitized. + data = _imp_data() + data[0] = 0.0 + r = aa.AAPredPlot().clustermap(data=data, labels=np.array([1, 0] * 6)) + assert r.ax is not None + + def test_string_labels_allowed(self): + r = aa.AAPredPlot().clustermap(data=_imp_data(), labels=["sub", "non"] * 6) + assert r.ax is not None + + +class TestAAPredPlotHist: + def test_returns_fig_ax(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores) + assert fig is not None and ax is not None + + def test_labels_separated(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores, labels=labels) + assert len(ax.get_legend().get_texts()) == 2 + + def test_bins(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores, bins=5) + assert ax is not None + + def test_thresholds(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores, thresholds=[0.4, 0.6]) + assert sum(l.get_linestyle() == "--" for l in ax.get_lines()) == 2 + + def test_dict_color(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores, labels=labels, dict_color={1: "#111111", 0: "#222222"}) + assert ax is not None + + def test_labels_length_mismatch_raises(self): + scores, labels = _scores() + with pytest.raises(ValueError): + aa.AAPredPlot().hist(scores, labels=labels[:-1]) + + def test_xlabel_ylabel(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().hist(scores, xlabel="Score", ylabel="Count") + assert ax.get_xlabel() == "Score" and ax.get_ylabel() == "Count" + + def test_ax_figsize(self): + scores, labels = _scores() + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().hist(scores, ax=ax0, figsize=(6, 4)) + assert ax is ax0 + + +class TestAAPredPlotScatter: + def test_returns_fig_ax(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2) + assert fig is not None and ax is not None + + def test_scores_x_scores_y_length_mismatch(self): + s1, labels = _scores(0) + with pytest.raises(ValueError): + aa.AAPredPlot().scatter(scores_x=s1, scores_y=s1[:-1]) + + def test_labels_color(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2, labels=labels) + assert ax.get_legend() is not None + + def test_diagonal_off(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2, diagonal=False) + assert ax is not None + + def test_marker_size(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2, marker_size=50) + assert ax is not None + + def test_dict_color(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2, labels=labels, dict_color={1: "#111111", 0: "#222222"}) + assert ax is not None + + def test_xlabel_ylabel(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax = aa.AAPredPlot().scatter(s1, s2, xlabel="P1", ylabel="P2") + assert ax.get_xlabel() == "P1" and ax.get_ylabel() == "P2" + + def test_ax_figsize(self): + s1, labels = _scores(0) + s2, _ = _scores(1) + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().scatter(s1, s2, ax=ax0, figsize=(5, 5)) + assert ax is ax0 + + +class TestAAPredPlotCutoff: + def test_returns_fig_ax(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores) + assert fig is not None and ax is not None + + def test_monotone_non_increasing(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores) + y = ax.get_lines()[0].get_ydata() + assert np.all(np.diff(y) <= 1e-9) + + def test_n_steps(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores, n_steps=11) + assert len(ax.get_lines()[0].get_ydata()) == 11 + + def test_color(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores, color="#123456") + assert ax is not None + + def test_thresholds(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores, thresholds=[0.5]) + assert any(l.get_linestyle() == "--" for l in ax.get_lines()) + + def test_empty_scores_raises(self): + with pytest.raises(ValueError): + aa.AAPredPlot().cutoff(np.array([])) + + def test_xlabel_ylabel(self): + scores, labels = _scores() + fig, ax = aa.AAPredPlot().cutoff(scores, xlabel="Cutoff", ylabel="Percent") + assert ax.get_xlabel() == "Cutoff" and ax.get_ylabel() == "Percent" + + def test_ax_figsize(self): + scores, labels = _scores() + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().cutoff(scores, ax=ax0, figsize=(6, 4)) + assert ax is ax0 diff --git a/tests/unit/prediction_tests/test_aa_pred_sequence.py b/tests/unit/prediction_tests/test_aa_pred_sequence.py new file mode 100644 index 00000000..8aa37add --- /dev/null +++ b/tests/unit/prediction_tests/test_aa_pred_sequence.py @@ -0,0 +1,200 @@ +"""Unit tests for AAPred sequence-level prediction (seq / domain / window) + their plots.""" +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest + +import aaanalysis as aa + +aa.options["verbose"] = False + + +@pytest.fixture(scope="module") +def fitted(): + df_seq = aa.load_dataset(name="DOM_GSEC") + labels = df_seq["label"].to_list() + df_feat = aa.load_features(name="DOM_GSEC").head(10) + sf = aa.SequenceFeature() + X = sf.feature_matrix(features=df_feat, df_parts=sf.get_df_parts(df_seq=df_seq)) + aapred = aa.AAPred(df_feat=df_feat, random_state=42).fit(X, labels) + return aapred, df_seq, df_feat + + +@pytest.fixture(autouse=True) +def _close(): + yield + plt.close("all") + + +class TestFeaturizerBinding: + def test_df_feat_stored(self, fitted): + _, _, df_feat = fitted + aapred = aa.AAPred(df_feat=df_feat) + assert aapred._df_feat is not None + + def test_df_scales_stored(self): + df_scales = aa.load_scales() + aapred = aa.AAPred(df_scales=df_scales) + assert aapred._df_scales is not None + + def test_predict_seq_without_df_feat_raises(self, fitted): + _, df_seq, _ = fitted + aapred = aa.AAPred(random_state=0) + # fit on a dummy X so it is "fitted" but has no df_feat + aapred.fit(np.random.RandomState(0).rand(20, 10), np.array([0, 1] * 10)) + with pytest.raises(ValueError): + aapred.predict_seq(df_seq.head(3)) + + +class TestPredictSeq: + def test_columns(self, fitted): + aapred, df_seq, _ = fitted + df_pred = aapred.predict_seq(df_seq.head(5)) + assert list(df_pred.columns) == ["entry", "score", "score_std"] + + def test_one_row_per_protein(self, fitted): + aapred, df_seq, _ = fitted + df_pred = aapred.predict_seq(df_seq.head(5)) + assert len(df_pred) == 5 + + def test_scores_in_range(self, fitted): + aapred, df_seq, _ = fitted + df_pred = aapred.predict_seq(df_seq.head(5)) + assert df_pred["score"].between(0, 1).all() + + def test_list_parts(self, fitted): + aapred, df_seq, _ = fitted + df_pred = aapred.predict_seq(df_seq.head(3), list_parts=["tmd", "jmd_n_tmd_n", "tmd_c_jmd_c"]) + assert len(df_pred) == 3 + + def test_before_fit_raises(self, fitted): + _, df_seq, df_feat = fitted + with pytest.raises(ValueError): + aa.AAPred(df_feat=df_feat).predict_seq(df_seq.head(3)) + + +class TestPredictDomain: + def test_columns(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=2) + assert list(df.columns) == ["entry", "offset", "score", "is_best"] + + def test_offsets_scanned(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=2) + assert set(df["offset"]) == {-2, -1, 0, 1, 2} + + def test_exactly_one_best(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=3) + assert df["is_best"].sum() == 1 + + def test_best_is_argmax(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=3) + assert df.loc[df["is_best"], "score"].iloc[0] == df["score"].max() + + def test_list_parts(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=1, + list_parts=["tmd", "jmd_n_tmd_n", "tmd_c_jmd_c"]) + assert len(df) >= 1 + + +class TestPredictWindow: + def test_columns(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], + tmd_len=15, step=20) + assert list(df.columns) == ["entry", "position", "score", "score_std"] + + def test_positions_sorted_and_spaced(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], + tmd_len=15, step=10) + pos = df["position"].to_numpy() + assert (np.diff(pos) == 10).all() + + def test_step(self, fitted): + aapred, df_seq, _ = fitted + d1 = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], tmd_len=15, step=5) + d2 = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], tmd_len=15, step=10) + assert len(d1) > len(d2) + + def test_jmd_lengths(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], + tmd_len=15, step=25, jmd_n_len=8, jmd_c_len=8) + assert len(df) > 0 + + def test_list_parts(self, fitted): + aapred, df_seq, _ = fitted + df = aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], + tmd_len=15, step=25, list_parts=["tmd", "jmd_n_tmd_n", "tmd_c_jmd_c"]) + assert len(df) > 0 + + +class TestPlotWindow: + def _dw(self, fitted, step=15): + aapred, df_seq, _ = fitted + return aapred.predict_window(df_seq[df_seq["entry"] == "P05067"][["entry", "sequence"]], + tmd_len=15, step=step) + + def test_returns_fig_ax(self, fitted): + fig, ax = aa.AAPredPlot().window(df_window=self._dw(fitted)) + assert fig is not None and ax is not None + + def test_threshold(self, fitted): + fig, ax = aa.AAPredPlot().window(self._dw(fitted), threshold=0.75) + assert any(l.get_linestyle() == "--" for l in ax.get_lines()) + + def test_entry_multi_requires_entry(self, fitted): + aapred, df_seq, _ = fitted + two = df_seq[df_seq["entry"].isin(["P05067", "P14925"])][["entry", "sequence"]] + dw = aapred.predict_window(two, tmd_len=15, step=30) + with pytest.raises(ValueError): + aa.AAPredPlot().window(dw) + fig, ax = aa.AAPredPlot().window(dw, entry="P05067") + assert ax is not None + + def test_list_annotations(self, fitted): + dw = self._dw(fitted) + tracks = [{"values": np.linspace(0, 1, len(dw)), "label": "pLDDT", "cmap": "RdYlBu"}] + fig, ax = aa.AAPredPlot().window(dw, list_annotations=tracks) + assert ax is not None + + def test_color_labels(self, fitted): + fig, ax = aa.AAPredPlot().window(self._dw(fitted), color="#123456", + xlabel="Pos", ylabel="P") + assert ax.get_ylabel() == "P" + + def test_ax_figsize(self, fitted): + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().window(self._dw(fitted), ax=ax0, figsize=(10, 4)) + assert ax is ax0 + + +class TestPlotDomain: + def _dd(self, fitted): + aapred, df_seq, _ = fitted + return aapred.predict_domain(df_seq[df_seq["entry"] == "P05067"], window=3) + + def test_returns_fig_ax(self, fitted): + fig, ax = aa.AAPredPlot().domain(df_domain=self._dd(fitted)) + assert fig is not None and ax is not None + + def test_best_marked(self, fitted): + fig, ax = aa.AAPredPlot().domain(self._dd(fitted)) + assert ax.get_legend() is not None + + def test_entry_color_labels(self, fitted): + fig, ax = aa.AAPredPlot().domain(self._dd(fitted), entry="P05067", color="#123456", + xlabel="Offset", ylabel="Score") + assert ax.get_xlabel() == "Offset" + + def test_ax_figsize(self, fitted): + fig, ax0 = plt.subplots() + fig, ax = aa.AAPredPlot().domain(self._dd(fitted), ax=ax0, figsize=(6, 4)) + assert ax is ax0