├── .gitignore ├── __init__.py ├── Pipfile ├── CONTRIBUTING.md ├── heom_example.py ├── README.md ├── HEOM.py ├── HVDM.py ├── VDM.py ├── CODE_OF_CONDUCT.md └── Pipfile.lock /.gitignore: -------------------------------------------------------------------------------- 1 | ./vscode 2 | ./pytest_cache 3 | **__pycache__ -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .VDM import * 3 | from .HEOM import * 4 | from .HVDM import * 5 | 6 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pylint = "*" 8 | 9 | [packages] 10 | numpy = "*" 11 | pytest = "*" 12 | sklearn = "*" 13 | 14 | [requires] 15 | python_version = "3.7" 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via opening the issue. 4 | 5 | Please note that there is a code of [conduct](), please follow it in all your interactions with the project. 6 | * [Suggesting a feature](#suggesting-a-feature) 7 | * [Filing a bug report](#filing-a-bug-report) 8 | * [Submitting a pull request](#submitting-a-pull-request) 9 | 10 | ## Suggesting a feature 11 | 1) Open an issue with a clear explanation why the new feature should be implemented 12 | 2) Please provide references/links to help us fully understand the benefit of the new feature (if available) 13 | 3) Suggest how the new feature can be implemented within the package 14 | 15 | ## Filling a bug report 16 | 1) Open an issue with a brief and clear description of the error (e.g. expected behaviour vs. error) 17 | 2) Please provide a minimum reproducible code that we can test 18 | 3) Please provide the error message 19 | 20 | ## Submitting a pull request 21 | 1) If the issue is related to the typo fixes, improving documentation, or minor bug fixes feel free to submit the pull request straightaway 22 | 2) If the issue is related to the feature suggestion, or major bug fix please open an issue first before submitting a pull request 23 | -------------------------------------------------------------------------------- /heom_example.py: -------------------------------------------------------------------------------- 1 | # Example code of how the HEOM metric can be used together with Scikit-Learn 2 | import numpy as np 3 | from sklearn.neighbors import NearestNeighbors 4 | from sklearn.datasets import load_boston 5 | from distython import HEOM 6 | 7 | # Load the dataset from sklearn 8 | boston = load_boston() 9 | boston_data = boston["data"] 10 | # Categorical variables in the data 11 | categorical_ix = [3, 8] 12 | # The problem here is that NearestNeighbors can't handle np.nan 13 | # So we have to set up the NaN equivalent 14 | nan_eqv = 12345 15 | 16 | # Introduce some missingness to the data for the purpose of the example 17 | row_cnt, col_cnt = boston_data.shape 18 | for i in range(row_cnt): 19 | for j in range(col_cnt): 20 | rand_val = np.random.randint(20, size=1) 21 | if rand_val == 10: 22 | boston_data[i, j] = nan_eqv 23 | 24 | # Declare the HEOM with a correct NaN equivalent value 25 | heom_metric = HEOM(boston_data, categorical_ix, nan_equivalents = [nan_eqv]) 26 | 27 | # Declare NearestNeighbor and link the metric 28 | neighbor = NearestNeighbors(metric = heom_metric.heom) 29 | 30 | # Fit the model which uses the custom distance metric 31 | neighbor.fit(boston_data) 32 | 33 | # Return 5-Nearest Neighbors to the 1st instance (row 1) 34 | result = neighbor.kneighbors(boston_data[0].reshape(1, -1), n_neighbors = 5) 35 | print(result) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Distython :straight_ruler: 2 | ## About the project 3 | The aim of the package is to provide ready-to-use heterogeneous distance metrics which are compatible with Scikit-Learn. It is an implementation of state-of-the-art distance metrics from research papers which can handle mixed-type data and missing values. At the moment, HEOM, HVDM and VDM are tested and working. The implementation of the algorithms is done in Numpy to make it fast and efficient. VDM and HVDM has been released recently so please report bugs, if there are any. 4 | # Installation 5 | **Recommended:** 6 | `pip install distython` 7 | 8 | **Alternatively:** 9 | Clone the repository with `git clone`. 10 | Install the necessary packages with `pipenv install` 11 | # Contributing 12 | Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to the package. 13 | 14 | # Getting started - HEOM example 15 | ```python 16 | # Example code of how the HEOM metric can be used together with Scikit-Learn 17 | import numpy as np 18 | from sklearn.neighbors import NearestNeighbors 19 | from sklearn.datasets import load_boston 20 | # Importing a custom metric class 21 | from distython import HEOM 22 | 23 | # Load the dataset from sklearn 24 | boston = load_boston() 25 | boston_data = boston["data"] 26 | # Categorical variables in the data 27 | categorical_ix = [3, 8] 28 | # The problem here is that NearestNeighbors can't handle np.nan 29 | # So we have to set up the NaN equivalent 30 | nan_eqv = 12345 31 | 32 | # Introduce some missingness to the data for the purpose of the example 33 | row_cnt, col_cnt = boston_data.shape 34 | for i in range(row_cnt): 35 | for j in range(col_cnt): 36 | rand_val = np.random.randint(20, size=1) 37 | if rand_val == 10: 38 | boston_data[i, j] = nan_eqv 39 | 40 | # Declare the HEOM with a correct NaN equivalent value 41 | heom_metric = HEOM(boston_data, categorical_ix, nan_equivalents = [nan_eqv]) 42 | 43 | # Declare NearestNeighbor and link the metric 44 | neighbor = NearestNeighbors(metric = heom_metric.heom) 45 | 46 | # Fit the model which uses the custom distance metric 47 | neighbor.fit(boston_data) 48 | 49 | # Return 5-Nearest Neighbors to the 1st instance (row 1) 50 | result = neighbor.kneighbors(boston_data[0].reshape(1, -1), n_neighbors = 5) 51 | print(result) 52 | ``` 53 | # Authors 54 | - Kacper Kubara: www.kacperkubara.com 55 | # Acknowledgments 56 | - The creation of the package was inspired by my research project at [IT Innovation Centre](http://www.it-innovation.soton.ac.uk/) 57 | - HEOM, HVDM, and VDM have been implemented based on the following literature: https://arxiv.org/pdf/cs/9701101.pdf 58 | 59 | -------------------------------------------------------------------------------- /HEOM.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class HEOM(): 5 | def __init__(self, X, cat_ix, nan_equivalents=[np.nan, 0], normalised="normal"): 6 | """ Heterogeneous Euclidean-Overlap Metric 7 | Distance metric class which initializes the parameters 8 | used in heom function 9 | 10 | Parameters 11 | ---------- 12 | X : array-like of shape = [n_rows, n_features] 13 | Dataset that will be used with HEOM. Needs to be provided 14 | here because minimum and maximimum values from numerical 15 | columns have to be extracted 16 | 17 | cat_ix : array-like of shape = [cat_columns_number] 18 | List containing categorical feature indices 19 | 20 | cat_ix : array-like of shape = [x] 21 | List containing missing values indicators 22 | 23 | normalised: string 24 | normalises euclidan distance function for numerical variables 25 | Can be set as "std". Default is a column range 26 | 27 | Returns 28 | ------- 29 | None 30 | """ 31 | 32 | self.nan_eqvs = nan_equivalents 33 | self.cat_ix = cat_ix 34 | self.col_ix = [i for i in range(X.shape[1])] 35 | # Get the normalization scheme for numerical variables 36 | if normalised == "std": 37 | self.range = 4* np.nanstd(X, axis = 0) 38 | else: 39 | self.range = np.nanmax(X, axis = 0) - np.nanmin(X, axis = 0) 40 | 41 | def heom(self, x, y): 42 | """ Distance metric function which calculates the distance 43 | between two instances. Handles heterogeneous data and missing values. 44 | It can be used as a custom defined function for distance metrics 45 | in Scikit-Learn 46 | 47 | Parameters 48 | ---------- 49 | x : array-like of shape = [n_features] 50 | First instance 51 | 52 | y : array-like of shape = [n_features] 53 | Second instance 54 | Returns 55 | ------- 56 | result: float 57 | Returns the result of the distance metrics function 58 | """ 59 | # Initialise results' array 60 | results_array = np.zeros(x.shape) 61 | 62 | # Get indices for missing values, if any 63 | nan_x_ix = np.flatnonzero( np.logical_or(np.isin(x, self.nan_eqvs), np.isnan(x))) 64 | nan_y_ix = np.flatnonzero( np.logical_or(np.isin(y, self.nan_eqvs), np.isnan(y))) 65 | nan_ix = np.unique(np.concatenate((nan_x_ix, nan_y_ix))) 66 | # Calculate the distance for missing values elements 67 | results_array[nan_ix] = 1 68 | 69 | # Get categorical indices without missing values elements 70 | cat_ix = np.setdiff1d(self.cat_ix, nan_ix) 71 | # Calculate the distance for categorical elements 72 | results_array[cat_ix]= np.not_equal(x[cat_ix], y[cat_ix]) * 1 # use "* 1" to convert it into int 73 | 74 | # Get numerical indices without missing values elements 75 | num_ix = np.setdiff1d(self.col_ix, self.cat_ix) 76 | num_ix = np.setdiff1d(num_ix, nan_ix) 77 | # Calculate the distance for numerical elements 78 | results_array[num_ix] = np.abs(x[num_ix] - y[num_ix]) / self.range[num_ix] 79 | 80 | # Return the final result 81 | # Square root is not computed in practice 82 | # As it doesn't change similarity between instances 83 | return np.sum(np.square(results_array)) -------------------------------------------------------------------------------- /HVDM.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from distython import VDM 3 | 4 | 5 | class HVDM(VDM): 6 | def __init__(self, X , y_ix, cat_ix, nan_equivalents=[np.nan, 0], normalised="variance"): 7 | """ Heterogeneous Value Difference Metric 8 | Distance metric class which initializes the parameters 9 | used in hvdm() function 10 | 11 | Parameters 12 | ---------- 13 | X : array-like of shape = [n_rows, n_features] 14 | Dataset that will be used with HVDM. Needs to be provided 15 | here because minimum and maximimum values from numerical 16 | columns have to be extracted 17 | 18 | y_ix : int array-like, list of shape [1] 19 | Single element array with indices for the categorical output variable 20 | If y is numerical it should be converted to categorical (if it makes sense) 21 | 22 | cat_ix : array-like of shape = [cat_columns_number] 23 | List containing categorical feature indices 24 | 25 | cat_ix : array-like of shape = [x] 26 | List containing missing values indicators 27 | 28 | normalised: string 29 | Normalises euclidan distance function for numerical variables 30 | Can be set as "std". The other option is a column range 31 | 32 | Returns 33 | ------- 34 | None 35 | """ 36 | # Initialize VDM object 37 | super().__init__(X, y_ix, cat_ix) 38 | self.nan_eqvs = nan_equivalents 39 | self.cat_ix = cat_ix 40 | self.col_ix = [i for i in range(X.shape[1])] 41 | # Get the normalization scheme for numerical variables 42 | if normalised == "std": 43 | self.range = 4* np.nanstd(X, axis = 0) 44 | else: 45 | self.range = np.nanmax(X, axis = 0) - np.nanmin(X, axis = 0) 46 | 47 | def hvdm(self, x, y): 48 | """ Heterogeneous Value Difference Metric 49 | Distance metric function which calculates the distance 50 | between two instances. Handles heterogeneous data and missing values. 51 | For categorical variables, it uses conditional probability 52 | that the output class is given 'c' when attribute 'a' has a value of 'n'. 53 | For numerical variables, it uses a normalized Euclidan distance. 54 | It can be used as a custom defined function for distance metrics 55 | in Scikit-Learn 56 | 57 | Parameters 58 | ---------- 59 | x : array-like of shape = [n_features] 60 | First instance 61 | 62 | y : array-like of shape = [n_features] 63 | Second instance 64 | Returns 65 | ------- 66 | result: float 67 | Returns the result of the distance metrics function 68 | """ 69 | # Initialise results array 70 | results_array = np.zeros(x.shape) 71 | 72 | # Get indices for missing values, if any 73 | nan_x_ix = np.flatnonzero(np.logical_or(np.isin(x, self.nan_eqvs), np.isnan(x))) 74 | nan_y_ix = np.flatnonzero(np.logical_or(np.isin(y, self.nan_eqvs), np.isnan(y))) 75 | nan_ix = np.unique(np.concatenate((nan_x_ix, nan_y_ix))) 76 | # Calculate the distance for missing values elements 77 | results_array[nan_ix] = 1 78 | 79 | # Get categorical indices without missing values elements 80 | cat_ix = np.setdiff1d(self.cat_ix, nan_ix) 81 | # Calculate the distance for categorical elements 82 | results_array[cat_ix] = super().vdm(x, y, nan_ix)[cat_ix] 83 | # Get numerical indices without missing values elements 84 | num_ix = np.setdiff1d(self.col_ix, self.cat_ix) 85 | num_ix = np.setdiff1d(num_ix, nan_ix) 86 | # Calculate the distance for numerical elements 87 | results_array[num_ix] = np.abs(x[num_ix] - y[num_ix]) / self.range[num_ix] 88 | 89 | # Return the final result 90 | # Square root is not computed in practice 91 | # As it doesn't change similarity between instances 92 | return np.sum(np.square(results_array)) -------------------------------------------------------------------------------- /VDM.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class VDM(): 5 | def __init__(self, X, y_ix, cat_ix): 6 | """ Value Difference Metric 7 | Distance metric class which initializes the parameters 8 | used in vdm() function 9 | 10 | Parameters 11 | ---------- 12 | X : array-like of shape = [n_rows, n_features] 13 | First instance 14 | 15 | y_ix : int array-like, list of shape [1] 16 | Single element array with indices for categorical output variable 17 | If y is numerical it should be converted to categorical (if it makes sense) 18 | 19 | cat_ix : array-like of shape = [cat_columns_number] 20 | List containing categorical feature indices 21 | 22 | Returns 23 | ------- 24 | None 25 | """ 26 | self.cat_ix = cat_ix 27 | self.col_ix = [i for i in range(X.shape[1])] 28 | self.y_ix = y_ix 29 | self.classes = np.unique(X[:, y_ix]) 30 | 31 | array_len = 0 32 | # Get the max no. of unique classes within columns to initialize the array 33 | for ix in self.cat_ix: 34 | max_val = len(np.unique(X[:, ix])) 35 | if max_val > array_len: 36 | array_len = max_val 37 | 38 | # Store the list of unique classes elements for each categorical column 39 | # self.col_ix is used here for clearer indices assignment 40 | self.unique_attributes = np.full((array_len, len(self.col_ix)), fill_value=-1) 41 | for ix in self.cat_ix: 42 | unique_vals = np.unique(X[:, ix]) 43 | self.unique_attributes[0:len(unique_vals), ix] = unique_vals 44 | 45 | # Declare the 3D numpy array which holds specifc count for each attribute 46 | # for each column for each output class 47 | # +1 in len(self.classes) + 1 is to store the sum (N_a,x) in the last element 48 | self.final_count = np.zeros((len(self.col_ix), self.unique_attributes.shape[0], len(self.classes) + 1)) 49 | # For each column 50 | for i, col in enumerate(self.cat_ix): 51 | # For each attribute value in the column 52 | for j, attr in enumerate(self.unique_attributes[:, col]): 53 | # If attribute exists 54 | if attr != -1: 55 | # For each output class value 56 | for k, val in enumerate(self.classes): 57 | # Get an attribute count for each output class 58 | row_ixs = np.argwhere(X[:, col] == attr) 59 | cnt = np.sum(X[row_ixs, y_ix] == val) 60 | self.final_count[col, j, k] = cnt 61 | # Get a sum of all occurences 62 | self.final_count[col, j, -1] = np.sum(self.final_count[col, j, :]) 63 | 64 | 65 | def vdm(self, x, y, nan_ix=[]): 66 | """ Value Difference Metric 67 | Distance metric function which calculates the distance 68 | between two instances. Handles heterogeneous data and missing values. 69 | For categorical variables, it uses conditional probability 70 | that the output class is given 'c' when attribute 'a' has a value of 'n'. 71 | It can be used as a custom defined function for distance metrics 72 | in Scikit-Learn 73 | Parameters 74 | ---------- 75 | x : array-like of shape = [n_features] 76 | First instance 77 | 78 | y : array-like of shape = [n_features] 79 | Second instance 80 | Returns 81 | ------- 82 | result: float 83 | Returns the result of the distance metrics function 84 | """ 85 | result = np.zeros(len(x)) 86 | cat_ix = np.setdiff1d(self.cat_ix, nan_ix) 87 | 88 | for i in cat_ix: 89 | # Get indices to access the final_count array 90 | x_ix = np.argwhere(self.unique_attributes[:, i] == x[i]).flatten() 91 | y_ix = np.argwhere(self.unique_attributes[:, i] == y[i]).flatten() 92 | # Get the count to calculate the conditional probability 93 | N_ax = self.final_count[i, x_ix, -1].flatten() 94 | N_ay = self.final_count[i, y_ix, -1].flatten() 95 | N_axc = self.final_count[i, x_ix].flatten() 96 | N_ayc = self.final_count[i, y_ix].flatten() 97 | if N_ax != 0 and N_ay != 0: 98 | temp_result = abs(N_axc/N_ax - N_ayc/N_ay) 99 | temp_result = np.sum(temp_result) 100 | else: 101 | print("Division by zero is not allowed!") 102 | result[i] = temp_result 103 | 104 | return result 105 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the project community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c5263128b3ca83238e54f4f95a04a4367c95bbb700fb0a7e0e4b54107e07ad47" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "atomicwrites": { 20 | "hashes": [ 21 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", 22 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" 23 | ], 24 | "version": "==1.3.0" 25 | }, 26 | "attrs": { 27 | "hashes": [ 28 | "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", 29 | "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" 30 | ], 31 | "version": "==19.1.0" 32 | }, 33 | "importlib-metadata": { 34 | "hashes": [ 35 | "sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", 36 | "sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db" 37 | ], 38 | "version": "==0.18" 39 | }, 40 | "joblib": { 41 | "hashes": [ 42 | "sha256:21e0c34a69ad7fde4f2b1f3402290e9ec46f545f15f1541c582edfe05d87b63a", 43 | "sha256:315d6b19643ec4afd4c41c671f9f2d65ea9d787da093487a81ead7b0bac94524" 44 | ], 45 | "version": "==0.13.2" 46 | }, 47 | "more-itertools": { 48 | "hashes": [ 49 | "sha256:3ad685ff8512bf6dc5a8b82ebf73543999b657eded8c11803d9ba6b648986f4d", 50 | "sha256:8bb43d1f51ecef60d81854af61a3a880555a14643691cc4b64a6ee269c78f09a" 51 | ], 52 | "version": "==7.1.0" 53 | }, 54 | "numpy": { 55 | "hashes": [ 56 | "sha256:0778076e764e146d3078b17c24c4d89e0ecd4ac5401beff8e1c87879043a0633", 57 | "sha256:141c7102f20abe6cf0d54c4ced8d565b86df4d3077ba2343b61a6db996cefec7", 58 | "sha256:14270a1ee8917d11e7753fb54fc7ffd1934f4d529235beec0b275e2ccf00333b", 59 | "sha256:27e11c7a8ec9d5838bc59f809bfa86efc8a4fd02e58960fa9c49d998e14332d5", 60 | "sha256:2a04dda79606f3d2f760384c38ccd3d5b9bb79d4c8126b67aff5eb09a253763e", 61 | "sha256:3c26010c1b51e1224a3ca6b8df807de6e95128b0908c7e34f190e7775455b0ca", 62 | "sha256:52c40f1a4262c896420c6ea1c6fda62cf67070e3947e3307f5562bd783a90336", 63 | "sha256:6e4f8d9e8aa79321657079b9ac03f3cf3fd067bf31c1cca4f56d49543f4356a5", 64 | "sha256:7242be12a58fec245ee9734e625964b97cf7e3f2f7d016603f9e56660ce479c7", 65 | "sha256:7dc253b542bfd4b4eb88d9dbae4ca079e7bf2e2afd819ee18891a43db66c60c7", 66 | "sha256:94f5bd885f67bbb25c82d80184abbf7ce4f6c3c3a41fbaa4182f034bba803e69", 67 | "sha256:a89e188daa119ffa0d03ce5123dee3f8ffd5115c896c2a9d4f0dbb3d8b95bfa3", 68 | "sha256:ad3399da9b0ca36e2f24de72f67ab2854a62e623274607e37e0ce5f5d5fa9166", 69 | "sha256:b0348be89275fd1d4c44ffa39530c41a21062f52299b1e3ee7d1c61f060044b8", 70 | "sha256:b5554368e4ede1856121b0dfa35ce71768102e4aa55e526cb8de7f374ff78722", 71 | "sha256:cbddc56b2502d3f87fda4f98d948eb5b11f36ff3902e17cb6cc44727f2200525", 72 | "sha256:d79f18f41751725c56eceab2a886f021d70fd70a6188fd386e29a045945ffc10", 73 | "sha256:dc2ca26a19ab32dc475dbad9dfe723d3a64c835f4c23f625c2b6566ca32b9f29", 74 | "sha256:dd9bcd4f294eb0633bb33d1a74febdd2b9018b8b8ed325f861fffcd2c7660bb8", 75 | "sha256:e8baab1bc7c9152715844f1faca6744f2416929de10d7639ed49555a85549f52", 76 | "sha256:ec31fe12668af687b99acf1567399632a7c47b0e17cfb9ae47c098644ef36797", 77 | "sha256:f12b4f7e2d8f9da3141564e6737d79016fe5336cc92de6814eba579744f65b0a", 78 | "sha256:f58ac38d5ca045a377b3b377c84df8175ab992c970a53332fa8ac2373df44ff7" 79 | ], 80 | "index": "pypi", 81 | "version": "==1.16.4" 82 | }, 83 | "packaging": { 84 | "hashes": [ 85 | "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", 86 | "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" 87 | ], 88 | "version": "==19.0" 89 | }, 90 | "pluggy": { 91 | "hashes": [ 92 | "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", 93 | "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" 94 | ], 95 | "version": "==0.12.0" 96 | }, 97 | "py": { 98 | "hashes": [ 99 | "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", 100 | "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" 101 | ], 102 | "version": "==1.8.0" 103 | }, 104 | "pyparsing": { 105 | "hashes": [ 106 | "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", 107 | "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" 108 | ], 109 | "version": "==2.4.0" 110 | }, 111 | "pytest": { 112 | "hashes": [ 113 | "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", 114 | "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" 115 | ], 116 | "index": "pypi", 117 | "version": "==5.0.1" 118 | }, 119 | "scikit-learn": { 120 | "hashes": [ 121 | "sha256:051c53f9e900b0e9eccff2391f5317d1673d72e842bcbcd3e5d0b132459086ed", 122 | "sha256:0aafc312a55ebf58073151b9308761a5fcfa45b7f7730cea4b1f066f824c72db", 123 | "sha256:185d88ee4955cd68d7ff57356d1dd99cfc2de4b6aa5e5d679cafbc9df54716ff", 124 | "sha256:195465c39daded4f3ef8759291ffde81365486d4293e63dd9e32de0f569ecbbf", 125 | "sha256:4a6398500d035a4402476a2e3ae9f65a7a3f1b366ec6a7f6dd45c289f72dc954", 126 | "sha256:56f14e98632fb9237e7d005c6d8e346d01fa67f7b92f5f5d57a0bd06c741f9f6", 127 | "sha256:77092513dd780e12affde46a6394b52947db3fc00cf1d8c1c8eede52b37591d1", 128 | "sha256:7d2cdfe16b1ae6f9a1760b69be27c2004a84fc362984f930df135c847c47b765", 129 | "sha256:82c3450fc375f27e3529fa05fec627c9fc75915e05fcd55de43f193b3aa907af", 130 | "sha256:a5fba00d9037b62b0e0906f64efe9e4a754e556bc091cc12f84bc81655b4a414", 131 | "sha256:acba6bf5928e415b6296799a7aa069b66254c9440bce88ed2e5915865a317093", 132 | "sha256:b474f00d2533f18761fb17fb0950b27e72baf0796176247b5a7cf0ee369790ee", 133 | "sha256:ca45e0def97f73a828cee417174fafa0ab35a41f8bdca4424120a29c5589c548", 134 | "sha256:f09e544a6756afbd9d31e1d8ddfde5a2c9c17f6d4274104c988fceb611e2d5c5", 135 | "sha256:f979bb85cbfd9ed4d54709d86ab8893b316726abd1c9ab04abe7e6414b71b753", 136 | "sha256:fb4c7a2294447515fffec33c1f5eedbe942e9be56edb8c6619607e7882531d40" 137 | ], 138 | "version": "==0.21.2" 139 | }, 140 | "scipy": { 141 | "hashes": [ 142 | "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", 143 | "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", 144 | "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", 145 | "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", 146 | "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", 147 | "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", 148 | "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", 149 | "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", 150 | "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", 151 | "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", 152 | "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", 153 | "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", 154 | "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", 155 | "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", 156 | "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", 157 | "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee" 158 | ], 159 | "version": "==1.3.0" 160 | }, 161 | "six": { 162 | "hashes": [ 163 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 164 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 165 | ], 166 | "version": "==1.12.0" 167 | }, 168 | "sklearn": { 169 | "hashes": [ 170 | "sha256:e23001573aa194b834122d2b9562459bf5ae494a2d59ca6b8aa22c85a44c0e31" 171 | ], 172 | "index": "pypi", 173 | "version": "==0.0" 174 | }, 175 | "wcwidth": { 176 | "hashes": [ 177 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 178 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 179 | ], 180 | "version": "==0.1.7" 181 | }, 182 | "zipp": { 183 | "hashes": [ 184 | "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", 185 | "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" 186 | ], 187 | "version": "==0.5.2" 188 | } 189 | }, 190 | "develop": { 191 | "astroid": { 192 | "hashes": [ 193 | "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", 194 | "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" 195 | ], 196 | "version": "==2.2.5" 197 | }, 198 | "isort": { 199 | "hashes": [ 200 | "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", 201 | "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" 202 | ], 203 | "version": "==4.3.21" 204 | }, 205 | "lazy-object-proxy": { 206 | "hashes": [ 207 | "sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", 208 | "sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", 209 | "sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", 210 | "sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", 211 | "sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", 212 | "sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", 213 | "sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", 214 | "sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", 215 | "sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", 216 | "sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", 217 | "sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", 218 | "sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", 219 | "sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", 220 | "sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", 221 | "sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", 222 | "sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", 223 | "sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", 224 | "sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1" 225 | ], 226 | "version": "==1.4.1" 227 | }, 228 | "mccabe": { 229 | "hashes": [ 230 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 231 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 232 | ], 233 | "version": "==0.6.1" 234 | }, 235 | "pylint": { 236 | "hashes": [ 237 | "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", 238 | "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1" 239 | ], 240 | "index": "pypi", 241 | "version": "==2.3.1" 242 | }, 243 | "six": { 244 | "hashes": [ 245 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 246 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 247 | ], 248 | "version": "==1.12.0" 249 | }, 250 | "typed-ast": { 251 | "hashes": [ 252 | "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", 253 | "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", 254 | "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", 255 | "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", 256 | "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", 257 | "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", 258 | "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", 259 | "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", 260 | "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", 261 | "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", 262 | "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", 263 | "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", 264 | "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", 265 | "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", 266 | "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" 267 | ], 268 | "markers": "implementation_name == 'cpython'", 269 | "version": "==1.4.0" 270 | }, 271 | "wrapt": { 272 | "hashes": [ 273 | "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" 274 | ], 275 | "version": "==1.11.2" 276 | } 277 | } 278 | } 279 | --------------------------------------------------------------------------------