sktime.clustering.base 源代码

"""Base class for clustering."""

__author__ = ["chrisholder", "TonyBagnall", "achieveordie"]
__all__ = ["BaseClusterer"]

import time

import numpy as np

from sktime.base import BaseEstimator
from sktime.datatypes import check_is_scitype, convert_to, scitype_to_mtype
from sktime.datatypes._dtypekind import DtypeKind
from sktime.utils.dependencies import _check_estimator_deps
from sktime.utils.sklearn import is_sklearn_transformer
from sktime.utils.validation import check_n_jobs


[文档]class BaseClusterer(BaseEstimator): """Abstract base class for time series clusterer. Parameters ---------- n_clusters: int, defaults = None Number of clusters for model. """ _tags = { "authors": "sktime developers", # author(s) of the object "maintainers": "sktime developers", # current maintainer(s) of the object "object_type": "clusterer", # type of object "X_inner_mtype": "numpy3D", # which type do _fit/_predict accept, usually # this is either "numpy3D" or "nested_univ" (nested pd.DataFrame). Other # types are allowable, see datatypes/panel/_registry.py for options. "capability:multivariate": False, "capability:unequal_length": False, "capability:missing_values": False, "capability:multithreading": False, "capability:out_of_sample": True, "capability:predict": True, "capability:predict_proba": True, } def __init__(self, n_clusters: int = None): self.fit_time_ = 0 self._class_dictionary = {} self._threads_to_use = 1 self.n_clusters = n_clusters super().__init__() _check_estimator_deps(self) def __rmul__(self, other): """Magic * method, return concatenated ClustererPipeline, transformers on left. Overloaded multiplication operation for clusterers. Implemented for ``other`` being a transformer, otherwise returns ``NotImplemented``. Parameters ---------- other: ``sktime`` transformer, must inherit from BaseTransformer otherwise, ``NotImplemented`` is returned Returns ------- ClustererPipeline object, concatenation of ``other`` (first) with ``self`` (last). """ from sktime.clustering.compose import ClustererPipeline from sktime.transformations.base import BaseTransformer from sktime.transformations.compose import TransformerPipeline from sktime.transformations.series.adapt import TabularToSeriesAdaptor # behaviour is implemented only if other inherits from BaseTransformer # in that case, distinctions arise from whether self or other is a pipeline # todo: this can probably be simplified further with "zero length" pipelines if isinstance(other, BaseTransformer): # ClustererPipeline already has the dunder method defined if isinstance(self, ClustererPipeline): return other * self # if other is a TransformerPipeline but self is not, first unwrap it elif isinstance(other, TransformerPipeline): return ClustererPipeline(clusterer=self, transformers=other.steps) # if neither self nor other are a pipeline, construct a ClustererPipeline else: return ClustererPipeline(clusterer=self, transformers=[other]) elif is_sklearn_transformer(other): return TabularToSeriesAdaptor(other) * self else: return NotImplemented
[文档] def fit(self, X, y=None): """Fit time series clusterer to training data. State change: Changes state to "fitted". Writes to self: Sets self.is_fitted to True. Sets fitted model attributes ending in "_". Parameters ---------- X : sktime compatible time series panel data container of Panel scitype time series to fit estimator to. Can be in any :term:`mtype` of ``Panel`` :term:`scitype`, for instance: * pd-multiindex: pd.DataFrame with columns = variables, index = pd.MultiIndex with first level = instance indices, second level = time indices * numpy3D: 3D np.array (any number of dimensions, equal length series) of shape [n_instances, n_dimensions, series_length] * or of any other supported ``Panel`` :term:`mtype` for list of mtypes, see ``datatypes.SCITYPE_REGISTER`` for specifications, see ``examples/AA_datatypes_and_datasets.ipynb`` Not all estimators support panels with multivariate or unequal length series, see the :ref:`tag reference <panel_tags>` for details. y : ignored, exists for API consistency reasons. Returns ------- self : Reference to self. """ # reset estimator at the start of fit self.reset() X = self._check_clusterer_input(X) multithread = self.get_tag("capability:multithreading") if multithread: try: self._threads_to_use = check_n_jobs(self.n_jobs) except NameError: raise AttributeError( "self.n_jobs must be set if capability:multithreading is True" ) start = int(round(time.time() * 1000)) self._fit(X) self.fit_time_ = int(round(time.time() * 1000)) - start self._is_fitted = True return self
[文档] def predict(self, X, y=None) -> np.ndarray: """Predict the closest cluster each sample in X belongs to. Parameters ---------- X : sktime compatible time series panel data container of Panel scitype time series to cluster. Can be in any :term:`mtype` of ``Panel`` :term:`scitype`, for instance: * pd-multiindex: pd.DataFrame with columns = variables, index = pd.MultiIndex with first level = instance indices, second level = time indices * numpy3D: 3D np.array (any number of dimensions, equal length series) of shape [n_instances, n_dimensions, series_length] * or of any other supported ``Panel`` :term:`mtype` for list of mtypes, see ``datatypes.SCITYPE_REGISTER`` for specifications, see ``examples/AA_datatypes_and_datasets.ipynb`` Not all estimators support panels with multivariate or unequal length series, see the :ref:`tag reference <panel_tags>` for details. y: ignored, exists for API consistency reasons. Returns ------- np.ndarray (1d array of shape (n_instances,)) Index of the cluster each time series in X belongs to. """ self.check_is_fitted() X = self._check_clusterer_input(X) return self._predict(X)
[文档] def fit_predict(self, X, y=None) -> np.ndarray: """Compute cluster centers and predict cluster index for each time series. Convenience method; equivalent of calling fit(X) followed by predict(X) Parameters ---------- X : sktime compatible time series panel data container of Panel scitype time series to cluster. Can be in any :term:`mtype` of ``Panel`` :term:`scitype`, for instance: * pd-multiindex: pd.DataFrame with columns = variables, index = pd.MultiIndex with first level = instance indices, second level = time indices * numpy3D: 3D np.array (any number of dimensions, equal length series) of shape [n_instances, n_dimensions, series_length] * or of any other supported ``Panel`` :term:`mtype` for list of mtypes, see ``datatypes.SCITYPE_REGISTER`` for specifications, see ``examples/AA_datatypes_and_datasets.ipynb`` Not all estimators support panels with multivariate or unequal length series, see the :ref:`tag reference <panel_tags>` for details. y: ignored, exists for API consistency reasons. Returns ------- np.ndarray (1d array of shape (n_instances,)) Index of the cluster each time series in X belongs to. """ self.fit(X) return self.predict(X)
[文档] def predict_proba(self, X): """Predicts labels probabilities for sequences in X. Default behaviour is to call _predict and set the predicted class probability to 1, other class probabilities to 0. Override if better estimates are obtainable. Parameters ---------- X : sktime compatible time series panel data container of Panel scitype time series to cluster. Can be in any :term:`mtype` of ``Panel`` :term:`scitype`, for instance: * pd-multiindex: pd.DataFrame with columns = variables, index = pd.MultiIndex with first level = instance indices, second level = time indices * numpy3D: 3D np.array (any number of dimensions, equal length series) of shape [n_instances, n_dimensions, series_length] * or of any other supported ``Panel`` :term:`mtype` for list of mtypes, see ``datatypes.SCITYPE_REGISTER`` for specifications, see ``examples/AA_datatypes_and_datasets.ipynb`` Not all estimators support panels with multivariate or unequal length series, see the :ref:`tag reference <panel_tags>` for details. Returns ------- y : 2D array of shape [n_instances, n_classes] - predicted class probabilities 1st dimension indices correspond to instance indices in X 2nd dimension indices correspond to possible labels (integers) (i, j)-th entry is predictive probability that i-th instance is of class j """ self.check_is_fitted() X = self._check_clusterer_input(X) return self._predict_proba(X)
[文档] def score(self, X, y=None) -> float: """Score the quality of the clusterer. Parameters ---------- X : np.ndarray (2d or 3d array of shape (n_instances, series_length) or shape (n_instances, n_dimensions, series_length)) or pd.DataFrame (where each column is a dimension, each cell is a pd.Series (any number of dimensions, equal or unequal length series)). Time series instances to train clusterer and then have indexes each belong to return. y: ignored, exists for API consistency reasons. Returns ------- score : float Score of the clusterer. """ self.check_is_fitted() X = self._check_clusterer_input(X) return self._score(X, y)
def _predict_proba(self, X): """Predicts labels probabilities for sequences in X. Default behaviour is to call _predict and set the predicted class probability to 1, other class probabilities to 0. Override if better estimates are obtainable. Parameters ---------- X : guaranteed to be of a type in self.get_tag("X_inner_mtype") if self.get_tag("X_inner_mtype") = "numpy3D": 3D np.ndarray of shape = [n_instances, n_dimensions, series_length] if self.get_tag("X_inner_mtype") = "nested_univ": pd.DataFrame with each column a dimension, each cell a pd.Series for list of other mtypes, see datatypes.SCITYPE_REGISTER for specifications, see examples/AA_datatypes_and_datasets.ipynb Returns ------- y : 2D array of shape [n_instances, n_classes] - predicted class probabilities 1st dimension indices correspond to instance indices in X 2nd dimension indices correspond to possible labels (integers) (i, j)-th entry is predictive probability that i-th instance is of class j """ preds = self._predict(X) n_instances = len(preds) if hasattr(self, "n_clusters") and self.n_clusters is not None: n_clusters = self.n_clusters else: n_clusters = max(preds) + 1 dists = np.zeros((X.shape[0], n_clusters)) for i in range(n_instances): dists[i, preds[i]] = 1 return dists def _score(self, X, y=None): raise NotImplementedError def _predict(self, X, y=None) -> np.ndarray: """Predict the closest cluster each sample in X belongs to. Parameters ---------- X : np.ndarray (2d or 3d array of shape (n_instances, series_length) or shape (n_instances,n_dimensions,series_length)) or pd.Dataframe. Time series instances to predict their cluster indexes. If data is not equal length a pd.Dataframe given, if another other type of data a np.ndarray given. y: ignored, exists for API consistency reasons. Returns ------- np.ndarray (1d array of shape (n_instances,)) Index of the cluster each time series in X belongs to. """ raise NotImplementedError def _fit(self, X, y=None) -> np.ndarray: """Fit time series clusterer to training data. Parameters ---------- X : np.ndarray (2d or 3d array of shape (n_instances, series_length) or shape (n_instances,n_dimensions,series_length)) or pd.Dataframe. Training time series instances to cluster. If data is not equal length a pd.Dataframe given, if another other type of data a np.ndarray given. Returns ------- self: Fitted estimator. """ raise NotImplementedError def _check_capabilities(self, missing: bool, multivariate: bool, unequal: bool): """Check the capabilities of the clusterer matches input data requirements. Parameters ---------- missing : boolean Defines if the data has missing value. True if data has missing values, False if no missing. multivariate : boolean Defines if the data is multivariate. True if data is multivariate, False if the data is univariate unequal : boolean Defines if the data is unequal length. True if data is unequal length, False if the data is equal length. Raises ------ ValueError if the capabilities in self._tags do not handle the data. """ allow_multivariate = self.get_tag("capability:multivariate") allow_missing = self.get_tag("capability:missing_values") allow_unequal = self.get_tag("capability:unequal_length") if missing and not allow_missing: raise ValueError( "The data has missing values, this clusterer cannot handle missing " "values" ) if multivariate and not allow_multivariate: raise ValueError( "X must be univariate, this clusterer cannot deal with " "multivariate input." ) if unequal and not allow_unequal: raise ValueError( "The data has unequal length series, this clusterer cannot handle " "unequal length series" ) @staticmethod def _initial_conversion(X): """Format data as valid panel mtype of the data. Parameters ---------- X: Any Data to convert to panel mtype. Returns ------- X: np.ndarray (at least 2d) or pd.Dataframe or List[pd.Dataframe] Converted X. """ if isinstance(X, np.ndarray) and X.ndim == 2: X = X.reshape(X.shape[0], 1, X.shape[1]) return X def _check_clusterer_input(self, X, enforce_min_instances: int = 1): """Validate the input and prepare for _fit. Parameters ---------- X : np.ndarray (2d or 3d array of shape (n_instances, series_length) or shape (n_instances,n_dimensions,series_length)) or nested pd.DataFrame ( n_instances,n_dimensions). Training time series instances to cluster. Returns ------- X : np.ndarray (3d of shape (n_instances,n_dimensions,series_length)) or pd.Dataframe (n_instances,n_dimensions). Converted X ready for _fit. Raises ------ ValueError If y or X is invalid input data type, or there is not enough data. """ X = self._initial_conversion(X) ALLOWED_SCITYPES = [ "Panel", ] FORBIDDEN_MTYPES = [] mtypes_messages = [] for scitype in ALLOWED_SCITYPES: mtypes = set(scitype_to_mtype(scitype)) mtypes = list(mtypes.difference(FORBIDDEN_MTYPES)) mtypes_messages.append(f"For {scitype} scitype: {mtypes}") X_metadata_required = [ "n_instances", "has_nans", "is_univariate", "is_equal_length", "feature_kind", ] X_valid, _, X_metadata = check_is_scitype( X, scitype=ALLOWED_SCITYPES, return_metadata=X_metadata_required ) if not X_valid: raise TypeError( "X must be in a sktime compatible format, of scitype: " f"{', '.join(ALLOWED_SCITYPES)}. " "For instance a pandas.DataFrame must have a 2-level MultiIndex. " "In case of numpy array, it must be " "a 3D array as (num_instance, num_vars, series). " "If you think X is already is an sktime supported input format, " "run `sktime.datatypes.check_raise(X, MTYPE)` to diagnose the error, " "where MTYPE is the string of the type specification you want for X. " "Possible mtype specification strings are as follows: " f"{', '.join(mtypes_messages)}" ) if DtypeKind.CATEGORICAL in X_metadata["feature_kind"]: raise TypeError( "Clustering does not support categorical features in endogeneous y." ) n_cases = X_metadata["n_instances"] if n_cases < enforce_min_instances: raise ValueError( f"Minimum number of cases required is {enforce_min_instances} but X " f"has : {n_cases}" ) missing = X_metadata["has_nans"] multivariate = not X_metadata["is_univariate"] unequal = not X_metadata["is_equal_length"] self._check_capabilities(missing, multivariate, unequal) return convert_to( X, to_type=self.get_tag("X_inner_mtype"), as_scitype="Panel", )