From 0aa9115665908ea06d98aec872dd01c31320d6a8 Mon Sep 17 00:00:00 2001 From: Anjali Ragupathi <45604611+anjali-rgpt@users.noreply.github.com> Date: Fri, 27 Aug 2021 20:21:07 +0530 Subject: [PATCH 1/8] Updated requirements doc with scickit-learn-extra --- docs/requirements_docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt index 639ce01..711f2a3 100644 --- a/docs/requirements_docs.txt +++ b/docs/requirements_docs.txt @@ -13,6 +13,7 @@ pytest-runner==5.2 pandas==1.1.1 PyYAML==5.3.1 scikit-learn==0.23.2 +scikit-learn-extra==0.2.0 sphinx-copybutton==0.3.1 sphinx_glpi_theme==0.3 importlib-metadata==1.6.0 From f76553d4f59e383f5be69facfbc030f038360acb Mon Sep 17 00:00:00 2001 From: Anjali Ragupathi <45604611+anjali-rgpt@users.noreply.github.com> Date: Fri, 27 Aug 2021 20:21:52 +0530 Subject: [PATCH 2/8] Revert code to original requirements --- docs/requirements_docs.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt index 711f2a3..639ce01 100644 --- a/docs/requirements_docs.txt +++ b/docs/requirements_docs.txt @@ -13,7 +13,6 @@ pytest-runner==5.2 pandas==1.1.1 PyYAML==5.3.1 scikit-learn==0.23.2 -scikit-learn-extra==0.2.0 sphinx-copybutton==0.3.1 sphinx_glpi_theme==0.3 importlib-metadata==1.6.0 From 72b146e3e0495e8f89cf74669fc65cf8468439af Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Mon, 30 Aug 2021 11:40:52 +0530 Subject: [PATCH 3/8] Added KMedoids to README and started KMedians class with description --- docs/README.rst | 2 +- igel/extras/kmedians.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 igel/extras/kmedians.py diff --git a/docs/README.rst b/docs/README.rst index a8caf60..e922fb3 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -157,7 +157,7 @@ Igel's supported models: | TheilSenRegression | NearestNeighbor | SpectralClustering | | GammaRegression | NeuralNetwork | MeanShift | | RANSACRegression | PassiveAgressiveClassifier | OPTICS | - | DecisionTree | Perceptron | ---- | + | DecisionTree | Perceptron | KMedoids | | ExtraTree | BernoulliRBM | ---- | | RandomForest | BoltzmannMachine | ---- | | ExtraTrees | CalibratedClassifier | ---- | diff --git a/igel/extras/kmedians.py b/igel/extras/kmedians.py new file mode 100644 index 0000000..021e47f --- /dev/null +++ b/igel/extras/kmedians.py @@ -0,0 +1,5 @@ +"""K-Medians Clustering is an alternative to the popular K-Means Clustering, whereing the goal is to determine the median of the points in a cluster instead of the mean, to represent a cluster centre. + +This algorithm differs from the K-Medoids algorithm in that the K-Medoids algorithm requires the cluster centres to be actual data samples, whereas the K-Medians and K-Means algorithms generate synthetic samples not necessarily present in the actual data. + +K-Medians seeks to minimize the 1-norm distance from each point to its nearest cluster center, as opposed to K-Means which uses the Euclidean or 2-norm distance.""" \ No newline at end of file From b887294131281827682dc2eca942cac7eded81bd Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Mon, 30 Aug 2021 14:28:13 +0530 Subject: [PATCH 4/8] Added basic protected functions in K-Medians class. Removed mentions of unimplemented functions in K-Medoids class. --- igel/extras/kmedians.py | 162 +++++++++++++++++++++++++++++++++++++++- igel/extras/kmedoids.py | 1 - 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/igel/extras/kmedians.py b/igel/extras/kmedians.py index 021e47f..2cdae9c 100644 --- a/igel/extras/kmedians.py +++ b/igel/extras/kmedians.py @@ -2,4 +2,164 @@ This algorithm differs from the K-Medoids algorithm in that the K-Medoids algorithm requires the cluster centres to be actual data samples, whereas the K-Medians and K-Means algorithms generate synthetic samples not necessarily present in the actual data. -K-Medians seeks to minimize the 1-norm distance from each point to its nearest cluster center, as opposed to K-Means which uses the Euclidean or 2-norm distance.""" \ No newline at end of file +K-Medians seeks to minimize the 1-norm distance from each point to its nearest cluster center, as opposed to K-Means which uses the Euclidean or 2-norm distance.""" + +import random +import numpy as np + +import warnings +from sklearn.exceptions import ConvergenceWarning + +from sklearn.base import BaseEstimator, ClusterMixin +from sklearn.utils import check_array +from sklearn.utils.validation import check_is_fitted + +from sklearn.metrics.pairwise import pairwise_distances, pairwise_distances_argmin + + +class KMedians(BaseEstimator, ClusterMixin): + """ Initialization of parameters + Parameters + ---------- + n_clusters : int, optional, default: 4 + The number of clusters to form as well as the number of medoids to + generate. + + metric : string, or callable, optional, default: 'euclidean' + What distance metric to use. See :func:metrics.pairwise_distances + metric can be 'precomputed', the user must then feed the fit method + with a precomputed kernel matrix and not the design matrix X. + + method : string, optional, default: 'per-axis' + Specify the method of computing the median for multidimensional data. + 'per-axis' takes the median for each attribute and combines them to make the cluster centre. + + More options can be implemented from the following: + + A. Juan and E. Vidal. Fast k-means-like clustering in metric spaces. Pattern Recognition Letters, 15(1):19–25, 1994. + A. Juan and E. Vidal. Fast Median Search in Metric Spaces. In A. Amin, D. Dori, P. Pudil, and H. Freeman, editors, Advances in Pattern Recognition, volume 1451, pages 905–912. Springer-Verlag, 1998 + Whelan, C., Harrell, G., & Wang, J. (2015). Understanding the K-Medians Problem. + + init : {'random', 'heuristic'}, optional, default: 'random' + Specify medoid initialization method. 'random' selects n_clusters + elements from the dataset. + + More options can be implemented from the following: + + Alfons Juan and Enrique Vidal. 2000. Comparison of Four Initialization Techniques for the K -Medians Clustering Algorithm. In Proceedings of the Joint IAPR International Workshops on Advances in Pattern Recognition. Springer-Verlag, Berlin, Heidelberg, 842–852. + + max_iter : int, optional, default : 300 + Specify the maximum number of iterations when fitting. It can be zero in + which case only the initialization is computed which may be suitable for + large datasets when the initialization is sufficiently efficient + + tol : float, optional, default : 0.0004 + Specify the tolerance of change in cluster centers. If change in cluster centers is less than tolerance, algorithm stops. + + random_state : int, RandomState instance or None, optional + Specify random state for the random number generator. Used to + initialise medoids when init='random'. + + Attributes + ---------- + cluster_centers_ : array, shape = (n_clusters, n_features) + or None if metric == 'precomputed' + Cluster centers, i.e. medoids (elements from the original dataset) + + medoid_indices_ : array, shape = (n_clusters,) + The indices of the medoid rows in X + + labels_ : array, shape = (n_samples,) + Labels of each point + + inertia_ : float + Sum of distances of samples to their closest cluster center. + + score_ : float + Negative of the inertia. The more negative the score, the higher the variation in cluster points, the worse the clustering. + """ + + def __init__(self, n_clusters = 4, metric = 'manhattan', method = 'per-axis', init = 'random', max_iter = 300, tol = 0.0004, random_state = None): + self.n_clusters = n_clusters + self.metric = metric + self.method = method + self.max_iter = max_iter + self.tol = tol + self.random_state = random_state + + def _get_random_state(self, seed = None): + if seed is None or seed is np.random: + return np.random.mtrand._rand + elif isinstance(seed, (int, np.integer)): + return np.random.RandomState(seed) + elif isinstance(seed, np.random.RandomState): + return seed + + def _is_nonnegative(self, value, variable, strict = True): + """Checks if the value passed is a non-negative integer which may or may not include zero""" + + if not isinstance(value,(int,np.integer)): + raise ValueError("%s should be an integer" % (variable)) + + if strict: + isnegative = value > 0 + else: + isnegative = value >= 0 + + if not isnegative: + raise ValueError("%s should be non-negative" % (variable)) + return isnegative + + def _check_arguments(self): + """Checks if arguments are as per specification""" + + if self._is_nonnegative(self.n_clusters,"n_clusters") and self._is_nonnegative(self.max_iter, "max_iter") and isinstance(self.tol, (float, np.floating)): + pass + else: + raise ValueError("Tolerance is not specified as floating-point value") + + if (isinstance(self.random_state, (int, np.integer, None, np.random.RandomState)) or self.random_state is np.random): + pass + else: + raise ValueError("Random state is not valid") + + distance_metrics = ['euclidean', 'manhattan', 'cosine', 'cityblock', 'l1', 'l2'] + median_computation_methods = ['per-axis'] + init_methods = ['random'] + + if self.metric not in distance_metrics: + raise ValueError('%s not a supported distance metric' % (self.metric)) + + if self.method not in median_computation_methods: + raise ValueError('%s not a supported median computation method' % (self.metric)) + + if self.init not in init_methods: + raise ValueError('%s not a supported initialization method' % (self.init)) + + def _initialize_centers(self, X, n_clusters, random_state_object): + """ Implementation of random initialization""" + + if self.init == 'random': + """Randomly chooses K points within set of samples""" + return random_state_object.choice(len(X), n_clusters) + + def fit(self, X, Y = None): + + self._check_arguments() + random_state_object = self._get_random_state(self.random_state) + + if Y: + raise Exception ("Clustering fit takes only one parameter") + + X = check_array(X, accept_sparse = ['csc','csr']) + + n_samples, n_features = X.shape[0], X.shape[1] + + if self.n_clusters > n_samples: + raise ValueError('Number of clusters %s cannot be greater than number of samples %s' % (self.n_clusters, n_samples)) + + distances = pairwise_distances(X, centers, metric = self.metric) + + + + diff --git a/igel/extras/kmedoids.py b/igel/extras/kmedoids.py index dd42e49..d8935ea 100644 --- a/igel/extras/kmedoids.py +++ b/igel/extras/kmedoids.py @@ -38,7 +38,6 @@ def __init__(self, n_clusters = 4, metric = 'euclidean', init = 'random', max_it Specify the maximum number of iterations when fitting. It can be zero in which case only the initialization is computed which may be suitable for large datasets when the initialization is sufficiently efficient - (i.e. for 'build' init). random_state : int, RandomState instance or None, optional Specify random state for the random number generator. Used to From e9b93078a0fd1687d12957a3e2b30e051a14ddaf Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Mon, 30 Aug 2021 14:28:56 +0530 Subject: [PATCH 5/8] Commented unnecessary print statement in K-Medoids --- igel/extras/kmedoids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igel/extras/kmedoids.py b/igel/extras/kmedoids.py index d8935ea..8580ee5 100644 --- a/igel/extras/kmedoids.py +++ b/igel/extras/kmedoids.py @@ -267,7 +267,7 @@ def fit(self, X , Y = None): raise ValueError('Number of clusters %s cannot be greater than number of samples %s' % (self.n_clusters, n_samples)) distances = pairwise_distances(X, Y, metric = self.metric) - print("Distances:", distances.shape) + #print("Distances:", distances.shape) medoids = self._initialize_medoids(distances, self.n_clusters, random_state_object) #Initialized medoids. d_closest_medoid, d_second_closest_medoid = np.sort(distances[medoids], axis=0)[[0, 1]] #Step 1. From e3f5b9b0392c1ad8b5911611029aaf3c5cde151f Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Tue, 31 Aug 2021 00:56:53 +0530 Subject: [PATCH 6/8] Completed K-Medians class and ran unit test as required by poetry --- igel/extras/kmedians.py | 104 +++++++++++++++++++++++++++-- poetry.lock | 143 +++++++++++++++++++--------------------- 2 files changed, 168 insertions(+), 79 deletions(-) diff --git a/igel/extras/kmedians.py b/igel/extras/kmedians.py index 2cdae9c..30a5da3 100644 --- a/igel/extras/kmedians.py +++ b/igel/extras/kmedians.py @@ -40,7 +40,7 @@ class KMedians(BaseEstimator, ClusterMixin): A. Juan and E. Vidal. Fast Median Search in Metric Spaces. In A. Amin, D. Dori, P. Pudil, and H. Freeman, editors, Advances in Pattern Recognition, volume 1451, pages 905–912. Springer-Verlag, 1998 Whelan, C., Harrell, G., & Wang, J. (2015). Understanding the K-Medians Problem. - init : {'random', 'heuristic'}, optional, default: 'random' + init : {'random'}, optional, default: 'random' Specify medoid initialization method. 'random' selects n_clusters elements from the dataset. @@ -53,7 +53,7 @@ class KMedians(BaseEstimator, ClusterMixin): which case only the initialization is computed which may be suitable for large datasets when the initialization is sufficiently efficient - tol : float, optional, default : 0.0004 + tol : float, optional, default : 0.0001 Specify the tolerance of change in cluster centers. If change in cluster centers is less than tolerance, algorithm stops. random_state : int, RandomState instance or None, optional @@ -79,10 +79,11 @@ class KMedians(BaseEstimator, ClusterMixin): Negative of the inertia. The more negative the score, the higher the variation in cluster points, the worse the clustering. """ - def __init__(self, n_clusters = 4, metric = 'manhattan', method = 'per-axis', init = 'random', max_iter = 300, tol = 0.0004, random_state = None): + def __init__(self, n_clusters = 4, metric = 'manhattan', method = 'per-axis', init = 'random', max_iter = 300, tol = 0.0001, random_state = None): self.n_clusters = n_clusters self.metric = metric self.method = method + self.init = init self.max_iter = max_iter self.tol = tol self.random_state = random_state @@ -143,7 +144,32 @@ def _initialize_centers(self, X, n_clusters, random_state_object): """Randomly chooses K points within set of samples""" return random_state_object.choice(len(X), n_clusters) + def _compute_inertia(self, distances, labels): + """Compute inertia of new samples. Inertia is defined as the sum of the + sample distances to closest cluster centers. + + Parameters + ---------- + distances : {array-like, sparse matrix}, shape=(n_samples, n_clusters) + Distances to cluster centers. + + labels : {array-like}, shape = {n_samples} + + Returns + ------- + Sum of sample distances to closest cluster centers. + """ + + # Define inertia as the sum of the sample-distances + # to closest cluster centers + inertia = 0 + for i in range(self.n_clusters): + indices = np.argwhere(labels == i) + inertia += np.sum(distances[i, indices]) + return inertia + def fit(self, X, Y = None): + """Fits the model to the data""" self._check_arguments() random_state_object = self._get_random_state(self.random_state) @@ -158,7 +184,77 @@ def fit(self, X, Y = None): if self.n_clusters > n_samples: raise ValueError('Number of clusters %s cannot be greater than number of samples %s' % (self.n_clusters, n_samples)) - distances = pairwise_distances(X, centers, metric = self.metric) + centers = X[self._initialize_centers(X, self.n_clusters, random_state_object)] + # print(centers) + distances = pairwise_distances(centers, X, metric = self.metric) + # print("Distances:", distances) + + medians = [None]* self.n_clusters + labels = None + + if self.method == 'per-axis': + for i in range(self.max_iter): + old_centers = np.copy(centers) + labels = np.argmin(distances, axis = 0) + + for item in range(self.n_clusters): + indices = np.argwhere(labels == item) + medians[item] = np.median(X[indices], axis = 0) + + centers = np.squeeze(np.asarray(medians)) + distances = pairwise_distances(centers, X, metric = self.metric) + + if np.all(np.abs(old_centers - centers) < self.tol): + break + elif i == self.max_iter - 1: + warnings.warn( + "Maximum number of iteration reached before " + "convergence. Consider increasing max_iter to " + "improve the fit.", + ConvergenceWarning, + ) + self.cluster_centers_ = centers + self.labels_ = np.argmin(distances, axis = 0) + self.inertia_ = self._compute_inertia(distances, self.labels_) + self.score_ = - self.inertia_ + + def transform(self, X): + """Transforms given data into cluster space of size {n_samples, n_clusters}""" + X = check_array(X, accept_sparse=["csr", "csc"]) + check_is_fitted(self, "cluster_centers_") + + Y = self.cluster_centers_ + return pairwise_distances(X, Y=Y, metric=self.metric) + + def predict(self, X): + """Predict the closest cluster for each sample in X. + + Parameters + ---------- + X : {array-like, sparse matrix}, shape (n_query, n_features), \ + or (n_query, n_indexed) if metric == 'precomputed' + New data to predict. + + Returns + ------- + labels : array, shape = (n_query,) + Index of the cluster each sample belongs to. + """ + check_is_fitted(self, "cluster_centers_") + + # Return data points to clusters based on which cluster assignment + # yields the smallest distance + return pairwise_distances_argmin(X, Y = self.cluster_centers_, metric=self.metric) + + def score(self, X): + """Returns score""" + return self.score_ + + + + + + diff --git a/poetry.lock b/poetry.lock index 1924310..442c658 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,14 +6,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "asgiref" version = "3.4.1" @@ -27,7 +19,7 @@ tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] name = "astroid" -version = "2.7.2" +version = "2.7.3" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -99,23 +91,28 @@ stevedore = ">=1.20.0" [[package]] name = "black" -version = "21.7b0" +version = "21.8b0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" click = ">=7.1.2" mypy-extensions = ">=0.4.3" -pathspec = ">=0.8.1,<1" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" regex = ">=2020.1.8" tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] python2 = ["typed-ast (>=1.4.2)"] uvloop = ["uvloop (>=0.15.2)"] @@ -366,7 +363,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.7.1" +version = "4.8.1" description = "Read metadata from Python packages" category = "dev" optional = false @@ -614,7 +611,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "pre-commit" -version = "2.14.0" +version = "2.14.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -777,7 +774,7 @@ python-versions = "*" [[package]] name = "pyupgrade" -version = "2.24.0" +version = "2.25.0" description = "A tool to automatically upgrade syntax for newer versions." category = "dev" optional = false @@ -821,7 +818,7 @@ md = ["cmarkgfm (>=0.5.0,<0.6.0)"] [[package]] name = "regex" -version = "2021.8.27" +version = "2021.8.28" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -1181,7 +1178,7 @@ tqdm = ">=4.14" [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -1287,17 +1284,13 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] astroid = [ - {file = "astroid-2.7.2-py3-none-any.whl", hash = "sha256:ecc50f9b3803ebf8ea19aa2c6df5622d8a5c31456a53c741d3be044d96ff0948"}, - {file = "astroid-2.7.2.tar.gz", hash = "sha256:b6c2d75cd7c2982d09e7d41d70213e863b3ba34d3bd4014e08f167cee966e99e"}, + {file = "astroid-2.7.3-py3-none-any.whl", hash = "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"}, + {file = "astroid-2.7.3.tar.gz", hash = "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, @@ -1320,8 +1313,8 @@ bandit = [ {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, ] black = [ - {file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"}, - {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"}, + {file = "black-21.8b0-py3-none-any.whl", hash = "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7"}, + {file = "black-21.8b0.tar.gz", hash = "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2"}, ] bleach = [ {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, @@ -1506,8 +1499,8 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.7.1-py3-none-any.whl", hash = "sha256:9e04bf59076a15a9b6dd9c27806e8fcdf15280ba529c6a8cc3f4d5b4875bdd61"}, - {file = "importlib_metadata-4.7.1.tar.gz", hash = "sha256:c4eb3dec5f697682e383a39701a7de11cd5c02daf8dd93534b69e3e6473f6b1b"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1730,8 +1723,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"}, - {file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"}, + {file = "pre_commit-2.14.1-py2.py3-none-any.whl", hash = "sha256:a22d12a02da4d8df314187dfe7a61bda6291d57992060522feed30c8cd658b68"}, + {file = "pre_commit-2.14.1.tar.gz", hash = "sha256:7977a3103927932d4823178cbe4719ab55bb336f42a9f3bb2776cff99007a117"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, @@ -1806,8 +1799,8 @@ pytz = [ {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] pyupgrade = [ - {file = "pyupgrade-2.24.0-py2.py3-none-any.whl", hash = "sha256:c7b8230f29e0d7e34d60d301365f260a1a2f008e360b932f79f966ecd1efa36a"}, - {file = "pyupgrade-2.24.0.tar.gz", hash = "sha256:66e4309fab139d7b58634bd938a0036427e5309d4bbeff0862b95ef90aba06a0"}, + {file = "pyupgrade-2.25.0-py2.py3-none-any.whl", hash = "sha256:8409f4a166d8fbbf475e4e63912817f8ec07c402979f9399658929ebafaa6043"}, + {file = "pyupgrade-2.25.0.tar.gz", hash = "sha256:a61084aae26ccedd6ae98fc0cc09383ce4e11b6d519653ff5e2fd131d2023723"}, ] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, @@ -1833,47 +1826,47 @@ readme-renderer = [ {file = "readme_renderer-29.0.tar.gz", hash = "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"}, ] regex = [ - {file = "regex-2021.8.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:507861cf3d97a86fbe26ea6cc04660ae028b9e4080b8290e28b99547b4e15d89"}, - {file = "regex-2021.8.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:105122fa63da98d8456d5026bc6ac5a1399fd82fa6bad22c6ea641b1572c9142"}, - {file = "regex-2021.8.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83946ca9278b304728b637bc8d8200ab1663a79de85e47724594917aeed0e892"}, - {file = "regex-2021.8.27-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee318974a1fdacba1701bc9e552e9015788d6345416364af6fa987424ff8df53"}, - {file = "regex-2021.8.27-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dde0ac721c7c5bfa5f9fc285e811274dec3c392f2c1225f7d07ca98a8187ca84"}, - {file = "regex-2021.8.27-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:862b6164e9a38b5c495be2c2854e75fd8af12c5be4c61dc9b42d255980d7e907"}, - {file = "regex-2021.8.27-cp310-cp310-win32.whl", hash = "sha256:7684016b73938ca12d160d2907d141f06b7597bd17d854e32bb7588be01afa1d"}, - {file = "regex-2021.8.27-cp310-cp310-win_amd64.whl", hash = "sha256:a5f3bc727fea58f21d99c22e6d4fca652dc11dbc2a1e7cfc4838cd53b2e3691f"}, - {file = "regex-2021.8.27-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db888d4fb33a2fd54b57ac55d5015e51fa849f0d8592bd799b4e47f83bd04e00"}, - {file = "regex-2021.8.27-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92eb03f47427fea452ff6956d11f5d5a3f22a048c90a0f34fa223e6badab6c85"}, - {file = "regex-2021.8.27-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7406dd2e44c7cfb4680c0a45a03264381802c67890cf506c147288f04c67177d"}, - {file = "regex-2021.8.27-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7db58ad61f3f6ea393aaf124d774ee0c58806320bc85c06dc9480f5c7219c250"}, - {file = "regex-2021.8.27-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd45b4542134de63e7b9dd653e0a2d7d47ffed9615e3637c27ca5f6b78ea68bb"}, - {file = "regex-2021.8.27-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e02dad60e3e8442eefd28095e99b2ac98f2b8667167493ac6a2f3aadb5d84a17"}, - {file = "regex-2021.8.27-cp36-cp36m-win32.whl", hash = "sha256:de0d06ccbc06af5bf93bddec10f4f80275c5d74ea6d28b456931f3955f58bc8c"}, - {file = "regex-2021.8.27-cp36-cp36m-win_amd64.whl", hash = "sha256:2a0a5e323cf86760784ce2b91d8ab5ea09d0865d6ef4da0151e03d15d097b24e"}, - {file = "regex-2021.8.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6530b7b9505123cdea40a2301225183ca65f389bc6129f0c225b9b41680268d8"}, - {file = "regex-2021.8.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f3e36086d6631ceaf468503f96a3be0d247caef0660c9452fb1b0c055783851"}, - {file = "regex-2021.8.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ddb4f9ce6bb388ecc97b4b3eb37e786f05d7d5815e8822e0d87a3dbd7100649"}, - {file = "regex-2021.8.27-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2de1429e4eeab799c168a4f6e6eecdf30fcaa389bba4039cc8a065d6b7aad647"}, - {file = "regex-2021.8.27-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f03fc0a25122cdcbf39136510d4ea7627f732206892db522adf510bc03b8c67"}, - {file = "regex-2021.8.27-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:503c1ba0920a46a1844363725215ef44d59fcac2bd2c03ae3c59aa9d08d29bd6"}, - {file = "regex-2021.8.27-cp37-cp37m-win32.whl", hash = "sha256:24d68499a27b2d93831fde4a9b84ea5b19e0ab141425fbc9ab1e5b4dad179df7"}, - {file = "regex-2021.8.27-cp37-cp37m-win_amd64.whl", hash = "sha256:6729914dd73483cd1c8aaace3ac082436fc98b0072743ac136eaea0b3811d42f"}, - {file = "regex-2021.8.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d9cbe0c755ab8b6f583169c0783f7278fc6b195e423b09c5a8da6f858025e96"}, - {file = "regex-2021.8.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2404336fd16788ea757d4218a2580de60adb052d9888031e765320be8884309"}, - {file = "regex-2021.8.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:208851a2f8dd31e468f0b5aa6c94433975bd67a107a4e7da3bdda947c9f85e25"}, - {file = "regex-2021.8.27-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3ee8ad16a35c45a5bab098e39020ecb6fec3b0e700a9d88983d35cbabcee79c8"}, - {file = "regex-2021.8.27-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56ae6e3cf0506ec0c40b466e31f41ee7a7149a2b505ae0ee50edd9043b423d27"}, - {file = "regex-2021.8.27-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2778c6cb379d804e429cc8e627392909e60db5152b42c695c37ae5757aae50ae"}, - {file = "regex-2021.8.27-cp38-cp38-win32.whl", hash = "sha256:e960fe211496333b2f7e36badf4c22a919d740386681f79139ee346b403d1ca1"}, - {file = "regex-2021.8.27-cp38-cp38-win_amd64.whl", hash = "sha256:116c277774f84266044e889501fe79cfd293a8b4336b7a5e89b9f20f1e5a9f21"}, - {file = "regex-2021.8.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32753eda8d413ce4f208cfe01dd61171a78068a6f5d5f38ccd751e00585cdf1d"}, - {file = "regex-2021.8.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84057cfae5676f456b03970eb78b7e182fddc80c2daafd83465a3d6ca9ff8dbf"}, - {file = "regex-2021.8.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6180dbf5945b27e9420e1b58c3cacfc79ad5278bdad3ea35109f5680fbe16d1"}, - {file = "regex-2021.8.27-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b158f673ae6a6523f13704f70aa7e4ce875f91e379bece4362c89db18db189d5"}, - {file = "regex-2021.8.27-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19acdb8831a4e3b03b23369db43178d8fee1f17b99c83af6cd907886f76bd9d4"}, - {file = "regex-2021.8.27-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12eaf0bbe568bd62e6cade7937e0bf01a2a4cef49a82f4fd204401e78409e158"}, - {file = "regex-2021.8.27-cp39-cp39-win32.whl", hash = "sha256:1401cfa4320691cbd91191ec678735c727dee674d0997b0902a5a38ad482faf5"}, - {file = "regex-2021.8.27-cp39-cp39-win_amd64.whl", hash = "sha256:0696eb934dee723e3292056a2c046ddb1e4dd3887685783a9f4af638e85dee76"}, - {file = "regex-2021.8.27.tar.gz", hash = "sha256:e9700c52749cb3e90c98efd72b730c97b7e4962992fca5fbcaf1363be8e3b849"}, + {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, + {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, + {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, + {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, + {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, + {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, + {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, + {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, + {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, + {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, + {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, + {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, + {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, + {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, + {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, + {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, @@ -2019,9 +2012,9 @@ twine = [ {file = "twine-3.2.0.tar.gz", hash = "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, From c3a6311b4b09d8f8f691dedefe58b014994e45af Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Tue, 31 Aug 2021 00:58:17 +0530 Subject: [PATCH 7/8] Updated docstrings --- igel/extras/kmedians.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/igel/extras/kmedians.py b/igel/extras/kmedians.py index 30a5da3..8b3a7a4 100644 --- a/igel/extras/kmedians.py +++ b/igel/extras/kmedians.py @@ -22,7 +22,7 @@ class KMedians(BaseEstimator, ClusterMixin): Parameters ---------- n_clusters : int, optional, default: 4 - The number of clusters to form as well as the number of medoids to + The number of clusters to form as well as the number of medians to generate. metric : string, or callable, optional, default: 'euclidean' @@ -41,7 +41,7 @@ class KMedians(BaseEstimator, ClusterMixin): Whelan, C., Harrell, G., & Wang, J. (2015). Understanding the K-Medians Problem. init : {'random'}, optional, default: 'random' - Specify medoid initialization method. 'random' selects n_clusters + Specify median initialization method. 'random' selects n_clusters elements from the dataset. More options can be implemented from the following: @@ -58,16 +58,16 @@ class KMedians(BaseEstimator, ClusterMixin): random_state : int, RandomState instance or None, optional Specify random state for the random number generator. Used to - initialise medoids when init='random'. + initialise medians when init='random'. Attributes ---------- cluster_centers_ : array, shape = (n_clusters, n_features) or None if metric == 'precomputed' - Cluster centers, i.e. medoids (elements from the original dataset) + Cluster centers, i.e. medians (elements from the original dataset) medoid_indices_ : array, shape = (n_clusters,) - The indices of the medoid rows in X + The indices of the median rows in X labels_ : array, shape = (n_samples,) Labels of each point From ab6ca571f6de93a44e6ee5a1bd44442f8fc58ce7 Mon Sep 17 00:00:00 2001 From: anjali-rgpt Date: Tue, 31 Aug 2021 01:08:46 +0530 Subject: [PATCH 8/8] Fixed indentation in kmedians class and added to model dictionary in data file --- igel/data.py | 9 ++++++++- igel/extras/kmedians.py | 38 +++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/igel/data.py b/igel/data.py index 84cf4c0..a3508d4 100644 --- a/igel/data.py +++ b/igel/data.py @@ -75,6 +75,7 @@ from sklearn.utils.multiclass import type_of_target from igel.extras.kmedoids import KMedoids +from igel.extras.kmedians import KMedians import logging @@ -415,7 +416,13 @@ "KMedoids": { "class": KMedoids, "link": "https://scikit-learn-extra.readthedocs.io/en/stable/generated/sklearn_extra.cluster.KMedoids.html" - "#sklearn_extra.cluster.KMedoids" + "#igel.extras.KMedoids" + }, + + "KMedians": { + "class": KMedians, + "link": "Local Implementation" + "#igel.extras.KMedians" }, "AffinityPropagation": { diff --git a/igel/extras/kmedians.py b/igel/extras/kmedians.py index 8b3a7a4..f9d7b00 100644 --- a/igel/extras/kmedians.py +++ b/igel/extras/kmedians.py @@ -194,25 +194,25 @@ def fit(self, X, Y = None): if self.method == 'per-axis': for i in range(self.max_iter): - old_centers = np.copy(centers) - labels = np.argmin(distances, axis = 0) - - for item in range(self.n_clusters): - indices = np.argwhere(labels == item) - medians[item] = np.median(X[indices], axis = 0) - - centers = np.squeeze(np.asarray(medians)) - distances = pairwise_distances(centers, X, metric = self.metric) - - if np.all(np.abs(old_centers - centers) < self.tol): - break - elif i == self.max_iter - 1: - warnings.warn( - "Maximum number of iteration reached before " - "convergence. Consider increasing max_iter to " - "improve the fit.", - ConvergenceWarning, - ) + old_centers = np.copy(centers) + labels = np.argmin(distances, axis = 0) + + for item in range(self.n_clusters): + indices = np.argwhere(labels == item) + medians[item] = np.median(X[indices], axis = 0) + + centers = np.squeeze(np.asarray(medians)) + distances = pairwise_distances(centers, X, metric = self.metric) + + if np.all(np.abs(old_centers - centers) < self.tol): + break + elif i == self.max_iter - 1: + warnings.warn( + "Maximum number of iteration reached before " + "convergence. Consider increasing max_iter to " + "improve the fit.", + ConvergenceWarning, + ) self.cluster_centers_ = centers self.labels_ = np.argmin(distances, axis = 0) self.inertia_ = self._compute_inertia(distances, self.labels_)