Source code for pertpy.tools._perturbation_space._clustering
from __future__ import annotations
from typing import TYPE_CHECKING
from sklearn.metrics import pairwise_distances
from pertpy.tools._perturbation_space._perturbation_space import PerturbationSpace, _resolve_matrix
if TYPE_CHECKING:
from collections.abc import Iterable
from anndata import AnnData
[docs]
class ClusteringSpace(PerturbationSpace):
"""Applies various clustering techniques to an embedding."""
[docs]
def evaluate_clustering(
self,
adata: AnnData,
true_label_col: str,
cluster_col: str,
metrics: Iterable[str] = None,
*,
layer_key: str | None = None,
embedding_key: str | None = None,
**kwargs,
):
"""Evaluation of previously computed clustering against ground truth labels.
Args:
adata: AnnData object that contains the clustered data and the cluster labels.
true_label_col: ground truth labels.
cluster_col: cluster computed labels.
metrics: Metrics to compute. If `None` it defaults to ``["nmi", "ari", "asw"]`` — the canonical
trio for clustering benchmarks (mutual information, agreement, silhouette).
layer_key: Layer to resolve cell coordinates from when computing ASW.
embedding_key: Embedding to resolve cell coordinates from when computing ASW.
**kwargs: Additional arguments to pass to the metrics. For nmi, average_method can be passed.
For asw, ``metric``, ``distances``, ``sample_size``, and ``random_state`` can be passed.
Examples:
Example usage with KMeansSpace:
>>> import pertpy as pt
>>> mdata = pt.dt.papalexi_2021()
>>> kmeans = pt.tl.KMeansSpace()
>>> kmeans_adata = kmeans.compute(mdata["rna"], n_clusters=26)
>>> results = kmeans.evaluate_clustering(
... kmeans_adata, true_label_col="gene_target", cluster_col="k-means", metrics=["nmi"]
... )
"""
if metrics is None:
metrics = ["nmi", "ari", "asw"]
true_labels = adata.obs[true_label_col]
results: dict[str, float] = {}
for metric in metrics:
if metric == "nmi":
from pertpy.tools._perturbation_space._metrics import nmi
if "average_method" not in kwargs:
kwargs["average_method"] = "arithmetic" # by default in sklearn implementation
results["nmi"] = nmi(
true_labels=true_labels,
predicted_labels=adata.obs[cluster_col],
average_method=kwargs["average_method"],
)
elif metric == "ari":
from pertpy.tools._perturbation_space._metrics import ari
results["ari"] = ari(true_labels=true_labels, predicted_labels=adata.obs[cluster_col])
elif metric == "asw":
from pertpy.tools._perturbation_space._metrics import asw
kwargs.setdefault("metric", "euclidean")
kwargs.setdefault("sample_size", None)
kwargs.setdefault("random_state", None)
if "distances" in kwargs:
distances = kwargs["distances"]
else:
distances = pairwise_distances(
_resolve_matrix(adata, layer_key=layer_key, embedding_key=embedding_key),
metric=kwargs["metric"],
)
results["asw"] = asw(
pairwise_distances=distances,
labels=true_labels,
metric=kwargs["metric"],
sample_size=kwargs["sample_size"],
random_state=kwargs["random_state"],
)
return results