From b109798f737a59991b7d6b9ae2ea5166438e1a68 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Sep 2019 10:48:34 +0200 Subject: [PATCH 01/98] Add KBinsDiscretizer to new preprocessing module Created a new preprocessing module and add KBinsDiscretizer as the first class to that module. Also included unittests for that class. --- cobra/preprocessing/__init__.py | 0 cobra/preprocessing/kbins_discretizer.py | 398 ++++++++++++++++++ tests/preprocessing/__init__.py | 0 tests/preprocessing/test_kbins_discretizer.py | 73 ++++ 4 files changed, 471 insertions(+) create mode 100644 cobra/preprocessing/__init__.py create mode 100644 cobra/preprocessing/kbins_discretizer.py create mode 100644 tests/preprocessing/__init__.py create mode 100644 tests/preprocessing/test_kbins_discretizer.py diff --git a/cobra/preprocessing/__init__.py b/cobra/preprocessing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py new file mode 100644 index 0000000..efd0e54 --- /dev/null +++ b/cobra/preprocessing/kbins_discretizer.py @@ -0,0 +1,398 @@ +""" +This class is a rework of +https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/_discretization.py +However, it is purely written in pandas instead of numpy because +it is more intuitive +""" +# standard lib imports +from copy import deepcopy +from typing import List +import numbers + +import logging +log = logging.getLogger(__name__) + +# third party imports +import numpy as np +import pandas as pd + +from sklearn.exceptions import NotFittedError + + +class KBinsDiscretizer: + + """Bin continuous data into intervals of predefined size + + Attributes + ---------- + auto_adapt_bins : bool + reduces the number of bins (starting from n_bins) as a function of + the number of missings + bins_by_column : dict + Placeholder for the fitted output + change_endpoint_format : bool + Whether or not to change the format of the lower and upper bins into + "< x" and "> y" resp. + closed : str + Whether to close the bins (intervals) from the left or right + label_format : str + format string to display the bin labels e.g. min - max, (min, max], ... + n_bins : int + Number of bins to produce. Raises ValueError if ``n_bins < 2``. + starting_precision : int + Initial precision for the bin edges to start from, + can also be negative. Given a list of bin edges, the class will + automatically choose the minimal precision required to have proper bins + e.g. [5.5555, 5.5744, ...] will be rounded to [5.56, 5.57, ...]. In + case of a negative number, an attempt will be made to round up the + numbers of the bin edges e.g. 5.55 -> 10, 146 -> 100, ... + strategy : str + Binning strategy. Currently only "uniform" and "quantile" + e.g. equifrequency is supported + """ + + valid_strategies = ("uniform", "quantile") + + def __init__(self, n_bins: int=10, strategy: str="quantile", + closed: str="right", + auto_adapt_bins: bool=False, + starting_precision: int=0, + label_format: str="{} - {}", + change_endpoint_format: bool=False): + + # validate number of bins + self._validate_n_bins(n_bins) + + self.n_bins = n_bins + self.strategy = strategy.lower() + self.closed = closed + self.auto_adapt_bins = auto_adapt_bins + self.starting_precision = starting_precision + self.label_format = label_format + self.change_endpoint_format = change_endpoint_format + + # dict to store fitted output in + self._bins_by_column = {} + + def _validate_n_bins(self, n_bins: int): + """Check if n_bins is of the proper type and if it is bigger than two + + Parameters + ---------- + n_bins : int + Number of bins KBinsDiscretizer has to produce for each variable + + Raises + ------ + ValueError + in case n_bins is not an integer or if n_bins < 2 + """ + if not isinstance(n_bins, numbers.Integral): + raise ValueError("{} received an invalid n_bins type. " + "Received {}, expected int." + .format(KBinsDiscretizer.__name__, + type(n_bins).__name__)) + if n_bins < 2: + raise ValueError("{} received an invalid number " + "of bins. Received {}, expected at least 2." + .format(KBinsDiscretizer.__name__, n_bins)) + + def set_bins_by_columns(self, bins_by_column: List[tuple]): + # To do: add checks! + self._bins_by_column = bins_by_column + + def _compute_minimal_precision_of_cutpoints(self, cutpoints: list) -> int: + """Compute the minimal precision of a list of cutpoints so that we end + up with a strictly ascending sequence of numbers. + The starting_precision attribute will be used as the initial precision. + In case of a negative starting_precision, the bin edges will be rounded + to the nearest 10, 100, ... (e.g. 5.55 -> 10, 246 -> 200, ...) + + Parameters + ---------- + cutpoints : list + The bin edges for binning a continuous variable + + Returns + ------- + int + minimal precision for the bin edges + """ + + precision = self.starting_precision + while True: + cont = False + for a, b in zip(cutpoints, cutpoints[1:]): + if a != b and round(a, precision) == round(b, precision): + # precision is not high enough, so increase + precision += 1 + cont = True # set cont to True to keep looping + break # break out of the for loop + if not cont: + # if minimal precision was found, + # return to break out of while loop + return precision + + def _compute_bins_from_cutpoints(self, cutpoints: list) -> List[tuple]: + """Given a list of bin edges, compute the minimal precision for which + we can make meaningful bins and make those bins + + Parameters + ---------- + cutpoints : list + The bin edges for binning a continuous variable + + Returns + ------- + List[tuple] + A (sorted) list of bins as tuples + """ + # compute the minimal precision of the cutpoints + # this can be a negative number, which then + # rounds numbers to the nearest 10, 100, ... + precision = self._compute_minimal_precision_of_cutpoints(cutpoints) + + bins = [] + for a, b in zip(cutpoints, cutpoints[1:]): + fmt_a = round(a, precision) + fmt_b = round(b, precision) + + bins.append((fmt_a, fmt_b)) + + return bins + + @staticmethod + def _create_index(intervals: List[tuple], + closed: str="right") -> pd.IntervalIndex: + """Create an pd.IntervalIndex based on a list of tuples. + This is basically a wrapper around pd.IntervalIndex.from_tuples + However, the lower bound of the first entry in the list (the lower bin) + is replaced by -np.inf. Similarly, the upper bound of the last entry in + the list (upper bin) is replaced by np.inf. + + Parameters + ---------- + intervals : List[tuple] + a list of tuples describing the intervals + closed : str, optional + Whether the intervals should be closed on the left-side, + right-side, both or neither. + + Returns + ------- + pd.IntervalIndex + Description + """ + # deepcopy variable because we do not want to modify the content + # of intervals (which is still used outside of this function) + _intervals = deepcopy(intervals) + # Modify min and max with -np.inf and np.inf resp. so that these values + # are guaranteed to be included when transforming the data + _intervals[0] = (-np.inf, _intervals[0][1]) + _intervals[-1] = (_intervals[-1][0], np.inf) + + return pd.IntervalIndex.from_tuples(_intervals, closed) + + def _create_bin_labels(self, bins: List[tuple]) -> list: + """Given a list of bins, create a list of string containing the bins + as a string with a specific format (e.g. bin labels) + + Parameters + ---------- + bins : List[tuple] + list of bins + + Returns + ------- + list + list of (formatted) bin labels + """ + bin_labels = [] + for interval in bins: + bin_labels.append(self.label_format.format(interval[0], + interval[1])) + + # Format first and last bin as < x and > y resp. + if self.change_endpoint_format: + bin_labels[0] = "< {}".format(bins[0][1]) + bin_labels[-1] = "> {}".format(bins[-1][0]) + + return bin_labels + + def _fit_column(self, data: pd.DataFrame, + column_name: str) -> List[tuple]: + """Compute bins for a specific column in data + + Parameters + ---------- + data : pd.DataFrame + Description + column_name : str + Description + + Returns + ------- + List[tuple] + list of bins as tuples + """ + + col_min, col_max = data[column_name].min(), data[column_name].max() + + if col_min == col_max: + log.warning("Predictor {} is constant and " + "will be ignored in computation".format(column_name)) + return None + + n_bins = self.n_bins + if self.auto_adapt_bins: + size = len(data.index) + missing_pct = data[column_name].isnull().sum()/size + n_bins = int(max((1 - missing_pct) * n_bins), 2) + + cutpoints = [] + if self.strategy == "quantile": + cutpoints = list(data[column_name] + .quantile(np.linspace(0, 1, n_bins + 1), + interpolation='linear')) + elif self.strategy == "uniform": + cutpoints = list(np.linspace(col_min, col_max, n_bins + 1)) + + # Make sure the cutpoints are unique and sorted + cutpoints = sorted(list(set(cutpoints))) + + if len(cutpoints) < 3: + log.warning("Only 1 bin was found for predictor {} and will be " + "ignored in computation".format(column_name)) + return None + + if len(cutpoints) < n_bins + 1: + log.warning("The number of actual bins for column {} is {} " + "which is smaller than the requested number of bins " + "{}".format(column_name, len(cutpoints) - 1, n_bins)) + + return self._compute_bins_from_cutpoints(cutpoints) + + def fit(self, data: pd.DataFrame, column_names: list): + """Fits the estimator + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + """ + + if self.strategy not in self.valid_strategies: + raise ValueError("{}: valid options for 'strategy' are {}. " + "Got strategy={!r} instead." + .format(KBinsDiscretizer.__name__, + self.valid_strategies, self.strategy)) + + for column_name in column_names: + + bins = self._fit_column(data, column_name) + + # Add to bins_by_column for later use + self._bins_by_column[column_name] = bins + + def _transform_column(self, data: pd.DataFrame, + column_name: str, + bins: List[tuple]) -> pd.DataFrame: + """Given a DataFrame, a column name and a list of bins, + create an additional column which determines the bin in which the value + of column_name lies in. + + Parameters + ---------- + data : pd.DataFrame + Original data to be discretized + column_name : str + name of the column to discretize + bins : List[tuple] + bins to discretize the data into + + Returns + ------- + pd.DataFrame + original DataFrame with an added binned column + """ + + interval_idx = KBinsDiscretizer._create_index(bins, self.closed) + + column_name_bin = column_name + "_bin" + + # use pd.cut to compute bins + data[column_name_bin] = pd.cut(x=data[column_name], + bins=interval_idx) + + # Rename bins so that the output has a proper format + bin_labels = self._create_bin_labels(bins) + + data[column_name_bin] = (data[column_name_bin] + .cat.rename_categories(bin_labels)) + + if data[column_name_bin].isnull().sum() > 0: + + # Add an additional bin for missing values + data[column_name_bin].cat.add_categories(["Missing"], inplace=True) + + # Replace NULL with "Missing" + # Otherwise these will be ignored in groupby + data[column_name_bin].fillna("Missing", inplace=True) + + return data + + def transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + if len(self._bins_by_column) == 0: + msg = ("{} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + for column_name in column_names: + if column_name not in self._bins_by_column: + log.warning("Column {} is not in fitted output " + "and will be skipped".format(column_name)) + continue + + # can be None for a column with a constant value! + bins = self._bins_by_column[column_name] + if bins: + data = self._transform_column(data, column_name, bins) + + return data + + def fit_transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + self.fit(data, column_names) + return self.transform(data, column_names) diff --git a/tests/preprocessing/__init__.py b/tests/preprocessing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py new file mode 100644 index 0000000..1b09ae6 --- /dev/null +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -0,0 +1,73 @@ +import pandas as pd +import pytest + +from cobra.preprocessing.kbins_discretizer import KBinsDiscretizer + + +class TestKBinsDiscretizer: + + # tests for _validate_n_bins function + + def test_validate_n_bins_exception_1(self): + + with pytest.raises(ValueError): + KBinsDiscretizer()._validate_n_bins(n_bins=1) + + def test_validate_n_bins_exception_no_integral(self): + + with pytest.raises(ValueError): + KBinsDiscretizer()._validate_n_bins(n_bins=10.5) + + def test_validate_n_bins_valid_n_bins(self): + + KBinsDiscretizer()._validate_n_bins(n_bins=2) + + # tests for _compute_minimal_precision_of_cutpoints + + def test_compute_minimal_precision_of_cutpoints_less_precision(self): + # If starting precision is bigger than actual precision, should return + # starting precision + + cutpoints = [-10, 0, 1, 2] + discretizer = KBinsDiscretizer(starting_precision=1) + res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + assert res == 1 + + def test_compute_minimal_precision_of_cutpoints_more_precision(self): + # If starting precision is smaller than actual precision, should return + # actual precision + + cutpoints = [-10, 0, 1, 1.01] + discretizer = KBinsDiscretizer() + res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + assert res == 2 + + def test_compute_minimal_precision_of_cutpoints_equal_precision(self): + # If starting precision is equal to actual precision, should return + # starting precision + + cutpoints = [-10, 0, 1, 1.1] + discretizer = KBinsDiscretizer(starting_precision=1) + res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + assert res == 1 + + def test_compute_minimal_precision_of_cutpoints_negative_start(self): + # Check if negative starting precision also leads to the correct result + + cutpoints = [-10, 0, 1, 2] + discretizer = KBinsDiscretizer(starting_precision=-1) + res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + assert res == 0 + + def test_compute_minimal_precision_of_cutpoints_round_up(self): + # Check if negative starting precision leads to rounding up + # bin edges to the nearest multiple of 10 + + cutpoints = [-10, 0, 10, 21] + discretizer = KBinsDiscretizer(starting_precision=-1) + res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + assert res == -1 + + # tests for _compute_bins_from_cutpoints + + # tests for _create_bin_labels From 41a79cb308a1df46238b8bb8b596b009ac02178d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Sep 2019 10:50:39 +0200 Subject: [PATCH 02/98] Add TargetEncoder to preprocessing module --- cobra/preprocessing/target_encoder.py | 205 ++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 cobra/preprocessing/target_encoder.py diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py new file mode 100644 index 0000000..d4dad60 --- /dev/null +++ b/cobra/preprocessing/target_encoder.py @@ -0,0 +1,205 @@ +import numpy as np +import pandas as pd + +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.exceptions import NotFittedError + + +# Prototype of TargetEncoder +class TargetEncoder(BaseEstimator, TransformerMixin): + + """Target encoding for categorical features. + + Replace each value of the categorical feature with the average of the + target values (in case of a binary target, this is the incidence of the + group). This encoding scheme is also called Mean encoding. + + The main problem with Target encoding is overfitting; the fact that we are + encoding the feature based on target classes may lead to data leakage, + rendering the feature biased. This can be solved using some type of + regularization. A popular way to handle this is to use cross-validation + and compute the means in each out-of-fold. However, the approach + implemented makes use of additive smoothing + (https://en.wikipedia.org/wiki/Additive_smoothing) + + Attributes + ---------- + columns : list + A list of columns to encode, if None, all string columns will be + encoded. + weight : float + Smoothing parameters (non-negative). The higher the value of the + parameter, the bigger the contribution of the overall mean. When set to + zero, there is no smoothing (e.g. the pure target incidence is used). + """ + + def __init__(self, columns: list=None, weight: float=0.0): + """Constructor + + Parameters + ---------- + columns : list, optional + A list of columns to encode, if None, all string columns will be + encoded. + weight : float, optional + Smoothing parameters (non-negative). The higher the value of the + parameter, the bigger the contribution of the overall mean. When + set to zero, there is no smoothing + (e.g. the pure target incidence is used) + """ + if weight < 0: + raise ValueError("The value of weight cannot be smaller than zero") + + self.columns = columns + self.weight = weight + self._mapping = {} # placeholder for fitted output + + # not implemented yet! + # randomized: bool=False, sigma=0.05 + # self.randomized = randomized + # self.sigma = sigma + + @staticmethod + def _get_categorical_columns(data: pd.DataFrame) -> list: + """Get the columns containing categorical data + (dtype "object" or "category") + + Parameters + ---------- + data : pd.DataFrame + Description + + Returns + ------- + list + List of column names containing categorical data + """ + object_columns = data.dtypes[data.dtypes == object].index + categorical_columns = data.dtypes[data.dtypes == "category"].index + + return list(set(object_columns).union(categorical_columns)) + + def fit(self, X: pd.DataFrame, y: pd.Series): + """Fit the TargetEncoder to X and y + + Parameters + ---------- + X : pd.DataFrame + data used to compute the mapping to encode the categorical + variables with. + y : pd.Series + series containing the targets for each observation + + Raises + ------ + ValueError + if the length of X and y are not equal + """ + # The lengths must be equal + if len(X.index) != len(y.index): + raise ValueError("The length of X is {}, but the length of y is {}" + .format(len(X.index), len(y.index))) + + if self.columns is None: + self.columns = TargetEncoder._get_categorical_columns(X) + + # compute global mean (target incidence in case of binary target) + global_mean = y.sum() / y.count() + + for column in self.columns: + self._mapping[column] = self._fit_column(X[column], y, global_mean) + + def _fit_column(self, X: pd.Series, y: pd.Series, + global_mean: float) -> pd.Series: + """Summary + + Parameters + ---------- + X : pd.Series + data used to compute the encoding mapping for an individual + categorical variable. + y : pd.Series + series containing the targets for each observation + global_mean : float + Global mean of the target + + Returns + ------- + pd.Series + Mapping containing the value to replace each group of the + categorical with. + """ + stats = y.groupby(X).agg(["mean", "count"]) + + # To do: add Gaussian noise to the estimate + # Q: do we need to do this here or during the transform phase??? + + # Note if self.weight = 0, we have the ordinary incidence replacement + numerator = stats["count"]*stats["mean"] + self.weight*global_mean + denominator = stats["count"] + self.weight + + return numerator/denominator + + @staticmethod + def _clean_column_name(column_name: str) -> str: + """Clean column name string by removing "_bin" and adding "_enc" + + Parameters + ---------- + column_name : str + column name to be cleaned + + Returns + ------- + str + cleaned column name + """ + if "_bin" in column_name: + return column_name.replace("_bin", "") + "_enc" + else: + return column_name + "_enc" + + def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: + """Summary + + Parameters + ---------- + X : pd.DataFrame + data to encode + y : pd.Series, optional + Ignored (added for compatibility with scikit-learn) + + Returns + ------- + pd.DataFrame + transformed data + + Raises + ------ + NotFittedError + Exception when TargetEncoder was not fitted before calling this + method + + """ + if len(self._mapping) == 0: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + transform_columns = list(X.columns) + new_columns = [] + for column in self.columns: + + if column not in transform_columns: + # skip if this column was not in X + # print WARNING here!!! + continue + + new_column = TargetEncoder._clean_column_name(column) + + X[new_column] = X[column].map(self._mapping[column]) + + new_columns.append(new_column) + + return X[new_columns] From 0b89146e4811bd737e05f113ed9eb3563920dfa3 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Sep 2019 15:24:13 +0200 Subject: [PATCH 03/98] Add a scripts module with a first example script The goal of the scripts module is to provide a set of ready-to-use functions for a set of concrete problems and situations. The first script for example generates a csv file containing the PIG tables for a given dataset, which can then be used to create PIG graphs for every predictor in the dataset. --- cobra/scripts/__init__.py | 0 cobra/scripts/export_pigs.py | 143 +++++++++++++++++++++++++++++++++++ cobra/utils.py | 69 +++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 cobra/scripts/__init__.py create mode 100644 cobra/scripts/export_pigs.py create mode 100644 cobra/utils.py diff --git a/cobra/scripts/__init__.py b/cobra/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cobra/scripts/export_pigs.py b/cobra/scripts/export_pigs.py new file mode 100644 index 0000000..086f342 --- /dev/null +++ b/cobra/scripts/export_pigs.py @@ -0,0 +1,143 @@ +# third party lib imports +import pandas as pd +# custom imports +import cobra.utils as utils +from cobra.preprocessing.kbins_discretizer import KBinsDiscretizer + + +def preprocess_categoricals(data: pd.DataFrame, + categorical_columns: list) -> pd.DataFrame: + + for column_name in categorical_columns: + + # change data to categorical + data[column_name] = data[column_name].astype("category") + + # check for null values + if data[column_name].isnull().sum() > 0: + + # Add an additional category + data[column_name].cat.add_categories(["Missing"], inplace=True) + + # Replace NULL with "Missing" + # Otherwise these will be ignored in groupby + data[column_name].fillna("Missing", inplace=True) + + return data + + +def compute_pig_table(data: pd.DataFrame, + column_name: str, + target_column_name: str, + id_column_name: str) -> pd.DataFrame: + """Compute the pig table of a given predictor for a given target + + Parameters + ---------- + data : pd.DataFrame + input data from which to compute the pig table + column_name : str + predictor name of which to compute the pig table + target_column_name : str + name of the target variable + id_column_name : str + name of the id column (used to count population size) + + Returns + ------- + pd.DataFrame + pig table as a DataFrame + """ + avg_incidence = data[target_column_name].mean() + + # group by the binned variable, compute the incidence + # (=mean of the target for the given bin) and compute the bin size + # (e.g. COUNT(id_column_name)). After that, rename the columns + res = (data.groupby(column_name) + .agg({target_column_name: "mean", id_column_name: "size"}) + .reset_index() + .rename(columns={column_name: "label", + target_column_name: "incidence", + id_column_name: "pop_size"})) + + # add the column name to a variable column + # add the average incidence + # replace population size by a percentage of total population + res["variable"] = column_name + res["avg_incidence"] = avg_incidence + res["pop_size"] = res["pop_size"]/len(data.index) + + # make sure to always return the data with the proper column order + column_order = ["variable", "label", "pop_size", + "avg_incidence", "incidence"] + + return res[column_order] + + +def generate_pig_tables(data: pd.DataFrame, + id_column_name: str, + target_column_name: str, + n_bins: int, + strategy: str, + label_format: str) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + basetable to compute PIG tables of + id_column_name : str + column name of the id (e.g. customernumber) + target_column_name : str + column name of the target + n_bins : int + Number of bins to produce after discretization + strategy : str + Binning strategy. Currently only "uniform" and "quantile" + e.g. equifrequency is supported + label_format : str + format string to display the bin labels e.g. min - max, (min, max], ... + + Returns + ------- + pd.DataFrame + DataFrame containing a PIG table for all predictors + """ + + # Based on the data, get column names by datatype + # threshold to decide whether a numeric column should be considered + # a categorical variable (if the number of distinct values is smaller + # or equal to the number of requested bins) + categorical_threshold = n_bins + columns_by_type = utils.get_column_datatypes(data, id_column_name, + target_column_name, + categorical_threshold) + + # process continuous variables + discretizer = KBinsDiscretizer(n_bins=n_bins, + strategy=strategy, + label_format=label_format) + + # Transform the data + data = discretizer.fit_transform(data, + columns_by_type["numeric_variables"]) + + # Process categorical and dummy variables + categorical_vars = columns_by_type["categorical_variables"] + dummy_vars = columns_by_type["dummy_variables"] + relevant_columns = set(categorical_vars).union(set(dummy_vars)) + + data = preprocess_categoricals(data, list(relevant_columns)) + + # Get relevant columns, e.g. the ones that are transformed + # into categorical dtypes by the preprocessing steps + relevant_columns = set(data.dtypes[data.dtypes == "category"].index) + + pigs = [compute_pig_table(data, column_name, target_column_name, + id_column_name) + for column_name in sorted(relevant_columns) + if column_name not in [id_column_name, target_column_name]] + + output = pd.concat(pigs) + + return output diff --git a/cobra/utils.py b/cobra/utils.py new file mode 100644 index 0000000..b6f25ec --- /dev/null +++ b/cobra/utils.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd + + +def get_column_datatypes(data: pd.DataFrame, + target_column_name: str=None, + id_column_name: str=None, + numeric_is_categorical_threshold: int=10) -> dict: + """Get a list of column names by data type from a pandas DataFrame, + excluding the id column and the target_column if provided + + Parameters + ---------- + data : pd.DataFrame + data to extract columns by type from + target_column_name : str, optional + Description + id_column_name : str, optional + Description + numeric_is_categorical_threshold : int, optional + Threshold to decide whether a numeric variable is categorical based + on the number of unique values in that column + + Returns + ------- + dict + Description + """ + column_names = list(data.columns) + + # dummies variables: case they have only 2 values + vars_dummy = set([col for col in column_names + if len(data[col].unique()) == 2]) + + # categorical vars + vars_cat = (set(data.dtypes[data.dtypes == object].index) + .union(set(data.dtypes[data.dtypes == "category"].index))) + + # Numeric variables + is_number = np.vectorize(lambda x: np.issubdtype(x, np.number)) + bool_arr_is_numeric = is_number(data.dtypes) + vars_numeric = set(data.columns[bool_arr_is_numeric]) + + # remove dummy variables from set + vars_numeric = vars_numeric.difference(vars_dummy) + + # Remark: numeric variables can still be "categorical" + # i.e. when they only contain some distinct values! + # We only consider a variable continuous if they have more distinct values + # than the requested number bins (using numeric_is_categorical_threshold) + + # continuous if more than numeric_is_categorical_threshold distinct values + threshold = numeric_is_categorical_threshold + vars_cat_numeric = set([col for col in vars_numeric + if len(data[col].unique()) < threshold]) + + # remove from numeric set + vars_numeric = vars_numeric.difference(vars_cat_numeric) + # add to categorical set + vars_cat = vars_cat.union(vars_cat_numeric) + + if id_column_name: + vars_cat = vars_cat.difference(set([id_column_name])) + if target_column_name: + vars_dummy = vars_dummy.difference(set([target_column_name])) + + return {"numeric_variables": vars_numeric, + "categorical_variables": vars_cat, + "dummy_variables": vars_dummy} From ed180799637ef83c93064199b5196518f07d68d1 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Sep 2019 17:05:25 +0200 Subject: [PATCH 04/98] Refactor KBinsDiscretizer - Renamed cutpoints to bin_edges throughout code as this is easier to understand - Reorganised code so that public methods come first in the source code. These functions are then easier to trace in the source code and as they are the ones that will most likely be inspected. - Migrated the computation of bin edges to a separate (private) method. - Added already a snippet (in comment) for kmeans discretization, but this still needs work --- cobra/preprocessing/kbins_discretizer.py | 373 ++++++++++-------- tests/preprocessing/test_kbins_discretizer.py | 22 +- 2 files changed, 224 insertions(+), 171 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index efd0e54..bc2a7fc 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -17,6 +17,7 @@ import pandas as pd from sklearn.exceptions import NotFittedError +from sklearn.cluster import KMeans class KBinsDiscretizer: @@ -101,123 +102,29 @@ def set_bins_by_columns(self, bins_by_column: List[tuple]): # To do: add checks! self._bins_by_column = bins_by_column - def _compute_minimal_precision_of_cutpoints(self, cutpoints: list) -> int: - """Compute the minimal precision of a list of cutpoints so that we end - up with a strictly ascending sequence of numbers. - The starting_precision attribute will be used as the initial precision. - In case of a negative starting_precision, the bin edges will be rounded - to the nearest 10, 100, ... (e.g. 5.55 -> 10, 246 -> 200, ...) - - Parameters - ---------- - cutpoints : list - The bin edges for binning a continuous variable - - Returns - ------- - int - minimal precision for the bin edges - """ - - precision = self.starting_precision - while True: - cont = False - for a, b in zip(cutpoints, cutpoints[1:]): - if a != b and round(a, precision) == round(b, precision): - # precision is not high enough, so increase - precision += 1 - cont = True # set cont to True to keep looping - break # break out of the for loop - if not cont: - # if minimal precision was found, - # return to break out of while loop - return precision - - def _compute_bins_from_cutpoints(self, cutpoints: list) -> List[tuple]: - """Given a list of bin edges, compute the minimal precision for which - we can make meaningful bins and make those bins + def fit(self, data: pd.DataFrame, column_names: list): + """Fits the estimator Parameters ---------- - cutpoints : list - The bin edges for binning a continuous variable - - Returns - ------- - List[tuple] - A (sorted) list of bins as tuples + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized """ - # compute the minimal precision of the cutpoints - # this can be a negative number, which then - # rounds numbers to the nearest 10, 100, ... - precision = self._compute_minimal_precision_of_cutpoints(cutpoints) - - bins = [] - for a, b in zip(cutpoints, cutpoints[1:]): - fmt_a = round(a, precision) - fmt_b = round(b, precision) - bins.append((fmt_a, fmt_b)) - - return bins - - @staticmethod - def _create_index(intervals: List[tuple], - closed: str="right") -> pd.IntervalIndex: - """Create an pd.IntervalIndex based on a list of tuples. - This is basically a wrapper around pd.IntervalIndex.from_tuples - However, the lower bound of the first entry in the list (the lower bin) - is replaced by -np.inf. Similarly, the upper bound of the last entry in - the list (upper bin) is replaced by np.inf. - - Parameters - ---------- - intervals : List[tuple] - a list of tuples describing the intervals - closed : str, optional - Whether the intervals should be closed on the left-side, - right-side, both or neither. - - Returns - ------- - pd.IntervalIndex - Description - """ - # deepcopy variable because we do not want to modify the content - # of intervals (which is still used outside of this function) - _intervals = deepcopy(intervals) - # Modify min and max with -np.inf and np.inf resp. so that these values - # are guaranteed to be included when transforming the data - _intervals[0] = (-np.inf, _intervals[0][1]) - _intervals[-1] = (_intervals[-1][0], np.inf) - - return pd.IntervalIndex.from_tuples(_intervals, closed) - - def _create_bin_labels(self, bins: List[tuple]) -> list: - """Given a list of bins, create a list of string containing the bins - as a string with a specific format (e.g. bin labels) - - Parameters - ---------- - bins : List[tuple] - list of bins + if self.strategy not in self.valid_strategies: + raise ValueError("{}: valid options for 'strategy' are {}. " + "Got strategy={!r} instead." + .format(KBinsDiscretizer.__name__, + self.valid_strategies, self.strategy)) - Returns - ------- - list - list of (formatted) bin labels - """ - bin_labels = [] - for interval in bins: - bin_labels.append(self.label_format.format(interval[0], - interval[1])) + for column_name in column_names: - # Format first and last bin as < x and > y resp. - if self.change_endpoint_format: - bin_labels[0] = "< {}".format(bins[0][1]) - bin_labels[-1] = "> {}".format(bins[-1][0]) + bins = self._fit_column(data, column_name) - return bin_labels + # Add to bins_by_column for later use + self._bins_by_column[column_name] = bins def _fit_column(self, data: pd.DataFrame, column_name: str) -> List[tuple]: @@ -249,31 +156,24 @@ def _fit_column(self, data: pd.DataFrame, missing_pct = data[column_name].isnull().sum()/size n_bins = int(max((1 - missing_pct) * n_bins), 2) - cutpoints = [] - if self.strategy == "quantile": - cutpoints = list(data[column_name] - .quantile(np.linspace(0, 1, n_bins + 1), - interpolation='linear')) - elif self.strategy == "uniform": - cutpoints = list(np.linspace(col_min, col_max, n_bins + 1)) - - # Make sure the cutpoints are unique and sorted - cutpoints = sorted(list(set(cutpoints))) + bin_edges = self._compute_bin_edges(data, column_name, n_bins, + col_min, col_max) - if len(cutpoints) < 3: + if len(bin_edges) < 3: log.warning("Only 1 bin was found for predictor {} and will be " "ignored in computation".format(column_name)) return None - if len(cutpoints) < n_bins + 1: + if len(bin_edges) < n_bins + 1: log.warning("The number of actual bins for column {} is {} " "which is smaller than the requested number of bins " - "{}".format(column_name, len(cutpoints) - 1, n_bins)) + "{}".format(column_name, len(bin_edges) - 1, n_bins)) - return self._compute_bins_from_cutpoints(cutpoints) + return self._compute_bins_from_edges(bin_edges) - def fit(self, data: pd.DataFrame, column_names: list): - """Fits the estimator + def transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary Parameters ---------- @@ -281,20 +181,30 @@ def fit(self, data: pd.DataFrame, column_names: list): Data to be discretized column_names : list Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables """ + if len(self._bins_by_column) == 0: + msg = ("{} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") - if self.strategy not in self.valid_strategies: - raise ValueError("{}: valid options for 'strategy' are {}. " - "Got strategy={!r} instead." - .format(KBinsDiscretizer.__name__, - self.valid_strategies, self.strategy)) + raise NotFittedError(msg.format(self.__class__.__name__)) for column_name in column_names: + if column_name not in self._bins_by_column: + log.warning("Column {} is not in fitted output " + "and will be skipped".format(column_name)) + continue - bins = self._fit_column(data, column_name) + # can be None for a column with a constant value! + bins = self._bins_by_column[column_name] + if bins: + data = self._transform_column(data, column_name, bins) - # Add to bins_by_column for later use - self._bins_by_column[column_name] = bins + return data def _transform_column(self, data: pd.DataFrame, column_name: str, @@ -343,8 +253,8 @@ def _transform_column(self, data: pd.DataFrame, return data - def transform(self, data: pd.DataFrame, - column_names: list) -> pd.DataFrame: + def fit_transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: """Summary Parameters @@ -359,40 +269,183 @@ def transform(self, data: pd.DataFrame, pd.DataFrame data with additional discretized variables """ - if len(self._bins_by_column) == 0: - msg = ("{} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") + self.fit(data, column_names) + return self.transform(data, column_names) - raise NotFittedError(msg.format(self.__class__.__name__)) + def _compute_bin_edges(self, data: pd.DataFrame, column_name: str, + n_bins: int, col_min: float, + col_max: float) -> list: + """Compute the bin edges for a given column, a DataFrame and the number + of required bins - for column_name in column_names: - if column_name not in self._bins_by_column: - log.warning("Column {} is not in fitted output " - "and will be skipped".format(column_name)) - continue + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_name : str + name of the column to discretize + n_bins : int + Number of bins to produce. + col_min : float + min value of the variable + col_max : float + max value of the variable - # can be None for a column with a constant value! - bins = self._bins_by_column[column_name] - if bins: - data = self._transform_column(data, column_name, bins) + Returns + ------- + list + list of bin edges from which to compute the bins + """ - return data + bin_edges = [] + if self.strategy == "quantile": + bin_edges = list(data[column_name] + .quantile(np.linspace(0, 1, n_bins + 1), + interpolation='linear')) + elif self.strategy == "uniform": + bin_edges = list(np.linspace(col_min, col_max, n_bins + 1)) - def fit_transform(self, data: pd.DataFrame, - column_names: list) -> pd.DataFrame: - """Summary + # elif self.strategy == "kmeans": + + # if data[column_name].isnull().sum() > 0: + # raise ValueError("{}: kmeans strategy cannot handle NULL " + # "values in the data." + # .format(KBinsDiscretizer.__name__)) + + # # Deterministic initialization with uniform spacing + # uniform_edges = np.linspace(col_min, col_max, n_bins + 1) + # init = (uniform_edges[1:] + uniform_edges[:-1])[:, None] * 0.5 + + # # 1D k-means + # kmeans = KMeans(n_clusters=n_bins, init=init, n_init=1) + # centers = (kmeans.fit(data[column_name][:, None]) + # .cluster_centers_[:, 0]) + + # # Make sure to sort centers as they may be unsorted, + # # even with sorted init! + # centers.sort() + + # # compute bin_edges from centers + # bin_edges = (centers[1:] + centers[:-1]) * 0.5 + # bin_edges = np.r_[col_min, bin_edges, col_max] + + # Make sure the bin_edges are unique and sorted + return sorted(list(set(bin_edges))) + + def _compute_minimal_precision_of_bin_edges(self, bin_edges: list) -> int: + """Compute the minimal precision of a list of bin_edges so that we end + up with a strictly ascending sequence of numbers. + The starting_precision attribute will be used as the initial precision. + In case of a negative starting_precision, the bin edges will be rounded + to the nearest 10, 100, ... (e.g. 5.55 -> 10, 246 -> 200, ...) Parameters ---------- - data : pd.DataFrame - Data to be discretized - column_names : list - Columns of data to be discretized + bin_edges : list + The bin edges for binning a continuous variable Returns ------- - pd.DataFrame - data with additional discretized variables + int + minimal precision for the bin edges """ - self.fit(data, column_names) - return self.transform(data, column_names) + + precision = self.starting_precision + while True: + cont = False + for a, b in zip(bin_edges, bin_edges[1:]): + if a != b and round(a, precision) == round(b, precision): + # precision is not high enough, so increase + precision += 1 + cont = True # set cont to True to keep looping + break # break out of the for loop + if not cont: + # if minimal precision was found, + # return to break out of while loop + return precision + + def _compute_bins_from_edges(self, bin_edges: list) -> List[tuple]: + """Given a list of bin edges, compute the minimal precision for which + we can make meaningful bins and make those bins + + Parameters + ---------- + bin_edges : list + The bin edges for binning a continuous variable + + Returns + ------- + List[tuple] + A (sorted) list of bins as tuples + """ + # compute the minimal precision of the bin_edges + # this can be a negative number, which then + # rounds numbers to the nearest 10, 100, ... + precision = self._compute_minimal_precision_of_bin_edges(bin_edges) + + bins = [] + for a, b in zip(bin_edges, bin_edges[1:]): + fmt_a = round(a, precision) + fmt_b = round(b, precision) + + bins.append((fmt_a, fmt_b)) + + return bins + + @staticmethod + def _create_index(intervals: List[tuple], + closed: str="right") -> pd.IntervalIndex: + """Create an pd.IntervalIndex based on a list of tuples. + This is basically a wrapper around pd.IntervalIndex.from_tuples + However, the lower bound of the first entry in the list (the lower bin) + is replaced by -np.inf. Similarly, the upper bound of the last entry in + the list (upper bin) is replaced by np.inf. + + Parameters + ---------- + intervals : List[tuple] + a list of tuples describing the intervals + closed : str, optional + Whether the intervals should be closed on the left-side, + right-side, both or neither. + + Returns + ------- + pd.IntervalIndex + Description + """ + # deepcopy variable because we do not want to modify the content + # of intervals (which is still used outside of this function) + _intervals = deepcopy(intervals) + # Replace min and max with -np.inf and np.inf resp. so that these + # values are guaranteed to be included when transforming the data + _intervals[0] = (-np.inf, _intervals[0][1]) + _intervals[-1] = (_intervals[-1][0], np.inf) + + return pd.IntervalIndex.from_tuples(_intervals, closed) + + def _create_bin_labels(self, bins: List[tuple]) -> list: + """Given a list of bins, create a list of string containing the bins + as a string with a specific format (e.g. bin labels) + + Parameters + ---------- + bins : List[tuple] + list of tuple containing for each bin the upper and lower bound + + Returns + ------- + list + list of (formatted) bin labels + """ + bin_labels = [] + for interval in bins: + bin_labels.append(self.label_format.format(interval[0], + interval[1])) + + # Format first and last bin as < x and > y resp. + if self.change_endpoint_format: + bin_labels[0] = "< {}".format(bins[0][1]) + bin_labels[-1] = "> {}".format(bins[-1][0]) + + return bin_labels diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index 1b09ae6..8fd89c8 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -22,50 +22,50 @@ def test_validate_n_bins_valid_n_bins(self): KBinsDiscretizer()._validate_n_bins(n_bins=2) - # tests for _compute_minimal_precision_of_cutpoints + # tests for _compute_minimal_precision_of_bin_edges - def test_compute_minimal_precision_of_cutpoints_less_precision(self): + def test_compute_minimal_precision_of_bin_edges_less_precision(self): # If starting precision is bigger than actual precision, should return # starting precision cutpoints = [-10, 0, 1, 2] discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) assert res == 1 - def test_compute_minimal_precision_of_cutpoints_more_precision(self): + def test_compute_minimal_precision_of_bin_edges_more_precision(self): # If starting precision is smaller than actual precision, should return # actual precision cutpoints = [-10, 0, 1, 1.01] discretizer = KBinsDiscretizer() - res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) assert res == 2 - def test_compute_minimal_precision_of_cutpoints_equal_precision(self): + def test_compute_minimal_precision_of_bin_edges_equal_precision(self): # If starting precision is equal to actual precision, should return # starting precision cutpoints = [-10, 0, 1, 1.1] discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) assert res == 1 - def test_compute_minimal_precision_of_cutpoints_negative_start(self): + def test_compute_minimal_precision_of_bin_edges_negative_start(self): # Check if negative starting precision also leads to the correct result cutpoints = [-10, 0, 1, 2] discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) assert res == 0 - def test_compute_minimal_precision_of_cutpoints_round_up(self): + def test_compute_minimal_precision_of_bin_edges_round_up(self): # Check if negative starting precision leads to rounding up # bin edges to the nearest multiple of 10 cutpoints = [-10, 0, 10, 21] discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_cutpoints(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) assert res == -1 # tests for _compute_bins_from_cutpoints From d37d936955e9cafb496fd1d4456148191acd30ef Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 2 Oct 2019 13:19:21 +0200 Subject: [PATCH 05/98] Refactor TargetEncoder - Reorganised code so that public methods become first in code as they are then easier to trace in the source code. - Added additional safety checks to make the code more robuust. - Added unittests for the TargetEncoder - Added a missing __init__.py in tests module --- cobra/preprocessing/target_encoder.py | 96 +++++++------- tests/__init__.py | 0 tests/preprocessing/test_target_encoder.py | 141 +++++++++++++++++++++ 3 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/preprocessing/test_target_encoder.py diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index d4dad60..91e3d6f 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -1,11 +1,13 @@ -import numpy as np +import logging +log = logging.getLogger(__name__) + +#import numpy as np import pandas as pd from sklearn.base import BaseEstimator, TransformerMixin from sklearn.exceptions import NotFittedError -# Prototype of TargetEncoder class TargetEncoder(BaseEstimator, TransformerMixin): """Target encoding for categorical features. @@ -59,26 +61,6 @@ def __init__(self, columns: list=None, weight: float=0.0): # self.randomized = randomized # self.sigma = sigma - @staticmethod - def _get_categorical_columns(data: pd.DataFrame) -> list: - """Get the columns containing categorical data - (dtype "object" or "category") - - Parameters - ---------- - data : pd.DataFrame - Description - - Returns - ------- - list - List of column names containing categorical data - """ - object_columns = data.dtypes[data.dtypes == object].index - categorical_columns = data.dtypes[data.dtypes == "category"].index - - return list(set(object_columns).union(categorical_columns)) - def fit(self, X: pd.DataFrame, y: pd.Series): """Fit the TargetEncoder to X and y @@ -107,6 +89,11 @@ def fit(self, X: pd.DataFrame, y: pd.Series): global_mean = y.sum() / y.count() for column in self.columns: + if column not in X.columns: + log.warning("DataFrame has no column {}, so it will be " + "skipped in fitting" .format(column)) + continue + self._mapping[column] = self._fit_column(X[column], y, global_mean) def _fit_column(self, X: pd.Series, y: pd.Series, @@ -140,25 +127,6 @@ def _fit_column(self, X: pd.Series, y: pd.Series, return numerator/denominator - @staticmethod - def _clean_column_name(column_name: str) -> str: - """Clean column name string by removing "_bin" and adding "_enc" - - Parameters - ---------- - column_name : str - column name to be cleaned - - Returns - ------- - str - cleaned column name - """ - if "_bin" in column_name: - return column_name.replace("_bin", "") + "_enc" - else: - return column_name + "_enc" - def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: """Summary @@ -187,13 +155,12 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: raise NotFittedError(msg.format(self.__class__.__name__)) - transform_columns = list(X.columns) new_columns = [] for column in self.columns: - if column not in transform_columns: - # skip if this column was not in X - # print WARNING here!!! + if column not in X.columns: + log.warning("Column {} is not in fitted output " + "and will be skipped".format(column)) continue new_column = TargetEncoder._clean_column_name(column) @@ -203,3 +170,42 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: new_columns.append(new_column) return X[new_columns] + + @staticmethod + def _get_categorical_columns(data: pd.DataFrame) -> list: + """Get the columns containing categorical data + (dtype "object" or "category") + + Parameters + ---------- + data : pd.DataFrame + Description + + Returns + ------- + list + List of column names containing categorical data + """ + object_columns = data.dtypes[data.dtypes == object].index + categorical_columns = data.dtypes[data.dtypes == "category"].index + + return list(set(object_columns).union(set(categorical_columns))) + + @staticmethod + def _clean_column_name(column_name: str) -> str: + """Clean column name string by removing "_bin" and adding "_enc" + + Parameters + ---------- + column_name : str + column name to be cleaned + + Returns + ------- + str + cleaned column name + """ + if "_bin" in column_name: + return column_name.replace("_bin", "") + "_enc" + else: + return column_name + "_enc" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py new file mode 100644 index 0000000..cea1f3d --- /dev/null +++ b/tests/preprocessing/test_target_encoder.py @@ -0,0 +1,141 @@ +import pytest +import pandas as pd + +from cobra.preprocessing.target_encoder import TargetEncoder + + +class TestTargetEncoder: + + def test_target_encoder_constructor_value_error(self): + with pytest.raises(ValueError): + TargetEncoder(weight=-1) + + # Tests for _fit_column + def test_target_encoder_fit_column(self): + + df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', + 'neutral', 'negative', 'positive', + 'negative', 'neutral', 'neutral', + 'neutral'], + 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + + encoder = TargetEncoder(columns=["variable"]) + actual = encoder._fit_column(X=df.variable, y=df.target, + global_mean=0.0) + + expected = pd.Series(data=[0.333333, 0.50000, 0.666667], + index=["negative", "neutral", "positive"]) + expected.index.name = "variable" + + pd.testing.assert_series_equal(actual, expected, + check_less_precise=5) + + def test_target_encoder_fit_column_global_mean(self): + + df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', + 'neutral', 'negative', 'positive', + 'negative', 'neutral', 'neutral', + 'neutral'], + 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + + global_mean = df.target.sum() / df.target.count() # is 0.5 + + encoder = TargetEncoder(columns=["variable"], weight=1) + actual = encoder._fit_column(X=df.variable, y=df.target, + global_mean=global_mean) + + expected = pd.Series(data=[0.375, 0.500, 0.625], + index=["negative", "neutral", "positive"]) + expected.index.name = "variable" + + pd.testing.assert_series_equal(actual, expected, + check_less_precise=3) + + # Tests for fit method + def test_target_encoder_fit_value_error(self): + + X = pd.DataFrame({'variable': ['positive', 'positive', 'negative']}) + + target = pd.Series([1, 1, 0, 0]) + + encoder = TargetEncoder(columns=["variable"]) + with pytest.raises(ValueError): + encoder.fit(X, target) + + def test_target_encoder_fit(self): + + df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', + 'neutral', 'negative', 'positive', + 'negative', 'neutral', 'neutral', + 'neutral'], + 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + + encoder = TargetEncoder(columns=["variable"]) + encoder.fit(X=df, y=df.target) + + expected = pd.Series(data=[0.333333, 0.50000, 0.666667], + index=["negative", "neutral", "positive"]) + expected.index.name = "variable" + actual = encoder._mapping["variable"] + + pd.testing.assert_series_equal(actual, expected, + check_less_precise=5) + + # Tests for transform method + def test_target_encoder_transform(self): + + df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', + 'neutral', 'negative', 'positive', + 'negative', 'neutral', 'neutral', + 'neutral'], + 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + + expected = pd.DataFrame({'variable_enc': [0.666667, 0.666667, + 0.333333, 0.50000, + 0.333333, 0.666667, + 0.333333, 0.50000, + 0.50000, 0.50000]}) + + encoder = TargetEncoder(columns=["variable"]) + encoder.fit(X=df, y=df.target) + actual = encoder.transform(X=df, y=df.target) + + pd.testing.assert_frame_equal(actual, expected, + check_less_precise=5) + + # Tests for _get_categorical_columns + def test_target_encoder_get_categorical_columns(self): + + df = pd.DataFrame({"continuous": [1.0, 1.5, 2.0], + "categorical": ["negative", "neutral", "positive"], + "object": ["cats", "dogs", "goldfish"]}) + + expected = ["categorical", "object"] + + encoder = TargetEncoder() + actual = encoder._get_categorical_columns(df) + + # It is OK to take sets here because we also do that in the + # _get_categorical_columns function + assert set(actual) == set(expected) + + # Tests for _clean_column_name + def test_target_encoder_clean_column_name(self): + + column_name = "test_column" + expected = "test_column_enc" + + encoder = TargetEncoder() + actual = encoder._clean_column_name(column_name) + + assert actual == expected + + def test_target_encoder_clean_column_name_binned_column(self): + + column_name = "test_column_bin" + expected = "test_column_enc" + + encoder = TargetEncoder() + actual = encoder._clean_column_name(column_name) + + assert actual == expected From 7f1a2b893420046d94460870331544cbdebd646f Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 2 Oct 2019 14:23:39 +0200 Subject: [PATCH 06/98] Add unittests for all private methods of KBinsDiscretizer --- cobra/preprocessing/kbins_discretizer.py | 10 +- cobra/preprocessing/target_encoder.py | 4 +- tests/preprocessing/test_kbins_discretizer.py | 100 +++++++++++++++--- 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index bc2a7fc..7767319 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -146,7 +146,7 @@ def _fit_column(self, data: pd.DataFrame, col_min, col_max = data[column_name].min(), data[column_name].max() if col_min == col_max: - log.warning("Predictor {} is constant and " + log.warning("Predictor '{}' is constant and " "will be ignored in computation".format(column_name)) return None @@ -160,12 +160,12 @@ def _fit_column(self, data: pd.DataFrame, col_min, col_max) if len(bin_edges) < 3: - log.warning("Only 1 bin was found for predictor {} and will be " - "ignored in computation".format(column_name)) + log.warning("Only 1 bin was found for predictor '{}' so it will " + "be ignored in computation".format(column_name)) return None if len(bin_edges) < n_bins + 1: - log.warning("The number of actual bins for column {} is {} " + log.warning("The number of actual bins for predictor '{}' is {} " "which is smaller than the requested number of bins " "{}".format(column_name, len(bin_edges) - 1, n_bins)) @@ -195,7 +195,7 @@ def transform(self, data: pd.DataFrame, for column_name in column_names: if column_name not in self._bins_by_column: - log.warning("Column {} is not in fitted output " + log.warning("Column '{}' is not in fitted output " "and will be skipped".format(column_name)) continue diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index 91e3d6f..7ce5fbb 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -90,7 +90,7 @@ def fit(self, X: pd.DataFrame, y: pd.Series): for column in self.columns: if column not in X.columns: - log.warning("DataFrame has no column {}, so it will be " + log.warning("DataFrame has no column '{}', so it will be " "skipped in fitting" .format(column)) continue @@ -159,7 +159,7 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: for column in self.columns: if column not in X.columns: - log.warning("Column {} is not in fitted output " + log.warning("Column '{}' is not in fitted output " "and will be skipped".format(column)) continue diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index 8fd89c8..19d92ae 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -6,68 +6,134 @@ class TestKBinsDiscretizer: - # tests for _validate_n_bins function + ################# Test for public methods ################# - def test_validate_n_bins_exception_1(self): + ################# Test for private methods ################# + # Tests for _validate_n_bins function + def test_kbins_discretizer_validate_n_bins_exception_1(self): with pytest.raises(ValueError): KBinsDiscretizer()._validate_n_bins(n_bins=1) - def test_validate_n_bins_exception_no_integral(self): + def test_kbins_discretizer_validate_n_bins_exception_no_integral(self): with pytest.raises(ValueError): KBinsDiscretizer()._validate_n_bins(n_bins=10.5) - def test_validate_n_bins_valid_n_bins(self): + def test_kbins_discretizer_validate_n_bins_valid_n_bins(self): KBinsDiscretizer()._validate_n_bins(n_bins=2) - # tests for _compute_minimal_precision_of_bin_edges + # Test for _compute_bin_edges + def test_kbins_discretizer_compute_bin_edges_quantile_method(self): + data = pd.DataFrame({"variable": list(range(0, 11))}) # ints from 0-10 + + discretizer = KBinsDiscretizer() + actual = discretizer._compute_bin_edges(data, column_name="variable", + n_bins=4, + col_min=data.variable.min(), + col_max=data.variable.max()) + expected = [0.0, 2.5, 5, 7.5, 10.0] + + assert expected == actual + + def test_kbins_discretizer_compute_bin_edges_uniform_method(self): + + data = pd.DataFrame({"variable": list(range(0, 10))}) # ints from 0-9 + + discretizer = KBinsDiscretizer(strategy="uniform") + actual = discretizer._compute_bin_edges(data, column_name="variable", + n_bins=3, + col_min=data.variable.min(), + col_max=data.variable.max()) + expected = [0.0, 3.0, 6.0, 9.0] + + assert expected == actual + + # Tests for _compute_minimal_precision_of_bin_edges def test_compute_minimal_precision_of_bin_edges_less_precision(self): # If starting precision is bigger than actual precision, should return # starting precision - cutpoints = [-10, 0, 1, 2] + bin_edges = [-10, 0, 1, 2] discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) assert res == 1 def test_compute_minimal_precision_of_bin_edges_more_precision(self): # If starting precision is smaller than actual precision, should return # actual precision - cutpoints = [-10, 0, 1, 1.01] + bin_edges = [-10, 0, 1, 1.01] discretizer = KBinsDiscretizer() - res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) assert res == 2 def test_compute_minimal_precision_of_bin_edges_equal_precision(self): # If starting precision is equal to actual precision, should return # starting precision - cutpoints = [-10, 0, 1, 1.1] + bin_edges = [-10, 0, 1, 1.1] discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) assert res == 1 def test_compute_minimal_precision_of_bin_edges_negative_start(self): # Check if negative starting precision also leads to the correct result - cutpoints = [-10, 0, 1, 2] + bin_edges = [-10, 0, 1, 2] discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) assert res == 0 def test_compute_minimal_precision_of_bin_edges_round_up(self): # Check if negative starting precision leads to rounding up # bin edges to the nearest multiple of 10 - cutpoints = [-10, 0, 10, 21] + bin_edges = [-10, 0, 10, 21] discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_bin_edges(cutpoints) + res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) assert res == -1 - # tests for _compute_bins_from_cutpoints + # Tests for _compute_bins_from_edges + def test_kbins_discretizer_compute_bins_from_edges(self): + + bin_edges = [0, 1, 1.5, 2] + + discretizer = KBinsDiscretizer() + actual = discretizer._compute_bins_from_edges(bin_edges) + + expected = [(0, 1), (1, 1.5), (1.5, 2)] + assert actual == expected + + def test_kbins_discretizer_compute_bins_from_edges_round_up(self): + + bin_edges = [0, 1, 1.5, 3] + + discretizer = KBinsDiscretizer() + actual = discretizer._compute_bins_from_edges(bin_edges) + + expected = [(0, 1), (1, 2), (2, 3)] + assert actual == expected + + # Tests for _create_bin_labels + def test_kbins_discretizer_create_bin_labels(self): + + bins = [(0, 1), (1, 2), (2, 3)] + + discretizer = KBinsDiscretizer() + actual = discretizer._create_bin_labels(bins) + expected = ["0 - 1", "1 - 2", "2 - 3"] + + assert actual == expected + + def test_kbins_discretizer_create_bin_labels_different_endpoint_fmt(self): + + bins = [(0, 1), (1, 2), (2, 3)] + + discretizer = KBinsDiscretizer(change_endpoint_format=True) + actual = discretizer._create_bin_labels(bins) + expected = ["< 1", "1 - 2", "> 2"] - # tests for _create_bin_labels + assert actual == expected From 72adaa31f9e7760eae8fd1a2132ac232f2584532 Mon Sep 17 00:00:00 2001 From: JanBenisek Date: Thu, 3 Oct 2019 09:25:48 +0200 Subject: [PATCH 07/98] added cobra_env --- .gitignore | 3 ++ cobra_env.txt | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 cobra_env.txt diff --git a/.gitignore b/.gitignore index 5a98460..9ef222c 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ ENV/ # mypy .mypy_cache/ + +#VScode settins +.vscode diff --git a/cobra_env.txt b/cobra_env.txt new file mode 100644 index 0000000..a7e89cf --- /dev/null +++ b/cobra_env.txt @@ -0,0 +1,97 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +@EXPLICIT +https://conda.anaconda.org/anaconda/win-64/blas-1.0-mkl.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/ca-certificates-2019.8.28-0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/icc_rt-2019.0.0-h0cc432a_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/intel-openmp-2019.5-281.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/msys2-conda-epoch-20160418-1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pandoc-2.2.3.2-0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/vs2015_runtime-15.5.2-3.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/winpty-0.4.3-4.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/m2w64-gmp-6.1.0-2.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/mkl-2019.5-281.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/vc-14.1-h21ff451_3.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/icu-58.2-ha66f8fd_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jpeg-9b-hb83a4c4_2.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/libsodium-1.0.16-h9d3ae62_0.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/openssl-1.1.1-he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/sqlite-3.29.0-he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/zlib-1.2.11-h62dcd97_3.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/libpng-1.6.37-h2a8f88b_0.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/python-3.7.4-h5263a28_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/zeromq-4.3.1-h33f27b4_3.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/attrs-19.1.0-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/backcall-0.1.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/certifi-2019.9.11-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/colorama-0.4.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/decorator-4.4.0-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/defusedxml-0.6.0-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/entrypoints-0.3-py37_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/freetype-2.10.0-h563cfd7_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/ipython_genutils-0.2.0-py37_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.1.0-py37he980bc4_0.tar.bz2 +https://repo.anaconda.com/pkgs/msys2/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/markupsafe-1.1.1-py37he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/mistune-0.8.4-py37he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/numpy-base-1.16.4-py37hc3f5095_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pandocfilters-1.4.2-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/parso-0.5.1-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pickleshare-0.7.5-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/prometheus_client-0.7.1-py_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-2.4.2-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/pytz-2019.2-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pywin32-223-py37hfa6e2cd_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pyzmq-18.1.0-py37ha925a31_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/qt-5.9.7-vc14h73c81de_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/send2trash-1.5.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/sip-4.19.13-py37ha925a31_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/six-1.12.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/testpath-0.4.2-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/tornado-6.0.3-py37he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/wcwidth-0.1.7-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/webencodings-0.5.1-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/wincertstore-0.2-py37_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.10.0-py_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jedi-0.15.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/mkl_random-1.0.2-py37h343c172_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pyqt-5.9.2-py37ha878b3d_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pyrsistent-0.14.11-py37he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/python-dateutil-2.8.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pywinpty-0.5.5-py37_1000.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/setuptools-41.2.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/traitlets-4.3.2-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/bleach-3.1.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jinja2-2.10.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jsonschema-3.0.2-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/jupyter_core-4.5.0-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/pygments-2.4.2-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/terminado-0.8.2-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/wheel-0.33.6-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jupyter_client-5.3.3-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/nbformat-4.4.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pip-19.2.3-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/prompt_toolkit-2.0.9-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/ipython-7.8.0-py37h39e3cac_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/nbconvert-5.6.0-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/ipykernel-5.1.2-py37h39e3cac_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jupyter_console-6.0.0-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/notebook-6.0.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/qtconsole-4.5.5-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/widgetsnbextension-3.5.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/noarch/ipywidgets-7.5.1-py_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/jupyter-1.0.0-py37_7.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/mkl-service-2.0.2-py37he774522_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/mkl_fft-1.0.12-py37h14836fe_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/numpy-1.16.4-py37h19fb1c0_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.1.1-py37h2852a4a_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/pandas-0.25.1-py37ha925a31_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/scipy-1.3.1-py37h29ff71c_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.1.1-py37_1.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/patsy-0.5.1-py37_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/statsmodels-0.10.1-py37h8c2d366_0.tar.bz2 +https://conda.anaconda.org/anaconda/win-64/seaborn-0.9.0-py37_0.tar.bz2 From b71d469837ecacbdbb248c42702f1c39b58e8c90 Mon Sep 17 00:00:00 2001 From: JanBenisek Date: Thu, 3 Oct 2019 11:45:29 +0200 Subject: [PATCH 08/98] new data, better init, test file --- cobra/preprocessing/__init__.py | 5 + datasets/titanic_data.csv | 892 ++++++++++++++++++++++++++++++++ examples/testing.py | 19 + 3 files changed, 916 insertions(+) create mode 100644 datasets/titanic_data.csv create mode 100644 examples/testing.py diff --git a/cobra/preprocessing/__init__.py b/cobra/preprocessing/__init__.py index e69de29..f3884e0 100644 --- a/cobra/preprocessing/__init__.py +++ b/cobra/preprocessing/__init__.py @@ -0,0 +1,5 @@ +from .kbins_discretizer import KBinsDiscretizer +from .target_encoder import TargetEncoder + +__all__ = ['KBinsDiscretizer', + 'TargetEncoder'] \ No newline at end of file diff --git a/datasets/titanic_data.csv b/datasets/titanic_data.csv new file mode 100644 index 0000000..5cc466e --- /dev/null +++ b/datasets/titanic_data.csv @@ -0,0 +1,892 @@ +PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked +1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S +2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C +3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S +4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S +5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S +6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q +7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S +8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S +9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S +10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C +11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4,1,1,PP 9549,16.7,G6,S +12,1,1,"Bonnell, Miss. Elizabeth",female,58,0,0,113783,26.55,C103,S +13,0,3,"Saundercock, Mr. William Henry",male,20,0,0,A/5. 2151,8.05,,S +14,0,3,"Andersson, Mr. Anders Johan",male,39,1,5,347082,31.275,,S +15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14,0,0,350406,7.8542,,S +16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55,0,0,248706,16,,S +17,0,3,"Rice, Master. Eugene",male,2,4,1,382652,29.125,,Q +18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13,,S +19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31,1,0,345763,18,,S +20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C +21,0,2,"Fynney, Mr. Joseph J",male,35,0,0,239865,26,,S +22,1,2,"Beesley, Mr. Lawrence",male,34,0,0,248698,13,D56,S +23,1,3,"McGowan, Miss. Anna ""Annie""",female,15,0,0,330923,8.0292,,Q +24,1,1,"Sloper, Mr. William Thompson",male,28,0,0,113788,35.5,A6,S +25,0,3,"Palsson, Miss. Torborg Danira",female,8,3,1,349909,21.075,,S +26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38,1,5,347077,31.3875,,S +27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C +28,0,1,"Fortune, Mr. Charles Alexander",male,19,3,2,19950,263,C23 C25 C27,S +29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q +30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S +31,0,1,"Uruchurtu, Don. Manuel E",male,40,0,0,PC 17601,27.7208,,C +32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C +33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q +34,0,2,"Wheadon, Mr. Edward H",male,66,0,0,C.A. 24579,10.5,,S +35,0,1,"Meyer, Mr. Edgar Joseph",male,28,1,0,PC 17604,82.1708,,C +36,0,1,"Holverson, Mr. Alexander Oskar",male,42,1,0,113789,52,,S +37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C +38,0,3,"Cann, Mr. Ernest Charles",male,21,0,0,A./5. 2152,8.05,,S +39,0,3,"Vander Planke, Miss. Augusta Maria",female,18,2,0,345764,18,,S +40,1,3,"Nicola-Yarred, Miss. Jamila",female,14,1,0,2651,11.2417,,C +41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40,1,0,7546,9.475,,S +42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27,1,0,11668,21,,S +43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C +44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3,1,2,SC/Paris 2123,41.5792,,C +45,1,3,"Devaney, Miss. Margaret Delia",female,19,0,0,330958,7.8792,,Q +46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S +47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q +48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q +49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C +50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18,1,0,349237,17.8,,S +51,0,3,"Panula, Master. Juha Niilo",male,7,4,1,3101295,39.6875,,S +52,0,3,"Nosworthy, Mr. Richard Cater",male,21,0,0,A/4. 39886,7.8,,S +53,1,1,"Harper, Mrs. Henry Sleeper (Myna Haxtun)",female,49,1,0,PC 17572,76.7292,D33,C +54,1,2,"Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)",female,29,1,0,2926,26,,S +55,0,1,"Ostby, Mr. Engelhart Cornelius",male,65,0,1,113509,61.9792,B30,C +56,1,1,"Woolner, Mr. Hugh",male,,0,0,19947,35.5,C52,S +57,1,2,"Rugg, Miss. Emily",female,21,0,0,C.A. 31026,10.5,,S +58,0,3,"Novel, Mr. Mansouer",male,28.5,0,0,2697,7.2292,,C +59,1,2,"West, Miss. Constance Mirium",female,5,1,2,C.A. 34651,27.75,,S +60,0,3,"Goodwin, Master. William Frederick",male,11,5,2,CA 2144,46.9,,S +61,0,3,"Sirayanian, Mr. Orsen",male,22,0,0,2669,7.2292,,C +62,1,1,"Icard, Miss. Amelie",female,38,0,0,113572,80,B28, +63,0,1,"Harris, Mr. Henry Birkhardt",male,45,1,0,36973,83.475,C83,S +64,0,3,"Skoog, Master. Harald",male,4,3,2,347088,27.9,,S +65,0,1,"Stewart, Mr. Albert A",male,,0,0,PC 17605,27.7208,,C +66,1,3,"Moubarek, Master. Gerios",male,,1,1,2661,15.2458,,C +67,1,2,"Nye, Mrs. (Elizabeth Ramell)",female,29,0,0,C.A. 29395,10.5,F33,S +68,0,3,"Crease, Mr. Ernest James",male,19,0,0,S.P. 3464,8.1583,,S +69,1,3,"Andersson, Miss. Erna Alexandra",female,17,4,2,3101281,7.925,,S +70,0,3,"Kink, Mr. Vincenz",male,26,2,0,315151,8.6625,,S +71,0,2,"Jenkin, Mr. Stephen Curnow",male,32,0,0,C.A. 33111,10.5,,S +72,0,3,"Goodwin, Miss. Lillian Amy",female,16,5,2,CA 2144,46.9,,S +73,0,2,"Hood, Mr. Ambrose Jr",male,21,0,0,S.O.C. 14879,73.5,,S +74,0,3,"Chronopoulos, Mr. Apostolos",male,26,1,0,2680,14.4542,,C +75,1,3,"Bing, Mr. Lee",male,32,0,0,1601,56.4958,,S +76,0,3,"Moen, Mr. Sigurd Hansen",male,25,0,0,348123,7.65,F G73,S +77,0,3,"Staneff, Mr. Ivan",male,,0,0,349208,7.8958,,S +78,0,3,"Moutal, Mr. Rahamin Haim",male,,0,0,374746,8.05,,S +79,1,2,"Caldwell, Master. Alden Gates",male,0.83,0,2,248738,29,,S +80,1,3,"Dowdell, Miss. Elizabeth",female,30,0,0,364516,12.475,,S +81,0,3,"Waelens, Mr. Achille",male,22,0,0,345767,9,,S +82,1,3,"Sheerlinck, Mr. Jan Baptist",male,29,0,0,345779,9.5,,S +83,1,3,"McDermott, Miss. Brigdet Delia",female,,0,0,330932,7.7875,,Q +84,0,1,"Carrau, Mr. Francisco M",male,28,0,0,113059,47.1,,S +85,1,2,"Ilett, Miss. Bertha",female,17,0,0,SO/C 14885,10.5,,S +86,1,3,"Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)",female,33,3,0,3101278,15.85,,S +87,0,3,"Ford, Mr. William Neal",male,16,1,3,W./C. 6608,34.375,,S +88,0,3,"Slocovski, Mr. Selman Francis",male,,0,0,SOTON/OQ 392086,8.05,,S +89,1,1,"Fortune, Miss. Mabel Helen",female,23,3,2,19950,263,C23 C25 C27,S +90,0,3,"Celotti, Mr. Francesco",male,24,0,0,343275,8.05,,S +91,0,3,"Christmann, Mr. Emil",male,29,0,0,343276,8.05,,S +92,0,3,"Andreasson, Mr. Paul Edvin",male,20,0,0,347466,7.8542,,S +93,0,1,"Chaffee, Mr. Herbert Fuller",male,46,1,0,W.E.P. 5734,61.175,E31,S +94,0,3,"Dean, Mr. Bertram Frank",male,26,1,2,C.A. 2315,20.575,,S +95,0,3,"Coxon, Mr. Daniel",male,59,0,0,364500,7.25,,S +96,0,3,"Shorney, Mr. Charles Joseph",male,,0,0,374910,8.05,,S +97,0,1,"Goldschmidt, Mr. George B",male,71,0,0,PC 17754,34.6542,A5,C +98,1,1,"Greenfield, Mr. William Bertram",male,23,0,1,PC 17759,63.3583,D10 D12,C +99,1,2,"Doling, Mrs. John T (Ada Julia Bone)",female,34,0,1,231919,23,,S +100,0,2,"Kantor, Mr. Sinai",male,34,1,0,244367,26,,S +101,0,3,"Petranec, Miss. Matilda",female,28,0,0,349245,7.8958,,S +102,0,3,"Petroff, Mr. Pastcho (""Pentcho"")",male,,0,0,349215,7.8958,,S +103,0,1,"White, Mr. Richard Frasar",male,21,0,1,35281,77.2875,D26,S +104,0,3,"Johansson, Mr. Gustaf Joel",male,33,0,0,7540,8.6542,,S +105,0,3,"Gustafsson, Mr. Anders Vilhelm",male,37,2,0,3101276,7.925,,S +106,0,3,"Mionoff, Mr. Stoytcho",male,28,0,0,349207,7.8958,,S +107,1,3,"Salkjelsvik, Miss. Anna Kristine",female,21,0,0,343120,7.65,,S +108,1,3,"Moss, Mr. Albert Johan",male,,0,0,312991,7.775,,S +109,0,3,"Rekic, Mr. Tido",male,38,0,0,349249,7.8958,,S +110,1,3,"Moran, Miss. Bertha",female,,1,0,371110,24.15,,Q +111,0,1,"Porter, Mr. Walter Chamberlain",male,47,0,0,110465,52,C110,S +112,0,3,"Zabour, Miss. Hileni",female,14.5,1,0,2665,14.4542,,C +113,0,3,"Barton, Mr. David John",male,22,0,0,324669,8.05,,S +114,0,3,"Jussila, Miss. Katriina",female,20,1,0,4136,9.825,,S +115,0,3,"Attalah, Miss. Malake",female,17,0,0,2627,14.4583,,C +116,0,3,"Pekoniemi, Mr. Edvard",male,21,0,0,STON/O 2. 3101294,7.925,,S +117,0,3,"Connors, Mr. Patrick",male,70.5,0,0,370369,7.75,,Q +118,0,2,"Turpin, Mr. William John Robert",male,29,1,0,11668,21,,S +119,0,1,"Baxter, Mr. Quigg Edmond",male,24,0,1,PC 17558,247.5208,B58 B60,C +120,0,3,"Andersson, Miss. Ellis Anna Maria",female,2,4,2,347082,31.275,,S +121,0,2,"Hickman, Mr. Stanley George",male,21,2,0,S.O.C. 14879,73.5,,S +122,0,3,"Moore, Mr. Leonard Charles",male,,0,0,A4. 54510,8.05,,S +123,0,2,"Nasser, Mr. Nicholas",male,32.5,1,0,237736,30.0708,,C +124,1,2,"Webber, Miss. Susan",female,32.5,0,0,27267,13,E101,S +125,0,1,"White, Mr. Percival Wayland",male,54,0,1,35281,77.2875,D26,S +126,1,3,"Nicola-Yarred, Master. Elias",male,12,1,0,2651,11.2417,,C +127,0,3,"McMahon, Mr. Martin",male,,0,0,370372,7.75,,Q +128,1,3,"Madsen, Mr. Fridtjof Arne",male,24,0,0,C 17369,7.1417,,S +129,1,3,"Peter, Miss. Anna",female,,1,1,2668,22.3583,F E69,C +130,0,3,"Ekstrom, Mr. Johan",male,45,0,0,347061,6.975,,S +131,0,3,"Drazenoic, Mr. Jozef",male,33,0,0,349241,7.8958,,C +132,0,3,"Coelho, Mr. Domingos Fernandeo",male,20,0,0,SOTON/O.Q. 3101307,7.05,,S +133,0,3,"Robins, Mrs. Alexander A (Grace Charity Laury)",female,47,1,0,A/5. 3337,14.5,,S +134,1,2,"Weisz, Mrs. Leopold (Mathilde Francoise Pede)",female,29,1,0,228414,26,,S +135,0,2,"Sobey, Mr. Samuel James Hayden",male,25,0,0,C.A. 29178,13,,S +136,0,2,"Richard, Mr. Emile",male,23,0,0,SC/PARIS 2133,15.0458,,C +137,1,1,"Newsom, Miss. Helen Monypeny",female,19,0,2,11752,26.2833,D47,S +138,0,1,"Futrelle, Mr. Jacques Heath",male,37,1,0,113803,53.1,C123,S +139,0,3,"Osen, Mr. Olaf Elon",male,16,0,0,7534,9.2167,,S +140,0,1,"Giglio, Mr. Victor",male,24,0,0,PC 17593,79.2,B86,C +141,0,3,"Boulos, Mrs. Joseph (Sultana)",female,,0,2,2678,15.2458,,C +142,1,3,"Nysten, Miss. Anna Sofia",female,22,0,0,347081,7.75,,S +143,1,3,"Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)",female,24,1,0,STON/O2. 3101279,15.85,,S +144,0,3,"Burke, Mr. Jeremiah",male,19,0,0,365222,6.75,,Q +145,0,2,"Andrew, Mr. Edgardo Samuel",male,18,0,0,231945,11.5,,S +146,0,2,"Nicholls, Mr. Joseph Charles",male,19,1,1,C.A. 33112,36.75,,S +147,1,3,"Andersson, Mr. August Edvard (""Wennerstrom"")",male,27,0,0,350043,7.7958,,S +148,0,3,"Ford, Miss. Robina Maggie ""Ruby""",female,9,2,2,W./C. 6608,34.375,,S +149,0,2,"Navratil, Mr. Michel (""Louis M Hoffman"")",male,36.5,0,2,230080,26,F2,S +150,0,2,"Byles, Rev. Thomas Roussel Davids",male,42,0,0,244310,13,,S +151,0,2,"Bateman, Rev. Robert James",male,51,0,0,S.O.P. 1166,12.525,,S +152,1,1,"Pears, Mrs. Thomas (Edith Wearne)",female,22,1,0,113776,66.6,C2,S +153,0,3,"Meo, Mr. Alfonzo",male,55.5,0,0,A.5. 11206,8.05,,S +154,0,3,"van Billiard, Mr. Austin Blyler",male,40.5,0,2,A/5. 851,14.5,,S +155,0,3,"Olsen, Mr. Ole Martin",male,,0,0,Fa 265302,7.3125,,S +156,0,1,"Williams, Mr. Charles Duane",male,51,0,1,PC 17597,61.3792,,C +157,1,3,"Gilnagh, Miss. Katherine ""Katie""",female,16,0,0,35851,7.7333,,Q +158,0,3,"Corn, Mr. Harry",male,30,0,0,SOTON/OQ 392090,8.05,,S +159,0,3,"Smiljanic, Mr. Mile",male,,0,0,315037,8.6625,,S +160,0,3,"Sage, Master. Thomas Henry",male,,8,2,CA. 2343,69.55,,S +161,0,3,"Cribb, Mr. John Hatfield",male,44,0,1,371362,16.1,,S +162,1,2,"Watt, Mrs. James (Elizabeth ""Bessie"" Inglis Milne)",female,40,0,0,C.A. 33595,15.75,,S +163,0,3,"Bengtsson, Mr. John Viktor",male,26,0,0,347068,7.775,,S +164,0,3,"Calic, Mr. Jovo",male,17,0,0,315093,8.6625,,S +165,0,3,"Panula, Master. Eino Viljami",male,1,4,1,3101295,39.6875,,S +166,1,3,"Goldsmith, Master. Frank John William ""Frankie""",male,9,0,2,363291,20.525,,S +167,1,1,"Chibnall, Mrs. (Edith Martha Bowerman)",female,,0,1,113505,55,E33,S +168,0,3,"Skoog, Mrs. William (Anna Bernhardina Karlsson)",female,45,1,4,347088,27.9,,S +169,0,1,"Baumann, Mr. John D",male,,0,0,PC 17318,25.925,,S +170,0,3,"Ling, Mr. Lee",male,28,0,0,1601,56.4958,,S +171,0,1,"Van der hoef, Mr. Wyckoff",male,61,0,0,111240,33.5,B19,S +172,0,3,"Rice, Master. Arthur",male,4,4,1,382652,29.125,,Q +173,1,3,"Johnson, Miss. Eleanor Ileen",female,1,1,1,347742,11.1333,,S +174,0,3,"Sivola, Mr. Antti Wilhelm",male,21,0,0,STON/O 2. 3101280,7.925,,S +175,0,1,"Smith, Mr. James Clinch",male,56,0,0,17764,30.6958,A7,C +176,0,3,"Klasen, Mr. Klas Albin",male,18,1,1,350404,7.8542,,S +177,0,3,"Lefebre, Master. Henry Forbes",male,,3,1,4133,25.4667,,S +178,0,1,"Isham, Miss. Ann Elizabeth",female,50,0,0,PC 17595,28.7125,C49,C +179,0,2,"Hale, Mr. Reginald",male,30,0,0,250653,13,,S +180,0,3,"Leonard, Mr. Lionel",male,36,0,0,LINE,0,,S +181,0,3,"Sage, Miss. Constance Gladys",female,,8,2,CA. 2343,69.55,,S +182,0,2,"Pernot, Mr. Rene",male,,0,0,SC/PARIS 2131,15.05,,C +183,0,3,"Asplund, Master. Clarence Gustaf Hugo",male,9,4,2,347077,31.3875,,S +184,1,2,"Becker, Master. Richard F",male,1,2,1,230136,39,F4,S +185,1,3,"Kink-Heilmann, Miss. Luise Gretchen",female,4,0,2,315153,22.025,,S +186,0,1,"Rood, Mr. Hugh Roscoe",male,,0,0,113767,50,A32,S +187,1,3,"O'Brien, Mrs. Thomas (Johanna ""Hannah"" Godfrey)",female,,1,0,370365,15.5,,Q +188,1,1,"Romaine, Mr. Charles Hallace (""Mr C Rolmane"")",male,45,0,0,111428,26.55,,S +189,0,3,"Bourke, Mr. John",male,40,1,1,364849,15.5,,Q +190,0,3,"Turcin, Mr. Stjepan",male,36,0,0,349247,7.8958,,S +191,1,2,"Pinsky, Mrs. (Rosa)",female,32,0,0,234604,13,,S +192,0,2,"Carbines, Mr. William",male,19,0,0,28424,13,,S +193,1,3,"Andersen-Jensen, Miss. Carla Christine Nielsine",female,19,1,0,350046,7.8542,,S +194,1,2,"Navratil, Master. Michel M",male,3,1,1,230080,26,F2,S +195,1,1,"Brown, Mrs. James Joseph (Margaret Tobin)",female,44,0,0,PC 17610,27.7208,B4,C +196,1,1,"Lurette, Miss. Elise",female,58,0,0,PC 17569,146.5208,B80,C +197,0,3,"Mernagh, Mr. Robert",male,,0,0,368703,7.75,,Q +198,0,3,"Olsen, Mr. Karl Siegwart Andreas",male,42,0,1,4579,8.4042,,S +199,1,3,"Madigan, Miss. Margaret ""Maggie""",female,,0,0,370370,7.75,,Q +200,0,2,"Yrois, Miss. Henriette (""Mrs Harbeck"")",female,24,0,0,248747,13,,S +201,0,3,"Vande Walle, Mr. Nestor Cyriel",male,28,0,0,345770,9.5,,S +202,0,3,"Sage, Mr. Frederick",male,,8,2,CA. 2343,69.55,,S +203,0,3,"Johanson, Mr. Jakob Alfred",male,34,0,0,3101264,6.4958,,S +204,0,3,"Youseff, Mr. Gerious",male,45.5,0,0,2628,7.225,,C +205,1,3,"Cohen, Mr. Gurshon ""Gus""",male,18,0,0,A/5 3540,8.05,,S +206,0,3,"Strom, Miss. Telma Matilda",female,2,0,1,347054,10.4625,G6,S +207,0,3,"Backstrom, Mr. Karl Alfred",male,32,1,0,3101278,15.85,,S +208,1,3,"Albimona, Mr. Nassef Cassem",male,26,0,0,2699,18.7875,,C +209,1,3,"Carr, Miss. Helen ""Ellen""",female,16,0,0,367231,7.75,,Q +210,1,1,"Blank, Mr. Henry",male,40,0,0,112277,31,A31,C +211,0,3,"Ali, Mr. Ahmed",male,24,0,0,SOTON/O.Q. 3101311,7.05,,S +212,1,2,"Cameron, Miss. Clear Annie",female,35,0,0,F.C.C. 13528,21,,S +213,0,3,"Perkin, Mr. John Henry",male,22,0,0,A/5 21174,7.25,,S +214,0,2,"Givard, Mr. Hans Kristensen",male,30,0,0,250646,13,,S +215,0,3,"Kiernan, Mr. Philip",male,,1,0,367229,7.75,,Q +216,1,1,"Newell, Miss. Madeleine",female,31,1,0,35273,113.275,D36,C +217,1,3,"Honkanen, Miss. Eliina",female,27,0,0,STON/O2. 3101283,7.925,,S +218,0,2,"Jacobsohn, Mr. Sidney Samuel",male,42,1,0,243847,27,,S +219,1,1,"Bazzani, Miss. Albina",female,32,0,0,11813,76.2917,D15,C +220,0,2,"Harris, Mr. Walter",male,30,0,0,W/C 14208,10.5,,S +221,1,3,"Sunderland, Mr. Victor Francis",male,16,0,0,SOTON/OQ 392089,8.05,,S +222,0,2,"Bracken, Mr. James H",male,27,0,0,220367,13,,S +223,0,3,"Green, Mr. George Henry",male,51,0,0,21440,8.05,,S +224,0,3,"Nenkoff, Mr. Christo",male,,0,0,349234,7.8958,,S +225,1,1,"Hoyt, Mr. Frederick Maxfield",male,38,1,0,19943,90,C93,S +226,0,3,"Berglund, Mr. Karl Ivar Sven",male,22,0,0,PP 4348,9.35,,S +227,1,2,"Mellors, Mr. William John",male,19,0,0,SW/PP 751,10.5,,S +228,0,3,"Lovell, Mr. John Hall (""Henry"")",male,20.5,0,0,A/5 21173,7.25,,S +229,0,2,"Fahlstrom, Mr. Arne Jonas",male,18,0,0,236171,13,,S +230,0,3,"Lefebre, Miss. Mathilde",female,,3,1,4133,25.4667,,S +231,1,1,"Harris, Mrs. Henry Birkhardt (Irene Wallach)",female,35,1,0,36973,83.475,C83,S +232,0,3,"Larsson, Mr. Bengt Edvin",male,29,0,0,347067,7.775,,S +233,0,2,"Sjostedt, Mr. Ernst Adolf",male,59,0,0,237442,13.5,,S +234,1,3,"Asplund, Miss. Lillian Gertrud",female,5,4,2,347077,31.3875,,S +235,0,2,"Leyson, Mr. Robert William Norman",male,24,0,0,C.A. 29566,10.5,,S +236,0,3,"Harknett, Miss. Alice Phoebe",female,,0,0,W./C. 6609,7.55,,S +237,0,2,"Hold, Mr. Stephen",male,44,1,0,26707,26,,S +238,1,2,"Collyer, Miss. Marjorie ""Lottie""",female,8,0,2,C.A. 31921,26.25,,S +239,0,2,"Pengelly, Mr. Frederick William",male,19,0,0,28665,10.5,,S +240,0,2,"Hunt, Mr. George Henry",male,33,0,0,SCO/W 1585,12.275,,S +241,0,3,"Zabour, Miss. Thamine",female,,1,0,2665,14.4542,,C +242,1,3,"Murphy, Miss. Katherine ""Kate""",female,,1,0,367230,15.5,,Q +243,0,2,"Coleridge, Mr. Reginald Charles",male,29,0,0,W./C. 14263,10.5,,S +244,0,3,"Maenpaa, Mr. Matti Alexanteri",male,22,0,0,STON/O 2. 3101275,7.125,,S +245,0,3,"Attalah, Mr. Sleiman",male,30,0,0,2694,7.225,,C +246,0,1,"Minahan, Dr. William Edward",male,44,2,0,19928,90,C78,Q +247,0,3,"Lindahl, Miss. Agda Thorilda Viktoria",female,25,0,0,347071,7.775,,S +248,1,2,"Hamalainen, Mrs. William (Anna)",female,24,0,2,250649,14.5,,S +249,1,1,"Beckwith, Mr. Richard Leonard",male,37,1,1,11751,52.5542,D35,S +250,0,2,"Carter, Rev. Ernest Courtenay",male,54,1,0,244252,26,,S +251,0,3,"Reed, Mr. James George",male,,0,0,362316,7.25,,S +252,0,3,"Strom, Mrs. Wilhelm (Elna Matilda Persson)",female,29,1,1,347054,10.4625,G6,S +253,0,1,"Stead, Mr. William Thomas",male,62,0,0,113514,26.55,C87,S +254,0,3,"Lobb, Mr. William Arthur",male,30,1,0,A/5. 3336,16.1,,S +255,0,3,"Rosblom, Mrs. Viktor (Helena Wilhelmina)",female,41,0,2,370129,20.2125,,S +256,1,3,"Touma, Mrs. Darwis (Hanne Youssef Razi)",female,29,0,2,2650,15.2458,,C +257,1,1,"Thorne, Mrs. Gertrude Maybelle",female,,0,0,PC 17585,79.2,,C +258,1,1,"Cherry, Miss. Gladys",female,30,0,0,110152,86.5,B77,S +259,1,1,"Ward, Miss. Anna",female,35,0,0,PC 17755,512.3292,,C +260,1,2,"Parrish, Mrs. (Lutie Davis)",female,50,0,1,230433,26,,S +261,0,3,"Smith, Mr. Thomas",male,,0,0,384461,7.75,,Q +262,1,3,"Asplund, Master. Edvin Rojj Felix",male,3,4,2,347077,31.3875,,S +263,0,1,"Taussig, Mr. Emil",male,52,1,1,110413,79.65,E67,S +264,0,1,"Harrison, Mr. William",male,40,0,0,112059,0,B94,S +265,0,3,"Henry, Miss. Delia",female,,0,0,382649,7.75,,Q +266,0,2,"Reeves, Mr. David",male,36,0,0,C.A. 17248,10.5,,S +267,0,3,"Panula, Mr. Ernesti Arvid",male,16,4,1,3101295,39.6875,,S +268,1,3,"Persson, Mr. Ernst Ulrik",male,25,1,0,347083,7.775,,S +269,1,1,"Graham, Mrs. William Thompson (Edith Junkins)",female,58,0,1,PC 17582,153.4625,C125,S +270,1,1,"Bissette, Miss. Amelia",female,35,0,0,PC 17760,135.6333,C99,S +271,0,1,"Cairns, Mr. Alexander",male,,0,0,113798,31,,S +272,1,3,"Tornquist, Mr. William Henry",male,25,0,0,LINE,0,,S +273,1,2,"Mellinger, Mrs. (Elizabeth Anne Maidment)",female,41,0,1,250644,19.5,,S +274,0,1,"Natsch, Mr. Charles H",male,37,0,1,PC 17596,29.7,C118,C +275,1,3,"Healy, Miss. Hanora ""Nora""",female,,0,0,370375,7.75,,Q +276,1,1,"Andrews, Miss. Kornelia Theodosia",female,63,1,0,13502,77.9583,D7,S +277,0,3,"Lindblom, Miss. Augusta Charlotta",female,45,0,0,347073,7.75,,S +278,0,2,"Parkes, Mr. Francis ""Frank""",male,,0,0,239853,0,,S +279,0,3,"Rice, Master. Eric",male,7,4,1,382652,29.125,,Q +280,1,3,"Abbott, Mrs. Stanton (Rosa Hunt)",female,35,1,1,C.A. 2673,20.25,,S +281,0,3,"Duane, Mr. Frank",male,65,0,0,336439,7.75,,Q +282,0,3,"Olsson, Mr. Nils Johan Goransson",male,28,0,0,347464,7.8542,,S +283,0,3,"de Pelsmaeker, Mr. Alfons",male,16,0,0,345778,9.5,,S +284,1,3,"Dorking, Mr. Edward Arthur",male,19,0,0,A/5. 10482,8.05,,S +285,0,1,"Smith, Mr. Richard William",male,,0,0,113056,26,A19,S +286,0,3,"Stankovic, Mr. Ivan",male,33,0,0,349239,8.6625,,C +287,1,3,"de Mulder, Mr. Theodore",male,30,0,0,345774,9.5,,S +288,0,3,"Naidenoff, Mr. Penko",male,22,0,0,349206,7.8958,,S +289,1,2,"Hosono, Mr. Masabumi",male,42,0,0,237798,13,,S +290,1,3,"Connolly, Miss. Kate",female,22,0,0,370373,7.75,,Q +291,1,1,"Barber, Miss. Ellen ""Nellie""",female,26,0,0,19877,78.85,,S +292,1,1,"Bishop, Mrs. Dickinson H (Helen Walton)",female,19,1,0,11967,91.0792,B49,C +293,0,2,"Levy, Mr. Rene Jacques",male,36,0,0,SC/Paris 2163,12.875,D,C +294,0,3,"Haas, Miss. Aloisia",female,24,0,0,349236,8.85,,S +295,0,3,"Mineff, Mr. Ivan",male,24,0,0,349233,7.8958,,S +296,0,1,"Lewy, Mr. Ervin G",male,,0,0,PC 17612,27.7208,,C +297,0,3,"Hanna, Mr. Mansour",male,23.5,0,0,2693,7.2292,,C +298,0,1,"Allison, Miss. Helen Loraine",female,2,1,2,113781,151.55,C22 C26,S +299,1,1,"Saalfeld, Mr. Adolphe",male,,0,0,19988,30.5,C106,S +300,1,1,"Baxter, Mrs. James (Helene DeLaudeniere Chaput)",female,50,0,1,PC 17558,247.5208,B58 B60,C +301,1,3,"Kelly, Miss. Anna Katherine ""Annie Kate""",female,,0,0,9234,7.75,,Q +302,1,3,"McCoy, Mr. Bernard",male,,2,0,367226,23.25,,Q +303,0,3,"Johnson, Mr. William Cahoone Jr",male,19,0,0,LINE,0,,S +304,1,2,"Keane, Miss. Nora A",female,,0,0,226593,12.35,E101,Q +305,0,3,"Williams, Mr. Howard Hugh ""Harry""",male,,0,0,A/5 2466,8.05,,S +306,1,1,"Allison, Master. Hudson Trevor",male,0.92,1,2,113781,151.55,C22 C26,S +307,1,1,"Fleming, Miss. Margaret",female,,0,0,17421,110.8833,,C +308,1,1,"Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)",female,17,1,0,PC 17758,108.9,C65,C +309,0,2,"Abelson, Mr. Samuel",male,30,1,0,P/PP 3381,24,,C +310,1,1,"Francatelli, Miss. Laura Mabel",female,30,0,0,PC 17485,56.9292,E36,C +311,1,1,"Hays, Miss. Margaret Bechstein",female,24,0,0,11767,83.1583,C54,C +312,1,1,"Ryerson, Miss. Emily Borie",female,18,2,2,PC 17608,262.375,B57 B59 B63 B66,C +313,0,2,"Lahtinen, Mrs. William (Anna Sylfven)",female,26,1,1,250651,26,,S +314,0,3,"Hendekovic, Mr. Ignjac",male,28,0,0,349243,7.8958,,S +315,0,2,"Hart, Mr. Benjamin",male,43,1,1,F.C.C. 13529,26.25,,S +316,1,3,"Nilsson, Miss. Helmina Josefina",female,26,0,0,347470,7.8542,,S +317,1,2,"Kantor, Mrs. Sinai (Miriam Sternin)",female,24,1,0,244367,26,,S +318,0,2,"Moraweck, Dr. Ernest",male,54,0,0,29011,14,,S +319,1,1,"Wick, Miss. Mary Natalie",female,31,0,2,36928,164.8667,C7,S +320,1,1,"Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)",female,40,1,1,16966,134.5,E34,C +321,0,3,"Dennis, Mr. Samuel",male,22,0,0,A/5 21172,7.25,,S +322,0,3,"Danoff, Mr. Yoto",male,27,0,0,349219,7.8958,,S +323,1,2,"Slayter, Miss. Hilda Mary",female,30,0,0,234818,12.35,,Q +324,1,2,"Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)",female,22,1,1,248738,29,,S +325,0,3,"Sage, Mr. George John Jr",male,,8,2,CA. 2343,69.55,,S +326,1,1,"Young, Miss. Marie Grice",female,36,0,0,PC 17760,135.6333,C32,C +327,0,3,"Nysveen, Mr. Johan Hansen",male,61,0,0,345364,6.2375,,S +328,1,2,"Ball, Mrs. (Ada E Hall)",female,36,0,0,28551,13,D,S +329,1,3,"Goldsmith, Mrs. Frank John (Emily Alice Brown)",female,31,1,1,363291,20.525,,S +330,1,1,"Hippach, Miss. Jean Gertrude",female,16,0,1,111361,57.9792,B18,C +331,1,3,"McCoy, Miss. Agnes",female,,2,0,367226,23.25,,Q +332,0,1,"Partner, Mr. Austen",male,45.5,0,0,113043,28.5,C124,S +333,0,1,"Graham, Mr. George Edward",male,38,0,1,PC 17582,153.4625,C91,S +334,0,3,"Vander Planke, Mr. Leo Edmondus",male,16,2,0,345764,18,,S +335,1,1,"Frauenthal, Mrs. Henry William (Clara Heinsheimer)",female,,1,0,PC 17611,133.65,,S +336,0,3,"Denkoff, Mr. Mitto",male,,0,0,349225,7.8958,,S +337,0,1,"Pears, Mr. Thomas Clinton",male,29,1,0,113776,66.6,C2,S +338,1,1,"Burns, Miss. Elizabeth Margaret",female,41,0,0,16966,134.5,E40,C +339,1,3,"Dahl, Mr. Karl Edwart",male,45,0,0,7598,8.05,,S +340,0,1,"Blackwell, Mr. Stephen Weart",male,45,0,0,113784,35.5,T,S +341,1,2,"Navratil, Master. Edmond Roger",male,2,1,1,230080,26,F2,S +342,1,1,"Fortune, Miss. Alice Elizabeth",female,24,3,2,19950,263,C23 C25 C27,S +343,0,2,"Collander, Mr. Erik Gustaf",male,28,0,0,248740,13,,S +344,0,2,"Sedgwick, Mr. Charles Frederick Waddington",male,25,0,0,244361,13,,S +345,0,2,"Fox, Mr. Stanley Hubert",male,36,0,0,229236,13,,S +346,1,2,"Brown, Miss. Amelia ""Mildred""",female,24,0,0,248733,13,F33,S +347,1,2,"Smith, Miss. Marion Elsie",female,40,0,0,31418,13,,S +348,1,3,"Davison, Mrs. Thomas Henry (Mary E Finck)",female,,1,0,386525,16.1,,S +349,1,3,"Coutts, Master. William Loch ""William""",male,3,1,1,C.A. 37671,15.9,,S +350,0,3,"Dimic, Mr. Jovan",male,42,0,0,315088,8.6625,,S +351,0,3,"Odahl, Mr. Nils Martin",male,23,0,0,7267,9.225,,S +352,0,1,"Williams-Lambert, Mr. Fletcher Fellows",male,,0,0,113510,35,C128,S +353,0,3,"Elias, Mr. Tannous",male,15,1,1,2695,7.2292,,C +354,0,3,"Arnold-Franchi, Mr. Josef",male,25,1,0,349237,17.8,,S +355,0,3,"Yousif, Mr. Wazli",male,,0,0,2647,7.225,,C +356,0,3,"Vanden Steen, Mr. Leo Peter",male,28,0,0,345783,9.5,,S +357,1,1,"Bowerman, Miss. Elsie Edith",female,22,0,1,113505,55,E33,S +358,0,2,"Funk, Miss. Annie Clemmer",female,38,0,0,237671,13,,S +359,1,3,"McGovern, Miss. Mary",female,,0,0,330931,7.8792,,Q +360,1,3,"Mockler, Miss. Helen Mary ""Ellie""",female,,0,0,330980,7.8792,,Q +361,0,3,"Skoog, Mr. Wilhelm",male,40,1,4,347088,27.9,,S +362,0,2,"del Carlo, Mr. Sebastiano",male,29,1,0,SC/PARIS 2167,27.7208,,C +363,0,3,"Barbara, Mrs. (Catherine David)",female,45,0,1,2691,14.4542,,C +364,0,3,"Asim, Mr. Adola",male,35,0,0,SOTON/O.Q. 3101310,7.05,,S +365,0,3,"O'Brien, Mr. Thomas",male,,1,0,370365,15.5,,Q +366,0,3,"Adahl, Mr. Mauritz Nils Martin",male,30,0,0,C 7076,7.25,,S +367,1,1,"Warren, Mrs. Frank Manley (Anna Sophia Atkinson)",female,60,1,0,110813,75.25,D37,C +368,1,3,"Moussa, Mrs. (Mantoura Boulos)",female,,0,0,2626,7.2292,,C +369,1,3,"Jermyn, Miss. Annie",female,,0,0,14313,7.75,,Q +370,1,1,"Aubart, Mme. Leontine Pauline",female,24,0,0,PC 17477,69.3,B35,C +371,1,1,"Harder, Mr. George Achilles",male,25,1,0,11765,55.4417,E50,C +372,0,3,"Wiklund, Mr. Jakob Alfred",male,18,1,0,3101267,6.4958,,S +373,0,3,"Beavan, Mr. William Thomas",male,19,0,0,323951,8.05,,S +374,0,1,"Ringhini, Mr. Sante",male,22,0,0,PC 17760,135.6333,,C +375,0,3,"Palsson, Miss. Stina Viola",female,3,3,1,349909,21.075,,S +376,1,1,"Meyer, Mrs. Edgar Joseph (Leila Saks)",female,,1,0,PC 17604,82.1708,,C +377,1,3,"Landergren, Miss. Aurora Adelia",female,22,0,0,C 7077,7.25,,S +378,0,1,"Widener, Mr. Harry Elkins",male,27,0,2,113503,211.5,C82,C +379,0,3,"Betros, Mr. Tannous",male,20,0,0,2648,4.0125,,C +380,0,3,"Gustafsson, Mr. Karl Gideon",male,19,0,0,347069,7.775,,S +381,1,1,"Bidois, Miss. Rosalie",female,42,0,0,PC 17757,227.525,,C +382,1,3,"Nakid, Miss. Maria (""Mary"")",female,1,0,2,2653,15.7417,,C +383,0,3,"Tikkanen, Mr. Juho",male,32,0,0,STON/O 2. 3101293,7.925,,S +384,1,1,"Holverson, Mrs. Alexander Oskar (Mary Aline Towner)",female,35,1,0,113789,52,,S +385,0,3,"Plotcharsky, Mr. Vasil",male,,0,0,349227,7.8958,,S +386,0,2,"Davies, Mr. Charles Henry",male,18,0,0,S.O.C. 14879,73.5,,S +387,0,3,"Goodwin, Master. Sidney Leonard",male,1,5,2,CA 2144,46.9,,S +388,1,2,"Buss, Miss. Kate",female,36,0,0,27849,13,,S +389,0,3,"Sadlier, Mr. Matthew",male,,0,0,367655,7.7292,,Q +390,1,2,"Lehmann, Miss. Bertha",female,17,0,0,SC 1748,12,,C +391,1,1,"Carter, Mr. William Ernest",male,36,1,2,113760,120,B96 B98,S +392,1,3,"Jansson, Mr. Carl Olof",male,21,0,0,350034,7.7958,,S +393,0,3,"Gustafsson, Mr. Johan Birger",male,28,2,0,3101277,7.925,,S +394,1,1,"Newell, Miss. Marjorie",female,23,1,0,35273,113.275,D36,C +395,1,3,"Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)",female,24,0,2,PP 9549,16.7,G6,S +396,0,3,"Johansson, Mr. Erik",male,22,0,0,350052,7.7958,,S +397,0,3,"Olsson, Miss. Elina",female,31,0,0,350407,7.8542,,S +398,0,2,"McKane, Mr. Peter David",male,46,0,0,28403,26,,S +399,0,2,"Pain, Dr. Alfred",male,23,0,0,244278,10.5,,S +400,1,2,"Trout, Mrs. William H (Jessie L)",female,28,0,0,240929,12.65,,S +401,1,3,"Niskanen, Mr. Juha",male,39,0,0,STON/O 2. 3101289,7.925,,S +402,0,3,"Adams, Mr. John",male,26,0,0,341826,8.05,,S +403,0,3,"Jussila, Miss. Mari Aina",female,21,1,0,4137,9.825,,S +404,0,3,"Hakkarainen, Mr. Pekka Pietari",male,28,1,0,STON/O2. 3101279,15.85,,S +405,0,3,"Oreskovic, Miss. Marija",female,20,0,0,315096,8.6625,,S +406,0,2,"Gale, Mr. Shadrach",male,34,1,0,28664,21,,S +407,0,3,"Widegren, Mr. Carl/Charles Peter",male,51,0,0,347064,7.75,,S +408,1,2,"Richards, Master. William Rowe",male,3,1,1,29106,18.75,,S +409,0,3,"Birkeland, Mr. Hans Martin Monsen",male,21,0,0,312992,7.775,,S +410,0,3,"Lefebre, Miss. Ida",female,,3,1,4133,25.4667,,S +411,0,3,"Sdycoff, Mr. Todor",male,,0,0,349222,7.8958,,S +412,0,3,"Hart, Mr. Henry",male,,0,0,394140,6.8583,,Q +413,1,1,"Minahan, Miss. Daisy E",female,33,1,0,19928,90,C78,Q +414,0,2,"Cunningham, Mr. Alfred Fleming",male,,0,0,239853,0,,S +415,1,3,"Sundman, Mr. Johan Julian",male,44,0,0,STON/O 2. 3101269,7.925,,S +416,0,3,"Meek, Mrs. Thomas (Annie Louise Rowley)",female,,0,0,343095,8.05,,S +417,1,2,"Drew, Mrs. James Vivian (Lulu Thorne Christian)",female,34,1,1,28220,32.5,,S +418,1,2,"Silven, Miss. Lyyli Karoliina",female,18,0,2,250652,13,,S +419,0,2,"Matthews, Mr. William John",male,30,0,0,28228,13,,S +420,0,3,"Van Impe, Miss. Catharina",female,10,0,2,345773,24.15,,S +421,0,3,"Gheorgheff, Mr. Stanio",male,,0,0,349254,7.8958,,C +422,0,3,"Charters, Mr. David",male,21,0,0,A/5. 13032,7.7333,,Q +423,0,3,"Zimmerman, Mr. Leo",male,29,0,0,315082,7.875,,S +424,0,3,"Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)",female,28,1,1,347080,14.4,,S +425,0,3,"Rosblom, Mr. Viktor Richard",male,18,1,1,370129,20.2125,,S +426,0,3,"Wiseman, Mr. Phillippe",male,,0,0,A/4. 34244,7.25,,S +427,1,2,"Clarke, Mrs. Charles V (Ada Maria Winfield)",female,28,1,0,2003,26,,S +428,1,2,"Phillips, Miss. Kate Florence (""Mrs Kate Louise Phillips Marshall"")",female,19,0,0,250655,26,,S +429,0,3,"Flynn, Mr. James",male,,0,0,364851,7.75,,Q +430,1,3,"Pickard, Mr. Berk (Berk Trembisky)",male,32,0,0,SOTON/O.Q. 392078,8.05,E10,S +431,1,1,"Bjornstrom-Steffansson, Mr. Mauritz Hakan",male,28,0,0,110564,26.55,C52,S +432,1,3,"Thorneycroft, Mrs. Percival (Florence Kate White)",female,,1,0,376564,16.1,,S +433,1,2,"Louch, Mrs. Charles Alexander (Alice Adelaide Slow)",female,42,1,0,SC/AH 3085,26,,S +434,0,3,"Kallio, Mr. Nikolai Erland",male,17,0,0,STON/O 2. 3101274,7.125,,S +435,0,1,"Silvey, Mr. William Baird",male,50,1,0,13507,55.9,E44,S +436,1,1,"Carter, Miss. Lucile Polk",female,14,1,2,113760,120,B96 B98,S +437,0,3,"Ford, Miss. Doolina Margaret ""Daisy""",female,21,2,2,W./C. 6608,34.375,,S +438,1,2,"Richards, Mrs. Sidney (Emily Hocking)",female,24,2,3,29106,18.75,,S +439,0,1,"Fortune, Mr. Mark",male,64,1,4,19950,263,C23 C25 C27,S +440,0,2,"Kvillner, Mr. Johan Henrik Johannesson",male,31,0,0,C.A. 18723,10.5,,S +441,1,2,"Hart, Mrs. Benjamin (Esther Ada Bloomfield)",female,45,1,1,F.C.C. 13529,26.25,,S +442,0,3,"Hampe, Mr. Leon",male,20,0,0,345769,9.5,,S +443,0,3,"Petterson, Mr. Johan Emil",male,25,1,0,347076,7.775,,S +444,1,2,"Reynaldo, Ms. Encarnacion",female,28,0,0,230434,13,,S +445,1,3,"Johannesen-Bratthammer, Mr. Bernt",male,,0,0,65306,8.1125,,S +446,1,1,"Dodge, Master. Washington",male,4,0,2,33638,81.8583,A34,S +447,1,2,"Mellinger, Miss. Madeleine Violet",female,13,0,1,250644,19.5,,S +448,1,1,"Seward, Mr. Frederic Kimber",male,34,0,0,113794,26.55,,S +449,1,3,"Baclini, Miss. Marie Catherine",female,5,2,1,2666,19.2583,,C +450,1,1,"Peuchen, Major. Arthur Godfrey",male,52,0,0,113786,30.5,C104,S +451,0,2,"West, Mr. Edwy Arthur",male,36,1,2,C.A. 34651,27.75,,S +452,0,3,"Hagland, Mr. Ingvald Olai Olsen",male,,1,0,65303,19.9667,,S +453,0,1,"Foreman, Mr. Benjamin Laventall",male,30,0,0,113051,27.75,C111,C +454,1,1,"Goldenberg, Mr. Samuel L",male,49,1,0,17453,89.1042,C92,C +455,0,3,"Peduzzi, Mr. Joseph",male,,0,0,A/5 2817,8.05,,S +456,1,3,"Jalsevac, Mr. Ivan",male,29,0,0,349240,7.8958,,C +457,0,1,"Millet, Mr. Francis Davis",male,65,0,0,13509,26.55,E38,S +458,1,1,"Kenyon, Mrs. Frederick R (Marion)",female,,1,0,17464,51.8625,D21,S +459,1,2,"Toomey, Miss. Ellen",female,50,0,0,F.C.C. 13531,10.5,,S +460,0,3,"O'Connor, Mr. Maurice",male,,0,0,371060,7.75,,Q +461,1,1,"Anderson, Mr. Harry",male,48,0,0,19952,26.55,E12,S +462,0,3,"Morley, Mr. William",male,34,0,0,364506,8.05,,S +463,0,1,"Gee, Mr. Arthur H",male,47,0,0,111320,38.5,E63,S +464,0,2,"Milling, Mr. Jacob Christian",male,48,0,0,234360,13,,S +465,0,3,"Maisner, Mr. Simon",male,,0,0,A/S 2816,8.05,,S +466,0,3,"Goncalves, Mr. Manuel Estanslas",male,38,0,0,SOTON/O.Q. 3101306,7.05,,S +467,0,2,"Campbell, Mr. William",male,,0,0,239853,0,,S +468,0,1,"Smart, Mr. John Montgomery",male,56,0,0,113792,26.55,,S +469,0,3,"Scanlan, Mr. James",male,,0,0,36209,7.725,,Q +470,1,3,"Baclini, Miss. Helene Barbara",female,0.75,2,1,2666,19.2583,,C +471,0,3,"Keefe, Mr. Arthur",male,,0,0,323592,7.25,,S +472,0,3,"Cacic, Mr. Luka",male,38,0,0,315089,8.6625,,S +473,1,2,"West, Mrs. Edwy Arthur (Ada Mary Worth)",female,33,1,2,C.A. 34651,27.75,,S +474,1,2,"Jerwan, Mrs. Amin S (Marie Marthe Thuillard)",female,23,0,0,SC/AH Basle 541,13.7917,D,C +475,0,3,"Strandberg, Miss. Ida Sofia",female,22,0,0,7553,9.8375,,S +476,0,1,"Clifford, Mr. George Quincy",male,,0,0,110465,52,A14,S +477,0,2,"Renouf, Mr. Peter Henry",male,34,1,0,31027,21,,S +478,0,3,"Braund, Mr. Lewis Richard",male,29,1,0,3460,7.0458,,S +479,0,3,"Karlsson, Mr. Nils August",male,22,0,0,350060,7.5208,,S +480,1,3,"Hirvonen, Miss. Hildur E",female,2,0,1,3101298,12.2875,,S +481,0,3,"Goodwin, Master. Harold Victor",male,9,5,2,CA 2144,46.9,,S +482,0,2,"Frost, Mr. Anthony Wood ""Archie""",male,,0,0,239854,0,,S +483,0,3,"Rouse, Mr. Richard Henry",male,50,0,0,A/5 3594,8.05,,S +484,1,3,"Turkula, Mrs. (Hedwig)",female,63,0,0,4134,9.5875,,S +485,1,1,"Bishop, Mr. Dickinson H",male,25,1,0,11967,91.0792,B49,C +486,0,3,"Lefebre, Miss. Jeannie",female,,3,1,4133,25.4667,,S +487,1,1,"Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)",female,35,1,0,19943,90,C93,S +488,0,1,"Kent, Mr. Edward Austin",male,58,0,0,11771,29.7,B37,C +489,0,3,"Somerton, Mr. Francis William",male,30,0,0,A.5. 18509,8.05,,S +490,1,3,"Coutts, Master. Eden Leslie ""Neville""",male,9,1,1,C.A. 37671,15.9,,S +491,0,3,"Hagland, Mr. Konrad Mathias Reiersen",male,,1,0,65304,19.9667,,S +492,0,3,"Windelov, Mr. Einar",male,21,0,0,SOTON/OQ 3101317,7.25,,S +493,0,1,"Molson, Mr. Harry Markland",male,55,0,0,113787,30.5,C30,S +494,0,1,"Artagaveytia, Mr. Ramon",male,71,0,0,PC 17609,49.5042,,C +495,0,3,"Stanley, Mr. Edward Roland",male,21,0,0,A/4 45380,8.05,,S +496,0,3,"Yousseff, Mr. Gerious",male,,0,0,2627,14.4583,,C +497,1,1,"Eustis, Miss. Elizabeth Mussey",female,54,1,0,36947,78.2667,D20,C +498,0,3,"Shellard, Mr. Frederick William",male,,0,0,C.A. 6212,15.1,,S +499,0,1,"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)",female,25,1,2,113781,151.55,C22 C26,S +500,0,3,"Svensson, Mr. Olof",male,24,0,0,350035,7.7958,,S +501,0,3,"Calic, Mr. Petar",male,17,0,0,315086,8.6625,,S +502,0,3,"Canavan, Miss. Mary",female,21,0,0,364846,7.75,,Q +503,0,3,"O'Sullivan, Miss. Bridget Mary",female,,0,0,330909,7.6292,,Q +504,0,3,"Laitinen, Miss. Kristina Sofia",female,37,0,0,4135,9.5875,,S +505,1,1,"Maioni, Miss. Roberta",female,16,0,0,110152,86.5,B79,S +506,0,1,"Penasco y Castellana, Mr. Victor de Satode",male,18,1,0,PC 17758,108.9,C65,C +507,1,2,"Quick, Mrs. Frederick Charles (Jane Richards)",female,33,0,2,26360,26,,S +508,1,1,"Bradley, Mr. George (""George Arthur Brayton"")",male,,0,0,111427,26.55,,S +509,0,3,"Olsen, Mr. Henry Margido",male,28,0,0,C 4001,22.525,,S +510,1,3,"Lang, Mr. Fang",male,26,0,0,1601,56.4958,,S +511,1,3,"Daly, Mr. Eugene Patrick",male,29,0,0,382651,7.75,,Q +512,0,3,"Webber, Mr. James",male,,0,0,SOTON/OQ 3101316,8.05,,S +513,1,1,"McGough, Mr. James Robert",male,36,0,0,PC 17473,26.2875,E25,S +514,1,1,"Rothschild, Mrs. Martin (Elizabeth L. Barrett)",female,54,1,0,PC 17603,59.4,,C +515,0,3,"Coleff, Mr. Satio",male,24,0,0,349209,7.4958,,S +516,0,1,"Walker, Mr. William Anderson",male,47,0,0,36967,34.0208,D46,S +517,1,2,"Lemore, Mrs. (Amelia Milley)",female,34,0,0,C.A. 34260,10.5,F33,S +518,0,3,"Ryan, Mr. Patrick",male,,0,0,371110,24.15,,Q +519,1,2,"Angle, Mrs. William A (Florence ""Mary"" Agnes Hughes)",female,36,1,0,226875,26,,S +520,0,3,"Pavlovic, Mr. Stefo",male,32,0,0,349242,7.8958,,S +521,1,1,"Perreault, Miss. Anne",female,30,0,0,12749,93.5,B73,S +522,0,3,"Vovk, Mr. Janko",male,22,0,0,349252,7.8958,,S +523,0,3,"Lahoud, Mr. Sarkis",male,,0,0,2624,7.225,,C +524,1,1,"Hippach, Mrs. Louis Albert (Ida Sophia Fischer)",female,44,0,1,111361,57.9792,B18,C +525,0,3,"Kassem, Mr. Fared",male,,0,0,2700,7.2292,,C +526,0,3,"Farrell, Mr. James",male,40.5,0,0,367232,7.75,,Q +527,1,2,"Ridsdale, Miss. Lucy",female,50,0,0,W./C. 14258,10.5,,S +528,0,1,"Farthing, Mr. John",male,,0,0,PC 17483,221.7792,C95,S +529,0,3,"Salonen, Mr. Johan Werner",male,39,0,0,3101296,7.925,,S +530,0,2,"Hocking, Mr. Richard George",male,23,2,1,29104,11.5,,S +531,1,2,"Quick, Miss. Phyllis May",female,2,1,1,26360,26,,S +532,0,3,"Toufik, Mr. Nakli",male,,0,0,2641,7.2292,,C +533,0,3,"Elias, Mr. Joseph Jr",male,17,1,1,2690,7.2292,,C +534,1,3,"Peter, Mrs. Catherine (Catherine Rizk)",female,,0,2,2668,22.3583,,C +535,0,3,"Cacic, Miss. Marija",female,30,0,0,315084,8.6625,,S +536,1,2,"Hart, Miss. Eva Miriam",female,7,0,2,F.C.C. 13529,26.25,,S +537,0,1,"Butt, Major. Archibald Willingham",male,45,0,0,113050,26.55,B38,S +538,1,1,"LeRoy, Miss. Bertha",female,30,0,0,PC 17761,106.425,,C +539,0,3,"Risien, Mr. Samuel Beard",male,,0,0,364498,14.5,,S +540,1,1,"Frolicher, Miss. Hedwig Margaritha",female,22,0,2,13568,49.5,B39,C +541,1,1,"Crosby, Miss. Harriet R",female,36,0,2,WE/P 5735,71,B22,S +542,0,3,"Andersson, Miss. Ingeborg Constanzia",female,9,4,2,347082,31.275,,S +543,0,3,"Andersson, Miss. Sigrid Elisabeth",female,11,4,2,347082,31.275,,S +544,1,2,"Beane, Mr. Edward",male,32,1,0,2908,26,,S +545,0,1,"Douglas, Mr. Walter Donald",male,50,1,0,PC 17761,106.425,C86,C +546,0,1,"Nicholson, Mr. Arthur Ernest",male,64,0,0,693,26,,S +547,1,2,"Beane, Mrs. Edward (Ethel Clarke)",female,19,1,0,2908,26,,S +548,1,2,"Padro y Manent, Mr. Julian",male,,0,0,SC/PARIS 2146,13.8625,,C +549,0,3,"Goldsmith, Mr. Frank John",male,33,1,1,363291,20.525,,S +550,1,2,"Davies, Master. John Morgan Jr",male,8,1,1,C.A. 33112,36.75,,S +551,1,1,"Thayer, Mr. John Borland Jr",male,17,0,2,17421,110.8833,C70,C +552,0,2,"Sharp, Mr. Percival James R",male,27,0,0,244358,26,,S +553,0,3,"O'Brien, Mr. Timothy",male,,0,0,330979,7.8292,,Q +554,1,3,"Leeni, Mr. Fahim (""Philip Zenni"")",male,22,0,0,2620,7.225,,C +555,1,3,"Ohman, Miss. Velin",female,22,0,0,347085,7.775,,S +556,0,1,"Wright, Mr. George",male,62,0,0,113807,26.55,,S +557,1,1,"Duff Gordon, Lady. (Lucille Christiana Sutherland) (""Mrs Morgan"")",female,48,1,0,11755,39.6,A16,C +558,0,1,"Robbins, Mr. Victor",male,,0,0,PC 17757,227.525,,C +559,1,1,"Taussig, Mrs. Emil (Tillie Mandelbaum)",female,39,1,1,110413,79.65,E67,S +560,1,3,"de Messemaeker, Mrs. Guillaume Joseph (Emma)",female,36,1,0,345572,17.4,,S +561,0,3,"Morrow, Mr. Thomas Rowan",male,,0,0,372622,7.75,,Q +562,0,3,"Sivic, Mr. Husein",male,40,0,0,349251,7.8958,,S +563,0,2,"Norman, Mr. Robert Douglas",male,28,0,0,218629,13.5,,S +564,0,3,"Simmons, Mr. John",male,,0,0,SOTON/OQ 392082,8.05,,S +565,0,3,"Meanwell, Miss. (Marion Ogden)",female,,0,0,SOTON/O.Q. 392087,8.05,,S +566,0,3,"Davies, Mr. Alfred J",male,24,2,0,A/4 48871,24.15,,S +567,0,3,"Stoytcheff, Mr. Ilia",male,19,0,0,349205,7.8958,,S +568,0,3,"Palsson, Mrs. Nils (Alma Cornelia Berglund)",female,29,0,4,349909,21.075,,S +569,0,3,"Doharr, Mr. Tannous",male,,0,0,2686,7.2292,,C +570,1,3,"Jonsson, Mr. Carl",male,32,0,0,350417,7.8542,,S +571,1,2,"Harris, Mr. George",male,62,0,0,S.W./PP 752,10.5,,S +572,1,1,"Appleton, Mrs. Edward Dale (Charlotte Lamson)",female,53,2,0,11769,51.4792,C101,S +573,1,1,"Flynn, Mr. John Irwin (""Irving"")",male,36,0,0,PC 17474,26.3875,E25,S +574,1,3,"Kelly, Miss. Mary",female,,0,0,14312,7.75,,Q +575,0,3,"Rush, Mr. Alfred George John",male,16,0,0,A/4. 20589,8.05,,S +576,0,3,"Patchett, Mr. George",male,19,0,0,358585,14.5,,S +577,1,2,"Garside, Miss. Ethel",female,34,0,0,243880,13,,S +578,1,1,"Silvey, Mrs. William Baird (Alice Munger)",female,39,1,0,13507,55.9,E44,S +579,0,3,"Caram, Mrs. Joseph (Maria Elias)",female,,1,0,2689,14.4583,,C +580,1,3,"Jussila, Mr. Eiriik",male,32,0,0,STON/O 2. 3101286,7.925,,S +581,1,2,"Christy, Miss. Julie Rachel",female,25,1,1,237789,30,,S +582,1,1,"Thayer, Mrs. John Borland (Marian Longstreth Morris)",female,39,1,1,17421,110.8833,C68,C +583,0,2,"Downton, Mr. William James",male,54,0,0,28403,26,,S +584,0,1,"Ross, Mr. John Hugo",male,36,0,0,13049,40.125,A10,C +585,0,3,"Paulner, Mr. Uscher",male,,0,0,3411,8.7125,,C +586,1,1,"Taussig, Miss. Ruth",female,18,0,2,110413,79.65,E68,S +587,0,2,"Jarvis, Mr. John Denzil",male,47,0,0,237565,15,,S +588,1,1,"Frolicher-Stehli, Mr. Maxmillian",male,60,1,1,13567,79.2,B41,C +589,0,3,"Gilinski, Mr. Eliezer",male,22,0,0,14973,8.05,,S +590,0,3,"Murdlin, Mr. Joseph",male,,0,0,A./5. 3235,8.05,,S +591,0,3,"Rintamaki, Mr. Matti",male,35,0,0,STON/O 2. 3101273,7.125,,S +592,1,1,"Stephenson, Mrs. Walter Bertram (Martha Eustis)",female,52,1,0,36947,78.2667,D20,C +593,0,3,"Elsbury, Mr. William James",male,47,0,0,A/5 3902,7.25,,S +594,0,3,"Bourke, Miss. Mary",female,,0,2,364848,7.75,,Q +595,0,2,"Chapman, Mr. John Henry",male,37,1,0,SC/AH 29037,26,,S +596,0,3,"Van Impe, Mr. Jean Baptiste",male,36,1,1,345773,24.15,,S +597,1,2,"Leitch, Miss. Jessie Wills",female,,0,0,248727,33,,S +598,0,3,"Johnson, Mr. Alfred",male,49,0,0,LINE,0,,S +599,0,3,"Boulos, Mr. Hanna",male,,0,0,2664,7.225,,C +600,1,1,"Duff Gordon, Sir. Cosmo Edmund (""Mr Morgan"")",male,49,1,0,PC 17485,56.9292,A20,C +601,1,2,"Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)",female,24,2,1,243847,27,,S +602,0,3,"Slabenoff, Mr. Petco",male,,0,0,349214,7.8958,,S +603,0,1,"Harrington, Mr. Charles H",male,,0,0,113796,42.4,,S +604,0,3,"Torber, Mr. Ernst William",male,44,0,0,364511,8.05,,S +605,1,1,"Homer, Mr. Harry (""Mr E Haven"")",male,35,0,0,111426,26.55,,C +606,0,3,"Lindell, Mr. Edvard Bengtsson",male,36,1,0,349910,15.55,,S +607,0,3,"Karaic, Mr. Milan",male,30,0,0,349246,7.8958,,S +608,1,1,"Daniel, Mr. Robert Williams",male,27,0,0,113804,30.5,,S +609,1,2,"Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)",female,22,1,2,SC/Paris 2123,41.5792,,C +610,1,1,"Shutes, Miss. Elizabeth W",female,40,0,0,PC 17582,153.4625,C125,S +611,0,3,"Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)",female,39,1,5,347082,31.275,,S +612,0,3,"Jardin, Mr. Jose Neto",male,,0,0,SOTON/O.Q. 3101305,7.05,,S +613,1,3,"Murphy, Miss. Margaret Jane",female,,1,0,367230,15.5,,Q +614,0,3,"Horgan, Mr. John",male,,0,0,370377,7.75,,Q +615,0,3,"Brocklebank, Mr. William Alfred",male,35,0,0,364512,8.05,,S +616,1,2,"Herman, Miss. Alice",female,24,1,2,220845,65,,S +617,0,3,"Danbom, Mr. Ernst Gilbert",male,34,1,1,347080,14.4,,S +618,0,3,"Lobb, Mrs. William Arthur (Cordelia K Stanlick)",female,26,1,0,A/5. 3336,16.1,,S +619,1,2,"Becker, Miss. Marion Louise",female,4,2,1,230136,39,F4,S +620,0,2,"Gavey, Mr. Lawrence",male,26,0,0,31028,10.5,,S +621,0,3,"Yasbeck, Mr. Antoni",male,27,1,0,2659,14.4542,,C +622,1,1,"Kimball, Mr. Edwin Nelson Jr",male,42,1,0,11753,52.5542,D19,S +623,1,3,"Nakid, Mr. Sahid",male,20,1,1,2653,15.7417,,C +624,0,3,"Hansen, Mr. Henry Damsgaard",male,21,0,0,350029,7.8542,,S +625,0,3,"Bowen, Mr. David John ""Dai""",male,21,0,0,54636,16.1,,S +626,0,1,"Sutton, Mr. Frederick",male,61,0,0,36963,32.3208,D50,S +627,0,2,"Kirkland, Rev. Charles Leonard",male,57,0,0,219533,12.35,,Q +628,1,1,"Longley, Miss. Gretchen Fiske",female,21,0,0,13502,77.9583,D9,S +629,0,3,"Bostandyeff, Mr. Guentcho",male,26,0,0,349224,7.8958,,S +630,0,3,"O'Connell, Mr. Patrick D",male,,0,0,334912,7.7333,,Q +631,1,1,"Barkworth, Mr. Algernon Henry Wilson",male,80,0,0,27042,30,A23,S +632,0,3,"Lundahl, Mr. Johan Svensson",male,51,0,0,347743,7.0542,,S +633,1,1,"Stahelin-Maeglin, Dr. Max",male,32,0,0,13214,30.5,B50,C +634,0,1,"Parr, Mr. William Henry Marsh",male,,0,0,112052,0,,S +635,0,3,"Skoog, Miss. Mabel",female,9,3,2,347088,27.9,,S +636,1,2,"Davis, Miss. Mary",female,28,0,0,237668,13,,S +637,0,3,"Leinonen, Mr. Antti Gustaf",male,32,0,0,STON/O 2. 3101292,7.925,,S +638,0,2,"Collyer, Mr. Harvey",male,31,1,1,C.A. 31921,26.25,,S +639,0,3,"Panula, Mrs. Juha (Maria Emilia Ojala)",female,41,0,5,3101295,39.6875,,S +640,0,3,"Thorneycroft, Mr. Percival",male,,1,0,376564,16.1,,S +641,0,3,"Jensen, Mr. Hans Peder",male,20,0,0,350050,7.8542,,S +642,1,1,"Sagesser, Mlle. Emma",female,24,0,0,PC 17477,69.3,B35,C +643,0,3,"Skoog, Miss. Margit Elizabeth",female,2,3,2,347088,27.9,,S +644,1,3,"Foo, Mr. Choong",male,,0,0,1601,56.4958,,S +645,1,3,"Baclini, Miss. Eugenie",female,0.75,2,1,2666,19.2583,,C +646,1,1,"Harper, Mr. Henry Sleeper",male,48,1,0,PC 17572,76.7292,D33,C +647,0,3,"Cor, Mr. Liudevit",male,19,0,0,349231,7.8958,,S +648,1,1,"Simonius-Blumer, Col. Oberst Alfons",male,56,0,0,13213,35.5,A26,C +649,0,3,"Willey, Mr. Edward",male,,0,0,S.O./P.P. 751,7.55,,S +650,1,3,"Stanley, Miss. Amy Zillah Elsie",female,23,0,0,CA. 2314,7.55,,S +651,0,3,"Mitkoff, Mr. Mito",male,,0,0,349221,7.8958,,S +652,1,2,"Doling, Miss. Elsie",female,18,0,1,231919,23,,S +653,0,3,"Kalvik, Mr. Johannes Halvorsen",male,21,0,0,8475,8.4333,,S +654,1,3,"O'Leary, Miss. Hanora ""Norah""",female,,0,0,330919,7.8292,,Q +655,0,3,"Hegarty, Miss. Hanora ""Nora""",female,18,0,0,365226,6.75,,Q +656,0,2,"Hickman, Mr. Leonard Mark",male,24,2,0,S.O.C. 14879,73.5,,S +657,0,3,"Radeff, Mr. Alexander",male,,0,0,349223,7.8958,,S +658,0,3,"Bourke, Mrs. John (Catherine)",female,32,1,1,364849,15.5,,Q +659,0,2,"Eitemiller, Mr. George Floyd",male,23,0,0,29751,13,,S +660,0,1,"Newell, Mr. Arthur Webster",male,58,0,2,35273,113.275,D48,C +661,1,1,"Frauenthal, Dr. Henry William",male,50,2,0,PC 17611,133.65,,S +662,0,3,"Badt, Mr. Mohamed",male,40,0,0,2623,7.225,,C +663,0,1,"Colley, Mr. Edward Pomeroy",male,47,0,0,5727,25.5875,E58,S +664,0,3,"Coleff, Mr. Peju",male,36,0,0,349210,7.4958,,S +665,1,3,"Lindqvist, Mr. Eino William",male,20,1,0,STON/O 2. 3101285,7.925,,S +666,0,2,"Hickman, Mr. Lewis",male,32,2,0,S.O.C. 14879,73.5,,S +667,0,2,"Butler, Mr. Reginald Fenton",male,25,0,0,234686,13,,S +668,0,3,"Rommetvedt, Mr. Knud Paust",male,,0,0,312993,7.775,,S +669,0,3,"Cook, Mr. Jacob",male,43,0,0,A/5 3536,8.05,,S +670,1,1,"Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)",female,,1,0,19996,52,C126,S +671,1,2,"Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)",female,40,1,1,29750,39,,S +672,0,1,"Davidson, Mr. Thornton",male,31,1,0,F.C. 12750,52,B71,S +673,0,2,"Mitchell, Mr. Henry Michael",male,70,0,0,C.A. 24580,10.5,,S +674,1,2,"Wilhelms, Mr. Charles",male,31,0,0,244270,13,,S +675,0,2,"Watson, Mr. Ennis Hastings",male,,0,0,239856,0,,S +676,0,3,"Edvardsson, Mr. Gustaf Hjalmar",male,18,0,0,349912,7.775,,S +677,0,3,"Sawyer, Mr. Frederick Charles",male,24.5,0,0,342826,8.05,,S +678,1,3,"Turja, Miss. Anna Sofia",female,18,0,0,4138,9.8417,,S +679,0,3,"Goodwin, Mrs. Frederick (Augusta Tyler)",female,43,1,6,CA 2144,46.9,,S +680,1,1,"Cardeza, Mr. Thomas Drake Martinez",male,36,0,1,PC 17755,512.3292,B51 B53 B55,C +681,0,3,"Peters, Miss. Katie",female,,0,0,330935,8.1375,,Q +682,1,1,"Hassab, Mr. Hammad",male,27,0,0,PC 17572,76.7292,D49,C +683,0,3,"Olsvigen, Mr. Thor Anderson",male,20,0,0,6563,9.225,,S +684,0,3,"Goodwin, Mr. Charles Edward",male,14,5,2,CA 2144,46.9,,S +685,0,2,"Brown, Mr. Thomas William Solomon",male,60,1,1,29750,39,,S +686,0,2,"Laroche, Mr. Joseph Philippe Lemercier",male,25,1,2,SC/Paris 2123,41.5792,,C +687,0,3,"Panula, Mr. Jaako Arnold",male,14,4,1,3101295,39.6875,,S +688,0,3,"Dakic, Mr. Branko",male,19,0,0,349228,10.1708,,S +689,0,3,"Fischer, Mr. Eberhard Thelander",male,18,0,0,350036,7.7958,,S +690,1,1,"Madill, Miss. Georgette Alexandra",female,15,0,1,24160,211.3375,B5,S +691,1,1,"Dick, Mr. Albert Adrian",male,31,1,0,17474,57,B20,S +692,1,3,"Karun, Miss. Manca",female,4,0,1,349256,13.4167,,C +693,1,3,"Lam, Mr. Ali",male,,0,0,1601,56.4958,,S +694,0,3,"Saad, Mr. Khalil",male,25,0,0,2672,7.225,,C +695,0,1,"Weir, Col. John",male,60,0,0,113800,26.55,,S +696,0,2,"Chapman, Mr. Charles Henry",male,52,0,0,248731,13.5,,S +697,0,3,"Kelly, Mr. James",male,44,0,0,363592,8.05,,S +698,1,3,"Mullens, Miss. Katherine ""Katie""",female,,0,0,35852,7.7333,,Q +699,0,1,"Thayer, Mr. John Borland",male,49,1,1,17421,110.8833,C68,C +700,0,3,"Humblen, Mr. Adolf Mathias Nicolai Olsen",male,42,0,0,348121,7.65,F G63,S +701,1,1,"Astor, Mrs. John Jacob (Madeleine Talmadge Force)",female,18,1,0,PC 17757,227.525,C62 C64,C +702,1,1,"Silverthorne, Mr. Spencer Victor",male,35,0,0,PC 17475,26.2875,E24,S +703,0,3,"Barbara, Miss. Saiide",female,18,0,1,2691,14.4542,,C +704,0,3,"Gallagher, Mr. Martin",male,25,0,0,36864,7.7417,,Q +705,0,3,"Hansen, Mr. Henrik Juul",male,26,1,0,350025,7.8542,,S +706,0,2,"Morley, Mr. Henry Samuel (""Mr Henry Marshall"")",male,39,0,0,250655,26,,S +707,1,2,"Kelly, Mrs. Florence ""Fannie""",female,45,0,0,223596,13.5,,S +708,1,1,"Calderhead, Mr. Edward Pennington",male,42,0,0,PC 17476,26.2875,E24,S +709,1,1,"Cleaver, Miss. Alice",female,22,0,0,113781,151.55,,S +710,1,3,"Moubarek, Master. Halim Gonios (""William George"")",male,,1,1,2661,15.2458,,C +711,1,1,"Mayne, Mlle. Berthe Antonine (""Mrs de Villiers"")",female,24,0,0,PC 17482,49.5042,C90,C +712,0,1,"Klaber, Mr. Herman",male,,0,0,113028,26.55,C124,S +713,1,1,"Taylor, Mr. Elmer Zebley",male,48,1,0,19996,52,C126,S +714,0,3,"Larsson, Mr. August Viktor",male,29,0,0,7545,9.4833,,S +715,0,2,"Greenberg, Mr. Samuel",male,52,0,0,250647,13,,S +716,0,3,"Soholt, Mr. Peter Andreas Lauritz Andersen",male,19,0,0,348124,7.65,F G73,S +717,1,1,"Endres, Miss. Caroline Louise",female,38,0,0,PC 17757,227.525,C45,C +718,1,2,"Troutt, Miss. Edwina Celia ""Winnie""",female,27,0,0,34218,10.5,E101,S +719,0,3,"McEvoy, Mr. Michael",male,,0,0,36568,15.5,,Q +720,0,3,"Johnson, Mr. Malkolm Joackim",male,33,0,0,347062,7.775,,S +721,1,2,"Harper, Miss. Annie Jessie ""Nina""",female,6,0,1,248727,33,,S +722,0,3,"Jensen, Mr. Svend Lauritz",male,17,1,0,350048,7.0542,,S +723,0,2,"Gillespie, Mr. William Henry",male,34,0,0,12233,13,,S +724,0,2,"Hodges, Mr. Henry Price",male,50,0,0,250643,13,,S +725,1,1,"Chambers, Mr. Norman Campbell",male,27,1,0,113806,53.1,E8,S +726,0,3,"Oreskovic, Mr. Luka",male,20,0,0,315094,8.6625,,S +727,1,2,"Renouf, Mrs. Peter Henry (Lillian Jefferys)",female,30,3,0,31027,21,,S +728,1,3,"Mannion, Miss. Margareth",female,,0,0,36866,7.7375,,Q +729,0,2,"Bryhl, Mr. Kurt Arnold Gottfrid",male,25,1,0,236853,26,,S +730,0,3,"Ilmakangas, Miss. Pieta Sofia",female,25,1,0,STON/O2. 3101271,7.925,,S +731,1,1,"Allen, Miss. Elisabeth Walton",female,29,0,0,24160,211.3375,B5,S +732,0,3,"Hassan, Mr. Houssein G N",male,11,0,0,2699,18.7875,,C +733,0,2,"Knight, Mr. Robert J",male,,0,0,239855,0,,S +734,0,2,"Berriman, Mr. William John",male,23,0,0,28425,13,,S +735,0,2,"Troupiansky, Mr. Moses Aaron",male,23,0,0,233639,13,,S +736,0,3,"Williams, Mr. Leslie",male,28.5,0,0,54636,16.1,,S +737,0,3,"Ford, Mrs. Edward (Margaret Ann Watson)",female,48,1,3,W./C. 6608,34.375,,S +738,1,1,"Lesurer, Mr. Gustave J",male,35,0,0,PC 17755,512.3292,B101,C +739,0,3,"Ivanoff, Mr. Kanio",male,,0,0,349201,7.8958,,S +740,0,3,"Nankoff, Mr. Minko",male,,0,0,349218,7.8958,,S +741,1,1,"Hawksford, Mr. Walter James",male,,0,0,16988,30,D45,S +742,0,1,"Cavendish, Mr. Tyrell William",male,36,1,0,19877,78.85,C46,S +743,1,1,"Ryerson, Miss. Susan Parker ""Suzette""",female,21,2,2,PC 17608,262.375,B57 B59 B63 B66,C +744,0,3,"McNamee, Mr. Neal",male,24,1,0,376566,16.1,,S +745,1,3,"Stranden, Mr. Juho",male,31,0,0,STON/O 2. 3101288,7.925,,S +746,0,1,"Crosby, Capt. Edward Gifford",male,70,1,1,WE/P 5735,71,B22,S +747,0,3,"Abbott, Mr. Rossmore Edward",male,16,1,1,C.A. 2673,20.25,,S +748,1,2,"Sinkkonen, Miss. Anna",female,30,0,0,250648,13,,S +749,0,1,"Marvin, Mr. Daniel Warner",male,19,1,0,113773,53.1,D30,S +750,0,3,"Connaghton, Mr. Michael",male,31,0,0,335097,7.75,,Q +751,1,2,"Wells, Miss. Joan",female,4,1,1,29103,23,,S +752,1,3,"Moor, Master. Meier",male,6,0,1,392096,12.475,E121,S +753,0,3,"Vande Velde, Mr. Johannes Joseph",male,33,0,0,345780,9.5,,S +754,0,3,"Jonkoff, Mr. Lalio",male,23,0,0,349204,7.8958,,S +755,1,2,"Herman, Mrs. Samuel (Jane Laver)",female,48,1,2,220845,65,,S +756,1,2,"Hamalainen, Master. Viljo",male,0.67,1,1,250649,14.5,,S +757,0,3,"Carlsson, Mr. August Sigfrid",male,28,0,0,350042,7.7958,,S +758,0,2,"Bailey, Mr. Percy Andrew",male,18,0,0,29108,11.5,,S +759,0,3,"Theobald, Mr. Thomas Leonard",male,34,0,0,363294,8.05,,S +760,1,1,"Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)",female,33,0,0,110152,86.5,B77,S +761,0,3,"Garfirth, Mr. John",male,,0,0,358585,14.5,,S +762,0,3,"Nirva, Mr. Iisakki Antino Aijo",male,41,0,0,SOTON/O2 3101272,7.125,,S +763,1,3,"Barah, Mr. Hanna Assi",male,20,0,0,2663,7.2292,,C +764,1,1,"Carter, Mrs. William Ernest (Lucile Polk)",female,36,1,2,113760,120,B96 B98,S +765,0,3,"Eklund, Mr. Hans Linus",male,16,0,0,347074,7.775,,S +766,1,1,"Hogeboom, Mrs. John C (Anna Andrews)",female,51,1,0,13502,77.9583,D11,S +767,0,1,"Brewe, Dr. Arthur Jackson",male,,0,0,112379,39.6,,C +768,0,3,"Mangan, Miss. Mary",female,30.5,0,0,364850,7.75,,Q +769,0,3,"Moran, Mr. Daniel J",male,,1,0,371110,24.15,,Q +770,0,3,"Gronnestad, Mr. Daniel Danielsen",male,32,0,0,8471,8.3625,,S +771,0,3,"Lievens, Mr. Rene Aime",male,24,0,0,345781,9.5,,S +772,0,3,"Jensen, Mr. Niels Peder",male,48,0,0,350047,7.8542,,S +773,0,2,"Mack, Mrs. (Mary)",female,57,0,0,S.O./P.P. 3,10.5,E77,S +774,0,3,"Elias, Mr. Dibo",male,,0,0,2674,7.225,,C +775,1,2,"Hocking, Mrs. Elizabeth (Eliza Needs)",female,54,1,3,29105,23,,S +776,0,3,"Myhrman, Mr. Pehr Fabian Oliver Malkolm",male,18,0,0,347078,7.75,,S +777,0,3,"Tobin, Mr. Roger",male,,0,0,383121,7.75,F38,Q +778,1,3,"Emanuel, Miss. Virginia Ethel",female,5,0,0,364516,12.475,,S +779,0,3,"Kilgannon, Mr. Thomas J",male,,0,0,36865,7.7375,,Q +780,1,1,"Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)",female,43,0,1,24160,211.3375,B3,S +781,1,3,"Ayoub, Miss. Banoura",female,13,0,0,2687,7.2292,,C +782,1,1,"Dick, Mrs. Albert Adrian (Vera Gillespie)",female,17,1,0,17474,57,B20,S +783,0,1,"Long, Mr. Milton Clyde",male,29,0,0,113501,30,D6,S +784,0,3,"Johnston, Mr. Andrew G",male,,1,2,W./C. 6607,23.45,,S +785,0,3,"Ali, Mr. William",male,25,0,0,SOTON/O.Q. 3101312,7.05,,S +786,0,3,"Harmer, Mr. Abraham (David Lishin)",male,25,0,0,374887,7.25,,S +787,1,3,"Sjoblom, Miss. Anna Sofia",female,18,0,0,3101265,7.4958,,S +788,0,3,"Rice, Master. George Hugh",male,8,4,1,382652,29.125,,Q +789,1,3,"Dean, Master. Bertram Vere",male,1,1,2,C.A. 2315,20.575,,S +790,0,1,"Guggenheim, Mr. Benjamin",male,46,0,0,PC 17593,79.2,B82 B84,C +791,0,3,"Keane, Mr. Andrew ""Andy""",male,,0,0,12460,7.75,,Q +792,0,2,"Gaskell, Mr. Alfred",male,16,0,0,239865,26,,S +793,0,3,"Sage, Miss. Stella Anna",female,,8,2,CA. 2343,69.55,,S +794,0,1,"Hoyt, Mr. William Fisher",male,,0,0,PC 17600,30.6958,,C +795,0,3,"Dantcheff, Mr. Ristiu",male,25,0,0,349203,7.8958,,S +796,0,2,"Otter, Mr. Richard",male,39,0,0,28213,13,,S +797,1,1,"Leader, Dr. Alice (Farnham)",female,49,0,0,17465,25.9292,D17,S +798,1,3,"Osman, Mrs. Mara",female,31,0,0,349244,8.6833,,S +799,0,3,"Ibrahim Shawah, Mr. Yousseff",male,30,0,0,2685,7.2292,,C +800,0,3,"Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)",female,30,1,1,345773,24.15,,S +801,0,2,"Ponesell, Mr. Martin",male,34,0,0,250647,13,,S +802,1,2,"Collyer, Mrs. Harvey (Charlotte Annie Tate)",female,31,1,1,C.A. 31921,26.25,,S +803,1,1,"Carter, Master. William Thornton II",male,11,1,2,113760,120,B96 B98,S +804,1,3,"Thomas, Master. Assad Alexander",male,0.42,0,1,2625,8.5167,,C +805,1,3,"Hedman, Mr. Oskar Arvid",male,27,0,0,347089,6.975,,S +806,0,3,"Johansson, Mr. Karl Johan",male,31,0,0,347063,7.775,,S +807,0,1,"Andrews, Mr. Thomas Jr",male,39,0,0,112050,0,A36,S +808,0,3,"Pettersson, Miss. Ellen Natalia",female,18,0,0,347087,7.775,,S +809,0,2,"Meyer, Mr. August",male,39,0,0,248723,13,,S +810,1,1,"Chambers, Mrs. Norman Campbell (Bertha Griggs)",female,33,1,0,113806,53.1,E8,S +811,0,3,"Alexander, Mr. William",male,26,0,0,3474,7.8875,,S +812,0,3,"Lester, Mr. James",male,39,0,0,A/4 48871,24.15,,S +813,0,2,"Slemen, Mr. Richard James",male,35,0,0,28206,10.5,,S +814,0,3,"Andersson, Miss. Ebba Iris Alfrida",female,6,4,2,347082,31.275,,S +815,0,3,"Tomlin, Mr. Ernest Portage",male,30.5,0,0,364499,8.05,,S +816,0,1,"Fry, Mr. Richard",male,,0,0,112058,0,B102,S +817,0,3,"Heininen, Miss. Wendla Maria",female,23,0,0,STON/O2. 3101290,7.925,,S +818,0,2,"Mallet, Mr. Albert",male,31,1,1,S.C./PARIS 2079,37.0042,,C +819,0,3,"Holm, Mr. John Fredrik Alexander",male,43,0,0,C 7075,6.45,,S +820,0,3,"Skoog, Master. Karl Thorsten",male,10,3,2,347088,27.9,,S +821,1,1,"Hays, Mrs. Charles Melville (Clara Jennings Gregg)",female,52,1,1,12749,93.5,B69,S +822,1,3,"Lulic, Mr. Nikola",male,27,0,0,315098,8.6625,,S +823,0,1,"Reuchlin, Jonkheer. John George",male,38,0,0,19972,0,,S +824,1,3,"Moor, Mrs. (Beila)",female,27,0,1,392096,12.475,E121,S +825,0,3,"Panula, Master. Urho Abraham",male,2,4,1,3101295,39.6875,,S +826,0,3,"Flynn, Mr. John",male,,0,0,368323,6.95,,Q +827,0,3,"Lam, Mr. Len",male,,0,0,1601,56.4958,,S +828,1,2,"Mallet, Master. Andre",male,1,0,2,S.C./PARIS 2079,37.0042,,C +829,1,3,"McCormack, Mr. Thomas Joseph",male,,0,0,367228,7.75,,Q +830,1,1,"Stone, Mrs. George Nelson (Martha Evelyn)",female,62,0,0,113572,80,B28, +831,1,3,"Yasbeck, Mrs. Antoni (Selini Alexander)",female,15,1,0,2659,14.4542,,C +832,1,2,"Richards, Master. George Sibley",male,0.83,1,1,29106,18.75,,S +833,0,3,"Saad, Mr. Amin",male,,0,0,2671,7.2292,,C +834,0,3,"Augustsson, Mr. Albert",male,23,0,0,347468,7.8542,,S +835,0,3,"Allum, Mr. Owen George",male,18,0,0,2223,8.3,,S +836,1,1,"Compton, Miss. Sara Rebecca",female,39,1,1,PC 17756,83.1583,E49,C +837,0,3,"Pasic, Mr. Jakob",male,21,0,0,315097,8.6625,,S +838,0,3,"Sirota, Mr. Maurice",male,,0,0,392092,8.05,,S +839,1,3,"Chip, Mr. Chang",male,32,0,0,1601,56.4958,,S +840,1,1,"Marechal, Mr. Pierre",male,,0,0,11774,29.7,C47,C +841,0,3,"Alhomaki, Mr. Ilmari Rudolf",male,20,0,0,SOTON/O2 3101287,7.925,,S +842,0,2,"Mudd, Mr. Thomas Charles",male,16,0,0,S.O./P.P. 3,10.5,,S +843,1,1,"Serepeca, Miss. Augusta",female,30,0,0,113798,31,,C +844,0,3,"Lemberopolous, Mr. Peter L",male,34.5,0,0,2683,6.4375,,C +845,0,3,"Culumovic, Mr. Jeso",male,17,0,0,315090,8.6625,,S +846,0,3,"Abbing, Mr. Anthony",male,42,0,0,C.A. 5547,7.55,,S +847,0,3,"Sage, Mr. Douglas Bullen",male,,8,2,CA. 2343,69.55,,S +848,0,3,"Markoff, Mr. Marin",male,35,0,0,349213,7.8958,,C +849,0,2,"Harper, Rev. John",male,28,0,1,248727,33,,S +850,1,1,"Goldenberg, Mrs. Samuel L (Edwiga Grabowska)",female,,1,0,17453,89.1042,C92,C +851,0,3,"Andersson, Master. Sigvard Harald Elias",male,4,4,2,347082,31.275,,S +852,0,3,"Svensson, Mr. Johan",male,74,0,0,347060,7.775,,S +853,0,3,"Boulos, Miss. Nourelain",female,9,1,1,2678,15.2458,,C +854,1,1,"Lines, Miss. Mary Conover",female,16,0,1,PC 17592,39.4,D28,S +855,0,2,"Carter, Mrs. Ernest Courtenay (Lilian Hughes)",female,44,1,0,244252,26,,S +856,1,3,"Aks, Mrs. Sam (Leah Rosen)",female,18,0,1,392091,9.35,,S +857,1,1,"Wick, Mrs. George Dennick (Mary Hitchcock)",female,45,1,1,36928,164.8667,,S +858,1,1,"Daly, Mr. Peter Denis ",male,51,0,0,113055,26.55,E17,S +859,1,3,"Baclini, Mrs. Solomon (Latifa Qurban)",female,24,0,3,2666,19.2583,,C +860,0,3,"Razi, Mr. Raihed",male,,0,0,2629,7.2292,,C +861,0,3,"Hansen, Mr. Claus Peter",male,41,2,0,350026,14.1083,,S +862,0,2,"Giles, Mr. Frederick Edward",male,21,1,0,28134,11.5,,S +863,1,1,"Swift, Mrs. Frederick Joel (Margaret Welles Barron)",female,48,0,0,17466,25.9292,D17,S +864,0,3,"Sage, Miss. Dorothy Edith ""Dolly""",female,,8,2,CA. 2343,69.55,,S +865,0,2,"Gill, Mr. John William",male,24,0,0,233866,13,,S +866,1,2,"Bystrom, Mrs. (Karolina)",female,42,0,0,236852,13,,S +867,1,2,"Duran y More, Miss. Asuncion",female,27,1,0,SC/PARIS 2149,13.8583,,C +868,0,1,"Roebling, Mr. Washington Augustus II",male,31,0,0,PC 17590,50.4958,A24,S +869,0,3,"van Melkebeke, Mr. Philemon",male,,0,0,345777,9.5,,S +870,1,3,"Johnson, Master. Harold Theodor",male,4,1,1,347742,11.1333,,S +871,0,3,"Balkic, Mr. Cerin",male,26,0,0,349248,7.8958,,S +872,1,1,"Beckwith, Mrs. Richard Leonard (Sallie Monypeny)",female,47,1,1,11751,52.5542,D35,S +873,0,1,"Carlsson, Mr. Frans Olof",male,33,0,0,695,5,B51 B53 B55,S +874,0,3,"Vander Cruyssen, Mr. Victor",male,47,0,0,345765,9,,S +875,1,2,"Abelson, Mrs. Samuel (Hannah Wizosky)",female,28,1,0,P/PP 3381,24,,C +876,1,3,"Najib, Miss. Adele Kiamie ""Jane""",female,15,0,0,2667,7.225,,C +877,0,3,"Gustafsson, Mr. Alfred Ossian",male,20,0,0,7534,9.8458,,S +878,0,3,"Petroff, Mr. Nedelio",male,19,0,0,349212,7.8958,,S +879,0,3,"Laleff, Mr. Kristo",male,,0,0,349217,7.8958,,S +880,1,1,"Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)",female,56,0,1,11767,83.1583,C50,C +881,1,2,"Shelley, Mrs. William (Imanita Parrish Hall)",female,25,0,1,230433,26,,S +882,0,3,"Markun, Mr. Johann",male,33,0,0,349257,7.8958,,S +883,0,3,"Dahlberg, Miss. Gerda Ulrika",female,22,0,0,7552,10.5167,,S +884,0,2,"Banfield, Mr. Frederick James",male,28,0,0,C.A./SOTON 34068,10.5,,S +885,0,3,"Sutehall, Mr. Henry Jr",male,25,0,0,SOTON/OQ 392076,7.05,,S +886,0,3,"Rice, Mrs. William (Margaret Norton)",female,39,0,5,382652,29.125,,Q +887,0,2,"Montvila, Rev. Juozas",male,27,0,0,211536,13,,S +888,1,1,"Graham, Miss. Margaret Edith",female,19,0,0,112053,30,B42,S +889,0,3,"Johnston, Miss. Catherine Helen ""Carrie""",female,,1,2,W./C. 6607,23.45,,S +890,1,1,"Behr, Mr. Karl Howell",male,26,0,0,111369,30,C148,C +891,0,3,"Dooley, Mr. Patrick",male,32,0,0,370376,7.75,,Q diff --git a/examples/testing.py b/examples/testing.py new file mode 100644 index 0000000..0345284 --- /dev/null +++ b/examples/testing.py @@ -0,0 +1,19 @@ +#%% +import pandas as pd +import numpy as np +import sys +sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra") + +ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" + +#%% +df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") + +#%% +from cobra.preprocessing import KBinsDiscretizer + +KBD = KBinsDiscretizer() +df_prep = KBD.fit_transform(data=df_data, column_names=['Age','Fare']) + +#%% +from cobra.preprocessing import TargetEncoder \ No newline at end of file From e022cb73ea9451959f548b45f5776e87516194cd Mon Sep 17 00:00:00 2001 From: JanBenisek Date: Fri, 4 Oct 2019 16:31:00 +0200 Subject: [PATCH 09/98] develop = cat regrouper --- cobra/metrics/__init__.py | 3 + cobra/metrics/all_metrics_plots.py | 527 +++++++++++++++++++++++++++++ dev/preprocessor/develop.py | 279 +++++++++++++++ dev/preprocessor/new_regroup.py | 189 +++++++++++ 4 files changed, 998 insertions(+) create mode 100644 cobra/metrics/__init__.py create mode 100644 cobra/metrics/all_metrics_plots.py create mode 100644 dev/preprocessor/develop.py create mode 100644 dev/preprocessor/new_regroup.py diff --git a/cobra/metrics/__init__.py b/cobra/metrics/__init__.py new file mode 100644 index 0000000..67656f7 --- /dev/null +++ b/cobra/metrics/__init__.py @@ -0,0 +1,3 @@ +from .all_metrics_plots import Evaluator + +__all__ = ['Evaluator'] \ No newline at end of file diff --git a/cobra/metrics/all_metrics_plots.py b/cobra/metrics/all_metrics_plots.py new file mode 100644 index 0000000..d18bb64 --- /dev/null +++ b/cobra/metrics/all_metrics_plots.py @@ -0,0 +1,527 @@ +""" +====================================================================================== +--------------------------------------- Evaluation Class code ------------------------ +====================================================================================== +author: jan.benisek@pythonpredictins.com - benoit.vandekerkhove@pythonpredictions.com +date: 23/09/2019 +purpose: library for model evaluation class + +""" +#%% +import numpy as np +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt +import sklearn.metrics as mt +from typing import Tuple +#%% + + +class Evaluator(): + ''' + Class to evaluate models + + Parameters + ----------- + y_true : array, shape = [1, n_features] + array with true values + + y_pred_p : array, shape = [1, n_features] + array with predicted values (probabilities) + + lift_at : int , default=0.05 + calculate lift at given level (0-1) + + save_pth : str, default=None + path to where save the plot + + binary_cutoff : float, default=0.5 + cutoff to convert predictions to binary + + ''' + + def __init__(self, y_true: np.ndarray, y_pred_p: np.ndarray, + lift_at: float=0.05, save_pth: str=None, binary_cutoff: int=0.5): + + self.y_true = y_true.flatten() + self.y_pred_p = y_pred_p.flatten() #As probability + self.lift_at = lift_at + self.save_pth = save_pth + self.binary_cutoff = binary_cutoff + + self.y_pred_b = np.where(self.y_pred_p > self.binary_cutoff,1,0) + + + + + '''============================================================= + ----------------------------- PLOTS ---------------------------- + =============================================================''' + def plotROCCurve(self, desc: str=None): + ''' + Plot ROC curve and print best cutoff value + Transform probabilities predictions to bool based on best AUC based cutoff + + Parameters + ---------- + desc : str, default=None + description of the plot, used also as a name of saved plot + + ''' + if desc is None: + desc = '' + + fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) + + #--------------------------- + #Calculate AUC + #-------------------------- + score = mt.roc_auc_score(self.y_true, self.y_pred_p) + + fig, ax = plt.subplots(figsize=(8,5)) + ax.plot(fpr,tpr, color='darkorange', lw=2, label='ROC curve (area = {s:.3})'.format(s=score)) + ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') + ax.set_xlabel('False Positive Rate', fontsize=15) + ax.set_ylabel('True Positive Rate', fontsize=15) + ax.legend(loc="lower right") + ax.set_title('ROC Curve {}' .format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + '''============================================================= + ---------------------------- METRICS --------------------------- + =============================================================''' + + def printPerformance(self): + ''' + Print out performance measures + + EV.printPerformance() + %timeit 2min 19s ± 784 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + ''' + + if self.threshold != np.nan : + out_perfo = self._evaluation() + + print('=== Test on', self.test_on, '===') + print('Precision: {s:.3}'.format(s=out_perfo['precision'])) #If we mark customer as a churner, how often we are correct + print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) #Overall performance + print('Recall: {s:.3}'.format(s=out_perfo['recall'])) #How many churners can the model detect + print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) # 2 * (precision * recall) / (precision + recall) + print('Lift at top {l}%: {s:.3}'.format(l=self.lift_at*100, s=out_perfo['lift'])) # 2 * (precision * recall) / (precision + recall) + print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) # 2 * (precision * recall) / (precision + recall) + + else : + raise ValueError('Please call .plotROCCurve() method first to get the best threshold for probabilities, and try again') + + def plotLift(self, desc : str=None, save_pth : str=None): + ''' + Method plots lift per decile + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- +# inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) + for perc_lift in np.arange(0.05,1.05,0.05)] + + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + plt.style.use('seaborn-darkgrid') + + nrows = len(lifts) + x_labels = [nrows/2-x/2 for x in np.arange(0,nrows,1)] + + #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") + plt.bar(x_labels[::-1], lifts, align='center', color="green", width=0.2) + plt.ylabel('lift', fontsize=15) + plt.xlabel('decile', fontsize=15) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color='darkorange', linestyle='--', + xmin=0.05, xmax=0.9, linewidth=3, label='Baseline') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + + + '''------------------------------------------------------------------- + -------------------------------- UTILS ------------------------------- + -------------------------------------------------------------------''' + def estimateCutoff(self) -> float: + ''' + Estimates optimal cutoff based on maximization of AUC curve + https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python + + Parameters + ---------- + None + + Returns + ------- + best_cutoff : float + optimal cutoff as a float <0;1> + + ''' + fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) + i = np.arange(len(tpr)) + roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), + 'threshold' : pd.Series(thresholds, index=i)}) + roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] + + best_cutoff = list(roc_t['threshold']) + + return best_cutoff[0] + + + def _testA(self, test : np.ndarray, pred : np.ndarray, train_M : np.ndarray) -> Tuple[np.ndarray, np.ndarray]: + ''' + Limits the evaluation to potential A offers + (that a customer has not purchase in the train timeframe) + + Parameters + ---------- + test: true values -> array + pred: predictions as probabilities -> array + train_M : train matrix of interactions -> ndarray + + Output + ------ + testA: vector of interaction on potential A offers -> array + predA: vector of predictions on potential A offers -> array + ''' + + train = train_M.flatten() + testA = np.where(train>0, np.nan, test) + predA = np.where(train>0, np.nan, pred) + testA = testA[testA>=0] + predA = predA[predA>=0] + + return testA, predA + + def _evaluation(self): + ''' + Convenient function, returns various performance measures in a dict + + Parameters + ---------- + y_true: true values + y_pred: predictions as booleans + + Output + ------ + Returns dictionary with the measures + ''' + + dict_perfo = {'precision': mt.precision_score(self.y_true, self.y_pred_b), + 'accuracy': mt.accuracy_score(self.y_true, self.y_pred_b), + 'recall': mt.recall_score(self.y_true, self.y_pred_b), + 'F1': mt.f1_score(self.y_true, self.y_pred_b, average=None)[1], + 'lift': np.round(Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=self.lift_at),2), + 'AUC': mt.roc_auc_score(self.y_true, self.y_pred_p) + } + return dict_perfo + + @staticmethod + def liftCalculator(y_true : np.ndarray, y_pred : np.ndarray, lift_at : float=0.05, **kwargs) -> float: + ''' + Calculates lift given two arrays on specified level + + Parameters + ---------- + y_true: numpy array with true values + y_pred: numpy array with predictions (probabilities) + lift_at: lift at what top percentage + + Output + ------ + Scalar value, lift. + + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) + ''' + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_),1) + y_pred_ = y_pred_.reshape(len(y_pred_),1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum('ij->j',y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = y_data[y_data[:,1].argsort()[::-1]][:stop,0].reshape(stop, 1) + + #Calculate lift (einsum is very fast way of summing, needs specific shape) + inc_in_top_n = np.einsum('ij->j',data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence,2)[0] + + return lift + + '''------------------------------------------------------------------- + ------------------------JUST IN CASE ------------------------------- + -------------------------------------------------------------------''' + + def plotConfusionMatrix(self, labels : list=None, color : str='Reds', + save_pth : str=None, desc : str=None): + ''' + Plot Confusion matrix + + Parameters + ---------- + y_test: True values of target y + pred: Predicted values of target y, boolean + labels: labels for the matrix, if empty, values from y_test_ are used + color: Color of the matrix, its a cmap, so many values possible + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if labels is None: + labels = [str(lab) for lab in np.unique(self.y_true)] + + if desc is None: + desc = '' + + cm = mt.confusion_matrix(self.y_true, self.y_pred_b) + + fig, ax = plt.subplots(figsize=(8,5)) + ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, xticklabels=labels, yticklabels=labels) + ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeGains(self, save_pth : str=None, desc : str=None): + ''' + Functions plot cumulative gains + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + #--------------------------- + #Calculate cumulative gains + #-------------------------- + nrows = len(self.y_true) + npositives = self.y_true.sum() + df_y_pred = pd.DataFrame({"y":self.y_true, "y_pred":self.y_pred_p}).sort_values(by='y_pred', ascending=False).reset_index(drop=True) + cgains = [0] + for stop in (np.linspace(0.01,1,100)*nrows).astype(int): + cgains.append(round(df_y_pred.loc[:stop,'y'].sum()/npositives*max(100,1),2)) + + #--------------------------- + #Plot it + #--------------------------- + plt.style.use('seaborn-darkgrid') + fig, ax_cgains = plt.subplots(figsize=(8,5)) + ax_cgains.plot(cgains, color='blue', linewidth=3, label='cumulative gains') + ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, ls="--", color="darkorange", label='random selection') + ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) + + ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) + #Format axes + ax_cgains.set_xlim([0,100]) + ax_cgains.set_ylim([0,100]) + #Format ticks + ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_yticks()]) + ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_xticks()]) + #Legend + ax_cgains.legend(loc='lower right') + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeResponse(self, desc : str=None, save_pth : str=None): + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- + inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) + for perc_lift in np.arange(0.1,1.1,0.1)] + lifts = np.array(lifts)*inc_rate*100 + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + #plt.style.use('seaborn-darkgrid') + plt.style.use('default') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0,nrows,1)] + + #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") + plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") + plt.ylabel('response (%)', fontsize=16) + plt.xlabel('decile', fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', + xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative response {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + +def plotIncidence(df, variable, dim=(12,8)): + ''' + Method plots Incidence plot on train partition + Returns plot + ---------------------------------------------------- + df: dataframe with cleaned, binned, partitioned and prepared data + variable: variable for which the incidence plot will be shown` + dim: tuple with width and lentgh of the plot + ---------------------------------------------------- + ''' + def masterOfOrder(x): + ''' + Function converts interval or string (category) to a number, so the incidence plot can be orderd. + In case of interval -> '(151, 361]' to integer 151. + In case of string -> order is alphabetical + Missings and Non-significants are always put at the end + + Parameters + ---------- + x: value to be converted + + Output + ------ + Order of given value + ''' + x_split = x.split(',')[0] + replace_strings = (('...', '0'),('Missing','999999999999'), ('Non-significants','999999999999')) + for repl_str in replace_strings: + x_split = x_split.replace(repl_str[0], repl_str[1]) + x_split = x_split.strip("()[]") + + try: + order = float(x_split) + except: + LETTERS = {letter: index for index, letter in enumerate(ascii_lowercase, start=1)} + order = LETTERS[x[0].lower()] + + return order + + plt.style.use('seaborn-darkgrid') + + #---------------------------------- + #------ Prepare the data -------- + #---------------------------------- + #Set up the variable and dataframe + var_prefix = 'B_' + variable + df_plt = df[['TARGET', var_prefix]][df['PARTITION'] == 'train'].copy() + + #Aggregate the data + avg_inc_rate = df_plt['TARGET'].mean() + + aggregations = { + 'bin_inc_rate': 'mean', + 'bin_size': 'count' + } + df_plt = df_plt.groupby(var_prefix, as_index=False)['TARGET'].agg(aggregations) + df_plt['avg_inc_rate'] = avg_inc_rate + + #create a sort column and sort by it + df_plt['sort_by'] = df_plt[var_prefix].apply(lambda x: masterOfOrder(x)) + df_plt.sort_values(by='sort_by', ascending=True, inplace=True) + df_plt.reset_index(inplace=True) + + #---------------------------------- + #----- Plot the incidence ------- + #---------------------------------- + fig, ax = plt.subplots(figsize=dim) + ##First Axis + #Bin size + y_pos = np.arange(len(df_plt[var_prefix])) + plt.bar(y_pos, df_plt['bin_size'].values.tolist(), align='center', color="cornflowerblue") + plt.xticks(y_pos, df_plt[var_prefix]) + plt.ylabel('Bin Size') + plt.xlabel(variable + ' Bins') + + max_inc = max(df_plt['bin_inc_rate']) + + ##Second Axis + ax2 = ax.twinx() + #incidence rate per bin + plt.plot(df_plt['bin_inc_rate'], color="darkorange", marker=".", markersize=20, linewidth=3, label='incidence rate per bin') + plt.plot(df_plt['avg_inc_rate'], color="dimgrey", linewidth=4, label='average incidence rate') + ax2.plot(np.nan, "cornflowerblue", linewidth=6, label = 'bin size') #dummy line to have label on second axis from first + ax2.set_yticks(np.arange(0, max_inc+0.05, 0.05)) + ax2.set_yticklabels(['{:3.1f}%'.format(x*100) for x in ax2.get_yticks()]) + plt.ylabel('Incidence') + + ##Set Axis + sns.despine(ax=ax, right=True, left=True) + sns.despine(ax=ax2, left=True, right=False) + ax2.spines['right'].set_color('white') + + #remove white line from second grid axes + #the white lines are reguler, Spyder sometimes fails to visualize it (try to export the pic!) + ax2.grid(False) + + ##Description + fig.suptitle('Incidence Plot - ' + variable, fontsize=20, y=1.02) + ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=1, mode="expand", borderaxespad=0.) + plt.show() diff --git a/dev/preprocessor/develop.py b/dev/preprocessor/develop.py new file mode 100644 index 0000000..b1cb9bf --- /dev/null +++ b/dev/preprocessor/develop.py @@ -0,0 +1,279 @@ +#%% +import pandas as pd +import numpy as np +from random import shuffle +from scipy import stats +from typing import Dict, Tuple + +import logging +log = logging.getLogger(__name__) + +ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" +df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") +df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) + +split = ['TRAIN']*int(df_data.shape[0]*0.5) + \ + ['TEST']*int(df_data.shape[0]*0.2)+ \ + ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.3)) + +shuffle(split) + +df_data['PARTITION'] = split + +df_x = pd.DataFrame(df_data[['Parch', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) +df_y = df_data['TARGET'][df_data['PARTITION'] == "TRAIN"] + + +#%% +column = 'Embarked' +scale_cont = True +incidence_mean = df_y.mean() +pval_thresh = 0.001 +keep_categories = [] +keep = 'Missing' +category_map = {} +replace_with = 'non-significant' + +for category in grps: + #category = 'S' + df_aux = pd.concat([df_x[column], df_y], axis=1) + df_aux['obs_other'] = np.where(df_aux[column] == category, 0, 1) + + cont_table = pd.crosstab(df_aux['obs_other'], df_aux['TARGET'], margins=False) + + if scale_cont: + size_other_cat = cont_table.iloc[1].sum() + cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cat + cont_table.iloc[1, 1] = incidence_mean*size_other_cat + cont_table = cont_table.values.astype(np.int64) + + pval = stats.chi2_contingency(cont_table, correction=False)[1] + #0.17914169501249405 + + if pval<=pval_thresh: + keep_categories.append(category) + +if keep not in keep_categories and keep in df_x[column].unique().tolist(): + keep_categories.append(keep) + +for category in df_x[column].unique().tolist(): + if category in keep_categories: + category_map[category] = category + else: + category_map[category] = replace_with + + + + + +#%% + +class CategoryRegrouper(): + + """ + TOOD + -test the keep_categories and give warning if category not in column + -transform will be just df.replace() with a dict + -I am keeping missings, but not in the original code, inspect + -ask geert about the _removeCategories function + + -write the rest + -combine with categorical_processor.py + -add to init + -test if same as the old code + -unit tests + + Regroups categories in categorical variables if based on signicicance + with target variable + + Attributes + ---------- + scale_cont : bool, default=True + whether contingency table should be scaled before chi-2 + pval_thresh : float, default=0.001 + significance threshold for regroupping + regroup_rename : str, default='non-significant' + new name of non-significant regroupped variables + missing_rename : str, default='Missing' + new name of missing categories + keep_missing : bool, default=True + whether missing category should be kept in the result + forced_categories : Dict, default=None + dictionary to force categories - + for each colum dict of {col:[forced vars]} + """ + + def __init__(self, scale_cont: bool=True, + pval_thresh: float=0.001, + regroup_rename: str="non-significant", + missing_rename: str="Missing", + keep_missing: bool=True, + forced_categories: Dict=None): + self.scale_cont = scale_cont + self.pval_thresh = pval_thresh + self.regroup_rename = regroup_rename + self.missing_rename = missing_rename + self.keep_missing = keep_missing + self.forced_categories = forced_categories + + def fit(self): + pass + + def _fit_column(self, X: pd.DataFrame, + y: pd.Series, + column: str) -> Dict: + + category_map = {} + keep_categories = [] + self.incidence_mean = y.mean() + all_uq_categories = X[column].unique().tolist() + + # Rename target + y.rename("TARGET", inplace=True) + + # Replace missings + X = self._replaceMissings(X=X, column=column, + replace_with=self.missing_rename) + + # Remove small categories + categories = self._removeCategories(X=X, y=y, column=column) + + # Inspect remaining categories and test significance + for category in categories: + df_aux = pd.concat([X[column], y], axis=1) + df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) + cont_table = pd.crosstab(index=df_aux['other_cats'], + columns=df_aux['TARGET'], + margins=False) + + # if true, we scale the "other" categories + if self.scale_cont: + size_other_cats = cont_table.iloc[1].sum() + cont_table.iloc[1, 0] = (1-self.incidence_mean)*size_other_cats + cont_table.iloc[1, 1] = self.incidence_mean*size_other_cats + cont_table = cont_table.values.astype(np.int64) + + pval = stats.chi2_contingency(cont_table, correction=False)[1] + + # If significant, keep it + if pval <= self.pval_thresh: + keep_categories.append(category) + + # Keep "Missing" even if it wasn't selected if + # it is in the original categories and set to True + if ((self.missing_rename not in keep_categories) and + (self.missing_rename in all_uq_categories) and self.keep_missing): + keep_categories.append(self.missing_rename) + + # Keep forced categories + if self.forced_categories is not None: + # If doesnt exists, give warning + forced = [col for col in self.forced_categories[column] + if col in all_uq_categories] + + # Extend list and remove duplicates + keep_categories = list(set(keep_categories.extend(forced))) + + difference = set(forced) - set(self.forced_categories[column]) + if len(difference) > 0: + log.warning("Following forced categories: {} " + "are not in column: {}.".format(difference, + column)) + + # Return dictionary as {old column : new column} + for category in all_uq_categories: + if category in keep_categories: + category_map[category] = category + else: + category_map[category] = self.regroup_rename + + return category_map + + def transform(self): + pass + + def _transform_column(self): + pass + + def fit_transform(self): + pass + + def _replaceMissings(self, X: pd.DataFrame, + column: str, + replace_with: str='Missing') -> pd.DataFrame: + """ + Method replaces missing and empty cells with `Missing` (default) in + a pd.DataFrame + + df_tst = _replaceMissings(X=df_x, column='Embarked') + + Parameters + ---------- + X : pd.DataFrame + Dataframe where a value will be replaced if empty or nan + column : str + Column to be analyzed for missings + replace_with : str default='Missing' + string to replace the missings + + Raises + ------ + ValueError + in case input column is not a string + + Returns + ------- + pd.DataFrame + modified dataframe with replaced missings + """ + if not X[column].dtype == 'object': + raise TypeError("columns must be a string") + + X[column].fillna(replace_with, inplace=True) + X[column] = X[column].str.strip() + X[column].replace('', replace_with, inplace=True) + + return X + + def _removeCategories(self, X: pd.DataFrame, + y: pd.Series, + column: str, + threshold: int=5) -> np.ndarray: + """ + Method removes category which fail to meet certain condition + + grps = _removeGroups(X=df_x, y=df_y, column='Embarked') + + Parameters + ---------- + X : pd.DataFrame + Dataframe with columns to be inspected for group removal + y : pd.Series + Series with target + column : str + Column to be analyzed group removal + threshold : int default=5 + Threshold for group removal + + Returns + ------- + np.ndarray + numpy array with groups to be kept + """ + category_cnts = pd.DataFrame(X.groupby(column)[column].count()) + train_inc = y.mean() + factor = max(train_inc, 1-train_inc) + keep_categories = category_cnts.where((category_cnts*factor) > + threshold) + + return np.array(keep_categories.index.tolist()) + + + +#%% +CR = CategoryRegrouper() + +output = CR._fit_column(X=df_x, y=df_y, column='Embarked') +output + +#%% diff --git a/dev/preprocessor/new_regroup.py b/dev/preprocessor/new_regroup.py new file mode 100644 index 0000000..6a467c2 --- /dev/null +++ b/dev/preprocessor/new_regroup.py @@ -0,0 +1,189 @@ +#%% +import pandas as pd +import numpy as np +from random import shuffle +from scipy import stats + +ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" +df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") +df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) + +split = ['TRAIN']*int(df_data.shape[0]*0.5) + \ + ['TEST']*int(df_data.shape[0]*0.2)+ \ + ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.3)) + +shuffle(split) + +df_data['PARTITION'] = split + +#%% +''' ORIGINAL CODE ''' +def __regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): + ''' + Method regroups categorical variables + Returns DF mask + ---------------------------------------------------- + var: input pd.Serie with cat column + target: pd.Serie with target variable + train: pd.Serie with parition variable + pval_thresh: threshold for regrouping + dummy: scale of booleans (?) + keep: keep specific groups (?) + rename: rename the insignificant category + ---------------------------------------------------- + - Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group + - The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or + - remaining groups average incidence' (if dummy=False) + - Groups with a pvalue above the threshold are relabled to a single group + ''' + + # Define the chi² test condition + # Groups that do not meet the condition are not analyzed and will be unconditionally relabled + def _chi2cond_(var=var,target=target,train=train): + varcounts = var[train].groupby(by=var).count() + train_inc = target[train].sum()/len(target[train]) + factor = max(train_inc, 1-train_inc) + analyze_mask = (varcounts*factor)>5 + analyze_groups = analyze_mask.index[analyze_mask].values + return analyze_groups + + # Compute overal incidence mean + incidence_mean = target[train].mean() + # Create container of which groups will be kept, compared to the groups which will be relabled + keepgroups = [] + # Cycle and test each group that meets the chi² condition + for group in _chi2cond_(): + # Container for target 0/1 observations of the group under scrutiny + obs_group = [] + # Counts of the target 0/1 occurences for the group under scrutiny + obs_group.append(((target[train]==0)&(var[train]==group)).sum()) + obs_group.append(((target[train]==1)&(var[train]==group)).sum()) + obs_group = np.array(obs_group) + # Container for target 0/1 observations of the remaining groups together + obs_other = [] + # Counts of the target 0/1 occurences for the remaining groups together + obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) + obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) + obs_other = np.array(obs_other) + # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence + # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups + if dummy: + obs_other_size = obs_other.sum() + obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) + obs_other[1]=( incidence_mean)*obs_other_size + obs = np.array([obs_group,obs_other]) + # Place at least 1 observation to avoid error in chi2 test + obs[obs==0] = 1 + # Perform chi² test + pval = stats.chi2_contingency(obs, correction=False)[1] + # If pval outperforms threshold, append the group in the keepgroups list + if pval<=pval_thresh: + keepgroups.append(group) + #elif group==keep: + # keepgroups.append(group) + # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list + if keep not in keepgroups: + keepgroups.append(keep) + # Makes a list of all groups not in the keepgroups list + regroup_mask = [val not in keepgroups for val in var.values] + var_regroup = var.copy() + # Rename those groups + var_regroup[regroup_mask] = rename + var_regroup.name = "B_"+var.name + info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) + return var_regroup, info + +#%% +''' RUN ORIGINAL CODE ''' +result = __regroup(var=df_data['Embarked'], #Cabin, Pclass, SibSp, Parch, Embarked + target=df_data.loc[:,'TARGET'], + train=df_data['PARTITION']=='TRAIN', + pval_thresh=0.05, + dummy=True, + keep='Missing', + rename='Non-significants') + +print(result[0].unique()) +print(result[0].head(n=5)) +print(result[1]) +df_tst = result[0] + +#%% +''' TEST CHISQR CONDITION ''' +def _chi2cond_(var,target,train): + #simple group by - pandas series + varcounts = var[train].groupby(by=var).count() + #train incidence - 0.3775280898876405 + train_inc = target[train].sum()/len(target[train]) + #Why? -0.6224719101123595 + factor = max(train_inc, 1-train_inc) + #which groups to analyze - boolean + analyze_mask = (varcounts*factor)>5 + #filter groups to be kept - array([0, 1, 2], dtype=int64) + analyze_groups = analyze_mask.index[analyze_mask].values + return analyze_groups + +chi = _chi2cond_(var=df_data['Embarked'], + target=df_data.loc[:,'TARGET'], + train=df_data['PARTITION']=='TRAIN') + +#%% +varcounts = df_data['Parch'][df_data['PARTITION']=='TRAIN'].groupby(by=df_data['Parch']).count() +train_inc = df_data.loc[:,'TARGET'][df_data['PARTITION']=='TRAIN'].sum()/len(df_data.loc[:,'TARGET'][df_data['PARTITION']=='TRAIN']) +factor = max(train_inc, 1-train_inc) +analyze_mask = (varcounts*factor)>5 +analyze_groups = analyze_mask.index[analyze_mask].values + +#%% +df_data['TARGET'][df_data['PARTITION']=='TRAIN'].mean() + +#%% +''' TEST TESTING ''' +target = df_data.loc[:,'TARGET'] +train = df_data['PARTITION']=='TRAIN' +var = df_data['Embarked'] + +for group in chi: + group == 'S' + # Container for target 0/1 observations of the group under scrutiny + obs_group = [] + # Counts of the target 0/1 occurences for the group under scrutiny + obs_group.append(((target[train]==0)&(var[train]==group)).sum()) + obs_group.append(((target[train]==1)&(var[train]==group)).sum()) + obs_group = np.array(obs_group) + # Container for target 0/1 observations of the remaining groups together + obs_other = [] + # Counts of the target 0/1 occurences for the remaining groups together + obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) + obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) + obs_other = np.array(obs_other) + +#S +#obs_group +#array([225, 95], dtype=int64) +# +#obs_other +#array([58, 67], dtype=int64) + +#%% +pd.crosstab(df.regiment, df_data.loc[:,'TARGET'], margins=True) + +#%% +incidence_mean = target[train].mean() +dummy=True + +if dummy: + obs_other_size = obs_other.sum() #400 + obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) + obs_other[1]=( incidence_mean)*obs_other_size +obs = np.array([obs_group,obs_other]) +# Place at least 1 observation to avoid error in chi2 test +obs[obs==0] = 1 +# Perform chi² test +pval = stats.chi2_contingency(obs, correction=False)[1] + +#obs +#array([[ 19, 26], +# [248, 151]], dtype=int64) + +#%% From dde17de1990fbdfd45da19d9ba49f06ef7ceb9a1 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 11 Oct 2019 13:51:55 +0200 Subject: [PATCH 10/98] Add matrics module to cobra --- cobra/metrics/__init__.py | 3 + cobra/metrics/all_metrics_plots.py | 527 +++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 cobra/metrics/__init__.py create mode 100644 cobra/metrics/all_metrics_plots.py diff --git a/cobra/metrics/__init__.py b/cobra/metrics/__init__.py new file mode 100644 index 0000000..67656f7 --- /dev/null +++ b/cobra/metrics/__init__.py @@ -0,0 +1,3 @@ +from .all_metrics_plots import Evaluator + +__all__ = ['Evaluator'] \ No newline at end of file diff --git a/cobra/metrics/all_metrics_plots.py b/cobra/metrics/all_metrics_plots.py new file mode 100644 index 0000000..d18bb64 --- /dev/null +++ b/cobra/metrics/all_metrics_plots.py @@ -0,0 +1,527 @@ +""" +====================================================================================== +--------------------------------------- Evaluation Class code ------------------------ +====================================================================================== +author: jan.benisek@pythonpredictins.com - benoit.vandekerkhove@pythonpredictions.com +date: 23/09/2019 +purpose: library for model evaluation class + +""" +#%% +import numpy as np +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt +import sklearn.metrics as mt +from typing import Tuple +#%% + + +class Evaluator(): + ''' + Class to evaluate models + + Parameters + ----------- + y_true : array, shape = [1, n_features] + array with true values + + y_pred_p : array, shape = [1, n_features] + array with predicted values (probabilities) + + lift_at : int , default=0.05 + calculate lift at given level (0-1) + + save_pth : str, default=None + path to where save the plot + + binary_cutoff : float, default=0.5 + cutoff to convert predictions to binary + + ''' + + def __init__(self, y_true: np.ndarray, y_pred_p: np.ndarray, + lift_at: float=0.05, save_pth: str=None, binary_cutoff: int=0.5): + + self.y_true = y_true.flatten() + self.y_pred_p = y_pred_p.flatten() #As probability + self.lift_at = lift_at + self.save_pth = save_pth + self.binary_cutoff = binary_cutoff + + self.y_pred_b = np.where(self.y_pred_p > self.binary_cutoff,1,0) + + + + + '''============================================================= + ----------------------------- PLOTS ---------------------------- + =============================================================''' + def plotROCCurve(self, desc: str=None): + ''' + Plot ROC curve and print best cutoff value + Transform probabilities predictions to bool based on best AUC based cutoff + + Parameters + ---------- + desc : str, default=None + description of the plot, used also as a name of saved plot + + ''' + if desc is None: + desc = '' + + fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) + + #--------------------------- + #Calculate AUC + #-------------------------- + score = mt.roc_auc_score(self.y_true, self.y_pred_p) + + fig, ax = plt.subplots(figsize=(8,5)) + ax.plot(fpr,tpr, color='darkorange', lw=2, label='ROC curve (area = {s:.3})'.format(s=score)) + ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') + ax.set_xlabel('False Positive Rate', fontsize=15) + ax.set_ylabel('True Positive Rate', fontsize=15) + ax.legend(loc="lower right") + ax.set_title('ROC Curve {}' .format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + '''============================================================= + ---------------------------- METRICS --------------------------- + =============================================================''' + + def printPerformance(self): + ''' + Print out performance measures + + EV.printPerformance() + %timeit 2min 19s ± 784 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + ''' + + if self.threshold != np.nan : + out_perfo = self._evaluation() + + print('=== Test on', self.test_on, '===') + print('Precision: {s:.3}'.format(s=out_perfo['precision'])) #If we mark customer as a churner, how often we are correct + print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) #Overall performance + print('Recall: {s:.3}'.format(s=out_perfo['recall'])) #How many churners can the model detect + print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) # 2 * (precision * recall) / (precision + recall) + print('Lift at top {l}%: {s:.3}'.format(l=self.lift_at*100, s=out_perfo['lift'])) # 2 * (precision * recall) / (precision + recall) + print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) # 2 * (precision * recall) / (precision + recall) + + else : + raise ValueError('Please call .plotROCCurve() method first to get the best threshold for probabilities, and try again') + + def plotLift(self, desc : str=None, save_pth : str=None): + ''' + Method plots lift per decile + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- +# inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) + for perc_lift in np.arange(0.05,1.05,0.05)] + + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + plt.style.use('seaborn-darkgrid') + + nrows = len(lifts) + x_labels = [nrows/2-x/2 for x in np.arange(0,nrows,1)] + + #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") + plt.bar(x_labels[::-1], lifts, align='center', color="green", width=0.2) + plt.ylabel('lift', fontsize=15) + plt.xlabel('decile', fontsize=15) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color='darkorange', linestyle='--', + xmin=0.05, xmax=0.9, linewidth=3, label='Baseline') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + + + '''------------------------------------------------------------------- + -------------------------------- UTILS ------------------------------- + -------------------------------------------------------------------''' + def estimateCutoff(self) -> float: + ''' + Estimates optimal cutoff based on maximization of AUC curve + https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python + + Parameters + ---------- + None + + Returns + ------- + best_cutoff : float + optimal cutoff as a float <0;1> + + ''' + fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) + i = np.arange(len(tpr)) + roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), + 'threshold' : pd.Series(thresholds, index=i)}) + roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] + + best_cutoff = list(roc_t['threshold']) + + return best_cutoff[0] + + + def _testA(self, test : np.ndarray, pred : np.ndarray, train_M : np.ndarray) -> Tuple[np.ndarray, np.ndarray]: + ''' + Limits the evaluation to potential A offers + (that a customer has not purchase in the train timeframe) + + Parameters + ---------- + test: true values -> array + pred: predictions as probabilities -> array + train_M : train matrix of interactions -> ndarray + + Output + ------ + testA: vector of interaction on potential A offers -> array + predA: vector of predictions on potential A offers -> array + ''' + + train = train_M.flatten() + testA = np.where(train>0, np.nan, test) + predA = np.where(train>0, np.nan, pred) + testA = testA[testA>=0] + predA = predA[predA>=0] + + return testA, predA + + def _evaluation(self): + ''' + Convenient function, returns various performance measures in a dict + + Parameters + ---------- + y_true: true values + y_pred: predictions as booleans + + Output + ------ + Returns dictionary with the measures + ''' + + dict_perfo = {'precision': mt.precision_score(self.y_true, self.y_pred_b), + 'accuracy': mt.accuracy_score(self.y_true, self.y_pred_b), + 'recall': mt.recall_score(self.y_true, self.y_pred_b), + 'F1': mt.f1_score(self.y_true, self.y_pred_b, average=None)[1], + 'lift': np.round(Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=self.lift_at),2), + 'AUC': mt.roc_auc_score(self.y_true, self.y_pred_p) + } + return dict_perfo + + @staticmethod + def liftCalculator(y_true : np.ndarray, y_pred : np.ndarray, lift_at : float=0.05, **kwargs) -> float: + ''' + Calculates lift given two arrays on specified level + + Parameters + ---------- + y_true: numpy array with true values + y_pred: numpy array with predictions (probabilities) + lift_at: lift at what top percentage + + Output + ------ + Scalar value, lift. + + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) + ''' + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_),1) + y_pred_ = y_pred_.reshape(len(y_pred_),1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum('ij->j',y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = y_data[y_data[:,1].argsort()[::-1]][:stop,0].reshape(stop, 1) + + #Calculate lift (einsum is very fast way of summing, needs specific shape) + inc_in_top_n = np.einsum('ij->j',data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence,2)[0] + + return lift + + '''------------------------------------------------------------------- + ------------------------JUST IN CASE ------------------------------- + -------------------------------------------------------------------''' + + def plotConfusionMatrix(self, labels : list=None, color : str='Reds', + save_pth : str=None, desc : str=None): + ''' + Plot Confusion matrix + + Parameters + ---------- + y_test: True values of target y + pred: Predicted values of target y, boolean + labels: labels for the matrix, if empty, values from y_test_ are used + color: Color of the matrix, its a cmap, so many values possible + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if labels is None: + labels = [str(lab) for lab in np.unique(self.y_true)] + + if desc is None: + desc = '' + + cm = mt.confusion_matrix(self.y_true, self.y_pred_b) + + fig, ax = plt.subplots(figsize=(8,5)) + ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, xticklabels=labels, yticklabels=labels) + ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeGains(self, save_pth : str=None, desc : str=None): + ''' + Functions plot cumulative gains + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + #--------------------------- + #Calculate cumulative gains + #-------------------------- + nrows = len(self.y_true) + npositives = self.y_true.sum() + df_y_pred = pd.DataFrame({"y":self.y_true, "y_pred":self.y_pred_p}).sort_values(by='y_pred', ascending=False).reset_index(drop=True) + cgains = [0] + for stop in (np.linspace(0.01,1,100)*nrows).astype(int): + cgains.append(round(df_y_pred.loc[:stop,'y'].sum()/npositives*max(100,1),2)) + + #--------------------------- + #Plot it + #--------------------------- + plt.style.use('seaborn-darkgrid') + fig, ax_cgains = plt.subplots(figsize=(8,5)) + ax_cgains.plot(cgains, color='blue', linewidth=3, label='cumulative gains') + ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, ls="--", color="darkorange", label='random selection') + ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) + + ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) + #Format axes + ax_cgains.set_xlim([0,100]) + ax_cgains.set_ylim([0,100]) + #Format ticks + ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_yticks()]) + ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_xticks()]) + #Legend + ax_cgains.legend(loc='lower right') + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeResponse(self, desc : str=None, save_pth : str=None): + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- + inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) + for perc_lift in np.arange(0.1,1.1,0.1)] + lifts = np.array(lifts)*inc_rate*100 + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + #plt.style.use('seaborn-darkgrid') + plt.style.use('default') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0,nrows,1)] + + #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") + plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") + plt.ylabel('response (%)', fontsize=16) + plt.xlabel('decile', fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', + xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative response {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + +def plotIncidence(df, variable, dim=(12,8)): + ''' + Method plots Incidence plot on train partition + Returns plot + ---------------------------------------------------- + df: dataframe with cleaned, binned, partitioned and prepared data + variable: variable for which the incidence plot will be shown` + dim: tuple with width and lentgh of the plot + ---------------------------------------------------- + ''' + def masterOfOrder(x): + ''' + Function converts interval or string (category) to a number, so the incidence plot can be orderd. + In case of interval -> '(151, 361]' to integer 151. + In case of string -> order is alphabetical + Missings and Non-significants are always put at the end + + Parameters + ---------- + x: value to be converted + + Output + ------ + Order of given value + ''' + x_split = x.split(',')[0] + replace_strings = (('...', '0'),('Missing','999999999999'), ('Non-significants','999999999999')) + for repl_str in replace_strings: + x_split = x_split.replace(repl_str[0], repl_str[1]) + x_split = x_split.strip("()[]") + + try: + order = float(x_split) + except: + LETTERS = {letter: index for index, letter in enumerate(ascii_lowercase, start=1)} + order = LETTERS[x[0].lower()] + + return order + + plt.style.use('seaborn-darkgrid') + + #---------------------------------- + #------ Prepare the data -------- + #---------------------------------- + #Set up the variable and dataframe + var_prefix = 'B_' + variable + df_plt = df[['TARGET', var_prefix]][df['PARTITION'] == 'train'].copy() + + #Aggregate the data + avg_inc_rate = df_plt['TARGET'].mean() + + aggregations = { + 'bin_inc_rate': 'mean', + 'bin_size': 'count' + } + df_plt = df_plt.groupby(var_prefix, as_index=False)['TARGET'].agg(aggregations) + df_plt['avg_inc_rate'] = avg_inc_rate + + #create a sort column and sort by it + df_plt['sort_by'] = df_plt[var_prefix].apply(lambda x: masterOfOrder(x)) + df_plt.sort_values(by='sort_by', ascending=True, inplace=True) + df_plt.reset_index(inplace=True) + + #---------------------------------- + #----- Plot the incidence ------- + #---------------------------------- + fig, ax = plt.subplots(figsize=dim) + ##First Axis + #Bin size + y_pos = np.arange(len(df_plt[var_prefix])) + plt.bar(y_pos, df_plt['bin_size'].values.tolist(), align='center', color="cornflowerblue") + plt.xticks(y_pos, df_plt[var_prefix]) + plt.ylabel('Bin Size') + plt.xlabel(variable + ' Bins') + + max_inc = max(df_plt['bin_inc_rate']) + + ##Second Axis + ax2 = ax.twinx() + #incidence rate per bin + plt.plot(df_plt['bin_inc_rate'], color="darkorange", marker=".", markersize=20, linewidth=3, label='incidence rate per bin') + plt.plot(df_plt['avg_inc_rate'], color="dimgrey", linewidth=4, label='average incidence rate') + ax2.plot(np.nan, "cornflowerblue", linewidth=6, label = 'bin size') #dummy line to have label on second axis from first + ax2.set_yticks(np.arange(0, max_inc+0.05, 0.05)) + ax2.set_yticklabels(['{:3.1f}%'.format(x*100) for x in ax2.get_yticks()]) + plt.ylabel('Incidence') + + ##Set Axis + sns.despine(ax=ax, right=True, left=True) + sns.despine(ax=ax2, left=True, right=False) + ax2.spines['right'].set_color('white') + + #remove white line from second grid axes + #the white lines are reguler, Spyder sometimes fails to visualize it (try to export the pic!) + ax2.grid(False) + + ##Description + fig.suptitle('Incidence Plot - ' + variable, fontsize=20, y=1.02) + ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=1, mode="expand", borderaxespad=0.) + plt.show() From 03b5b4d3b957b0f3dd1b3f7699d9f29f497f8296 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 11 Oct 2019 13:53:14 +0200 Subject: [PATCH 11/98] Remove metrics module which was put in the wrong branch --- cobra/metrics/__init__.py | 3 - cobra/metrics/all_metrics_plots.py | 527 ----------------------------- 2 files changed, 530 deletions(-) delete mode 100644 cobra/metrics/__init__.py delete mode 100644 cobra/metrics/all_metrics_plots.py diff --git a/cobra/metrics/__init__.py b/cobra/metrics/__init__.py deleted file mode 100644 index 67656f7..0000000 --- a/cobra/metrics/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .all_metrics_plots import Evaluator - -__all__ = ['Evaluator'] \ No newline at end of file diff --git a/cobra/metrics/all_metrics_plots.py b/cobra/metrics/all_metrics_plots.py deleted file mode 100644 index d18bb64..0000000 --- a/cobra/metrics/all_metrics_plots.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -====================================================================================== ---------------------------------------- Evaluation Class code ------------------------ -====================================================================================== -author: jan.benisek@pythonpredictins.com - benoit.vandekerkhove@pythonpredictions.com -date: 23/09/2019 -purpose: library for model evaluation class - -""" -#%% -import numpy as np -import pandas as pd -import seaborn as sns -import matplotlib.pyplot as plt -import sklearn.metrics as mt -from typing import Tuple -#%% - - -class Evaluator(): - ''' - Class to evaluate models - - Parameters - ----------- - y_true : array, shape = [1, n_features] - array with true values - - y_pred_p : array, shape = [1, n_features] - array with predicted values (probabilities) - - lift_at : int , default=0.05 - calculate lift at given level (0-1) - - save_pth : str, default=None - path to where save the plot - - binary_cutoff : float, default=0.5 - cutoff to convert predictions to binary - - ''' - - def __init__(self, y_true: np.ndarray, y_pred_p: np.ndarray, - lift_at: float=0.05, save_pth: str=None, binary_cutoff: int=0.5): - - self.y_true = y_true.flatten() - self.y_pred_p = y_pred_p.flatten() #As probability - self.lift_at = lift_at - self.save_pth = save_pth - self.binary_cutoff = binary_cutoff - - self.y_pred_b = np.where(self.y_pred_p > self.binary_cutoff,1,0) - - - - - '''============================================================= - ----------------------------- PLOTS ---------------------------- - =============================================================''' - def plotROCCurve(self, desc: str=None): - ''' - Plot ROC curve and print best cutoff value - Transform probabilities predictions to bool based on best AUC based cutoff - - Parameters - ---------- - desc : str, default=None - description of the plot, used also as a name of saved plot - - ''' - if desc is None: - desc = '' - - fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) - - #--------------------------- - #Calculate AUC - #-------------------------- - score = mt.roc_auc_score(self.y_true, self.y_pred_p) - - fig, ax = plt.subplots(figsize=(8,5)) - ax.plot(fpr,tpr, color='darkorange', lw=2, label='ROC curve (area = {s:.3})'.format(s=score)) - ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') - ax.set_xlabel('False Positive Rate', fontsize=15) - ax.set_ylabel('True Positive Rate', fontsize=15) - ax.legend(loc="lower right") - ax.set_title('ROC Curve {}' .format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - '''============================================================= - ---------------------------- METRICS --------------------------- - =============================================================''' - - def printPerformance(self): - ''' - Print out performance measures - - EV.printPerformance() - %timeit 2min 19s ± 784 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) - ''' - - if self.threshold != np.nan : - out_perfo = self._evaluation() - - print('=== Test on', self.test_on, '===') - print('Precision: {s:.3}'.format(s=out_perfo['precision'])) #If we mark customer as a churner, how often we are correct - print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) #Overall performance - print('Recall: {s:.3}'.format(s=out_perfo['recall'])) #How many churners can the model detect - print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) # 2 * (precision * recall) / (precision + recall) - print('Lift at top {l}%: {s:.3}'.format(l=self.lift_at*100, s=out_perfo['lift'])) # 2 * (precision * recall) / (precision + recall) - print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) # 2 * (precision * recall) / (precision + recall) - - else : - raise ValueError('Please call .plotROCCurve() method first to get the best threshold for probabilities, and try again') - - def plotLift(self, desc : str=None, save_pth : str=None): - ''' - Method plots lift per decile - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- -# inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) - for perc_lift in np.arange(0.05,1.05,0.05)] - - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - plt.style.use('seaborn-darkgrid') - - nrows = len(lifts) - x_labels = [nrows/2-x/2 for x in np.arange(0,nrows,1)] - - #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") - plt.bar(x_labels[::-1], lifts, align='center', color="green", width=0.2) - plt.ylabel('lift', fontsize=15) - plt.xlabel('decile', fontsize=15) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=1, color='darkorange', linestyle='--', - xmin=0.05, xmax=0.9, linewidth=3, label='Baseline') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - - - '''------------------------------------------------------------------- - -------------------------------- UTILS ------------------------------- - -------------------------------------------------------------------''' - def estimateCutoff(self) -> float: - ''' - Estimates optimal cutoff based on maximization of AUC curve - https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python - - Parameters - ---------- - None - - Returns - ------- - best_cutoff : float - optimal cutoff as a float <0;1> - - ''' - fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) - i = np.arange(len(tpr)) - roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), - 'threshold' : pd.Series(thresholds, index=i)}) - roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] - - best_cutoff = list(roc_t['threshold']) - - return best_cutoff[0] - - - def _testA(self, test : np.ndarray, pred : np.ndarray, train_M : np.ndarray) -> Tuple[np.ndarray, np.ndarray]: - ''' - Limits the evaluation to potential A offers - (that a customer has not purchase in the train timeframe) - - Parameters - ---------- - test: true values -> array - pred: predictions as probabilities -> array - train_M : train matrix of interactions -> ndarray - - Output - ------ - testA: vector of interaction on potential A offers -> array - predA: vector of predictions on potential A offers -> array - ''' - - train = train_M.flatten() - testA = np.where(train>0, np.nan, test) - predA = np.where(train>0, np.nan, pred) - testA = testA[testA>=0] - predA = predA[predA>=0] - - return testA, predA - - def _evaluation(self): - ''' - Convenient function, returns various performance measures in a dict - - Parameters - ---------- - y_true: true values - y_pred: predictions as booleans - - Output - ------ - Returns dictionary with the measures - ''' - - dict_perfo = {'precision': mt.precision_score(self.y_true, self.y_pred_b), - 'accuracy': mt.accuracy_score(self.y_true, self.y_pred_b), - 'recall': mt.recall_score(self.y_true, self.y_pred_b), - 'F1': mt.f1_score(self.y_true, self.y_pred_b, average=None)[1], - 'lift': np.round(Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=self.lift_at),2), - 'AUC': mt.roc_auc_score(self.y_true, self.y_pred_p) - } - return dict_perfo - - @staticmethod - def liftCalculator(y_true : np.ndarray, y_pred : np.ndarray, lift_at : float=0.05, **kwargs) -> float: - ''' - Calculates lift given two arrays on specified level - - Parameters - ---------- - y_true: numpy array with true values - y_pred: numpy array with predictions (probabilities) - lift_at: lift at what top percentage - - Output - ------ - Scalar value, lift. - - 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) - ''' - #Make sure it is numpy array - y_true_ = np.array(y_true) - y_pred_ = np.array(y_pred) - - #Make sure it has correct shape - y_true_ = y_true_.reshape(len(y_true_),1) - y_pred_ = y_pred_.reshape(len(y_pred_),1) - - #Merge data together - y_data = np.hstack([y_true_, y_pred_]) - - #Calculate necessary variables - nrows = len(y_data) - stop = int(np.floor(nrows*lift_at)) - avg_incidence = np.einsum('ij->j',y_true_)/float(len(y_true_)) - - #Sort and filter data - data_sorted = y_data[y_data[:,1].argsort()[::-1]][:stop,0].reshape(stop, 1) - - #Calculate lift (einsum is very fast way of summing, needs specific shape) - inc_in_top_n = np.einsum('ij->j',data_sorted)/float(len(data_sorted)) - - lift = np.round(inc_in_top_n/avg_incidence,2)[0] - - return lift - - '''------------------------------------------------------------------- - ------------------------JUST IN CASE ------------------------------- - -------------------------------------------------------------------''' - - def plotConfusionMatrix(self, labels : list=None, color : str='Reds', - save_pth : str=None, desc : str=None): - ''' - Plot Confusion matrix - - Parameters - ---------- - y_test: True values of target y - pred: Predicted values of target y, boolean - labels: labels for the matrix, if empty, values from y_test_ are used - color: Color of the matrix, its a cmap, so many values possible - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if labels is None: - labels = [str(lab) for lab in np.unique(self.y_true)] - - if desc is None: - desc = '' - - cm = mt.confusion_matrix(self.y_true, self.y_pred_b) - - fig, ax = plt.subplots(figsize=(8,5)) - ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, xticklabels=labels, yticklabels=labels) - ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeGains(self, save_pth : str=None, desc : str=None): - ''' - Functions plot cumulative gains - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - #--------------------------- - #Calculate cumulative gains - #-------------------------- - nrows = len(self.y_true) - npositives = self.y_true.sum() - df_y_pred = pd.DataFrame({"y":self.y_true, "y_pred":self.y_pred_p}).sort_values(by='y_pred', ascending=False).reset_index(drop=True) - cgains = [0] - for stop in (np.linspace(0.01,1,100)*nrows).astype(int): - cgains.append(round(df_y_pred.loc[:stop,'y'].sum()/npositives*max(100,1),2)) - - #--------------------------- - #Plot it - #--------------------------- - plt.style.use('seaborn-darkgrid') - fig, ax_cgains = plt.subplots(figsize=(8,5)) - ax_cgains.plot(cgains, color='blue', linewidth=3, label='cumulative gains') - ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, ls="--", color="darkorange", label='random selection') - ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) - - ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) - #Format axes - ax_cgains.set_xlim([0,100]) - ax_cgains.set_ylim([0,100]) - #Format ticks - ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_yticks()]) - ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_xticks()]) - #Legend - ax_cgains.legend(loc='lower right') - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeResponse(self, desc : str=None, save_pth : str=None): - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- - inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) - for perc_lift in np.arange(0.1,1.1,0.1)] - lifts = np.array(lifts)*inc_rate*100 - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - #plt.style.use('seaborn-darkgrid') - plt.style.use('default') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0,nrows,1)] - - #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") - plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") - plt.ylabel('response (%)', fontsize=16) - plt.xlabel('decile', fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', - xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative response {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - -def plotIncidence(df, variable, dim=(12,8)): - ''' - Method plots Incidence plot on train partition - Returns plot - ---------------------------------------------------- - df: dataframe with cleaned, binned, partitioned and prepared data - variable: variable for which the incidence plot will be shown` - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - def masterOfOrder(x): - ''' - Function converts interval or string (category) to a number, so the incidence plot can be orderd. - In case of interval -> '(151, 361]' to integer 151. - In case of string -> order is alphabetical - Missings and Non-significants are always put at the end - - Parameters - ---------- - x: value to be converted - - Output - ------ - Order of given value - ''' - x_split = x.split(',')[0] - replace_strings = (('...', '0'),('Missing','999999999999'), ('Non-significants','999999999999')) - for repl_str in replace_strings: - x_split = x_split.replace(repl_str[0], repl_str[1]) - x_split = x_split.strip("()[]") - - try: - order = float(x_split) - except: - LETTERS = {letter: index for index, letter in enumerate(ascii_lowercase, start=1)} - order = LETTERS[x[0].lower()] - - return order - - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - #Set up the variable and dataframe - var_prefix = 'B_' + variable - df_plt = df[['TARGET', var_prefix]][df['PARTITION'] == 'train'].copy() - - #Aggregate the data - avg_inc_rate = df_plt['TARGET'].mean() - - aggregations = { - 'bin_inc_rate': 'mean', - 'bin_size': 'count' - } - df_plt = df_plt.groupby(var_prefix, as_index=False)['TARGET'].agg(aggregations) - df_plt['avg_inc_rate'] = avg_inc_rate - - #create a sort column and sort by it - df_plt['sort_by'] = df_plt[var_prefix].apply(lambda x: masterOfOrder(x)) - df_plt.sort_values(by='sort_by', ascending=True, inplace=True) - df_plt.reset_index(inplace=True) - - #---------------------------------- - #----- Plot the incidence ------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - ##First Axis - #Bin size - y_pos = np.arange(len(df_plt[var_prefix])) - plt.bar(y_pos, df_plt['bin_size'].values.tolist(), align='center', color="cornflowerblue") - plt.xticks(y_pos, df_plt[var_prefix]) - plt.ylabel('Bin Size') - plt.xlabel(variable + ' Bins') - - max_inc = max(df_plt['bin_inc_rate']) - - ##Second Axis - ax2 = ax.twinx() - #incidence rate per bin - plt.plot(df_plt['bin_inc_rate'], color="darkorange", marker=".", markersize=20, linewidth=3, label='incidence rate per bin') - plt.plot(df_plt['avg_inc_rate'], color="dimgrey", linewidth=4, label='average incidence rate') - ax2.plot(np.nan, "cornflowerblue", linewidth=6, label = 'bin size') #dummy line to have label on second axis from first - ax2.set_yticks(np.arange(0, max_inc+0.05, 0.05)) - ax2.set_yticklabels(['{:3.1f}%'.format(x*100) for x in ax2.get_yticks()]) - plt.ylabel('Incidence') - - ##Set Axis - sns.despine(ax=ax, right=True, left=True) - sns.despine(ax=ax2, left=True, right=False) - ax2.spines['right'].set_color('white') - - #remove white line from second grid axes - #the white lines are reguler, Spyder sometimes fails to visualize it (try to export the pic!) - ax2.grid(False) - - ##Description - fig.suptitle('Incidence Plot - ' + variable, fontsize=20, y=1.02) - ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=1, mode="expand", borderaxespad=0.) - plt.show() From 67703d3d7cd6f310b1ca3acc83732217123e1efc Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 14 Oct 2019 08:46:10 +0200 Subject: [PATCH 12/98] Add model building module --- cobra/model_building/univariate_selection.py | 101 +++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 cobra/model_building/univariate_selection.py diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py new file mode 100644 index 0000000..725792e --- /dev/null +++ b/cobra/model_building/univariate_selection.py @@ -0,0 +1,101 @@ +"""Summary +""" +import pandas as pd +from sklearn.metrics import roc_auc_score + + +def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, + target_enc_selection_data: pd.DataFrame, + predictors: list, + target_column: str, + preselect_auc_threshold: float, + preselect_overtrain_threshold: float + ) -> pd.DataFrame: + """Summary + + Args: + target_enc_train_data (pd.DataFrame): Train data + target_enc_selection_data (pd.DataFrame): Selection data + predictors (list): list of predictors (e.g. column names in the train + and selection data sets) + target_column (str): name of the target column + preselect_auc_threshold (float): Description + preselect_overtrain_threshold (float): Description + + Returns: + pd.DataFrame: DataFrame containing for each variable the train auc and + test auc allong with a boolean indicating whether or not it is selected + based on the criteria + """ + result = [] + + for predictor in predictors: + + cleaned_predictor = _clean_predictor_name(predictor) + + auc_train = roc_auc_score(target_enc_train_data[predictor], + target_enc_train_data[target_column]) + + auc_selection = roc_auc_score( + target_enc_selection_data[predictor], + target_enc_selection_data[target_column] + ) + + result.append({"predictor": cleaned_predictor, + "AUC train": auc_train, + "AUC selection": auc_selection}) + + df_auc = pd.DataFrame(result) + + # Filter based on min AUC + auc_thresh = df_auc.loc[:, "AUC selection"] > preselect_auc_threshold + + # Identify those variables for which the AUC difference between train + # and selection is within a user-defined ratio + auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) * 100 + < preselect_overtrain_threshold) + + df_auc["preselection"] = auc_thresh & auc_overtrain + + return df_auc + + +def compute_correlations(target_enc_train_data: pd.DataFrame, + predictors: list) -> pd.DataFrame: + """Given a DataFrame and a list of predictors, compute the correlations + amongst the predictors in the DataFrame + + Args: + target_enc_train_data (pd.DataFrame): data to compute correlation + matrix from + predictors (list): List of column names of the DataFrame between which + to compute correlations + + Returns: + pd.DataFrame: The correlation matrix of the training set + """ + + correlations = target_enc_train_data[predictors].corr() + + predictors_cleaned = [_clean_predictor_name(predictor) + for predictor in predictors] + + # Change index and columns with the cleaned version of the predictors + # e.g. change "var1_enc" with "var1" + correlations.columns = predictors_cleaned + correlations.index = predictors_cleaned + + return correlations + + +def _clean_predictor_name(predictor: str) -> str: + """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor + name to return a clean version of the predictor + + Args: + predictor (str): Description + + Returns: + str: Description + """ + return predictor.replace("_enc", "").replace("_bin", "") From 1e8bdaa35c1fc4cb5a07d43d2f9f833eed1d74dd Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 14 Oct 2019 15:08:53 +0200 Subject: [PATCH 13/98] Bug fix in KbinsDiscretizer _fit_column The bug occured when auto_adapt_bins was set to True, but it fixed right now. Tests are updated so that this function is also covered. In fact, all private member functions related to fitting are covered at the moment. --- cobra/preprocessing/kbins_discretizer.py | 4 +-- tests/preprocessing/test_kbins_discretizer.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 7767319..5e8eb56 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -17,7 +17,7 @@ import pandas as pd from sklearn.exceptions import NotFittedError -from sklearn.cluster import KMeans +#from sklearn.cluster import KMeans class KBinsDiscretizer: @@ -154,7 +154,7 @@ def _fit_column(self, data: pd.DataFrame, if self.auto_adapt_bins: size = len(data.index) missing_pct = data[column_name].isnull().sum()/size - n_bins = int(max((1 - missing_pct) * n_bins), 2) + n_bins = int(max(round((1 - missing_pct) * n_bins), 2)) bin_edges = self._compute_bin_edges(data, column_name, n_bins, col_min, col_max) diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index 19d92ae..2512186 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -1,3 +1,4 @@ +import numpy as np import pandas as pd import pytest @@ -117,6 +118,41 @@ def test_kbins_discretizer_compute_bins_from_edges_round_up(self): expected = [(0, 1), (1, 2), (2, 3)] assert actual == expected + # Tests for _fit_column + def test_kbins_discretizer_fit_column_regular(self): + + data = pd.DataFrame({"variable": list(range(0, 11))}) # ints from 0-10 + + discretizer = KBinsDiscretizer(n_bins=4) + actual = discretizer._fit_column(data, column_name="variable") + + expected = [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), (8.0, 10.0)] + + assert expected == actual + + def test_kbins_discretizer_fit_column_auto_adapt_bins(self): + + data = pd.DataFrame({"variable": list(range(0, 11)) + + ([np.nan] * 17)}) # ints from 0-10 with 17 nan's + + discretizer = KBinsDiscretizer(auto_adapt_bins=True) + actual = discretizer._fit_column(data, column_name="variable") + + expected = [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), (8.0, 10.0)] + + assert expected == actual + + def test_kbins_discretizer_fit_column_two_bin_edges(self): + + data = pd.DataFrame({"variable": [0] + ([1] * 100)}) # almost constant + + discretizer = KBinsDiscretizer() + actual = discretizer._fit_column(data, column_name="variable") + + expected = None + + assert expected == actual + # Tests for _create_bin_labels def test_kbins_discretizer_create_bin_labels(self): From e596c12154aba6e10b806cc306a106bc01a9a712 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 14 Oct 2019 15:28:55 +0200 Subject: [PATCH 14/98] Add more docstrings to preprocessing module methods --- cobra/preprocessing/kbins_discretizer.py | 9 +++++++-- cobra/preprocessing/target_encoder.py | 18 +++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 5e8eb56..391c842 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -22,7 +22,11 @@ class KBinsDiscretizer: - """Bin continuous data into intervals of predefined size + """Bin continuous data into intervals of predefined size. This provides a + way to partition continuous data into discrete values, i.e. tranform + continuous data into nominal data. This can make a linear model more + expressive as it introduces nonlinearity to the model, while maintaining + the interpretability of the model afterwards. Attributes ---------- @@ -173,7 +177,8 @@ def _fit_column(self, data: pd.DataFrame, def transform(self, data: pd.DataFrame, column_names: list) -> pd.DataFrame: - """Summary + """Discretizes the data in the given list of columns by mapping each + number to the appropriate bin computed by the fit method Parameters ---------- diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index 7ce5fbb..be9da12 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -1,3 +1,6 @@ +""" + Incidence Replacement Module +""" import logging log = logging.getLogger(__name__) @@ -36,19 +39,7 @@ class TargetEncoder(BaseEstimator, TransformerMixin): """ def __init__(self, columns: list=None, weight: float=0.0): - """Constructor - Parameters - ---------- - columns : list, optional - A list of columns to encode, if None, all string columns will be - encoded. - weight : float, optional - Smoothing parameters (non-negative). The higher the value of the - parameter, the bigger the contribution of the overall mean. When - set to zero, there is no smoothing - (e.g. the pure target incidence is used) - """ if weight < 0: raise ValueError("The value of weight cannot be smaller than zero") @@ -128,7 +119,8 @@ def _fit_column(self, X: pd.Series, y: pd.Series, return numerator/denominator def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: - """Summary + """Replace (e.g. encode) categories of each column with its average + incidence which was computed when the fit method was called Parameters ---------- From c2603af4132bf5b509b5a34179e49d3782bd9f20 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 21 Oct 2019 15:36:21 +0200 Subject: [PATCH 15/98] Bug fix in label formatting of KBinsDiscretizer When the change_endpoint format was set to True, the label formatting did not depend on the "closed" attribute so we had to fix that. --- cobra/preprocessing/kbins_discretizer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 391c842..8ae7e3d 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -70,7 +70,7 @@ def __init__(self, n_bins: int=10, strategy: str="quantile", self.n_bins = n_bins self.strategy = strategy.lower() - self.closed = closed + self.closed = closed.lower() self.auto_adapt_bins = auto_adapt_bins self.starting_precision = starting_precision self.label_format = label_format @@ -419,6 +419,14 @@ def _create_index(intervals: List[tuple], pd.IntervalIndex Description """ + + # check if closed is of the proper form + if closed not in ["left", "right"]: + raise ValueError("{}: valid options for 'closed' are {}. " + "Got strategy={!r} instead." + .format(KBinsDiscretizer.__name__, + ["left", "right"], closed)) + # deepcopy variable because we do not want to modify the content # of intervals (which is still used outside of this function) _intervals = deepcopy(intervals) @@ -450,7 +458,11 @@ def _create_bin_labels(self, bins: List[tuple]) -> list: # Format first and last bin as < x and > y resp. if self.change_endpoint_format: - bin_labels[0] = "< {}".format(bins[0][1]) - bin_labels[-1] = "> {}".format(bins[-1][0]) + if self.closed == "left": + bin_labels[0] = "< {}".format(bins[0][1]) + bin_labels[-1] = ">= {}".format(bins[-1][0]) + else: + bin_labels[0] = "<= {}".format(bins[0][1]) + bin_labels[-1] = "> {}".format(bins[-1][0]) return bin_labels From fcb6ef39805d6298c7a5dd867bebdaf66f88d628 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 21 Oct 2019 15:37:56 +0200 Subject: [PATCH 16/98] Refactor test organization of kbins_discretizer Cleaned tests by making them parametrizable. --- tests/preprocessing/test_kbins_discretizer.py | 252 ++++++++---------- 1 file changed, 112 insertions(+), 140 deletions(-) diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index 2512186..a2ecdc2 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -1,175 +1,147 @@ +from contextlib import contextmanager +import pytest + import numpy as np import pandas as pd -import pytest from cobra.preprocessing.kbins_discretizer import KBinsDiscretizer +@contextmanager +def does_not_raise(): + yield + + class TestKBinsDiscretizer: ################# Test for public methods ################# ################# Test for private methods ################# - # Tests for _validate_n_bins function - def test_kbins_discretizer_validate_n_bins_exception_1(self): - - with pytest.raises(ValueError): - KBinsDiscretizer()._validate_n_bins(n_bins=1) - - def test_kbins_discretizer_validate_n_bins_exception_no_integral(self): - - with pytest.raises(ValueError): - KBinsDiscretizer()._validate_n_bins(n_bins=10.5) + @pytest.mark.parametrize("n_bins, expectation", + [(1, pytest.raises(ValueError)), + (10.5, pytest.raises(ValueError)), + (2, does_not_raise())], + ids=["invalid_int", "float", "normal"]) + def test_validate_n_bins_exception(self, n_bins, expectation): + with expectation: + assert KBinsDiscretizer()._validate_n_bins(n_bins=n_bins) is None + + def test_transform_column(self): + + data = pd.DataFrame({"variable": list(range(0, 10)) + [np.nan]}) + discretizer = KBinsDiscretizer(n_bins=3, strategy="unform") + + bins = [(0.0, 3.0), (3.0, 6.0), (6.0, 9.0)] + + actual = discretizer._transform_column(data, "variable", bins) + + categories = ["0.0 - 3.0", "3.0 - 6.0", "6.0 - 9.0", "Missing"] + + expected = pd.DataFrame({"variable": list(range(0, 10)) + [np.nan]}) + expected["variable_bin"] = pd.Categorical(["0.0 - 3.0"]*4 + + ["3.0 - 6.0"]*3 + + ["6.0 - 9.0"]*3 + + ["Missing"], + categories=categories, + ordered=True) + + # assert using pandas testing module + pd.testing.assert_frame_equal(actual, expected) + + @pytest.mark.parametrize("n_bins, auto_adapt_bins, data, expected", + [(4, False, + pd.DataFrame({"variable": list(range(0, 11))}), + [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), + (8.0, 10.0)]), + (10, True, + # ints from 0-10 with 17 nan's + pd.DataFrame({"variable": list(range(0, 11)) + + ([np.nan] * 17)}), + [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), + (8.0, 10.0)]), + (10, False, + # almost constant + pd.DataFrame({"variable": [0] + ([1] * 100)}), + None)], + ids=["regular", "auto_adapt_bins", + "two bin edges"]) + def test_fit_column(self, n_bins, auto_adapt_bins, data, expected): + discretizer = KBinsDiscretizer(n_bins=n_bins, + auto_adapt_bins=auto_adapt_bins) - def test_kbins_discretizer_validate_n_bins_valid_n_bins(self): - - KBinsDiscretizer()._validate_n_bins(n_bins=2) - - # Test for _compute_bin_edges - def test_kbins_discretizer_compute_bin_edges_quantile_method(self): - - data = pd.DataFrame({"variable": list(range(0, 11))}) # ints from 0-10 - - discretizer = KBinsDiscretizer() - actual = discretizer._compute_bin_edges(data, column_name="variable", - n_bins=4, - col_min=data.variable.min(), - col_max=data.variable.max()) - expected = [0.0, 2.5, 5, 7.5, 10.0] - - assert expected == actual + actual = discretizer._fit_column(data, column_name="variable") - def test_kbins_discretizer_compute_bin_edges_uniform_method(self): + assert actual == expected - data = pd.DataFrame({"variable": list(range(0, 10))}) # ints from 0-9 + @pytest.mark.parametrize("strategy, n_bins, data, expected", + [("quantile", # strategy + 4, # n_bins + # data (ints from 0 - 10): + pd.DataFrame({"variable": list(range(0, 11))}), + [0.0, 2.5, 5, 7.5, 10.0]), # expected result + ("uniform", # strategy + 3, # n_bins + # data (ints from 0 - 9): + pd.DataFrame({"variable": list(range(0, 10))}), + [0.0, 3.0, 6.0, 9.0])], # expected result + ids=["quantile", "uniform"]) + def test_compute_bin_edges(self, strategy, n_bins, data, expected): + + discretizer = KBinsDiscretizer(strategy=strategy) - discretizer = KBinsDiscretizer(strategy="uniform") actual = discretizer._compute_bin_edges(data, column_name="variable", - n_bins=3, + n_bins=n_bins, col_min=data.variable.min(), col_max=data.variable.max()) - expected = [0.0, 3.0, 6.0, 9.0] - - assert expected == actual - - # Tests for _compute_minimal_precision_of_bin_edges - def test_compute_minimal_precision_of_bin_edges_less_precision(self): - # If starting precision is bigger than actual precision, should return - # starting precision - - bin_edges = [-10, 0, 1, 2] - discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - assert res == 1 - - def test_compute_minimal_precision_of_bin_edges_more_precision(self): - # If starting precision is smaller than actual precision, should return - # actual precision - - bin_edges = [-10, 0, 1, 1.01] - discretizer = KBinsDiscretizer() - res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - assert res == 2 - - def test_compute_minimal_precision_of_bin_edges_equal_precision(self): - # If starting precision is equal to actual precision, should return - # starting precision - - bin_edges = [-10, 0, 1, 1.1] - discretizer = KBinsDiscretizer(starting_precision=1) - res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - assert res == 1 - - def test_compute_minimal_precision_of_bin_edges_negative_start(self): - # Check if negative starting precision also leads to the correct result - - bin_edges = [-10, 0, 1, 2] - discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - assert res == 0 - - def test_compute_minimal_precision_of_bin_edges_round_up(self): - # Check if negative starting precision leads to rounding up - # bin edges to the nearest multiple of 10 - - bin_edges = [-10, 0, 10, 21] - discretizer = KBinsDiscretizer(starting_precision=-1) - res = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - assert res == -1 - # Tests for _compute_bins_from_edges - def test_kbins_discretizer_compute_bins_from_edges(self): - - bin_edges = [0, 1, 1.5, 2] - - discretizer = KBinsDiscretizer() - actual = discretizer._compute_bins_from_edges(bin_edges) - - expected = [(0, 1), (1, 1.5), (1.5, 2)] assert actual == expected - def test_kbins_discretizer_compute_bins_from_edges_round_up(self): + @pytest.mark.parametrize("bin_edges, starting_precision, expected", + [([-10, 0, 1, 2], 1, 1), + ([-10, 0, 1, 1.01], 0, 2), + ([-10, 0, 1, 1.1], 1, 1), + ([-10, 0, 1, 2], -1, 0), + ([-10, 0, 10, 21], -1, -1)], + ids=["less precision", "more precision", + "equal precision", "negative start", + "round up"]) + def test_compute_minimal_precision_of_bin_edges(self, bin_edges, + starting_precision, + expected): - bin_edges = [0, 1, 1.5, 3] + discretizer = KBinsDiscretizer(starting_precision=starting_precision) - discretizer = KBinsDiscretizer() - actual = discretizer._compute_bins_from_edges(bin_edges) + actual = discretizer._compute_minimal_precision_of_bin_edges(bin_edges) - expected = [(0, 1), (1, 2), (2, 3)] assert actual == expected - # Tests for _fit_column - def test_kbins_discretizer_fit_column_regular(self): - - data = pd.DataFrame({"variable": list(range(0, 11))}) # ints from 0-10 - - discretizer = KBinsDiscretizer(n_bins=4) - actual = discretizer._fit_column(data, column_name="variable") - - expected = [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), (8.0, 10.0)] - - assert expected == actual - - def test_kbins_discretizer_fit_column_auto_adapt_bins(self): - - data = pd.DataFrame({"variable": list(range(0, 11)) + - ([np.nan] * 17)}) # ints from 0-10 with 17 nan's - - discretizer = KBinsDiscretizer(auto_adapt_bins=True) - actual = discretizer._fit_column(data, column_name="variable") - - expected = [(0.0, 2.0), (2.0, 5.0), (5.0, 8.0), (8.0, 10.0)] - - assert expected == actual - - def test_kbins_discretizer_fit_column_two_bin_edges(self): - - data = pd.DataFrame({"variable": [0] + ([1] * 100)}) # almost constant + @pytest.mark.parametrize("bin_edges, expected", + [([0, 1, 1.5, 2], [(0, 1), (1, 1.5), (1.5, 2)]), + ([0, 1, 1.5, 3], [(0, 1), (1, 2), (2, 3)])]) + def test_compute_bins_from_edges(self, bin_edges, expected): discretizer = KBinsDiscretizer() - actual = discretizer._fit_column(data, column_name="variable") - - expected = None - - assert expected == actual - - # Tests for _create_bin_labels - def test_kbins_discretizer_create_bin_labels(self): - - bins = [(0, 1), (1, 2), (2, 3)] - - discretizer = KBinsDiscretizer() - actual = discretizer._create_bin_labels(bins) - expected = ["0 - 1", "1 - 2", "2 - 3"] + actual = discretizer._compute_bins_from_edges(bin_edges) assert actual == expected - def test_kbins_discretizer_create_bin_labels_different_endpoint_fmt(self): - - bins = [(0, 1), (1, 2), (2, 3)] + @pytest.mark.parametrize("change_endpoint_format, closed, bins, expected", + [(False, "right", [(0, 1), (1, 2), (2, 3)], + ["0 - 1", "1 - 2", "2 - 3"]), + (True, "right", [(0, 1), (1, 2), (2, 3)], + ["<= 1", "1 - 2", "> 2"]), + (True, "left", [(0, 1), (1, 2), (2, 3)], + ["< 1", "1 - 2", ">= 2"])], + ids=["standard format", "different endpoints", + "different endpoints left"]) + def test_create_bin_labels(self, change_endpoint_format, closed, + bins, expected): + + discretizer = KBinsDiscretizer( + closed=closed, + change_endpoint_format=change_endpoint_format + ) - discretizer = KBinsDiscretizer(change_endpoint_format=True) actual = discretizer._create_bin_labels(bins) - expected = ["< 1", "1 - 2", "> 2"] assert actual == expected From 20dc24b66b37fca56c50ea33c4df693dedb0cf26 Mon Sep 17 00:00:00 2001 From: JanBenisek Date: Fri, 22 Nov 2019 14:35:04 +0100 Subject: [PATCH 17/98] Add categorical regrouper Part of preprocessin module. The class regroups categorical variables based on incidence. --- cobra/preprocessing/__init__.py | 4 +- cobra/preprocessing/categorical_regrouper.py | 391 +++++++++++++++++++ dev/preprocessor/__init__.py | 0 dev/preprocessor/categorical_regrouper.py | 391 +++++++++++++++++++ dev/preprocessor/compare.py | 154 ++++++++ dev/preprocessor/develop.py | 255 +----------- 6 files changed, 952 insertions(+), 243 deletions(-) create mode 100644 cobra/preprocessing/categorical_regrouper.py create mode 100644 dev/preprocessor/__init__.py create mode 100644 dev/preprocessor/categorical_regrouper.py create mode 100644 dev/preprocessor/compare.py diff --git a/cobra/preprocessing/__init__.py b/cobra/preprocessing/__init__.py index f3884e0..98c3ef9 100644 --- a/cobra/preprocessing/__init__.py +++ b/cobra/preprocessing/__init__.py @@ -1,5 +1,7 @@ from .kbins_discretizer import KBinsDiscretizer from .target_encoder import TargetEncoder +from .categorical_regrouper import CategoryRegrouper __all__ = ['KBinsDiscretizer', - 'TargetEncoder'] \ No newline at end of file + 'TargetEncoder', + 'CategoryRegrouper'] \ No newline at end of file diff --git a/cobra/preprocessing/categorical_regrouper.py b/cobra/preprocessing/categorical_regrouper.py new file mode 100644 index 0000000..a6276d4 --- /dev/null +++ b/cobra/preprocessing/categorical_regrouper.py @@ -0,0 +1,391 @@ + +import pandas as pd +import numpy as np +from scipy import stats +from typing import Dict +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.exceptions import NotFittedError +import logging + +log = logging.getLogger(__name__) + + +class CategoryRegrouper(BaseEstimator, TransformerMixin): + """ + Regroups categories in categorical variables based on significance + with target variable. + + Parameters + ---------- + scale_cont : bool, default=True + Whether contingency table should be scaled before chi^2.' + + pval_thresh : float, default=0.001 + Significance threshold for regroupping. + + regroup_rename : str, default='non-significant' + New name of non-significant regroupped variables. + + missing_rename : str, default='Missing' + New name of missing categories. + + keep_missing : bool, default=Falsse + Whether missing category should be kept in the result. + + forced_categories : Dict, default=None + Dictionary to force categories - + for each colum dict of {col:[forced vars]}. + + Attributes + ---------- + all_category_map_ : Dict + Dictionary with mapping for each variable. + """ + def __init__(self, scale_cont: bool = True, + pval_thresh: float = 0.001, + regroup_rename: str = "non-significant", + missing_rename: str = "Missing", + keep_missing: bool = False, + forced_categories: Dict = None): + self.scale_cont = scale_cont + self.pval_thresh = pval_thresh + self.regroup_rename = regroup_rename + self.missing_rename = missing_rename + self.keep_missing = keep_missing + self.forced_categories = forced_categories + + def fit(self, X: pd.DataFrame, + y: pd.Series, + columns: list = []): + """ + Method regroups categories whole DataFrame. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + y: pd.Series + Series with target variable. + + columns : list, default=[] + Columns to be regrouped. + + Raises + ------ + ValueError + In case X and y are not of the same length. + + Returns + ------- + None + Only fits the instance of the class. + """ + self.all_category_map_ = {} + + if len(X.index) != len(y.index): + raise ValueError("The length of X is {}, but the length of y is {}" + .format(len(X.index), len(y.index))) + + if not columns: + columns = CategoryRegrouper._get_categorical_columns(X) + log.warning("All object-type columns have been selected") + + for column in columns: + if column not in X.columns: + log.warning("DataFrame has no column '{}', so it will be " + "skipped in fitting" .format(column)) + continue + + self.all_category_map_[column] = self._fit_column(X=X, + y=y, + column=column) + + def _fit_column(self, X: pd.DataFrame, + y: pd.Series, + column: str) -> Dict: + """ + Method regroups categories in given column. + + Parameters + ---------- + X : pd.Series + Series with one column to be transformed. + + y: pd.Series + Series with target variable + + column : str + Column to be regrouped. + + Raises + ------ + ValueError + in case input column is not a string. + + Returns + ------- + Dict + Returns dictionary as {old category : new category} for + specific column. + """ + category_map = {} + keep_categories = [] + incidence_mean = y.mean() + + # Rename target + y.rename("TARGET", inplace=True) + + # Replace missings + X = self._replaceMissings(X=X, column=column, + replace_with=self.missing_rename) + + all_uq_categories = X[column].unique().tolist() + + # Remove small categories + categories = self._removeCategories(X=X, y=y, column=column) + + # Inspect remaining categories and test significance + for category in categories: + df_aux = pd.concat([X[column], y], axis=1) + df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) + cont_table = pd.crosstab(index=df_aux['other_cats'], + columns=df_aux['TARGET'], + margins=False) + + # if true, we scale the "other" categories + if self.scale_cont: + size_other_cats = cont_table.iloc[1].sum() + cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cats + cont_table.iloc[1, 1] = incidence_mean*size_other_cats + cont_table = cont_table.values.astype(np.int64) + + pval = stats.chi2_contingency(cont_table, correction=False)[1] + + # If significant, keep it + if pval <= self.pval_thresh: + keep_categories.append(category) + + # Keep "Missing" even if it wasn't selected if + # it is in the original categories and set to True + if ((self.missing_rename not in keep_categories) and + (self.missing_rename in all_uq_categories) and self.keep_missing): + keep_categories.append(self.missing_rename) + + # Keep forced categories + if self.forced_categories is not None: + # If doesnt exists, give warning + forced = [col for col in self.forced_categories[column] + if col in all_uq_categories] + + # Extend list and remove duplicates + keep_categories = list(set(keep_categories.extend(forced))) + + difference = set(forced) - set(self.forced_categories[column]) + if len(difference) > 0: + log.warning("Following forced categories: {} " + "are not in column: {}.".format(difference, + column)) + + # Return dictionary as {old column : new column} + for category in all_uq_categories: + if category in keep_categories: + category_map[category] = category + else: + category_map[category] = self.regroup_rename + + return category_map + + def transform(self, X: pd.DataFrame, + columns: list = []) -> pd.DataFrame: + """ + Method transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + columns : list, default=[] + Columns to be regrouped. + + Raises + ------ + NotFittedError + If fit() method has not been called. + + ValueError + If columns to be transformed have not been fitted. + + Returns + ------- + pd.DataFrame + Returns transformed DataFrame with new columns as "col_regrouped". + """ + if len(self.all_category_map_) == 0: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + fitted_columns = list(self.all_category_map_.keys()) + + # if specified columns not in fitted Dict, raise error + if not set(columns).issubset(set(fitted_columns)): + diff_cols = set(columns).difference(set(fitted_columns)) + raise ValueError("Following columns are not fitted: " + "{}".format(diff_cols)) + + X_tr = X.copy() + for column in columns: + X_tr[column + "_regrouped"] = self._transform_column(X=X, + column=column) + + return X_tr + + def _transform_column(self, X: pd.DataFrame, + column: str) -> pd.Series: + """ + Method transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + column : str + Column to be regrouped. + + Returns + ------- + pd.Series + Returns DataFrame with regrouped variable as category datatype. + """ + X_tr = X[column].copy() + X_tr[column + "_regrouped"] = X_tr.replace( + to_replace=self.all_category_map_[column]) + + X_tr[column + "_regrouped"] = X_tr[column + + "_regrouped"].astype('category') + + return X_tr[column + "_regrouped"] + + def fit_transform(self, X: pd.DataFrame, + y: pd.Series, + columns: list = []) -> pd.DataFrame: + """ + Auxiliary method fits and transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + y : pd.Series + Series with target variable + + column : list, default=[] + Columns to be regrouped. + + Returns + ------- + pd.DataFrame + Returns DataFrame with regrouped variable as category datatype. + """ + self.fit(X=X, y=y, columns=columns) + + X_tr = self.transform(X=X, columns=columns) + + return X_tr + + def _replaceMissings(self, X: pd.DataFrame, + column: str, + replace_with: str = 'Missing') -> pd.DataFrame: + """ + Method replaces missing and empty cells with `Missing` (default) in + a pd.DataFrame. + + Parameters + ---------- + X : pd.DataFrame + Dataframe where a value will be replaced if empty or nan. + + column : str + Column to be analyzed for missings. + + replace_with : str default='Missing' + String to replace the missings. + + Raises + ------ + ValueError + In case input column is not a string. + + Returns + ------- + pd.DataFrame + Modified dataframe with replaced missings. + """ + if X[column].dtype != 'O' or X[column].dtype != 'object': + raise TypeError("column {} must be a string".format(column)) + + X[column].fillna(replace_with, inplace=True) + X[column] = X[column].astype(str).str.strip() + X[column].replace('', replace_with, inplace=True) + + return X + + def _removeCategories(self, X: pd.DataFrame, + y: pd.Series, + column: str, + threshold: int = 5) -> np.ndarray: + """ + Method removes category which fail to meet certain condition + + Parameters + ---------- + X : pd.DataFrame + Dataframe with columns to be inspected for group removal. + + y : pd.Series + Series with target. + + column : str + Column to be analyzed group removal. + + threshold : int default=5 + Threshold for group removal. + + Returns + ------- + np.ndarray + Numpy array with groups to be kept. + """ + category_cnts = pd.DataFrame(X.groupby(column)[column].count()) + train_inc = y.mean() + factor = max(train_inc, 1-train_inc) + keep_categories = category_cnts.where((category_cnts*factor) > + threshold) + + return np.array(keep_categories.index.tolist()) + + @staticmethod + def _get_categorical_columns(data: pd.DataFrame) -> list: + """Get the columns containing categorical data + (dtype "object" or "category") + + Parameters + ---------- + data : pd.DataFrame + Dataframe from which categorical variables + will be extracted. + + Returns + ------- + list + List of column names containing categorical data. + """ + object_columns = data.dtypes[data.dtypes == object].index + categorical_columns = data.dtypes[data.dtypes == "category"].index + + return list(set(object_columns).union(set(categorical_columns))) diff --git a/dev/preprocessor/__init__.py b/dev/preprocessor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dev/preprocessor/categorical_regrouper.py b/dev/preprocessor/categorical_regrouper.py new file mode 100644 index 0000000..a6276d4 --- /dev/null +++ b/dev/preprocessor/categorical_regrouper.py @@ -0,0 +1,391 @@ + +import pandas as pd +import numpy as np +from scipy import stats +from typing import Dict +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.exceptions import NotFittedError +import logging + +log = logging.getLogger(__name__) + + +class CategoryRegrouper(BaseEstimator, TransformerMixin): + """ + Regroups categories in categorical variables based on significance + with target variable. + + Parameters + ---------- + scale_cont : bool, default=True + Whether contingency table should be scaled before chi^2.' + + pval_thresh : float, default=0.001 + Significance threshold for regroupping. + + regroup_rename : str, default='non-significant' + New name of non-significant regroupped variables. + + missing_rename : str, default='Missing' + New name of missing categories. + + keep_missing : bool, default=Falsse + Whether missing category should be kept in the result. + + forced_categories : Dict, default=None + Dictionary to force categories - + for each colum dict of {col:[forced vars]}. + + Attributes + ---------- + all_category_map_ : Dict + Dictionary with mapping for each variable. + """ + def __init__(self, scale_cont: bool = True, + pval_thresh: float = 0.001, + regroup_rename: str = "non-significant", + missing_rename: str = "Missing", + keep_missing: bool = False, + forced_categories: Dict = None): + self.scale_cont = scale_cont + self.pval_thresh = pval_thresh + self.regroup_rename = regroup_rename + self.missing_rename = missing_rename + self.keep_missing = keep_missing + self.forced_categories = forced_categories + + def fit(self, X: pd.DataFrame, + y: pd.Series, + columns: list = []): + """ + Method regroups categories whole DataFrame. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + y: pd.Series + Series with target variable. + + columns : list, default=[] + Columns to be regrouped. + + Raises + ------ + ValueError + In case X and y are not of the same length. + + Returns + ------- + None + Only fits the instance of the class. + """ + self.all_category_map_ = {} + + if len(X.index) != len(y.index): + raise ValueError("The length of X is {}, but the length of y is {}" + .format(len(X.index), len(y.index))) + + if not columns: + columns = CategoryRegrouper._get_categorical_columns(X) + log.warning("All object-type columns have been selected") + + for column in columns: + if column not in X.columns: + log.warning("DataFrame has no column '{}', so it will be " + "skipped in fitting" .format(column)) + continue + + self.all_category_map_[column] = self._fit_column(X=X, + y=y, + column=column) + + def _fit_column(self, X: pd.DataFrame, + y: pd.Series, + column: str) -> Dict: + """ + Method regroups categories in given column. + + Parameters + ---------- + X : pd.Series + Series with one column to be transformed. + + y: pd.Series + Series with target variable + + column : str + Column to be regrouped. + + Raises + ------ + ValueError + in case input column is not a string. + + Returns + ------- + Dict + Returns dictionary as {old category : new category} for + specific column. + """ + category_map = {} + keep_categories = [] + incidence_mean = y.mean() + + # Rename target + y.rename("TARGET", inplace=True) + + # Replace missings + X = self._replaceMissings(X=X, column=column, + replace_with=self.missing_rename) + + all_uq_categories = X[column].unique().tolist() + + # Remove small categories + categories = self._removeCategories(X=X, y=y, column=column) + + # Inspect remaining categories and test significance + for category in categories: + df_aux = pd.concat([X[column], y], axis=1) + df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) + cont_table = pd.crosstab(index=df_aux['other_cats'], + columns=df_aux['TARGET'], + margins=False) + + # if true, we scale the "other" categories + if self.scale_cont: + size_other_cats = cont_table.iloc[1].sum() + cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cats + cont_table.iloc[1, 1] = incidence_mean*size_other_cats + cont_table = cont_table.values.astype(np.int64) + + pval = stats.chi2_contingency(cont_table, correction=False)[1] + + # If significant, keep it + if pval <= self.pval_thresh: + keep_categories.append(category) + + # Keep "Missing" even if it wasn't selected if + # it is in the original categories and set to True + if ((self.missing_rename not in keep_categories) and + (self.missing_rename in all_uq_categories) and self.keep_missing): + keep_categories.append(self.missing_rename) + + # Keep forced categories + if self.forced_categories is not None: + # If doesnt exists, give warning + forced = [col for col in self.forced_categories[column] + if col in all_uq_categories] + + # Extend list and remove duplicates + keep_categories = list(set(keep_categories.extend(forced))) + + difference = set(forced) - set(self.forced_categories[column]) + if len(difference) > 0: + log.warning("Following forced categories: {} " + "are not in column: {}.".format(difference, + column)) + + # Return dictionary as {old column : new column} + for category in all_uq_categories: + if category in keep_categories: + category_map[category] = category + else: + category_map[category] = self.regroup_rename + + return category_map + + def transform(self, X: pd.DataFrame, + columns: list = []) -> pd.DataFrame: + """ + Method transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + columns : list, default=[] + Columns to be regrouped. + + Raises + ------ + NotFittedError + If fit() method has not been called. + + ValueError + If columns to be transformed have not been fitted. + + Returns + ------- + pd.DataFrame + Returns transformed DataFrame with new columns as "col_regrouped". + """ + if len(self.all_category_map_) == 0: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + fitted_columns = list(self.all_category_map_.keys()) + + # if specified columns not in fitted Dict, raise error + if not set(columns).issubset(set(fitted_columns)): + diff_cols = set(columns).difference(set(fitted_columns)) + raise ValueError("Following columns are not fitted: " + "{}".format(diff_cols)) + + X_tr = X.copy() + for column in columns: + X_tr[column + "_regrouped"] = self._transform_column(X=X, + column=column) + + return X_tr + + def _transform_column(self, X: pd.DataFrame, + column: str) -> pd.Series: + """ + Method transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + column : str + Column to be regrouped. + + Returns + ------- + pd.Series + Returns DataFrame with regrouped variable as category datatype. + """ + X_tr = X[column].copy() + X_tr[column + "_regrouped"] = X_tr.replace( + to_replace=self.all_category_map_[column]) + + X_tr[column + "_regrouped"] = X_tr[column + + "_regrouped"].astype('category') + + return X_tr[column + "_regrouped"] + + def fit_transform(self, X: pd.DataFrame, + y: pd.Series, + columns: list = []) -> pd.DataFrame: + """ + Auxiliary method fits and transforms specified columns. + + Parameters + ---------- + X : pd.DataFrame + Dataframe with all the columns. + + y : pd.Series + Series with target variable + + column : list, default=[] + Columns to be regrouped. + + Returns + ------- + pd.DataFrame + Returns DataFrame with regrouped variable as category datatype. + """ + self.fit(X=X, y=y, columns=columns) + + X_tr = self.transform(X=X, columns=columns) + + return X_tr + + def _replaceMissings(self, X: pd.DataFrame, + column: str, + replace_with: str = 'Missing') -> pd.DataFrame: + """ + Method replaces missing and empty cells with `Missing` (default) in + a pd.DataFrame. + + Parameters + ---------- + X : pd.DataFrame + Dataframe where a value will be replaced if empty or nan. + + column : str + Column to be analyzed for missings. + + replace_with : str default='Missing' + String to replace the missings. + + Raises + ------ + ValueError + In case input column is not a string. + + Returns + ------- + pd.DataFrame + Modified dataframe with replaced missings. + """ + if X[column].dtype != 'O' or X[column].dtype != 'object': + raise TypeError("column {} must be a string".format(column)) + + X[column].fillna(replace_with, inplace=True) + X[column] = X[column].astype(str).str.strip() + X[column].replace('', replace_with, inplace=True) + + return X + + def _removeCategories(self, X: pd.DataFrame, + y: pd.Series, + column: str, + threshold: int = 5) -> np.ndarray: + """ + Method removes category which fail to meet certain condition + + Parameters + ---------- + X : pd.DataFrame + Dataframe with columns to be inspected for group removal. + + y : pd.Series + Series with target. + + column : str + Column to be analyzed group removal. + + threshold : int default=5 + Threshold for group removal. + + Returns + ------- + np.ndarray + Numpy array with groups to be kept. + """ + category_cnts = pd.DataFrame(X.groupby(column)[column].count()) + train_inc = y.mean() + factor = max(train_inc, 1-train_inc) + keep_categories = category_cnts.where((category_cnts*factor) > + threshold) + + return np.array(keep_categories.index.tolist()) + + @staticmethod + def _get_categorical_columns(data: pd.DataFrame) -> list: + """Get the columns containing categorical data + (dtype "object" or "category") + + Parameters + ---------- + data : pd.DataFrame + Dataframe from which categorical variables + will be extracted. + + Returns + ------- + list + List of column names containing categorical data. + """ + object_columns = data.dtypes[data.dtypes == object].index + categorical_columns = data.dtypes[data.dtypes == "category"].index + + return list(set(object_columns).union(set(categorical_columns))) diff --git a/dev/preprocessor/compare.py b/dev/preprocessor/compare.py new file mode 100644 index 0000000..f863bfa --- /dev/null +++ b/dev/preprocessor/compare.py @@ -0,0 +1,154 @@ +#%% +import pandas as pd +import numpy as np +from random import shuffle +from scipy import stats +from typing import Dict, Tuple +import sys + +sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra/dev") + +import preprocessor.categorical_regrouper as pr + +import logging +log = logging.getLogger(__name__) + +ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" +df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") +df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) +df_data['Pclass'] = df_data['Pclass'].astype(object) + +split = ['TRAIN']*int(df_data.shape[0]*0.7) + \ + ['TEST']*int(df_data.shape[0]*0.2)+ \ + ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.1)) + +shuffle(split) + +df_data['PARTITION'] = split + +df_x = pd.DataFrame(df_data[['Pclass', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) +df_y = df_data['TARGET'][df_data['PARTITION'] == "TRAIN"] + +#%% +""" NEW SOLUTION """ +CR = pr.CategoryRegrouper() + +CR.fit(X=df_x, y=df_y, columns=["Embarked", "Pclass"]) +print(CR.all_category_map_) +df_new = CR.transform(X=df_x, columns=["Embarked", "Pclass"]) + +#%% +""" OLD SOLUTION """ +def __regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): + ''' + Method regroups categorical variables + Returns DF mask + ---------------------------------------------------- + var: input pd.Serie with cat column + target: pd.Serie with target variable + train: pd.Serie with parition variable + pval_thresh: threshold for regrouping + dummy: scale of booleans (?) + keep: keep specific groups (?) + rename: rename the insignificant category + ---------------------------------------------------- + - Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group + - The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or + - remaining groups average incidence' (if dummy=False) + - Groups with a pvalue above the threshold are relabled to a single group + ''' + + # Define the chi² test condition + # Groups that do not meet the condition are not analyzed and will be unconditionally relabled + def _chi2cond_(var=var,target=target,train=train): + varcounts = var[train].groupby(by=var).count() + train_inc = target[train].sum()/len(target[train]) + factor = max(train_inc, 1-train_inc) + analyze_mask = (varcounts*factor)>5 + analyze_groups = analyze_mask.index[analyze_mask].values + return analyze_groups + + # Compute overal incidence mean + incidence_mean = target[train].mean() + # Create container of which groups will be kept, compared to the groups which will be relabled + keepgroups = [] + # Cycle and test each group that meets the chi² condition + for group in _chi2cond_(): + # Container for target 0/1 observations of the group under scrutiny + obs_group = [] + # Counts of the target 0/1 occurences for the group under scrutiny + obs_group.append(((target[train]==0)&(var[train]==group)).sum()) + obs_group.append(((target[train]==1)&(var[train]==group)).sum()) + obs_group = np.array(obs_group) + # Container for target 0/1 observations of the remaining groups together + obs_other = [] + # Counts of the target 0/1 occurences for the remaining groups together + obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) + obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) + obs_other = np.array(obs_other) + # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence + # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups + if dummy: + obs_other_size = obs_other.sum() + obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) + obs_other[1]=( incidence_mean)*obs_other_size + obs = np.array([obs_group,obs_other]) + # Place at least 1 observation to avoid error in chi2 test + obs[obs==0] = 1 + # Perform chi² test + pval = stats.chi2_contingency(obs, correction=False)[1] + # If pval outperforms threshold, append the group in the keepgroups list + if pval<=pval_thresh: + keepgroups.append(group) + #elif group==keep: + # keepgroups.append(group) + # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list + if keep not in keepgroups: + keepgroups.append(keep) + # Makes a list of all groups not in the keepgroups list + regroup_mask = [val not in keepgroups for val in var.values] + var_regroup = var.copy() + # Rename those groups + var_regroup[regroup_mask] = rename + var_regroup.name = "B_"+var.name + info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) + return var_regroup, info + +#%% +result = __regroup(var=df_data['Pclass'], #Cabin, Pclass, SibSp, Parch, Embarked + target=df_data.loc[:,'TARGET'], + train=df_data['PARTITION']=='TRAIN', + pval_thresh=0.05, + dummy=True, + keep='Missing', + rename='non-significant') + +print(result[0].unique()) +print(result[0].head(n=5)) +#print(result[1]) +df_orig = result[0].to_frame() +df_orig.columns = ["old"] +df_orig["old"] = df_orig["old"].astype('str') +df_orig["old"] = df_orig["old"].astype('category') + +df_orig['split'] = df_data['PARTITION'] +df_orig = df_orig[df_orig['split'] == 'TRAIN'] + + +#%% +""" COMPARE """ +#df_orig.loc[:,"new"] = df_new['Embarked_regrouped'].copy() +df_orig.loc[:,"new"] = df_new['Pclass_regrouped'].copy() + +print(df_orig) + +df_orig['compare'] = df_orig["new"] == df_orig["old"] + +print(df_orig[df_orig['compare'] == False]) + + + +#%% + + +#%% diff --git a/dev/preprocessor/develop.py b/dev/preprocessor/develop.py index b1cb9bf..6e8c4dc 100644 --- a/dev/preprocessor/develop.py +++ b/dev/preprocessor/develop.py @@ -4,6 +4,11 @@ from random import shuffle from scipy import stats from typing import Dict, Tuple +import sys + +sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra/dev/preprocessor") + +import preprocessor.categorical_regrouper as pr import logging log = logging.getLogger(__name__) @@ -11,6 +16,7 @@ ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) +df_data['Pclass'] = df_data['Pclass'].astype(object) split = ['TRAIN']*int(df_data.shape[0]*0.5) + \ ['TEST']*int(df_data.shape[0]*0.2)+ \ @@ -20,260 +26,25 @@ df_data['PARTITION'] = split -df_x = pd.DataFrame(df_data[['Parch', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) +df_x = pd.DataFrame(df_data[['Pclass', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) df_y = df_data['TARGET'][df_data['PARTITION'] == "TRAIN"] #%% -column = 'Embarked' -scale_cont = True -incidence_mean = df_y.mean() -pval_thresh = 0.001 -keep_categories = [] -keep = 'Missing' -category_map = {} -replace_with = 'non-significant' - -for category in grps: - #category = 'S' - df_aux = pd.concat([df_x[column], df_y], axis=1) - df_aux['obs_other'] = np.where(df_aux[column] == category, 0, 1) - - cont_table = pd.crosstab(df_aux['obs_other'], df_aux['TARGET'], margins=False) - - if scale_cont: - size_other_cat = cont_table.iloc[1].sum() - cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cat - cont_table.iloc[1, 1] = incidence_mean*size_other_cat - cont_table = cont_table.values.astype(np.int64) - - pval = stats.chi2_contingency(cont_table, correction=False)[1] - #0.17914169501249405 - - if pval<=pval_thresh: - keep_categories.append(category) - -if keep not in keep_categories and keep in df_x[column].unique().tolist(): - keep_categories.append(keep) - -for category in df_x[column].unique().tolist(): - if category in keep_categories: - category_map[category] = category - else: - category_map[category] = replace_with - - - +""" NEW SOLUTION """ +CR = pr.CategoryRegrouper() +CR.fit(X=df_x, y=df_y, columns=["Embarked", "Pclass"]) +print(CR.all_category_map_) +df_X_tr = CR.transform(X=df_x, columns=["Embarked", "Pclass"]) #%% +""" OLD SOLUTION """ -class CategoryRegrouper(): - - """ - TOOD - -test the keep_categories and give warning if category not in column - -transform will be just df.replace() with a dict - -I am keeping missings, but not in the original code, inspect - -ask geert about the _removeCategories function - - -write the rest - -combine with categorical_processor.py - -add to init - -test if same as the old code - -unit tests - - Regroups categories in categorical variables if based on signicicance - with target variable - - Attributes - ---------- - scale_cont : bool, default=True - whether contingency table should be scaled before chi-2 - pval_thresh : float, default=0.001 - significance threshold for regroupping - regroup_rename : str, default='non-significant' - new name of non-significant regroupped variables - missing_rename : str, default='Missing' - new name of missing categories - keep_missing : bool, default=True - whether missing category should be kept in the result - forced_categories : Dict, default=None - dictionary to force categories - - for each colum dict of {col:[forced vars]} - """ - - def __init__(self, scale_cont: bool=True, - pval_thresh: float=0.001, - regroup_rename: str="non-significant", - missing_rename: str="Missing", - keep_missing: bool=True, - forced_categories: Dict=None): - self.scale_cont = scale_cont - self.pval_thresh = pval_thresh - self.regroup_rename = regroup_rename - self.missing_rename = missing_rename - self.keep_missing = keep_missing - self.forced_categories = forced_categories - - def fit(self): - pass - - def _fit_column(self, X: pd.DataFrame, - y: pd.Series, - column: str) -> Dict: - - category_map = {} - keep_categories = [] - self.incidence_mean = y.mean() - all_uq_categories = X[column].unique().tolist() - - # Rename target - y.rename("TARGET", inplace=True) - - # Replace missings - X = self._replaceMissings(X=X, column=column, - replace_with=self.missing_rename) - - # Remove small categories - categories = self._removeCategories(X=X, y=y, column=column) - - # Inspect remaining categories and test significance - for category in categories: - df_aux = pd.concat([X[column], y], axis=1) - df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) - cont_table = pd.crosstab(index=df_aux['other_cats'], - columns=df_aux['TARGET'], - margins=False) - - # if true, we scale the "other" categories - if self.scale_cont: - size_other_cats = cont_table.iloc[1].sum() - cont_table.iloc[1, 0] = (1-self.incidence_mean)*size_other_cats - cont_table.iloc[1, 1] = self.incidence_mean*size_other_cats - cont_table = cont_table.values.astype(np.int64) - pval = stats.chi2_contingency(cont_table, correction=False)[1] - # If significant, keep it - if pval <= self.pval_thresh: - keep_categories.append(category) - # Keep "Missing" even if it wasn't selected if - # it is in the original categories and set to True - if ((self.missing_rename not in keep_categories) and - (self.missing_rename in all_uq_categories) and self.keep_missing): - keep_categories.append(self.missing_rename) - # Keep forced categories - if self.forced_categories is not None: - # If doesnt exists, give warning - forced = [col for col in self.forced_categories[column] - if col in all_uq_categories] - - # Extend list and remove duplicates - keep_categories = list(set(keep_categories.extend(forced))) - - difference = set(forced) - set(self.forced_categories[column]) - if len(difference) > 0: - log.warning("Following forced categories: {} " - "are not in column: {}.".format(difference, - column)) - - # Return dictionary as {old column : new column} - for category in all_uq_categories: - if category in keep_categories: - category_map[category] = category - else: - category_map[category] = self.regroup_rename - - return category_map - - def transform(self): - pass - - def _transform_column(self): - pass - - def fit_transform(self): - pass - - def _replaceMissings(self, X: pd.DataFrame, - column: str, - replace_with: str='Missing') -> pd.DataFrame: - """ - Method replaces missing and empty cells with `Missing` (default) in - a pd.DataFrame - - df_tst = _replaceMissings(X=df_x, column='Embarked') - - Parameters - ---------- - X : pd.DataFrame - Dataframe where a value will be replaced if empty or nan - column : str - Column to be analyzed for missings - replace_with : str default='Missing' - string to replace the missings - - Raises - ------ - ValueError - in case input column is not a string - - Returns - ------- - pd.DataFrame - modified dataframe with replaced missings - """ - if not X[column].dtype == 'object': - raise TypeError("columns must be a string") - - X[column].fillna(replace_with, inplace=True) - X[column] = X[column].str.strip() - X[column].replace('', replace_with, inplace=True) - - return X - - def _removeCategories(self, X: pd.DataFrame, - y: pd.Series, - column: str, - threshold: int=5) -> np.ndarray: - """ - Method removes category which fail to meet certain condition - - grps = _removeGroups(X=df_x, y=df_y, column='Embarked') - - Parameters - ---------- - X : pd.DataFrame - Dataframe with columns to be inspected for group removal - y : pd.Series - Series with target - column : str - Column to be analyzed group removal - threshold : int default=5 - Threshold for group removal - - Returns - ------- - np.ndarray - numpy array with groups to be kept - """ - category_cnts = pd.DataFrame(X.groupby(column)[column].count()) - train_inc = y.mean() - factor = max(train_inc, 1-train_inc) - keep_categories = category_cnts.where((category_cnts*factor) > - threshold) - - return np.array(keep_categories.index.tolist()) - - - -#%% -CR = CategoryRegrouper() -output = CR._fit_column(X=df_x, y=df_y, column='Embarked') -output #%% From 855caaf49a284bcccb1b7091b0861ccdcf1bc627 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 28 Nov 2019 14:03:16 +0100 Subject: [PATCH 18/98] Bug fix in TargetEncoder When applying incidence replacement on variables of type "category", the resulting dtype was also of type category which should of course be float instead! --- cobra/preprocessing/kbins_discretizer.py | 15 +++++++++++---- cobra/preprocessing/target_encoder.py | 13 +++++++++++-- tests/preprocessing/test_target_encoder.py | 3 +++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 8ae7e3d..57bd08c 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -1,8 +1,15 @@ """ -This class is a rework of -https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/_discretization.py -However, it is purely written in pandas instead of numpy because -it is more intuitive +This module is a rework of +https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/ +_discretization.py +However, it is purely written in pandas instead of numpy because it is more +intuitive + +Also, some custom modifications were included to allign it with our methodology + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (implementation) """ # standard lib imports from copy import deepcopy diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index be9da12..f487f94 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -1,5 +1,10 @@ """ - Incidence Replacement Module +Incidence Replacement Module. The implementation is inspired by +https://contrib.scikit-learn.org/categorical-encoding/index.html + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (implementation) """ import logging log = logging.getLogger(__name__) @@ -157,7 +162,11 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: new_column = TargetEncoder._clean_column_name(column) - X[new_column] = X[column].map(self._mapping[column]) + # Convert dtype to float because when the original dtype + # is of type "category", the resulting dtype is also of type + # "category" + X[new_column] = (X[column].map(self._mapping[column]) + .astype("float")) new_columns.append(new_column) diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py index cea1f3d..6766571 100644 --- a/tests/preprocessing/test_target_encoder.py +++ b/tests/preprocessing/test_target_encoder.py @@ -90,6 +90,9 @@ def test_target_encoder_transform(self): 'neutral'], 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + # inputs of TargetEncoder will be of dtype category + df["variable"] = df["variable"].astype("category") + expected = pd.DataFrame({'variable_enc': [0.666667, 0.666667, 0.333333, 0.50000, 0.333333, 0.666667, From d6a5ceb5cb50771d4893cde50af063159418b239 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 28 Nov 2019 16:54:20 +0100 Subject: [PATCH 19/98] Refactor API of TargetEncoder - Change API of TargetEncoder to make it compatable with the one of KBinsDiscretizer - Add (de)serialization options to TargetEncoder - Modify tests accordingly --- cobra/preprocessing/target_encoder.py | 123 ++++++++++++--------- tests/preprocessing/test_target_encoder.py | 114 ++++++++++++------- 2 files changed, 147 insertions(+), 90 deletions(-) diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index f487f94..2fc2369 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -12,11 +12,11 @@ #import numpy as np import pandas as pd -from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.base import BaseEstimator from sklearn.exceptions import NotFittedError -class TargetEncoder(BaseEstimator, TransformerMixin): +class TargetEncoder(BaseEstimator): """Target encoding for categorical features. @@ -43,12 +43,11 @@ class TargetEncoder(BaseEstimator, TransformerMixin): zero, there is no smoothing (e.g. the pure target incidence is used). """ - def __init__(self, columns: list=None, weight: float=0.0): + def __init__(self, weight: float=0.0): if weight < 0: raise ValueError("The value of weight cannot be smaller than zero") - self.columns = columns self.weight = weight self._mapping = {} # placeholder for fitted output @@ -57,40 +56,79 @@ def __init__(self, columns: list=None, weight: float=0.0): # self.randomized = randomized # self.sigma = sigma - def fit(self, X: pd.DataFrame, y: pd.Series): - """Fit the TargetEncoder to X and y + def to_dict(self) -> dict: + """Return the attributes of TargetEncoder in a dictionary + + Returns + ------- + dict + Contains the attributes of TargetEncoder instance with the names + as keys + """ + params = self.get_params() + + params["_mapping"] = { + key: value.to_dict() + for key, value in self._mapping.items() + } + + return params + + def from_dict(self, params: dict): + """Summary Parameters ---------- - X : pd.DataFrame + params : dict + Description + """ + + if "weight" in params and type(params["weight"]) == float: + self.weight = params["weight"] + + _mapping = {} + if "_mapping" in params and type(params["_mapping"]) == dict: + _mapping = params["_mapping"] + + def dict_to_series(key, value): + s = pd.Series(value) + s.index.name = key + return s + + self._mapping = { + key: dict_to_series(key, value) + for key, value in _mapping.items() + } + + return self + + def fit(self, data: pd.DataFrame, column_names: list, + target_column: str): + """Fit the TargetEncoder to data and y + + Parameters + ---------- + data : pd.DataFrame data used to compute the mapping to encode the categorical variables with. - y : pd.Series - series containing the targets for each observation - - Raises - ------ - ValueError - if the length of X and y are not equal + column_names : list + Columns of data to be encoded + target_column : str + Column name of the target """ - # The lengths must be equal - if len(X.index) != len(y.index): - raise ValueError("The length of X is {}, but the length of y is {}" - .format(len(X.index), len(y.index))) - - if self.columns is None: - self.columns = TargetEncoder._get_categorical_columns(X) # compute global mean (target incidence in case of binary target) + y = data[target_column] global_mean = y.sum() / y.count() - for column in self.columns: - if column not in X.columns: + for column in column_names: + if column not in data.columns: log.warning("DataFrame has no column '{}', so it will be " "skipped in fitting" .format(column)) continue - self._mapping[column] = self._fit_column(X[column], y, global_mean) + self._mapping[column] = self._fit_column(data[column], y, + global_mean) def _fit_column(self, X: pd.Series, y: pd.Series, global_mean: float) -> pd.Series: @@ -123,7 +161,8 @@ def _fit_column(self, X: pd.Series, y: pd.Series, return numerator/denominator - def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: + def transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: """Replace (e.g. encode) categories of each column with its average incidence which was computed when the fit method was called @@ -131,8 +170,8 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: ---------- X : pd.DataFrame data to encode - y : pd.Series, optional - Ignored (added for compatibility with scikit-learn) + column_names : list + Columns of data to be encoded Returns ------- @@ -153,9 +192,9 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: raise NotFittedError(msg.format(self.__class__.__name__)) new_columns = [] - for column in self.columns: + for column in column_names: - if column not in X.columns: + if column not in data.columns: log.warning("Column '{}' is not in fitted output " "and will be skipped".format(column)) continue @@ -165,32 +204,12 @@ def transform(self, X: pd.DataFrame, y: pd.Series=None) -> pd.DataFrame: # Convert dtype to float because when the original dtype # is of type "category", the resulting dtype is also of type # "category" - X[new_column] = (X[column].map(self._mapping[column]) - .astype("float")) + data[new_column] = (data[column].map(self._mapping[column]) + .astype("float")) new_columns.append(new_column) - return X[new_columns] - - @staticmethod - def _get_categorical_columns(data: pd.DataFrame) -> list: - """Get the columns containing categorical data - (dtype "object" or "category") - - Parameters - ---------- - data : pd.DataFrame - Description - - Returns - ------- - list - List of column names containing categorical data - """ - object_columns = data.dtypes[data.dtypes == object].index - categorical_columns = data.dtypes[data.dtypes == "category"].index - - return list(set(object_columns).union(set(categorical_columns))) + return data @staticmethod def _clean_column_name(column_name: str) -> str: diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py index 6766571..2b4144a 100644 --- a/tests/preprocessing/test_target_encoder.py +++ b/tests/preprocessing/test_target_encoder.py @@ -10,6 +10,71 @@ def test_target_encoder_constructor_value_error(self): with pytest.raises(ValueError): TargetEncoder(weight=-1) + # Tests for to_dict and from_dict + def test_target_encoder_to_dict(self): + + encoder = TargetEncoder() + + mapping_data = pd.Series(data=[0.333333, 0.50000, 0.666667], + index=["negative", "neutral", "positive"]) + mapping_data.index.name = "variable" + + encoder._mapping["variable"] = mapping_data + + actual = encoder.to_dict() + + expected = {"weight": 0.0, + "_mapping": {"variable": { + "negative": 0.333333, + "neutral": 0.50000, + "positive": 0.666667 + }}} + + assert actual == expected + + @pytest.mark.parametrize("attribute", + [("weight",), ("mapping",)], + ids=["test_weight", "test_mapping"]) + def test_target_encoder_from_dict_unfitted(self, attribute): + + encoder = TargetEncoder() + + data = {"weight": 1.0} + encoder.from_dict(data) + + if attribute == "weight": + actual = encoder.weight + expected = 1.0 + + assert expected == actual + elif attribute == "mapping": + actual = encoder._mapping + expected = {} + + assert expected == actual + + def test_target_encoder_from_dict(self): + + encoder = TargetEncoder() + + data = {"weight": 0.0, + "_mapping": {"variable": { + "negative": 0.333333, + "neutral": 0.50000, + "positive": 0.666667 + }}} + + encoder.from_dict(data) + + expected = pd.Series(data=[0.333333, 0.50000, 0.666667], + index=["negative", "neutral", "positive"]) + expected.index.name = "variable" + + actual = encoder._mapping["variable"] + + pd.testing.assert_series_equal(actual, expected, + check_less_precise=5) + # Tests for _fit_column def test_target_encoder_fit_column(self): @@ -19,7 +84,7 @@ def test_target_encoder_fit_column(self): 'neutral'], 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) - encoder = TargetEncoder(columns=["variable"]) + encoder = TargetEncoder() actual = encoder._fit_column(X=df.variable, y=df.target, global_mean=0.0) @@ -40,7 +105,7 @@ def test_target_encoder_fit_column_global_mean(self): global_mean = df.target.sum() / df.target.count() # is 0.5 - encoder = TargetEncoder(columns=["variable"], weight=1) + encoder = TargetEncoder(weight=1) actual = encoder._fit_column(X=df.variable, y=df.target, global_mean=global_mean) @@ -52,16 +117,6 @@ def test_target_encoder_fit_column_global_mean(self): check_less_precise=3) # Tests for fit method - def test_target_encoder_fit_value_error(self): - - X = pd.DataFrame({'variable': ['positive', 'positive', 'negative']}) - - target = pd.Series([1, 1, 0, 0]) - - encoder = TargetEncoder(columns=["variable"]) - with pytest.raises(ValueError): - encoder.fit(X, target) - def test_target_encoder_fit(self): df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', @@ -70,8 +125,8 @@ def test_target_encoder_fit(self): 'neutral'], 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) - encoder = TargetEncoder(columns=["variable"]) - encoder.fit(X=df, y=df.target) + encoder = TargetEncoder() + encoder.fit(data=df, column_names=["variable"], target_column="target") expected = pd.Series(data=[0.333333, 0.50000, 0.666667], index=["negative", "neutral", "positive"]) @@ -93,35 +148,18 @@ def test_target_encoder_transform(self): # inputs of TargetEncoder will be of dtype category df["variable"] = df["variable"].astype("category") - expected = pd.DataFrame({'variable_enc': [0.666667, 0.666667, - 0.333333, 0.50000, - 0.333333, 0.666667, - 0.333333, 0.50000, - 0.50000, 0.50000]}) + expected = df.copy() + expected["variable_enc"] = [0.666667, 0.666667, 0.333333, 0.50000, + 0.333333, 0.666667, 0.333333, 0.50000, + 0.50000, 0.50000] - encoder = TargetEncoder(columns=["variable"]) - encoder.fit(X=df, y=df.target) - actual = encoder.transform(X=df, y=df.target) + encoder = TargetEncoder() + encoder.fit(data=df, column_names=["variable"], target_column="target") + actual = encoder.transform(data=df, column_names=["variable"]) pd.testing.assert_frame_equal(actual, expected, check_less_precise=5) - # Tests for _get_categorical_columns - def test_target_encoder_get_categorical_columns(self): - - df = pd.DataFrame({"continuous": [1.0, 1.5, 2.0], - "categorical": ["negative", "neutral", "positive"], - "object": ["cats", "dogs", "goldfish"]}) - - expected = ["categorical", "object"] - - encoder = TargetEncoder() - actual = encoder._get_categorical_columns(df) - - # It is OK to take sets here because we also do that in the - # _get_categorical_columns function - assert set(actual) == set(expected) - # Tests for _clean_column_name def test_target_encoder_clean_column_name(self): From 4ffc7f07b04bfaf55612e7813c0c6feaf9ea1018 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 28 Nov 2019 18:53:14 +0100 Subject: [PATCH 20/98] Add (de)serialization to KBinsDiscretizer Serialization transforms instance attributes to JSON and back Added unittests for these methods Renamed serialization methods in TargetEncoder and fixed bug in unittests of TargetEncoder --- cobra/preprocessing/kbins_discretizer.py | 62 +++++++++++++++++-- cobra/preprocessing/target_encoder.py | 10 +-- tests/preprocessing/test_kbins_discretizer.py | 60 ++++++++++++++++++ tests/preprocessing/test_target_encoder.py | 16 ++--- 4 files changed, 132 insertions(+), 16 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 57bd08c..89c4621 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -23,11 +23,12 @@ import numpy as np import pandas as pd +from sklearn.base import BaseEstimator from sklearn.exceptions import NotFittedError #from sklearn.cluster import KMeans -class KBinsDiscretizer: +class KBinsDiscretizer(BaseEstimator): """Bin continuous data into intervals of predefined size. This provides a way to partition continuous data into discrete values, i.e. tranform @@ -64,6 +65,9 @@ class KBinsDiscretizer: """ valid_strategies = ("uniform", "quantile") + valid_keys = ["n_bins", "strategy", "closed", "auto_adapt_bins", + "starting_precision", "label_format", + "change_endpoint_format"] def __init__(self, n_bins: int=10, strategy: str="quantile", closed: str="right", @@ -109,9 +113,59 @@ def _validate_n_bins(self, n_bins: int): "of bins. Received {}, expected at least 2." .format(KBinsDiscretizer.__name__, n_bins)) - def set_bins_by_columns(self, bins_by_column: List[tuple]): - # To do: add checks! - self._bins_by_column = bins_by_column + def attributes_to_dict(self) -> dict: + """Return the attributes of KBinsDiscretizer in a dictionary + + Returns + ------- + dict + Contains the attributes of KBinsDiscretizer instance with the names + as keys + """ + params = self.get_params() + + params["_bins_by_column"] = { + key: [list(tup) for tup in value] + for key, value in self._bins_by_column.items() + } + + return params + + def set_attributes_from_dict(self, params: dict): + """Set instance attributes from a dictionary of values with key the + name of the attribute. + + Parameters + ---------- + params : dict + Contains the attributes of KBinsDiscretizer with their + names as key. + + Raises + ------ + ValueError + In case _bins_by_column is not of type dict + """ + _bins_by_column = params.pop("_bins_by_column", {}) + + if type(_bins_by_column) != dict: + raise ValueError("_bins_by_column is expected to be a dict " + "but is of type {} instead" + .format(type(_bins_by_column))) + + # Clean out params dictionary to remove unknown keys (for safety!) + params = {key: params[key] for key in params if key in self.valid_keys} + + # We cannot turn this method into a classmethod as we want to make use + # of the following method from BaseEstimator: + self.set_params(**params) + + self._bins_by_column = { + key: [tuple(l) for l in value] + for key, value in _bins_by_column.items() + } + + return self def fit(self, data: pd.DataFrame, column_names: list): """Fits the estimator diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index 2fc2369..86ff882 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -56,7 +56,7 @@ def __init__(self, weight: float=0.0): # self.randomized = randomized # self.sigma = sigma - def to_dict(self) -> dict: + def attributes_to_dict(self) -> dict: """Return the attributes of TargetEncoder in a dictionary Returns @@ -74,13 +74,15 @@ def to_dict(self) -> dict: return params - def from_dict(self, params: dict): - """Summary + def set_attributes_from_dict(self, params: dict): + """Set instance attributes from a dictionary of values with key the + name of the attribute. Parameters ---------- params : dict - Description + Contains the attributes of TargetEncoder with their + names as key. """ if "weight" in params and type(params["weight"]) == float: diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index a2ecdc2..4c39e90 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -15,6 +15,66 @@ def does_not_raise(): class TestKBinsDiscretizer: ################# Test for public methods ################# + def test_attributes_to_dict(self): + + discretizer = KBinsDiscretizer() + + bins = [(0.0, 3.0), (3.0, 6.0), (6.0, 9.0)] + discretizer._bins_by_column = {"variable": bins} + + actual = discretizer.attributes_to_dict() + + expected = { + "n_bins": 10, + "strategy": "quantile", + "closed": "right", + "auto_adapt_bins": False, + "starting_precision": 0, + "label_format": "{} - {}", + "change_endpoint_format": False, + "_bins_by_column": {"variable": [[0.0, 3.0], [3.0, 6.0], + [6.0, 9.0]]} + } + + assert actual == expected + + @pytest.mark.parametrize("attribute", + ["n_bins", "strategy", "closed", + "auto_adapt_bins", "starting_precision", + "label_format", "change_endpoint_format", + "_bins_by_column"], + ids=["n_bins", "strategy", "closed", + "auto_adapt_bins", "starting_precision", + "label_format", "change_endpoint_format", + "_bins_by_column"]) + def test_set_attributes_from_dict(self, attribute): + + discretizer = KBinsDiscretizer() + + params = { + "n_bins": 5, + "strategy": "uniform", + "closed": "left", + "auto_adapt_bins": True, + "starting_precision": 1, + "label_format": "[,)", + "change_endpoint_format": True, + "_bins_by_column": {"variable": [[0.0, 3.0], [3.0, 6.0], + [6.0, 9.0]]} + } + + expected = params[attribute] + + if attribute == "_bins_by_column": + # list of list is transformed to a list of tuples + # in KBinsDiscretizer!!! + expected = {"variable": [(0.0, 3.0), (3.0, 6.0), (6.0, 9.0)]} + + discretizer.set_attributes_from_dict(params) + + actual = getattr(discretizer, attribute) + + assert actual == expected ################# Test for private methods ################# @pytest.mark.parametrize("n_bins, expectation", diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py index 2b4144a..25b5f3b 100644 --- a/tests/preprocessing/test_target_encoder.py +++ b/tests/preprocessing/test_target_encoder.py @@ -10,8 +10,8 @@ def test_target_encoder_constructor_value_error(self): with pytest.raises(ValueError): TargetEncoder(weight=-1) - # Tests for to_dict and from_dict - def test_target_encoder_to_dict(self): + # Tests for attributes_attributes_to_dict and set_attributes_from_dict + def test_target_encoder_attributes_to_dict(self): encoder = TargetEncoder() @@ -21,7 +21,7 @@ def test_target_encoder_to_dict(self): encoder._mapping["variable"] = mapping_data - actual = encoder.to_dict() + actual = encoder.attributes_to_dict() expected = {"weight": 0.0, "_mapping": {"variable": { @@ -33,14 +33,14 @@ def test_target_encoder_to_dict(self): assert actual == expected @pytest.mark.parametrize("attribute", - [("weight",), ("mapping",)], + ["weight", "mapping"], ids=["test_weight", "test_mapping"]) - def test_target_encoder_from_dict_unfitted(self, attribute): + def test_target_encoder_set_attributes_from_dict_unfitted(self, attribute): encoder = TargetEncoder() data = {"weight": 1.0} - encoder.from_dict(data) + encoder.set_attributes_from_dict(data) if attribute == "weight": actual = encoder.weight @@ -53,7 +53,7 @@ def test_target_encoder_from_dict_unfitted(self, attribute): assert expected == actual - def test_target_encoder_from_dict(self): + def test_target_encoder_set_attributes_from_dict(self): encoder = TargetEncoder() @@ -64,7 +64,7 @@ def test_target_encoder_from_dict(self): "positive": 0.666667 }}} - encoder.from_dict(data) + encoder.set_attributes_from_dict(data) expected = pd.Series(data=[0.333333, 0.50000, 0.666667], index=["negative", "neutral", "positive"]) From 1d58ba452db864a7443c1c8715b4763201190415 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 13 Dec 2019 16:05:50 +0100 Subject: [PATCH 21/98] Clean up of old files --- cobra/preprocessing/kbins_discretizer.py | 5 + cobra/utils.py | 10 +- dev/preprocessor/__init__.py | 0 dev/preprocessor/categorical_regrouper.py | 391 --------------------- dev/preprocessor/compare.py | 154 --------- dev/preprocessor/develop.py | 50 --- dev/preprocessor/new_regroup.py | 189 ----------- tests/model_build.PNG | Bin 106292 -> 0 bytes tests/model_comp.PNG | Bin 84607 -> 0 bytes tests/predictors.PNG | Bin 72134 -> 0 bytes tests/stability_tests.py | 396 ---------------------- tests/tests.py | 394 --------------------- 12 files changed, 10 insertions(+), 1579 deletions(-) delete mode 100644 dev/preprocessor/__init__.py delete mode 100644 dev/preprocessor/categorical_regrouper.py delete mode 100644 dev/preprocessor/compare.py delete mode 100644 dev/preprocessor/develop.py delete mode 100644 dev/preprocessor/new_regroup.py delete mode 100644 tests/model_build.PNG delete mode 100644 tests/model_comp.PNG delete mode 100644 tests/predictors.PNG delete mode 100644 tests/stability_tests.py delete mode 100644 tests/tests.py diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 89c4621..603bac5 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -186,6 +186,11 @@ def fit(self, data: pd.DataFrame, column_names: list): for column_name in column_names: + if column_name not in data.columns: + log.warning("DataFrame has no column '{}', so it will be " + "skipped in fitting" .format(column_name)) + continue + bins = self._fit_column(data, column_name) # Add to bins_by_column for later use diff --git a/cobra/utils.py b/cobra/utils.py index b6f25ec..ea09c64 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -5,7 +5,7 @@ def get_column_datatypes(data: pd.DataFrame, target_column_name: str=None, id_column_name: str=None, - numeric_is_categorical_threshold: int=10) -> dict: + threshold_numeric_is_categorical: int=10) -> dict: """Get a list of column names by data type from a pandas DataFrame, excluding the id column and the target_column if provided @@ -17,7 +17,7 @@ def get_column_datatypes(data: pd.DataFrame, Description id_column_name : str, optional Description - numeric_is_categorical_threshold : int, optional + threshold_numeric_is_categorical : int, optional Threshold to decide whether a numeric variable is categorical based on the number of unique values in that column @@ -47,10 +47,10 @@ def get_column_datatypes(data: pd.DataFrame, # Remark: numeric variables can still be "categorical" # i.e. when they only contain some distinct values! # We only consider a variable continuous if they have more distinct values - # than the requested number bins (using numeric_is_categorical_threshold) + # than the requested number bins (using threshold_numeric_is_categorical) - # continuous if more than numeric_is_categorical_threshold distinct values - threshold = numeric_is_categorical_threshold + # continuous if more than threshold_numeric_is_categorical distinct values + threshold = threshold_numeric_is_categorical vars_cat_numeric = set([col for col in vars_numeric if len(data[col].unique()) < threshold]) diff --git a/dev/preprocessor/__init__.py b/dev/preprocessor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dev/preprocessor/categorical_regrouper.py b/dev/preprocessor/categorical_regrouper.py deleted file mode 100644 index a6276d4..0000000 --- a/dev/preprocessor/categorical_regrouper.py +++ /dev/null @@ -1,391 +0,0 @@ - -import pandas as pd -import numpy as np -from scipy import stats -from typing import Dict -from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.exceptions import NotFittedError -import logging - -log = logging.getLogger(__name__) - - -class CategoryRegrouper(BaseEstimator, TransformerMixin): - """ - Regroups categories in categorical variables based on significance - with target variable. - - Parameters - ---------- - scale_cont : bool, default=True - Whether contingency table should be scaled before chi^2.' - - pval_thresh : float, default=0.001 - Significance threshold for regroupping. - - regroup_rename : str, default='non-significant' - New name of non-significant regroupped variables. - - missing_rename : str, default='Missing' - New name of missing categories. - - keep_missing : bool, default=Falsse - Whether missing category should be kept in the result. - - forced_categories : Dict, default=None - Dictionary to force categories - - for each colum dict of {col:[forced vars]}. - - Attributes - ---------- - all_category_map_ : Dict - Dictionary with mapping for each variable. - """ - def __init__(self, scale_cont: bool = True, - pval_thresh: float = 0.001, - regroup_rename: str = "non-significant", - missing_rename: str = "Missing", - keep_missing: bool = False, - forced_categories: Dict = None): - self.scale_cont = scale_cont - self.pval_thresh = pval_thresh - self.regroup_rename = regroup_rename - self.missing_rename = missing_rename - self.keep_missing = keep_missing - self.forced_categories = forced_categories - - def fit(self, X: pd.DataFrame, - y: pd.Series, - columns: list = []): - """ - Method regroups categories whole DataFrame. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - y: pd.Series - Series with target variable. - - columns : list, default=[] - Columns to be regrouped. - - Raises - ------ - ValueError - In case X and y are not of the same length. - - Returns - ------- - None - Only fits the instance of the class. - """ - self.all_category_map_ = {} - - if len(X.index) != len(y.index): - raise ValueError("The length of X is {}, but the length of y is {}" - .format(len(X.index), len(y.index))) - - if not columns: - columns = CategoryRegrouper._get_categorical_columns(X) - log.warning("All object-type columns have been selected") - - for column in columns: - if column not in X.columns: - log.warning("DataFrame has no column '{}', so it will be " - "skipped in fitting" .format(column)) - continue - - self.all_category_map_[column] = self._fit_column(X=X, - y=y, - column=column) - - def _fit_column(self, X: pd.DataFrame, - y: pd.Series, - column: str) -> Dict: - """ - Method regroups categories in given column. - - Parameters - ---------- - X : pd.Series - Series with one column to be transformed. - - y: pd.Series - Series with target variable - - column : str - Column to be regrouped. - - Raises - ------ - ValueError - in case input column is not a string. - - Returns - ------- - Dict - Returns dictionary as {old category : new category} for - specific column. - """ - category_map = {} - keep_categories = [] - incidence_mean = y.mean() - - # Rename target - y.rename("TARGET", inplace=True) - - # Replace missings - X = self._replaceMissings(X=X, column=column, - replace_with=self.missing_rename) - - all_uq_categories = X[column].unique().tolist() - - # Remove small categories - categories = self._removeCategories(X=X, y=y, column=column) - - # Inspect remaining categories and test significance - for category in categories: - df_aux = pd.concat([X[column], y], axis=1) - df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) - cont_table = pd.crosstab(index=df_aux['other_cats'], - columns=df_aux['TARGET'], - margins=False) - - # if true, we scale the "other" categories - if self.scale_cont: - size_other_cats = cont_table.iloc[1].sum() - cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cats - cont_table.iloc[1, 1] = incidence_mean*size_other_cats - cont_table = cont_table.values.astype(np.int64) - - pval = stats.chi2_contingency(cont_table, correction=False)[1] - - # If significant, keep it - if pval <= self.pval_thresh: - keep_categories.append(category) - - # Keep "Missing" even if it wasn't selected if - # it is in the original categories and set to True - if ((self.missing_rename not in keep_categories) and - (self.missing_rename in all_uq_categories) and self.keep_missing): - keep_categories.append(self.missing_rename) - - # Keep forced categories - if self.forced_categories is not None: - # If doesnt exists, give warning - forced = [col for col in self.forced_categories[column] - if col in all_uq_categories] - - # Extend list and remove duplicates - keep_categories = list(set(keep_categories.extend(forced))) - - difference = set(forced) - set(self.forced_categories[column]) - if len(difference) > 0: - log.warning("Following forced categories: {} " - "are not in column: {}.".format(difference, - column)) - - # Return dictionary as {old column : new column} - for category in all_uq_categories: - if category in keep_categories: - category_map[category] = category - else: - category_map[category] = self.regroup_rename - - return category_map - - def transform(self, X: pd.DataFrame, - columns: list = []) -> pd.DataFrame: - """ - Method transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - columns : list, default=[] - Columns to be regrouped. - - Raises - ------ - NotFittedError - If fit() method has not been called. - - ValueError - If columns to be transformed have not been fitted. - - Returns - ------- - pd.DataFrame - Returns transformed DataFrame with new columns as "col_regrouped". - """ - if len(self.all_category_map_) == 0: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - fitted_columns = list(self.all_category_map_.keys()) - - # if specified columns not in fitted Dict, raise error - if not set(columns).issubset(set(fitted_columns)): - diff_cols = set(columns).difference(set(fitted_columns)) - raise ValueError("Following columns are not fitted: " - "{}".format(diff_cols)) - - X_tr = X.copy() - for column in columns: - X_tr[column + "_regrouped"] = self._transform_column(X=X, - column=column) - - return X_tr - - def _transform_column(self, X: pd.DataFrame, - column: str) -> pd.Series: - """ - Method transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - column : str - Column to be regrouped. - - Returns - ------- - pd.Series - Returns DataFrame with regrouped variable as category datatype. - """ - X_tr = X[column].copy() - X_tr[column + "_regrouped"] = X_tr.replace( - to_replace=self.all_category_map_[column]) - - X_tr[column + "_regrouped"] = X_tr[column + - "_regrouped"].astype('category') - - return X_tr[column + "_regrouped"] - - def fit_transform(self, X: pd.DataFrame, - y: pd.Series, - columns: list = []) -> pd.DataFrame: - """ - Auxiliary method fits and transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - y : pd.Series - Series with target variable - - column : list, default=[] - Columns to be regrouped. - - Returns - ------- - pd.DataFrame - Returns DataFrame with regrouped variable as category datatype. - """ - self.fit(X=X, y=y, columns=columns) - - X_tr = self.transform(X=X, columns=columns) - - return X_tr - - def _replaceMissings(self, X: pd.DataFrame, - column: str, - replace_with: str = 'Missing') -> pd.DataFrame: - """ - Method replaces missing and empty cells with `Missing` (default) in - a pd.DataFrame. - - Parameters - ---------- - X : pd.DataFrame - Dataframe where a value will be replaced if empty or nan. - - column : str - Column to be analyzed for missings. - - replace_with : str default='Missing' - String to replace the missings. - - Raises - ------ - ValueError - In case input column is not a string. - - Returns - ------- - pd.DataFrame - Modified dataframe with replaced missings. - """ - if X[column].dtype != 'O' or X[column].dtype != 'object': - raise TypeError("column {} must be a string".format(column)) - - X[column].fillna(replace_with, inplace=True) - X[column] = X[column].astype(str).str.strip() - X[column].replace('', replace_with, inplace=True) - - return X - - def _removeCategories(self, X: pd.DataFrame, - y: pd.Series, - column: str, - threshold: int = 5) -> np.ndarray: - """ - Method removes category which fail to meet certain condition - - Parameters - ---------- - X : pd.DataFrame - Dataframe with columns to be inspected for group removal. - - y : pd.Series - Series with target. - - column : str - Column to be analyzed group removal. - - threshold : int default=5 - Threshold for group removal. - - Returns - ------- - np.ndarray - Numpy array with groups to be kept. - """ - category_cnts = pd.DataFrame(X.groupby(column)[column].count()) - train_inc = y.mean() - factor = max(train_inc, 1-train_inc) - keep_categories = category_cnts.where((category_cnts*factor) > - threshold) - - return np.array(keep_categories.index.tolist()) - - @staticmethod - def _get_categorical_columns(data: pd.DataFrame) -> list: - """Get the columns containing categorical data - (dtype "object" or "category") - - Parameters - ---------- - data : pd.DataFrame - Dataframe from which categorical variables - will be extracted. - - Returns - ------- - list - List of column names containing categorical data. - """ - object_columns = data.dtypes[data.dtypes == object].index - categorical_columns = data.dtypes[data.dtypes == "category"].index - - return list(set(object_columns).union(set(categorical_columns))) diff --git a/dev/preprocessor/compare.py b/dev/preprocessor/compare.py deleted file mode 100644 index f863bfa..0000000 --- a/dev/preprocessor/compare.py +++ /dev/null @@ -1,154 +0,0 @@ -#%% -import pandas as pd -import numpy as np -from random import shuffle -from scipy import stats -from typing import Dict, Tuple -import sys - -sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra/dev") - -import preprocessor.categorical_regrouper as pr - -import logging -log = logging.getLogger(__name__) - -ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" -df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") -df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) -df_data['Pclass'] = df_data['Pclass'].astype(object) - -split = ['TRAIN']*int(df_data.shape[0]*0.7) + \ - ['TEST']*int(df_data.shape[0]*0.2)+ \ - ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.1)) - -shuffle(split) - -df_data['PARTITION'] = split - -df_x = pd.DataFrame(df_data[['Pclass', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) -df_y = df_data['TARGET'][df_data['PARTITION'] == "TRAIN"] - -#%% -""" NEW SOLUTION """ -CR = pr.CategoryRegrouper() - -CR.fit(X=df_x, y=df_y, columns=["Embarked", "Pclass"]) -print(CR.all_category_map_) -df_new = CR.transform(X=df_x, columns=["Embarked", "Pclass"]) - -#%% -""" OLD SOLUTION """ -def __regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): - ''' - Method regroups categorical variables - Returns DF mask - ---------------------------------------------------- - var: input pd.Serie with cat column - target: pd.Serie with target variable - train: pd.Serie with parition variable - pval_thresh: threshold for regrouping - dummy: scale of booleans (?) - keep: keep specific groups (?) - rename: rename the insignificant category - ---------------------------------------------------- - - Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group - - The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or - - remaining groups average incidence' (if dummy=False) - - Groups with a pvalue above the threshold are relabled to a single group - ''' - - # Define the chi² test condition - # Groups that do not meet the condition are not analyzed and will be unconditionally relabled - def _chi2cond_(var=var,target=target,train=train): - varcounts = var[train].groupby(by=var).count() - train_inc = target[train].sum()/len(target[train]) - factor = max(train_inc, 1-train_inc) - analyze_mask = (varcounts*factor)>5 - analyze_groups = analyze_mask.index[analyze_mask].values - return analyze_groups - - # Compute overal incidence mean - incidence_mean = target[train].mean() - # Create container of which groups will be kept, compared to the groups which will be relabled - keepgroups = [] - # Cycle and test each group that meets the chi² condition - for group in _chi2cond_(): - # Container for target 0/1 observations of the group under scrutiny - obs_group = [] - # Counts of the target 0/1 occurences for the group under scrutiny - obs_group.append(((target[train]==0)&(var[train]==group)).sum()) - obs_group.append(((target[train]==1)&(var[train]==group)).sum()) - obs_group = np.array(obs_group) - # Container for target 0/1 observations of the remaining groups together - obs_other = [] - # Counts of the target 0/1 occurences for the remaining groups together - obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) - obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) - obs_other = np.array(obs_other) - # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence - # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups - if dummy: - obs_other_size = obs_other.sum() - obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) - obs_other[1]=( incidence_mean)*obs_other_size - obs = np.array([obs_group,obs_other]) - # Place at least 1 observation to avoid error in chi2 test - obs[obs==0] = 1 - # Perform chi² test - pval = stats.chi2_contingency(obs, correction=False)[1] - # If pval outperforms threshold, append the group in the keepgroups list - if pval<=pval_thresh: - keepgroups.append(group) - #elif group==keep: - # keepgroups.append(group) - # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list - if keep not in keepgroups: - keepgroups.append(keep) - # Makes a list of all groups not in the keepgroups list - regroup_mask = [val not in keepgroups for val in var.values] - var_regroup = var.copy() - # Rename those groups - var_regroup[regroup_mask] = rename - var_regroup.name = "B_"+var.name - info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) - return var_regroup, info - -#%% -result = __regroup(var=df_data['Pclass'], #Cabin, Pclass, SibSp, Parch, Embarked - target=df_data.loc[:,'TARGET'], - train=df_data['PARTITION']=='TRAIN', - pval_thresh=0.05, - dummy=True, - keep='Missing', - rename='non-significant') - -print(result[0].unique()) -print(result[0].head(n=5)) -#print(result[1]) -df_orig = result[0].to_frame() -df_orig.columns = ["old"] -df_orig["old"] = df_orig["old"].astype('str') -df_orig["old"] = df_orig["old"].astype('category') - -df_orig['split'] = df_data['PARTITION'] -df_orig = df_orig[df_orig['split'] == 'TRAIN'] - - -#%% -""" COMPARE """ -#df_orig.loc[:,"new"] = df_new['Embarked_regrouped'].copy() -df_orig.loc[:,"new"] = df_new['Pclass_regrouped'].copy() - -print(df_orig) - -df_orig['compare'] = df_orig["new"] == df_orig["old"] - -print(df_orig[df_orig['compare'] == False]) - - - -#%% - - -#%% diff --git a/dev/preprocessor/develop.py b/dev/preprocessor/develop.py deleted file mode 100644 index 6e8c4dc..0000000 --- a/dev/preprocessor/develop.py +++ /dev/null @@ -1,50 +0,0 @@ -#%% -import pandas as pd -import numpy as np -from random import shuffle -from scipy import stats -from typing import Dict, Tuple -import sys - -sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra/dev/preprocessor") - -import preprocessor.categorical_regrouper as pr - -import logging -log = logging.getLogger(__name__) - -ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" -df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") -df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) -df_data['Pclass'] = df_data['Pclass'].astype(object) - -split = ['TRAIN']*int(df_data.shape[0]*0.5) + \ - ['TEST']*int(df_data.shape[0]*0.2)+ \ - ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.3)) - -shuffle(split) - -df_data['PARTITION'] = split - -df_x = pd.DataFrame(df_data[['Pclass', 'Embarked']][df_data['PARTITION'] == "TRAIN"]) -df_y = df_data['TARGET'][df_data['PARTITION'] == "TRAIN"] - - -#%% -""" NEW SOLUTION """ -CR = pr.CategoryRegrouper() - -CR.fit(X=df_x, y=df_y, columns=["Embarked", "Pclass"]) -print(CR.all_category_map_) -df_X_tr = CR.transform(X=df_x, columns=["Embarked", "Pclass"]) - -#%% -""" OLD SOLUTION """ - - - - - - - -#%% diff --git a/dev/preprocessor/new_regroup.py b/dev/preprocessor/new_regroup.py deleted file mode 100644 index 6a467c2..0000000 --- a/dev/preprocessor/new_regroup.py +++ /dev/null @@ -1,189 +0,0 @@ -#%% -import pandas as pd -import numpy as np -from random import shuffle -from scipy import stats - -ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" -df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") -df_data.rename(columns={'Survived': 'TARGET'}, inplace=True) - -split = ['TRAIN']*int(df_data.shape[0]*0.5) + \ - ['TEST']*int(df_data.shape[0]*0.2)+ \ - ['VALIDATION']*int(np.ceil(df_data.shape[0]*0.3)) - -shuffle(split) - -df_data['PARTITION'] = split - -#%% -''' ORIGINAL CODE ''' -def __regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): - ''' - Method regroups categorical variables - Returns DF mask - ---------------------------------------------------- - var: input pd.Serie with cat column - target: pd.Serie with target variable - train: pd.Serie with parition variable - pval_thresh: threshold for regrouping - dummy: scale of booleans (?) - keep: keep specific groups (?) - rename: rename the insignificant category - ---------------------------------------------------- - - Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group - - The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or - - remaining groups average incidence' (if dummy=False) - - Groups with a pvalue above the threshold are relabled to a single group - ''' - - # Define the chi² test condition - # Groups that do not meet the condition are not analyzed and will be unconditionally relabled - def _chi2cond_(var=var,target=target,train=train): - varcounts = var[train].groupby(by=var).count() - train_inc = target[train].sum()/len(target[train]) - factor = max(train_inc, 1-train_inc) - analyze_mask = (varcounts*factor)>5 - analyze_groups = analyze_mask.index[analyze_mask].values - return analyze_groups - - # Compute overal incidence mean - incidence_mean = target[train].mean() - # Create container of which groups will be kept, compared to the groups which will be relabled - keepgroups = [] - # Cycle and test each group that meets the chi² condition - for group in _chi2cond_(): - # Container for target 0/1 observations of the group under scrutiny - obs_group = [] - # Counts of the target 0/1 occurences for the group under scrutiny - obs_group.append(((target[train]==0)&(var[train]==group)).sum()) - obs_group.append(((target[train]==1)&(var[train]==group)).sum()) - obs_group = np.array(obs_group) - # Container for target 0/1 observations of the remaining groups together - obs_other = [] - # Counts of the target 0/1 occurences for the remaining groups together - obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) - obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) - obs_other = np.array(obs_other) - # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence - # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups - if dummy: - obs_other_size = obs_other.sum() - obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) - obs_other[1]=( incidence_mean)*obs_other_size - obs = np.array([obs_group,obs_other]) - # Place at least 1 observation to avoid error in chi2 test - obs[obs==0] = 1 - # Perform chi² test - pval = stats.chi2_contingency(obs, correction=False)[1] - # If pval outperforms threshold, append the group in the keepgroups list - if pval<=pval_thresh: - keepgroups.append(group) - #elif group==keep: - # keepgroups.append(group) - # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list - if keep not in keepgroups: - keepgroups.append(keep) - # Makes a list of all groups not in the keepgroups list - regroup_mask = [val not in keepgroups for val in var.values] - var_regroup = var.copy() - # Rename those groups - var_regroup[regroup_mask] = rename - var_regroup.name = "B_"+var.name - info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) - return var_regroup, info - -#%% -''' RUN ORIGINAL CODE ''' -result = __regroup(var=df_data['Embarked'], #Cabin, Pclass, SibSp, Parch, Embarked - target=df_data.loc[:,'TARGET'], - train=df_data['PARTITION']=='TRAIN', - pval_thresh=0.05, - dummy=True, - keep='Missing', - rename='Non-significants') - -print(result[0].unique()) -print(result[0].head(n=5)) -print(result[1]) -df_tst = result[0] - -#%% -''' TEST CHISQR CONDITION ''' -def _chi2cond_(var,target,train): - #simple group by - pandas series - varcounts = var[train].groupby(by=var).count() - #train incidence - 0.3775280898876405 - train_inc = target[train].sum()/len(target[train]) - #Why? -0.6224719101123595 - factor = max(train_inc, 1-train_inc) - #which groups to analyze - boolean - analyze_mask = (varcounts*factor)>5 - #filter groups to be kept - array([0, 1, 2], dtype=int64) - analyze_groups = analyze_mask.index[analyze_mask].values - return analyze_groups - -chi = _chi2cond_(var=df_data['Embarked'], - target=df_data.loc[:,'TARGET'], - train=df_data['PARTITION']=='TRAIN') - -#%% -varcounts = df_data['Parch'][df_data['PARTITION']=='TRAIN'].groupby(by=df_data['Parch']).count() -train_inc = df_data.loc[:,'TARGET'][df_data['PARTITION']=='TRAIN'].sum()/len(df_data.loc[:,'TARGET'][df_data['PARTITION']=='TRAIN']) -factor = max(train_inc, 1-train_inc) -analyze_mask = (varcounts*factor)>5 -analyze_groups = analyze_mask.index[analyze_mask].values - -#%% -df_data['TARGET'][df_data['PARTITION']=='TRAIN'].mean() - -#%% -''' TEST TESTING ''' -target = df_data.loc[:,'TARGET'] -train = df_data['PARTITION']=='TRAIN' -var = df_data['Embarked'] - -for group in chi: - group == 'S' - # Container for target 0/1 observations of the group under scrutiny - obs_group = [] - # Counts of the target 0/1 occurences for the group under scrutiny - obs_group.append(((target[train]==0)&(var[train]==group)).sum()) - obs_group.append(((target[train]==1)&(var[train]==group)).sum()) - obs_group = np.array(obs_group) - # Container for target 0/1 observations of the remaining groups together - obs_other = [] - # Counts of the target 0/1 occurences for the remaining groups together - obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) - obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) - obs_other = np.array(obs_other) - -#S -#obs_group -#array([225, 95], dtype=int64) -# -#obs_other -#array([58, 67], dtype=int64) - -#%% -pd.crosstab(df.regiment, df_data.loc[:,'TARGET'], margins=True) - -#%% -incidence_mean = target[train].mean() -dummy=True - -if dummy: - obs_other_size = obs_other.sum() #400 - obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) - obs_other[1]=( incidence_mean)*obs_other_size -obs = np.array([obs_group,obs_other]) -# Place at least 1 observation to avoid error in chi2 test -obs[obs==0] = 1 -# Perform chi² test -pval = stats.chi2_contingency(obs, correction=False)[1] - -#obs -#array([[ 19, 26], -# [248, 151]], dtype=int64) - -#%% diff --git a/tests/model_build.PNG b/tests/model_build.PNG deleted file mode 100644 index 7560c6941ee00e8ad9255b2bfd3b0fca6052e780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106292 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%0DXZP>ZNo#b_X5&8+wm@o$2 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 TT6bGW(9HH7+?%!g_{IMNQ(%%H diff --git a/tests/model_comp.PNG b/tests/model_comp.PNG deleted file mode 100644 index 8325248d8b6aa574984ad272f0ae622dc54b0bfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84607 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 FzW_XJ^)dhe diff --git a/tests/stability_tests.py b/tests/stability_tests.py deleted file mode 100644 index 727745c..0000000 --- a/tests/stability_tests.py +++ /dev/null @@ -1,396 +0,0 @@ -''' -====================================================================================================================== ----------------------------------------------------- STABILITY TESTING --------------------------------------------- -====================================================================================================================== -TEST stability of partitioning and binning --> binning IS stable, but partitioning is not --> as a result, number of binns and their size is not stable --> therefore, incidence replacement will differ --> which means that the logit will be trained on different data, giving different coefficients and AUC --> which leads to instability - i) forward selection stops because there are no positive coefs - ii) it has an effect on AUC - it can be lower so we will drop the variable even if it is useful --> I found this behavior only for one variables, but I belive it will be case of more -''' - -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -import seaborn as sns -from sklearn.linear_model import LogisticRegression -from sklearn import metrics -import math -pd.set_option("display.max_columns",50) - -data_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data.csv' -data_types_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data_types.csv' - - - -def __eqfreq(var, train, autobins=True, nbins=10, precision=0, twobins=True, catchLarge=True): - ''' - Special method for binning continuous variables into bins - ---------------------------------------------------- - var: input pd.Serie with continuous columns - train: mask with rows which belongs to train - autobins: adapts number of bins - nbins: number of bins - precision: precision to form meaningful bins - twobins: if only two bins are found, iterate to find more - catchLarge: check when groups are too big - ---------------------------------------------------- - - This function is a reworked version of pd.qcut to satisfy our particular needs - - Takes for var a continuous pd.Series as input and returns a pd.Series with bin-labels (e.g. [4,6[ ) - - Train takes a series/list of booleans (note: we define bins based on the training set) - - Autobins reduces the number of bins (starting from nbins) as a function of the number of missings - - Nbins is the wished number of bins - - Precision=0 results in integer bin-labels if possible - - twobins=True forces the function to output at least two bins - - catchLarge tests if some groups (or missing group) are very large, and if so catches and outputs two groups - - note: catchLarge makes twobins irrelevant - ''' - - # Test for large groups and if one exists pass them with two bins: Large_group,Other - if catchLarge: - catchPercentage=1-(1/nbins) - groupCount = var[train].groupby(by=var[train]).count() - maxGroupPerc = groupCount.max()/len(var[train]) - missingPerc = sum(var[train].isnull())/len(var[train]) - if maxGroupPerc>=catchPercentage: - largeGroup = groupCount.sort_values(ascending=False).index[0] - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned!=largeGroup]='Other' - cutpoints=None - info = (var.name+": One large group, outputting 2 groups") - return x_binned, cutpoints, info - elif missingPerc>=catchPercentage: - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned.isnull()]='Missing' - x_binned[x_binned!='Missing']='Other' - cutpoints=None - info = (var.name+": One large missing group, outputting 2 groups") - return x_binned, cutpoints, info - # Adapt number of bins as a function of number of missings - if autobins: - length = len(var[train]) - missing_total = var[train].isnull().sum() - missing_perten = missing_total/length*10 - nbins = max(round(10-missing_perten)*nbins/10 ,1) - # Store the name and index of the variable - name = var.name - series_index = var.index - # Transform var and train to a np.array and list respectively, which is needed for some particular function&methods - x = np.asarray(var) - train = list(train) - # First step in finding the bins is determining what the quantiles are (named as cutpoints) - # If the quantile lies between 2 points we use lin interpolation to determine it - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1),interpolation = 'linear') - # If the variable results only in 2 unique quantiles (due to skewness) increase number of quantiles until more than 2 bins can be formed - if twobins: - extrasteps = 1 - # Include a max. extrasteps to avoid infinite loop - while (len(cutpoints.unique())<=2) & (extrasteps<20): - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1+extrasteps),interpolation = 'linear') - extrasteps+=1 - # We store which rows of the variable x lies under/above the lowest/highest cutpoint - # Without np.errstate(): xcutpoints.max() can give if x contains nan values (missings) - # However the function will result in False in both >&< cases, which is a correct result, so the warning can be ignored - with np.errstate(invalid='ignore'): - under_lowestbin = x < cutpoints.min() - above_highestbin= x > cutpoints.max() - - - def _binnedx_from_cutpoints(x, cutpoints, precision, under_lowestbin, above_highestbin): - ### Attributes the correct bin ........................ - ### Function that, based on the cutpoints, seeks the lowest precision necessary to have meaningful bins - ### e.g. (5.5,5.5] ==> (5.51,5.54] - ### Attributes those bins to each value of x, to achieve a binned version of x - - # Store unique cutpoints (e.g. from 1,3,3,5 to 1,3,5) to avoid inconsistensies when bin-label making - # Indeed, bins [...,1], (1,3], (3,3], (3,5], (5,...] do not make much sense - # While, bins [...,1], (1,3], (3,5], (5,...] do make sense - unique_cutpoints = cutpoints.unique() - # If there are only 2 unique cutpoints (and thus only one bin will be returned), - # keep original values and code missings as 'Missing' - if len(unique_cutpoints) <= 2: - cutpoints = None - x_binned = pd.Series(x) - x_binned[x_binned.isnull()] = 'Missing' - info = (var.name+": Only one resulting bin, keeping original values instead") - return x_binned, cutpoints, info - # Store info on whether or not the number of resulting bins equals the desired number of bins - elif len(unique_cutpoints) < len(cutpoints): - info = (var.name+": Resulting # bins < whished # bins") - else: - info = (var.name+": Resulting # bins as desired") - # Finally, recode the cutpoints (which can have doubles) as the unique cutpoints - cutpoints = unique_cutpoints - - # Store missing values in the variable as a mask, and create a flag to test if there are any missing in the variable - na_mask = np.isnan(x) - has_nas = na_mask.any() - # Attribute to every x-value the index of the cutpoint (from the sorted cutpoint list) which is equal or higher than - # the x-value, effectively encompasing that x-value. - # e.g. for x=6 and for sorted_cutpoint_list=[0,3,5,8,...] the resulting_index=3 - ids = cutpoints.searchsorted(x, side='left') - # x-values equal to the lowest cutpoint will recieve a ids value of 0 - # but our code to attribute bins to x-values based on ids (see end of this subfunction) requires a min. value of 1 - ids[x == cutpoints[0]] = 1 - # Idem as previous: x-values below the lowest cutpoint should recieve a min. value of 1 - if under_lowestbin.any(): - ids[under_lowestbin] = 1 - # Similar as previous: x-values above the highest cutpoint should recieve the max. allowed ids - if above_highestbin.any(): - max_ids_allowed = ids[(above_highestbin == False) & (na_mask==False)].max() - ids[above_highestbin] = max_ids_allowed - # Maximal ids can now be defined if we neglect ids of missing values - max_ids = ids[na_mask==False].max() - - # Based on the cutpoints create bin-labels - # Iteratively go through each precision (= number of decimals) until meaningful bins are formed - # If theoretical bin is ]5.51689,5.83654] we will prefer ]5.5,5.8] as output bin - increases = 0 - original_precision = precision - while True: - try: - bins = _format_bins(cutpoints, precision) - except ValueError: - increases += 1 - precision += 1 - #if increases >= 5: - #warnings.warn("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - #print("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - else: - break - - # Make array of bins to allow vector-like attribution - bins = np.asarray(bins, dtype=object) - # If x has nas: for each na-value, set the ids-value to max_ids+1 - # this will allow na-values to be attributed the highest bin which we define right below - if has_nas: - np.putmask(ids, na_mask, max_ids+1) - # The highest bin is defined as 'Missing' - bins = np.append(bins,'Missing') - # ids-1 is used as index in the bin-labels list to attribute a bin-label to each x. Example: - # x=6 sorted_cutpoint_list=[0,3,5,8,...] ids=3 levels=[[0,3],(3,5],(5,8],...] - # The correct bin level for x is (5,8] which has index 2 which is equal to the ids-1 - x_binned = bins[ids-1] - return x_binned, cutpoints, info - - - def _format_bins(cutpoints, prec): - # Based on the quantile list create bins. Raise error if values are similar within one bin. - # On error _binnedx_from_cutpoints will increase precision - - fmt = lambda v: _format_label(v, precision=prec) - bins = [] - for a, b in zip(cutpoints, cutpoints[1:]): - fa, fb = fmt(a), fmt(b) - - if a != b and fa == fb: - raise ValueError('precision too low') - - formatted = '(%s, %s]' % (fa, fb) - bins.append(formatted) - - bins[0] = '[...,' + bins[0].split(",")[-1] - bins[-1] = bins[-1].split(",")[0] + ',...]' - return bins - - - def _format_label(x, precision): - # For a specific precision, returns the value formatted with the appropriate amount of numbers after comma and correct brackets - - if isinstance(x,float): - frac, whole = np.modf(x) - sgn = '-' if x < 0 else '' - whole = abs(whole) - if frac != 0.0: - val = '{0:.{1}f}'.format(frac, precision) - val = _trim_zeros(val) - if '.' in val: - return sgn + '.'.join(('%d' % whole, val.split('.')[1])) - else: - if '0' in val: - return sgn + '%0.f' % whole - else: - return sgn + '%0.f' % (whole+1) - else: - return sgn + '%0.f' % whole - else: - return str(x) - - - def _trim_zeros(x): - # Removes unnecessary zeros and commas - while len(x) > 1 and x[-1] == '0': - x = x[:-1] - if len(x) > 1 and x[-1] == '.': - x = x[:-1] - return x - - x_binned, cutpoints, info = _binnedx_from_cutpoints(x, cutpoints, precision=precision, under_lowestbin=under_lowestbin, above_highestbin=above_highestbin) - x_binned = pd.Series(x_binned, index=series_index, name="B_"+name) - return x_binned, cutpoints, info - -def __increp(b_var, target, train): - ''' - Method for incidence replacement - Returns replaced pd.Serie - ---------------------------------------------------- - b_var: input pd.Serie to be replaced - target: pd.Serie with target variable - train: pd.Serie with parition variable - ---------------------------------------------------- - ''' - - #get variable name - name = b_var.name - #get overall incidence - incidence_mean = target[train].mean() - #get incidence per group - incidences = target[train].groupby(b_var).mean() - #construct dataframe with incidences - idf = pd.DataFrame(incidences).reset_index() - #get values that are in the data but not in the labels - bin_labels = incidences.index - newgroups = list(set(b_var.unique()) ^ set(bin_labels)) - #if newgroups, add mean incidence to incidence dataframe for each new group - if len(newgroups)>0: - #make dataframe: - ngdf = pd.DataFrame(newgroups) - ngdf.columns = [name] - ngdf["TARGET"] = incidence_mean - #dataframe with incidences: - idf = idf.append(ngdf) - #dataframe with the variable - vdf = pd.DataFrame(b_var) - #discretized variable by merge - d_var = pd.merge(vdf,idf,how='left',on=name)["TARGET"] - return pd.Series(d_var, name="D_"+name[2:]) - - - - -# -# LOAD CSV -# -df = pd.read_csv(data_path, header=0, sep=None, engine='python') - -_partitioning_settings = {'train':0.5, - 'selection':0.3, - 'validation':0.2} - -df_simulation = pd.DataFrame(None,columns=[ - 'variable', - 'iteration', - 'length', - 'coef', - 'AUC' - ]) - -cont_clmns = ['age', 'fnlwgt', 'education-num','capital-gain', 'capital-loss', 'hours-per-week', - 'scont_1', 'scont_2', 'scont_3', 'scont_4', 'scont_5', 'scont_6', 'scont_7', 'scont_8', - 'scont_9', 'scont_10', 'scat_1','scat_2', 'scat_3', 'scat_4', 'scat_5', 'sflag_1', - 'sflag_2', 'sflag_3','sflag_4', 'sflag_5'] - -row = 0 -for clmn in cont_clmns: - - df_prep = df[['TARGET',clmn]] - - for i in range(1,51): - #PARTITION - df_prep = df_prep.iloc[np.random.permutation(len(df_prep))].sort_values(by='TARGET', ascending=False).reset_index(drop=True) - partition = [] - sorted_target=df_prep['TARGET'] #Just the target since it is allready sorted (see above) - for target in [sorted_target.iloc[0],sorted_target.iloc[-1]]: - target_length = (sorted_target==target).sum() - - for part, size in _partitioning_settings.items(): - partition.extend([part]*math.ceil(target_length*size)) - - df_prep["PARTITION"] = partition[:len(df_prep)] - - #Binns - result = __eqfreq(var=df_prep[clmn], - train=df_prep["PARTITION"]=="train", - autobins=True, - nbins=5, - precision=0, - twobins=True, - # TRUE OPTION STILL PRODUCES ERROR IN SORTNUMERIC function AND SCORING procedure !!!!!!!!! - catchLarge=False) - - bin_serie = pd.Series(result[0]) - # uncommemt this to see the counts - they change! - #print(bin_serie.groupby(bin_serie).count()) - - #REPLACE INCIDENCE - inc_rep = __increp(b_var=bin_serie, - target=df_prep['TARGET'], - train=df_prep['PARTITION']=="train") - - df_prep['D_'+clmn] = inc_rep - - #PREDICT - y_train = df_prep['TARGET'][df_prep['PARTITION'] == 'train'].as_matrix() - x_train = df_prep['D_'+clmn][df_prep['PARTITION'] == 'train'].as_matrix().reshape(-1,1) - - logit = LogisticRegression(fit_intercept=True, C=1e9, solver = 'liblinear') - logit.fit(y=y_train, X=x_train) - y_pred_train = logit.predict_proba(x_train) - - AUC_train = metrics.roc_auc_score(y_true=y_train, y_score=y_pred_train[:,1]) - - coefs = logit.coef_[0] - - df_simulation.loc[row] = [ - clmn, - i, - len(np.unique(result[0])), - coefs[0], - np.round(AUC_train,3) - ] - row +=1 - - #print('ITERATION {}, lenght: {}, coef: {}, AUC: {}.'.format(i, len(np.unique(result[0])),coefs,np.round(AUC_train,3))) - - -print(df_simulation.groupby(['variable','length'])['length'].count()) -print(df_simulation.groupby(['variable'])['coef'].mean()) -print(df_simulation.groupby(['variable'])['coef'].std()) -print(df_simulation.groupby(['variable'])['AUC'].mean()) -print(df_simulation.groupby(['variable'])['AUC'].std()) - - - - - -''' -df_transformed.groupby('D_scont_1')['D_scont_1'].count() -df_transformed.groupby('B_scont_1')['B_scont_1'].count() - -res = pd.Series(result[0]) -res.groupby(res).count() -''' - - - - - - - - - - - - - - - - - - diff --git a/tests/tests.py b/tests/tests.py deleted file mode 100644 index 1c35c0a..0000000 --- a/tests/tests.py +++ /dev/null @@ -1,394 +0,0 @@ -''' -====================================================================================================================== ---------------------------------------------------------- TESTING --------------------------------------------------- -====================================================================================================================== -This is my (Honza) script to test and develop in Cobra -import sys -sys.path.append('C:/Local/pers/Documents/GitHub/COBRA/source_code') -''' - -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -import seaborn as sns -pd.set_option("display.max_columns",50) - -data_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data.csv' -data_types_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data_types.csv' - -''' -TO-DO --sometimes error - no variables with positive coef. Even if error is thrown, return stuff! --the functions can be applied in a vectorized way --further improve the forward selection - -TEST -''' - - -'''=================== TEST COBRA INTERFACE ===================''' -import cobra.cobra as c - -build = c.COBRA(data_path, - data_types_path, - partition_train=0.5, - partition_select=0.3, - partition_valid=0.2, - sampling_1=1, - sampling_0=1, - discret_nbins=5, - regroup_sign=0.001, - rseed=0) -df_transformed = build.transform() - - -#I want to try more unisel -df_unisel, df_corr = build.fit_univariate(df_transformed, - preselect_auc=0.53, - preselect_overtrain=5) - -build.plotPredictorQuality(df_unisel) -build.plotCorrMatrix(df_corr) -build.plotIncidence(df_transformed, 'age') - -#I want to try more models -#first model -df_model1 = build.fit_model(df_transformed, - df_unisel, - modeling_nsteps=30, - forced_vars=['scont_1', 'scont_2'], - excluded_vars=None, - name='All variables') - -build.plotAUC(df_model1) -build.plotVariableImportance(df_model1, 5) -build.plotCumulatives([(df_model1,3)], df_transformed) - - -#second model -df_model2 = build.fit_model(df_transformed, - df_unisel, - modeling_nsteps=30, - forced_vars=None, - excluded_vars=None, - name='Experiment') - -build.plotAUC(df_model2) -build.plotVariableImportance(df_model2, 6) -build.plotCumulatives([(df_model2, 5)], df_transformed) - -#Model comparison -build.plotAUCComparison([(df_model1,3), (df_model2,5)]) -build.plotCumulatives([(df_model1,3), (df_model2,5)], df_transformed) - -'''=================== PROFILING & SPEED ===================''' -import cProfile -import timeit - -cProfile.run('build.fit_univariate(df_transformed,preselect_auc=0.53, preselect_overtrain=5)') - -def classWrapper(): - build = c.COBRA(data_path, - data_types_path, - partition_train=0.5, - partition_select=0.3, - partition_valid=0.2, - sampling_1=1, - sampling_0=1, - discret_nbins=5, - regroup_sign=0.001, - rseed=0) - df_transformed = build.transform() - - df_unisel, df_corr = build.fit_univariate(df_transformed, - preselect_auc=0.53, - preselect_overtrain=5) - - df_model1 = build.fit_model(df_transformed, - df_unisel, - modeling_nsteps=30, - forced_vars=None, - excluded_vars=None, - name='All variables') - -%timeit classWrapper() -''' -Improvements: -------------- -- 06/03/2018, JBE - 11 s ± 267 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) - - -''' - - - -def _getTrainSelectValidXY(df): - ''' - Method split given DF into train/test/validation set in respect to X and Y. - Returns dictionary with DFs - ---------------------------------------------------- - df: transformed dataset - ---------------------------------------------------- - ''' - - dvars = [n for n in df.columns if n[:2] == 'D_'] - - mask_train = df['PARTITION']=="train" - mask_selection = df['PARTITION']=="selection" - mask_validation = df['PARTITION']=="validation" - - y_train = df.loc[mask_train,'TARGET'] - y_selection = df.loc[mask_selection,'TARGET'] - y_validation = df.loc[mask_validation,'TARGET'] - - x_train = df.loc[mask_train,dvars] - x_selection = df.loc[mask_selection,dvars] - x_validation = df.loc[mask_validation,dvars] - - dict_out = {'y_train':y_train, 'y_selection':y_selection, 'y_validation':y_validation, - 'x_train':x_train, 'x_selection':x_selection, 'x_validation':x_validation} - - return dict_out - -_partition_dict = _getTrainSelectValidXY(df_transformed) - -''' -============================================================================================================= -======================================= FORWARD SELECTION =================================================== -============================================================================================================= --Only boolean target -''' -from sklearn.linear_model import LogisticRegression -from sklearn import metrics -import numpy as np - -df_sel = df_unisel -forced_vars = ['scont_1', 'scont_2'] -excluded_vars = None -positive_only = True - -#if None, replace by empty list -if not excluded_vars: - excluded_vars = [] - -if not forced_vars: - forced_vars = [] - -#Sort -df_sel = df_sel.sort_values(by='AUC selection', ascending=False) - -#Build list of variables to be used for Forward selection -preselected_vars = df_sel['variable'][df_sel['preselection'] == True].tolist() -preselected_vars = [var for var in preselected_vars if var not in forced_vars+excluded_vars] -all_vars = ['D_' + var for var in forced_vars + preselected_vars] - - - -''' ------------------- MAIN LOOP ------------------ -''' -df_forward_selection = pd.DataFrame(None,columns=[ - 'step', - 'coef', - 'all_coefs_positive', - 'AUC_train', - 'AUC_selection', - 'AUC_validation', - 'predictors_subset', - 'last_var_added', - 'AUC_train_rank', - 'selected_model', - 'pred_training', - 'pred_selection', - 'pred_validation' - ]) - -f_position_forced = lambda i, forced, all_vars: len(forced) if i <= len(forced) else len(all_vars) - -n_steps = min(30,len(all_vars)) -predictors = [] -row = 0 - -for step in range(1,n_steps): - print('*******************Iter {}*******************'.format(step)) - - pos = f_position_forced(step, forced_vars, all_vars) - remaining_predictors = [var for var in all_vars[:pos] if var not in predictors] - - for predictor in remaining_predictors: - predictors_subset = predictors + [predictor] - #Train - train model - logit = LogisticRegression(fit_intercept=True, C=1e9, solver = 'liblinear') - logit.fit(y=_partition_dict['y_train'], X=_partition_dict['x_train'][predictors_subset]) - - #Train - predict and AUC - y_pred_train = logit.predict_proba(_partition_dict['x_train'][predictors_subset]) - AUC_train = metrics.roc_auc_score(y_true=_partition_dict['y_train'], y_score=y_pred_train[:,1]) - - #Selection - predict and AUC - y_pred_selection = logit.predict_proba(_partition_dict['x_selection'][predictors_subset]) - AUC_selection = metrics.roc_auc_score(y_true=_partition_dict['y_selection'], y_score=y_pred_selection[:,1]) - - #Validation - predict and AUC - y_pred_validation = logit.predict_proba(_partition_dict['x_validation'][predictors_subset]) - AUC_validation = metrics.roc_auc_score(y_true=_partition_dict['y_validation'], y_score=y_pred_validation[:,1]) - - #check if coefs are positive - all_coefs_positive = (logit.coef_[0] >= 0).all() - - df_forward_selection.loc[row] = [ - step, - logit.coef_, - all_coefs_positive, - AUC_train, - AUC_selection, - AUC_validation, - predictors_subset, - predictors_subset[-1], - 0, - False, - y_pred_train, - y_pred_selection, - y_pred_validation - ] - row +=1 - - #Only positive coefs - if positive_only: - if len(df_forward_selection[(df_forward_selection['all_coefs_positive'] == True) & (df_forward_selection['step'] == step)]) == 0: - raise ValueError("No models with only positive coefficients","NormalStop") - - - ##Find best model - #Sort AUC by size - df_forward_selection['AUC_train_rank'] = df_forward_selection.groupby('step')['AUC_train'].rank(ascending=False) - - #Find model where AUC is highest AND all coefs are positive - convert to boolean flag - df_forward_selection['selected_model'] = df_forward_selection[df_forward_selection['all_coefs_positive'] == True].groupby(['step'])['AUC_train'].transform(max) - df_forward_selection['selected_model'] = (df_forward_selection['selected_model'] == df_forward_selection['AUC_train']) - else: - ##Highest AUC, regardless of coefs - df_forward_selection['selected_model'] = (df_forward_selection.groupby(['step'])['AUC_train'].transform(max) == df_forward_selection['AUC_train']) - - ##Add next predictor - add_variable = df_forward_selection.loc[(df_forward_selection['selected_model'] == True) & (df_forward_selection['step'] == step), 'last_var_added'].iloc[0] - predictors.append(add_variable) - - clmns_out = ['step', 'coef', 'AUC_train', 'AUC_selection', 'AUC_validation', 'predictors_subset', 'last_var_added', - 'pred_training','pred_selection','pred_validation'] - -df_tst = df_forward_selection[clmns_out][df_forward_selection['selected_model'] == True] - - - - -''' -============================================================================================================= -=========================================== CSV IMPORT ====================================================== -============================================================================================================= --Only boolean target -''' - - - - -#Loads Data types -types_exist = True - -#load data_types -try: - df_types = pd.read_csv(data_types_path, header=None) - df_types.columns = ['variable','data_type'] -except FileNotFoundError: - types_exist = False - df_types = pd.DataFrame() - -#load data -df = pd.read_csv(data_path, header=0, sep=None, engine='python') - - -#change datatypes -if types_exist: - for row in df_types.itertuples(): #0:index, 1:variable, 2:data_type - if row[2] == 'int': - df[row[1]] = df[row[1]].astype(np.int64) - if row[2] in ['str', 'bool']: - df[row[1]] = df[row[1]].apply(str) - -return df, df_types - - - -types_exists = True -data_types_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data_types.csv' - -if types_exists: - try: - df_dtypes = pd.read_csv(data_types_path, header=None) - df_dtypes.columns = ['variable','data_type'] - - - - dict_dtypes = {k: getattr(__builtins__, v) for k, v in df_dtypes.to_dict('dict')['data_type'].items()} - except FileNotFoundError: - raise - - df = pd.read_csv(data_path, header=0, sep=',', engine='c', converters=dict_dtypes) - -else: - - - -set(np.unique(df_dtypes['data_type'])) < set(['int','str','bool']) - - - - - - - - - - - - -df_types = pd.read_csv(data_types_path, header=None) -df_types.columns = ['variable','data_type'] -dict_types = df_types.to_dict('dict') - -tst = dict_types['data_type'] - -df = pd.read_csv(data_path, header=0, sep=None, engine='python', converters=dict_types['data_type']) - -string = 'str' -string = string[1:] -print(string) - - -import re -string = 'str' -string = re.sub(r'^"|"$', '', string) -print(string) - -d2 = {k: re.sub(r'^"|"$', '', v) for k, v in tst.items()} -d2 = {k: getattr(__builtins__, v) for k, v in tst.items()} - - - - - - - - - - - - - - - - - - - From 447e7fae280f2b97d7a1f3f51d2d338b1a034ede Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 19 Dec 2019 09:20:12 +0100 Subject: [PATCH 22/98] Add more unittests for KBinsDiscretizer --- tests/preprocessing/test_kbins_discretizer.py | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/tests/preprocessing/test_kbins_discretizer.py b/tests/preprocessing/test_kbins_discretizer.py index 4c39e90..ced0ddc 100644 --- a/tests/preprocessing/test_kbins_discretizer.py +++ b/tests/preprocessing/test_kbins_discretizer.py @@ -42,11 +42,7 @@ def test_attributes_to_dict(self): ["n_bins", "strategy", "closed", "auto_adapt_bins", "starting_precision", "label_format", "change_endpoint_format", - "_bins_by_column"], - ids=["n_bins", "strategy", "closed", - "auto_adapt_bins", "starting_precision", - "label_format", "change_endpoint_format", - "_bins_by_column"]) + "_bins_by_column"]) def test_set_attributes_from_dict(self, attribute): discretizer = KBinsDiscretizer() @@ -76,12 +72,57 @@ def test_set_attributes_from_dict(self, attribute): assert actual == expected + # no further tests here as this is just a wrapper around _fit_column! + @pytest.mark.parametrize("strategy, expectation", + [("trees", pytest.raises(ValueError)), + ("quantile", does_not_raise())]) + def test_fit_exception(self, strategy, expectation): + discretizer = KBinsDiscretizer(strategy=strategy) + + data = pd.DataFrame({"variable": list(range(0, 10)) + [np.nan]}) + + with expectation: + discretizer.fit(data, ["variable"]) + + # no further tests here as this is just a wrapper around _transform_column! + @pytest.mark.parametrize("scenario, expectation", + [("raise", pytest.raises(ValueError)), + ("regular_test", does_not_raise()), + ("constant_data", does_not_raise())]) + def test_transform(self, scenario, expectation): + + discretizer = KBinsDiscretizer(n_bins=3, strategy="uniform") + + data = pd.DataFrame({"variable": ([1] * 10)}) + expected = data.copy() + + if scenario == "regular_test": + # overwrite data and expected with DataFrame containing + # a non-constant variable + data = pd.DataFrame({"variable": list(range(0, 10)) + [np.nan]}) + expected = data.copy() + + discretizer.fit(data, ["variable"]) + + categories = ["0.0 - 3.0", "3.0 - 6.0", "6.0 - 9.0", "Missing"] + expected["variable_bin"] = pd.Categorical(["0.0 - 3.0"]*4 + + ["3.0 - 6.0"]*3 + + ["6.0 - 9.0"]*3 + + ["Missing"], + categories=categories, + ordered=True) + elif scenario == "constant_data": + discretizer.fit(data, ["variable"]) + + with expectation: + actual = discretizer.transform(data, ["variable"]) + pd.testing.assert_frame_equal(actual, expected) + ################# Test for private methods ################# @pytest.mark.parametrize("n_bins, expectation", [(1, pytest.raises(ValueError)), (10.5, pytest.raises(ValueError)), - (2, does_not_raise())], - ids=["invalid_int", "float", "normal"]) + (2, does_not_raise())]) def test_validate_n_bins_exception(self, n_bins, expectation): with expectation: assert KBinsDiscretizer()._validate_n_bins(n_bins=n_bins) is None @@ -89,7 +130,7 @@ def test_validate_n_bins_exception(self, n_bins, expectation): def test_transform_column(self): data = pd.DataFrame({"variable": list(range(0, 10)) + [np.nan]}) - discretizer = KBinsDiscretizer(n_bins=3, strategy="unform") + discretizer = KBinsDiscretizer(n_bins=3, strategy="uniform") bins = [(0.0, 3.0), (3.0, 6.0), (6.0, 9.0)] From c0dcc5a400b096249dff474e7905d9f8f8fa2979 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 19 Dec 2019 17:03:18 +0100 Subject: [PATCH 23/98] Refactor and rename categorical_regrouper Refactor is required to make it compatible with the other modules. The following changes where made: - Rewritten logic to improve readability - Made regrouping optional so that only Missing values are replaced and datatypes are changed to "category" - Great care was taken to avoid accidental modification of original data (as variables in Python are passed by reference!) --- cobra/preprocessing/__init__.py | 4 +- .../categorical_data_processor.py | 358 ++++++++++++++++++ .../test_categorical_data_processor.py | 59 +++ 3 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 cobra/preprocessing/categorical_data_processor.py create mode 100644 tests/preprocessing/test_categorical_data_processor.py diff --git a/cobra/preprocessing/__init__.py b/cobra/preprocessing/__init__.py index 98c3ef9..2008235 100644 --- a/cobra/preprocessing/__init__.py +++ b/cobra/preprocessing/__init__.py @@ -1,7 +1,7 @@ from .kbins_discretizer import KBinsDiscretizer from .target_encoder import TargetEncoder -from .categorical_regrouper import CategoryRegrouper +from .categorical_data_processor import CategoricalDataProcessor __all__ = ['KBinsDiscretizer', 'TargetEncoder', - 'CategoryRegrouper'] \ No newline at end of file + 'CategoricalDataProcessor'] diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py new file mode 100644 index 0000000..705d018 --- /dev/null +++ b/cobra/preprocessing/categorical_data_processor.py @@ -0,0 +1,358 @@ +""" +This class implements the Python Prediction's way of dealing with +categorical data preprocessing. There are two steps involved here: +- An optional regrouping of the different categories based on category size + and significance of the category +- Missing value replacement with the additional category "Missing" + +Authors: +- Geert Verstraeten (methodology) +- Jan Benisek (implementation) +- Matthias Roels (implementation) +""" +# standard lib imports +import re +from typing import Optional + +import logging +log = logging.getLogger(__name__) + +# third party imports +import numpy as np +import pandas as pd +from scipy import stats + +from sklearn.base import BaseEstimator +from sklearn.exceptions import NotFittedError + + +class CategoricalDataProcessor(BaseEstimator): + """ + Regroups categories in categorical variables based on significance + with target variable. + + Attributes + ---------- + category_size_threshold : int, optional + minimal size of a category to keep it as a separate category + forced_categories : dict, optional + Map to prevent certain categories from being group into "Other" + for each colum - dict of the form {col:[forced vars]}. + keep_missing : bool + Whether or not to keep missing as a separate category + p_value_threshold : float + Significance threshold for regroupping. + regroup : bool + Whether or not to regroup categories + regroup_name : str + New name of the non-significant regrouped variables + scale_contingency_table : bool + Whether contingency table should be scaled before chi^2.' + """ + + def __init__(self, regroup: bool=True, regroup_name: str="Other", + keep_missing: bool=True, + category_size_threshold: Optional[int]=None, + p_value_threshold: float=0.001, + scale_contingency_table: bool=True, + forced_categories: Optional[dict]=None): + + self.regroup = regroup + self.regroup_name = regroup_name + self.keep_missing = keep_missing + self.category_size_threshold = category_size_threshold + self.p_value_threshold = p_value_threshold + self.scale_contingency_table = scale_contingency_table + self.forced_categories = forced_categories + + # dict to store fitted output in + self._combined_categories_by_column = {} + + def fit(self, data: pd.DataFrame, column_names: list, + target_column: str): + """Fit the CategoricalDataProcessor + + Parameters + ---------- + data : pd.DataFrame + data used to compute the mapping to encode the categorical + variables with. + column_names : list + Columns of data to be processed + target_column : str + Column name of the target + """ + + if not self.regroup: + # We do not need to fit anything if regroup is set to False! + log.info("regroup was set to False, so no fitting is required") + return None + + for column_name in column_names: + + if column_name not in data.columns: + log.warning("DataFrame has no column '{}', so it will be " + "skipped in fitting" .format(column_name)) + continue + + combined_cats = self._fit_column(data, column_name, target_column) + + # Add to _combined_categories_by_column for later use + self._combined_categories_by_column[column_name] = combined_cats + + def _fit_column(self, data: pd.DataFrame, column_name: str, + target_column) -> list: + """Compute which categories to regroup into "Other" for a particular + column + + Parameters + ---------- + data : pd.DataFrame + Description + column_name : str + Description + + Returns + ------- + list + list of categories to combine into a category "Other" + """ + + X = data[column_name] + y = data[target_column] + incidence = y.mean() + + combined_categories = set() + + # replace missings and get unique categories as a list + X = CategoricalDataProcessor._replace_missings(X) + unique_categories = list(X.unique()) + + # get small categories and add them to the merged category list + small_categories = (CategoricalDataProcessor + ._get_small_categories( + X, + incidence, + self.category_size_threshold)) + combined_categories = combined_categories.union(small_categories) + + for category in unique_categories: + if category in small_categories: + continue + + pval = (CategoricalDataProcessor + ._compute_p_value(X, y, category, + self.scale_contingency_table)) + + # if not significant, add it to the list + if pval > self.p_value_threshold: + combined_categories.add(category) + + # Remove missing category from combined_categories if required + if self.keep_missing: + combined_categories.discard("Missing") + + return combined_categories + + def transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + + if self.regroup and len(self._combined_categories_by_column) == 0: + msg = ("{} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + for column_name in column_names: + + data = self._transform_column(data, column_name) + + return data + + def _transform_column(self, data: pd.DataFrame, + column_name: str) -> pd.DataFrame: + """Given a DataFrame, a column name and a list of categories to + combine, create an additional column which combines these categories + into "Other" + + Parameters + ---------- + data : pd.DataFrame + Original data to be tranformed + column_name : str + name of the column to transform + + Returns + ------- + pd.DataFrame + original DataFrame with an added processed column + """ + + column_name_clean = column_name + "_processed" + data[column_name_clean] = data[column_name] + + # Fill missings first + data[column_name_clean] = (CategoricalDataProcessor + ._replace_missings(data, + column_name_clean)) + + if self.regroup: + categories = self._combined_categories_by_column.get(column_name) + + data[column_name_clean] = (CategoricalDataProcessor + ._replace_categories( + data[column_name_clean], + categories)) + + # change data to categorical + data[column_name_clean] = data[column_name_clean].astype("category") + + return data + + def fit_transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + self.fit(data, column_names) + return self.transform(data, column_names) + + @staticmethod + def _get_small_categories(predictor_series: pd.Series, + incidence: float, + category_size_threshold: int) -> set: + """Fetch categories with a size below a certain threshold. + Note that we use an additional weighting with the overall incidence + + Parameters + ---------- + predictor_series : pd.Series + Description + incidence : float + global train incidence + category_size_threshold : int + minimal size of a category to keep it as a separate category + + Returns + ------- + set + List a categories with a count below a certain threshold + """ + category_counts = predictor_series.groupby(predictor_series).size() + factor = max(incidence, 1 - incidence) + + # Get all categories with a count below a threshold + bool_mask = (category_counts*factor) <= category_size_threshold + return set(category_counts[bool_mask].index.tolist()) + + @staticmethod + def _replace_missings(data: pd.DataFrame, + column_names: Optional[list]=None) -> pd.DataFrame: + """Replace missing values (incl empty strings) + + Parameters + ---------- + data : pd.DataFrame + data to replace missings in + column_names: list, optional + list of predictors to replace missings in + + Returns + ------- + list + list of unique values in the data + """ + # replace missings (incl. empty string) + regex = re.compile("^\\s+|\\s+$") + + temp = None + if column_names: + temp = data[column_names] + else: + temp = data.copy() + temp = temp.fillna("Missing") + temp = temp.replace(regex, "") + temp = temp.replace("", "Missing") + + return temp + + @staticmethod + def _compute_p_value(X: pd.Series, y: pd.Series, category: str, + scale_contingency_table: bool) -> float: + """Summary + + Parameters + ---------- + X : pd.Series + Description + y : pd.Series + Description + category : str + Description + scale_contingency_table : bool + Description + + Returns + ------- + float + Description + """ + df = pd.concat([X, y], axis=1) + df["other_categories"] = np.where(X == category, 0, 1) + + contigency_table = pd.crosstab(index=df['other_categories'], columns=y, + margins=False) + + # if true, we scale the "other" categories + if scale_contingency_table: + size_other_cats = contigency_table.iloc[1].sum() + incidence_mean = y.mean() + + contigency_table.iloc[1, 0] = (1-incidence_mean) * size_other_cats + contigency_table.iloc[1, 1] = incidence_mean * size_other_cats + contigency_table = contigency_table.values.astype(np.int64) + + return stats.chi2_contingency(contigency_table, correction=False)[1] + + @staticmethod + def _replace_categories(data: pd.Series, categories: set) -> pd.Series: + """replace categories in set with "Other" + + Parameters + ---------- + data : pd.Series + Description + categories : set + Description + + Returns + ------- + pd.Series + Description + """ + return data.apply(lambda x: x if x not in categories else "Other") diff --git a/tests/preprocessing/test_categorical_data_processor.py b/tests/preprocessing/test_categorical_data_processor.py new file mode 100644 index 0000000..5155127 --- /dev/null +++ b/tests/preprocessing/test_categorical_data_processor.py @@ -0,0 +1,59 @@ +import pytest + +import numpy as np +import pandas as pd + +from cobra.preprocessing import CategoricalDataProcessor + + +class TestCategoricalDataProcessor: + + @pytest.mark.parametrize("scale_contingency_table, expected", + [(False, 0.013288667), + (True, 0.434373)]) + def test_compute_p_value(self, scale_contingency_table, expected): + + X = pd.Series(data=(["c1"]*70 + ["c2"]*20 + ["c3"]*10)) + y = pd.Series(data=([0]*35 + [1]*35 + [0]*15 + [1]*5 + [0]*8 + [1]*2)) + category = "c1" + + actual = (CategoricalDataProcessor + ._compute_p_value(X, y, category, scale_contingency_table)) + + assert pytest.approx(actual) == expected + + def test_get_small_categories(self): + + data = pd.Series(data=(["c1"]*50 + ["c2"]*25 + ["c3"]*15 + ["c4"]*5)) + incidence = 0.35 + threshold = 10 # to make it easy to manualy compute + expected = {"c3", "c4"} + + actual = (CategoricalDataProcessor + ._get_small_categories(data, incidence, threshold)) + + assert actual == expected + + def test_replace_missings(self): + + data = pd.DataFrame({"variable": ["c1", "c2", np.nan, "", " "]}) + expected = pd.DataFrame({"variable": ["c1", "c2", "Missing", "Missing", + "Missing"] + }) + actual = (CategoricalDataProcessor + ._replace_missings(data, ["variable"])) + + pd.testing.assert_frame_equal(actual, expected) + + @pytest.mark.parametrize("combined_categories, expected", + [({"c3", "c4"}, + pd.Series(data=["c1", "c2", "Other", "Other"])), + ({}, pd.Series(data=["c1", "c2", "c3", "c4"]))]) + def test_replace_categories(self, combined_categories, expected): + + data = pd.Series(data=["c1", "c2", "c3", "c4"]) + + actual = (CategoricalDataProcessor + ._replace_categories(data, combined_categories)) + + pd.testing.assert_series_equal(actual, expected) From 77a43363e96e1aeca92462cb3a8fce24d6fb677a Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 10:12:12 +0100 Subject: [PATCH 24/98] Added forced category option to CategoricalDataProcessor --- .../categorical_data_processor.py | 33 +- cobra/preprocessing/categorical_regrouper.py | 391 ------------------ 2 files changed, 23 insertions(+), 401 deletions(-) delete mode 100644 cobra/preprocessing/categorical_regrouper.py diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index 705d018..a77851f 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -1,9 +1,10 @@ """ This class implements the Python Prediction's way of dealing with -categorical data preprocessing. There are two steps involved here: +categorical data preprocessing. There are three steps involved here: - An optional regrouping of the different categories based on category size - and significance of the category + and significance of the category w.r.t. the target - Missing value replacement with the additional category "Missing" +- Change of dtype to "category" (could potentially lead to memory optimization) Authors: - Geert Verstraeten (methodology) @@ -33,9 +34,9 @@ class CategoricalDataProcessor(BaseEstimator): Attributes ---------- - category_size_threshold : int, optional + category_size_threshold : int minimal size of a category to keep it as a separate category - forced_categories : dict, optional + forced_categories : dict Map to prevent certain categories from being group into "Other" for each colum - dict of the form {col:[forced vars]}. keep_missing : bool @@ -52,10 +53,10 @@ class CategoricalDataProcessor(BaseEstimator): def __init__(self, regroup: bool=True, regroup_name: str="Other", keep_missing: bool=True, - category_size_threshold: Optional[int]=None, + category_size_threshold: int=5, p_value_threshold: float=0.001, scale_contingency_table: bool=True, - forced_categories: Optional[dict]=None): + forced_categories: dict={}): self.regroup = regroup self.regroup_name = regroup_name @@ -97,11 +98,15 @@ def fit(self, data: pd.DataFrame, column_names: list, combined_cats = self._fit_column(data, column_name, target_column) + # Remove forced categories + forced_cats = self.forced_categories.get(column_name, set()) + combined_cats = combined_cats.difference(forced_cats) + # Add to _combined_categories_by_column for later use self._combined_categories_by_column[column_name] = combined_cats def _fit_column(self, data: pd.DataFrame, column_name: str, - target_column) -> list: + target_column) -> set: """Compute which categories to regroup into "Other" for a particular column @@ -117,15 +122,13 @@ def _fit_column(self, data: pd.DataFrame, column_name: str, list list of categories to combine into a category "Other" """ - - X = data[column_name] y = data[target_column] incidence = y.mean() combined_categories = set() # replace missings and get unique categories as a list - X = CategoricalDataProcessor._replace_missings(X) + X = CategoricalDataProcessor._replace_missings(data[column_name]) unique_categories = list(X.unique()) # get small categories and add them to the merged category list @@ -179,6 +182,11 @@ def transform(self, data: pd.DataFrame, for column_name in column_names: + if column_name not in data.columns: + log.warning("Unknown column '{}' will be skipped" + .format(column_name)) + continue + data = self._transform_column(data, column_name) return data @@ -213,6 +221,11 @@ def _transform_column(self, data: pd.DataFrame, if self.regroup: categories = self._combined_categories_by_column.get(column_name) + if not categories: + log.warning("Column '{}' is not in fitted output " + "and will be skipped".format(column_name)) + return data + data[column_name_clean] = (CategoricalDataProcessor ._replace_categories( data[column_name_clean], diff --git a/cobra/preprocessing/categorical_regrouper.py b/cobra/preprocessing/categorical_regrouper.py deleted file mode 100644 index a6276d4..0000000 --- a/cobra/preprocessing/categorical_regrouper.py +++ /dev/null @@ -1,391 +0,0 @@ - -import pandas as pd -import numpy as np -from scipy import stats -from typing import Dict -from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.exceptions import NotFittedError -import logging - -log = logging.getLogger(__name__) - - -class CategoryRegrouper(BaseEstimator, TransformerMixin): - """ - Regroups categories in categorical variables based on significance - with target variable. - - Parameters - ---------- - scale_cont : bool, default=True - Whether contingency table should be scaled before chi^2.' - - pval_thresh : float, default=0.001 - Significance threshold for regroupping. - - regroup_rename : str, default='non-significant' - New name of non-significant regroupped variables. - - missing_rename : str, default='Missing' - New name of missing categories. - - keep_missing : bool, default=Falsse - Whether missing category should be kept in the result. - - forced_categories : Dict, default=None - Dictionary to force categories - - for each colum dict of {col:[forced vars]}. - - Attributes - ---------- - all_category_map_ : Dict - Dictionary with mapping for each variable. - """ - def __init__(self, scale_cont: bool = True, - pval_thresh: float = 0.001, - regroup_rename: str = "non-significant", - missing_rename: str = "Missing", - keep_missing: bool = False, - forced_categories: Dict = None): - self.scale_cont = scale_cont - self.pval_thresh = pval_thresh - self.regroup_rename = regroup_rename - self.missing_rename = missing_rename - self.keep_missing = keep_missing - self.forced_categories = forced_categories - - def fit(self, X: pd.DataFrame, - y: pd.Series, - columns: list = []): - """ - Method regroups categories whole DataFrame. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - y: pd.Series - Series with target variable. - - columns : list, default=[] - Columns to be regrouped. - - Raises - ------ - ValueError - In case X and y are not of the same length. - - Returns - ------- - None - Only fits the instance of the class. - """ - self.all_category_map_ = {} - - if len(X.index) != len(y.index): - raise ValueError("The length of X is {}, but the length of y is {}" - .format(len(X.index), len(y.index))) - - if not columns: - columns = CategoryRegrouper._get_categorical_columns(X) - log.warning("All object-type columns have been selected") - - for column in columns: - if column not in X.columns: - log.warning("DataFrame has no column '{}', so it will be " - "skipped in fitting" .format(column)) - continue - - self.all_category_map_[column] = self._fit_column(X=X, - y=y, - column=column) - - def _fit_column(self, X: pd.DataFrame, - y: pd.Series, - column: str) -> Dict: - """ - Method regroups categories in given column. - - Parameters - ---------- - X : pd.Series - Series with one column to be transformed. - - y: pd.Series - Series with target variable - - column : str - Column to be regrouped. - - Raises - ------ - ValueError - in case input column is not a string. - - Returns - ------- - Dict - Returns dictionary as {old category : new category} for - specific column. - """ - category_map = {} - keep_categories = [] - incidence_mean = y.mean() - - # Rename target - y.rename("TARGET", inplace=True) - - # Replace missings - X = self._replaceMissings(X=X, column=column, - replace_with=self.missing_rename) - - all_uq_categories = X[column].unique().tolist() - - # Remove small categories - categories = self._removeCategories(X=X, y=y, column=column) - - # Inspect remaining categories and test significance - for category in categories: - df_aux = pd.concat([X[column], y], axis=1) - df_aux['other_cats'] = np.where(df_aux[column] == category, 0, 1) - cont_table = pd.crosstab(index=df_aux['other_cats'], - columns=df_aux['TARGET'], - margins=False) - - # if true, we scale the "other" categories - if self.scale_cont: - size_other_cats = cont_table.iloc[1].sum() - cont_table.iloc[1, 0] = (1-incidence_mean)*size_other_cats - cont_table.iloc[1, 1] = incidence_mean*size_other_cats - cont_table = cont_table.values.astype(np.int64) - - pval = stats.chi2_contingency(cont_table, correction=False)[1] - - # If significant, keep it - if pval <= self.pval_thresh: - keep_categories.append(category) - - # Keep "Missing" even if it wasn't selected if - # it is in the original categories and set to True - if ((self.missing_rename not in keep_categories) and - (self.missing_rename in all_uq_categories) and self.keep_missing): - keep_categories.append(self.missing_rename) - - # Keep forced categories - if self.forced_categories is not None: - # If doesnt exists, give warning - forced = [col for col in self.forced_categories[column] - if col in all_uq_categories] - - # Extend list and remove duplicates - keep_categories = list(set(keep_categories.extend(forced))) - - difference = set(forced) - set(self.forced_categories[column]) - if len(difference) > 0: - log.warning("Following forced categories: {} " - "are not in column: {}.".format(difference, - column)) - - # Return dictionary as {old column : new column} - for category in all_uq_categories: - if category in keep_categories: - category_map[category] = category - else: - category_map[category] = self.regroup_rename - - return category_map - - def transform(self, X: pd.DataFrame, - columns: list = []) -> pd.DataFrame: - """ - Method transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - columns : list, default=[] - Columns to be regrouped. - - Raises - ------ - NotFittedError - If fit() method has not been called. - - ValueError - If columns to be transformed have not been fitted. - - Returns - ------- - pd.DataFrame - Returns transformed DataFrame with new columns as "col_regrouped". - """ - if len(self.all_category_map_) == 0: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - fitted_columns = list(self.all_category_map_.keys()) - - # if specified columns not in fitted Dict, raise error - if not set(columns).issubset(set(fitted_columns)): - diff_cols = set(columns).difference(set(fitted_columns)) - raise ValueError("Following columns are not fitted: " - "{}".format(diff_cols)) - - X_tr = X.copy() - for column in columns: - X_tr[column + "_regrouped"] = self._transform_column(X=X, - column=column) - - return X_tr - - def _transform_column(self, X: pd.DataFrame, - column: str) -> pd.Series: - """ - Method transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - column : str - Column to be regrouped. - - Returns - ------- - pd.Series - Returns DataFrame with regrouped variable as category datatype. - """ - X_tr = X[column].copy() - X_tr[column + "_regrouped"] = X_tr.replace( - to_replace=self.all_category_map_[column]) - - X_tr[column + "_regrouped"] = X_tr[column + - "_regrouped"].astype('category') - - return X_tr[column + "_regrouped"] - - def fit_transform(self, X: pd.DataFrame, - y: pd.Series, - columns: list = []) -> pd.DataFrame: - """ - Auxiliary method fits and transforms specified columns. - - Parameters - ---------- - X : pd.DataFrame - Dataframe with all the columns. - - y : pd.Series - Series with target variable - - column : list, default=[] - Columns to be regrouped. - - Returns - ------- - pd.DataFrame - Returns DataFrame with regrouped variable as category datatype. - """ - self.fit(X=X, y=y, columns=columns) - - X_tr = self.transform(X=X, columns=columns) - - return X_tr - - def _replaceMissings(self, X: pd.DataFrame, - column: str, - replace_with: str = 'Missing') -> pd.DataFrame: - """ - Method replaces missing and empty cells with `Missing` (default) in - a pd.DataFrame. - - Parameters - ---------- - X : pd.DataFrame - Dataframe where a value will be replaced if empty or nan. - - column : str - Column to be analyzed for missings. - - replace_with : str default='Missing' - String to replace the missings. - - Raises - ------ - ValueError - In case input column is not a string. - - Returns - ------- - pd.DataFrame - Modified dataframe with replaced missings. - """ - if X[column].dtype != 'O' or X[column].dtype != 'object': - raise TypeError("column {} must be a string".format(column)) - - X[column].fillna(replace_with, inplace=True) - X[column] = X[column].astype(str).str.strip() - X[column].replace('', replace_with, inplace=True) - - return X - - def _removeCategories(self, X: pd.DataFrame, - y: pd.Series, - column: str, - threshold: int = 5) -> np.ndarray: - """ - Method removes category which fail to meet certain condition - - Parameters - ---------- - X : pd.DataFrame - Dataframe with columns to be inspected for group removal. - - y : pd.Series - Series with target. - - column : str - Column to be analyzed group removal. - - threshold : int default=5 - Threshold for group removal. - - Returns - ------- - np.ndarray - Numpy array with groups to be kept. - """ - category_cnts = pd.DataFrame(X.groupby(column)[column].count()) - train_inc = y.mean() - factor = max(train_inc, 1-train_inc) - keep_categories = category_cnts.where((category_cnts*factor) > - threshold) - - return np.array(keep_categories.index.tolist()) - - @staticmethod - def _get_categorical_columns(data: pd.DataFrame) -> list: - """Get the columns containing categorical data - (dtype "object" or "category") - - Parameters - ---------- - data : pd.DataFrame - Dataframe from which categorical variables - will be extracted. - - Returns - ------- - list - List of column names containing categorical data. - """ - object_columns = data.dtypes[data.dtypes == object].index - categorical_columns = data.dtypes[data.dtypes == "category"].index - - return list(set(object_columns).union(set(categorical_columns))) From 49b922781b4d26c444a5a8e086b4d6e5a8b70a0d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 10:13:01 +0100 Subject: [PATCH 25/98] Add additional log statement to target_encoder --- cobra/preprocessing/target_encoder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index 86ff882..328d431 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -197,6 +197,10 @@ def transform(self, data: pd.DataFrame, for column in column_names: if column not in data.columns: + log.warning("Unknown column '{}' will be skipped" + .format(column)) + continue + elif column not in self._mapping: log.warning("Column '{}' is not in fitted output " "and will be skipped".format(column)) continue From 287270711a285ee1fd2647b992f97ad661d044d5 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 10:15:13 +0100 Subject: [PATCH 26/98] Remove legacy code from repo as part of clean-up --- legacy_code/modeling[171005].ipynb | 1383 ----------------------- legacy_code/notes on backend.txt | 4 - legacy_code/scorecode.py | 131 --- legacy_code/scoring[171005].ipynb | 649 ----------- legacy_code/script_sprint2_.py | 991 ----------------- legacy_code/script_sprint3.py | 662 ----------- legacy_code/script_sprint4_1.py | 38 - legacy_code/script_sprint4_2.py | 458 -------- legacy_code/script_sprint4_3.py | 25 - legacy_code/univariate[171005].ipynb | 1516 -------------------------- 10 files changed, 5857 deletions(-) delete mode 100644 legacy_code/modeling[171005].ipynb delete mode 100644 legacy_code/notes on backend.txt delete mode 100644 legacy_code/scorecode.py delete mode 100644 legacy_code/scoring[171005].ipynb delete mode 100644 legacy_code/script_sprint2_.py delete mode 100644 legacy_code/script_sprint3.py delete mode 100644 legacy_code/script_sprint4_1.py delete mode 100644 legacy_code/script_sprint4_2.py delete mode 100644 legacy_code/script_sprint4_3.py delete mode 100644 legacy_code/univariate[171005].ipynb diff --git a/legacy_code/modeling[171005].ipynb b/legacy_code/modeling[171005].ipynb deleted file mode 100644 index ef957db..0000000 --- a/legacy_code/modeling[171005].ipynb +++ /dev/null @@ -1,1383 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Modeling" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### General Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import time\n", - "import math\n", - "import random\n", - "import csv\n", - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import statsmodels.api as sm\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from scipy import stats\n", - "from sklearn import metrics\n", - "from sklearn.linear_model import LogisticRegression\n", - "from itertools import chain " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Miscellaneous" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "log = []" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# When code is in script, we define the path of the script's parent folder location as the root directory\n", - "# From this root we can travel to the relevant folders with minimal adjustment\n", - "try:\n", - " root = os.path.dirname(os.path.realpath(__file__))\n", - " root = \"/\".join(root.split('\\\\')[:-1])\n", - " log.append('Dynamic paths'+'\\n')\n", - "except:\n", - " root = 'C:/wamp64/www/python_predictions_4/assets/scripts'\n", - " log.append('Static paths'+'\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# To allow pandas dataframes to display more columns\n", - "pd.set_option(\"display.max_columns\",50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read data and organize" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Read-in univariate output with asssumed ID, TARGET, PARTITION and D_VARS" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "df_univariate_path = root+\"/data/univariate/df_univariate.csv\"\n", - "df_in = pd.read_csv(df_univariate_path, sep=\";\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Reference X and Y for each partition individually" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "dvars = [n for n in df_in.columns if n[:2] == 'D_']" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "mask_train = df_in.PARTITION==\"train\"\n", - "mask_selection = df_in.PARTITION==\"selection\"\n", - "mask_validation = df_in.PARTITION==\"validation\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "y_train = df_in.loc[mask_train,'TARGET']\n", - "y_selection = df_in.loc[mask_selection,'TARGET']\n", - "y_validation = df_in.loc[mask_validation,'TARGET']" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "x_train = df_in.loc[mask_train,dvars]\n", - "x_selection = df_in.loc[mask_selection,dvars]\n", - "x_validation = df_in.loc[mask_validation,dvars]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Analysis settings" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df_settings = pd.read_csv(root+'/python/analysis_settings.csv', sep=',', index_col=0, header=None).T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Modeltab info" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df_modeltab = pd.read_csv(root+'/data/univariate/modeltab_info.csv',sep=';', index_col=0, header=None).T\n", - "modelrun = df_modeltab.run[1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Variable selections" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df_selections = pd.read_csv(root+'/data/univariate/variable_selections.csv',sep=';')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model making and recording" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define functions" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Function to make logistic model on a predefined set of predictors + compute train AUC of resulting model \n", - "def processSubset(predictors_subset):\n", - " from sklearn.linear_model import LogisticRegression\n", - " from sklearn import metrics\n", - " # Fit model on predictors_subset and retrieve performance metric\n", - " model = LogisticRegression(fit_intercept=True, C=1e9, solver = 'liblinear')\n", - " modelfit = model.fit(y=y_train, X=x_train[predictors_subset])\n", - " # Position of the TARGET==1 class\n", - " pos = [i for i,h in enumerate(modelfit.classes_) if h==1]\n", - " # Prediction probabilities for the TARGET==1\n", - " y_pred = modelfit.predict_proba(x_train[predictors_subset])[:,pos]\n", - " auc = metrics.roc_auc_score(y_true=y_train, y_score=y_pred)\n", - " return {\"modelfit\":modelfit,\"auc\":auc,\"predictor_names\":predictors_subset,\"predictor_lastadd\":predictors_subset[-1]}" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Function for computing AUC of all sets (train, selection & validation)\n", - "def getAuc(df_without_auc):\n", - " import pandas as pd\n", - " from sklearn import metrics\n", - " df_with_auc = df_without_auc[:]\n", - " for x,y,part in [(x_train,y_train,'train'),\n", - " (x_selection,y_selection,'selection'),\n", - " (x_validation,y_validation,'validation')]:\n", - " pos = [i for i,h in enumerate(df_without_auc.modelfit.classes_) if h==1]\n", - " y_pred = df_without_auc.modelfit.predict_proba(x[df_without_auc['predictor_names']])[:,pos]\n", - " df_with_auc[\"auc_\"+part] = metrics.roc_auc_score(y_true=y, y_score=y_pred)\n", - " df_with_auc[\"pred_\"+part] = y_pred\n", - " return(df_with_auc)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "# Forward selection function that uses processSubset and getAuc\n", - "def forward(current_predictors, pool_predictors, positive_only=True):\n", - " import pandas as pd\n", - " import numpy as np\n", - " tic = time.time()\n", - " \n", - " #Pull out predictors we still need to process\n", - " remaining_predictors = [p for p in pool_predictors if p not in current_predictors]\n", - " # If there are no more predictors left to use, raise an error we can easily identify as normal\n", - " if len(remaining_predictors)==0:\n", - " raise ValueError(\"No more predictors left to use\",\"NormalStop\")\n", - " \n", - " #Create a model for each combination of: current predictor(s) + one of the remaining predictors\n", - " #Keep track of the submodels and their performance\n", - " #If error skip to next and do not include in comparison table\n", - " results = []\n", - " errorcount = 0\n", - " for p in remaining_predictors:\n", - " try:\n", - " results.append(processSubset(current_predictors+[p]))\n", - " except:\n", - " errorcount += 1 \n", - " models = pd.DataFrame(results)\n", - " \n", - " # If we require all coefficients to be positive...\n", - " if positive_only:\n", - " #Create a flag for each submodel to test if all coefficients are positive \n", - " all_positive = pd.Series(None, index=models.index)\n", - " for i in range(0,len(models)):\n", - " all_positive[i] = (models.modelfit[i].coef_ >= 0 ).all()\n", - " \n", - " # if no model exist with only positive coefficients raise error we can easily identify as normal\n", - " if (all_positive==0).all():\n", - " raise ValueError(\"No models with only positive coefficients\",\"NormalStop\")\n", - " \n", - " #Choose model with best performance and only positive coefficients\n", - " best_model = models.loc[models[all_positive==1].auc.argmax()]\n", - " best_model = getAuc(best_model)\n", - " \n", - " # If we don't require all coefficients to be positive... \n", - " else:\n", - " #Choose model with best performance\n", - " best_model = models.loc[models.auc.argmax()]\n", - " best_model = getAuc(best_model)\n", - "\n", - " \n", - " tac = time.time()\n", - " info = (\"Processed \"\n", - " + str(models.shape[0])\n", - " + \" models on \"\n", - " + str(len(current_predictors)+1) \n", - " + \" predictors in \" \n", - " + str(round(tac-tic,2)) \n", - " +\" sec with \" \n", - " + str(errorcount) \n", - " +\" errors\")\n", - " \n", - " return best_model, info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create recipient vars" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "best_models = pd.DataFrame(columns=[\"modelfit\",\n", - " \"predictor_names\",\n", - " \"predictor_lastadd\",\n", - " \"auc_train\",\n", - " \"auc_selection\",\n", - " \"auc_validation\",\n", - " \"pred_train\",\n", - " \"pred_selection\",\n", - " \"pred_validation\"])\n", - "predictors = []" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define number of steps depending on settings and total number of predictors" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "step_setting = int(df_settings.modeling_nsteps)\n", - "n_steps = min(step_setting,len(x_train.columns))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define which variables to pass, force and filter" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [], - "source": [ - "mask_pass = (df_selections.preselect == 1) & (df_selections[modelrun]==0)\n", - "varname_list_pass = 'D_'+df_selections.loc[mask_pass,'variable']\n", - "length_pass = len(varname_list_pass)\n", - "mask_force = (df_selections.preselect == 1) & (df_selections[modelrun]==1)\n", - "varname_list_force = 'D_'+df_selections.loc[mask_force,'variable']\n", - "length_force = len(varname_list_force)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Execute forward modeling process" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "tic = time.time()\n", - "use_predictors = varname_list_force #x_train.columns\n", - "for i in range(1,n_steps+1):\n", - " try:\n", - " # Use predictors to be forced first. Once through the list, append the remaining variables to be passed.\n", - " use_predictors = varname_list_force.append(varname_list_pass[[i>length_force]*length_pass]).reset_index(drop=True)\n", - " result = forward(current_predictors=predictors\n", - " ,pool_predictors= use_predictors\n", - " ,positive_only=True)\n", - " best_models.loc[i] = result[0]\n", - " predictors = best_models.loc[i].predictor_names\n", - " log.append(result[1])\n", - " except Exception as e:\n", - " # Normal errors (i.e. no more predictors to be used / no models with only positive coefficients)\n", - " if e.args[-1]=='NormalStop':\n", - " log.append(\"Stopped modeling at \"+str(i)+\" predictors: \"+ e.args[-2])\n", - " # Other unknown errors\n", - " else:\n", - " log.append(\"Stopped modeling at \"+str(i)+\" predictors: unknown error\")\n", - " break\n", - "toc = time.time()\n", - "log.append(\"Forward selection modeling: \" + str(round((toc-tic)/60,0)) + \" min\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# WIP" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = range(1,n_steps+1)[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "use_predictors = varname_list_force.append(varname_list_pass[[i>length_force]*length_pass]).reset_index(drop=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "use_predictors" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "current_predictors=predictors\n", - "pool_predictors= use_predictors\n", - "positive_only=True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "remaining_predictors = [p for p in pool_predictors if p not in current_predictors]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "results = []\n", - "errorcount = 0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "for p in remaining_predictors:\n", - " try:\n", - " results.append(processSubset(current_predictors+[p]))\n", - " except:\n", - " errorcount += 1 \n", - "models = pd.DataFrame(results)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "all_positive = pd.Series(None, index=models.index)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "for i in range(0,len(models)):\n", - " all_positive[i] = (models.modelfit[i].coef_ >= 0 ).all()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "if (all_positive==0).all():\n", - " raise ValueError(\"No models with only positive coefficients\",\"NormalStop\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "best_model = models.loc[models[all_positive==1].auc.argmax()]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "best_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df_without_auc = best_model\n", - "df_with_auc = df_without_auc[:]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pos = [i for i,h in enumerate(df_without_auc.modelfit.classes_) if h==1]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = x_validation\n", - "y = y_validation\n", - "part = 'validation'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y.mean() # THIS IS WHY THERE IS AN ERROR. ONLY ZEROS IN THE VALIDATION SET.............. ????!!!!!!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred = df_without_auc.modelfit.predict_proba(x[df_without_auc['predictor_names']])[:,pos]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "metrics.roc_auc_score(y_true=y, y_score=y_pred)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# WIP/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Optimal model criterion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define functions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def comparefit(p,g=2):\n", - " # We fit a second degree (g=2) polyline through our auccurve \n", - " # This serves as a starting base for finding our optimal stopping point\n", - " import numpy as np\n", - " import pandas as pd\n", - " z = np.polyfit(p.index, p, g)\n", - " f = np.poly1d(z)\n", - " y_new = f(p.index)\n", - " return pd.Series(y_new,index=p.index)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def slopepoint(p,p_fit,thresh_ratio=0.2):\n", - " # We take the polyline from comparefit and look for the point of which the slope lies just below some percentage of the max. slope\n", - " slopes = [p_fit[i+1]-p_fit[i] for i in range(1,len(p_fit))]\n", - " slopes = pd.Series(slopes, index=range(1,len(p_fit)))\n", - " thresh = slopes.max()*thresh_ratio\n", - " p_best_index = (slopes[slopes>thresh])[-1:].index\n", - " p_best = p.loc[p_best_index]\n", - " return p_best" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def moveright(p,p_fit,p_best,n_steps=5,dampening=0.01):\n", - " # We look nsteps right on the polyline (starting from the slopepoint) and take the point with largest difference with real line\n", - " # We move to that point if that difference is larger than some multiplication of the difference at the slopepoint\n", - " # That multiplication gets larger as current the current difference gets smaller with a certain amount of dampening. \n", - " # The rationale behind this is as follows: \n", - " # if the current difference is already large than the larger difference will definitely be noteworthy\n", - " # if however the current difference is near zero than there needs to be much larger difference to be noteworthy\n", - " in_index = p_best.index.values[0]\n", - " lower = (in_index-1)\n", - " upper = (in_index+n_steps-1)\n", - " p_diff = p[lower:upper]-p_fit[lower:upper]\n", - " out_index = p_diff.argmax()\n", - " factor = 1/abs(p_diff[in_index])\n", - " if (p_diff[out_index]>p_diff[in_index]+(abs(p_diff[in_index])*factor*dampening)):\n", - " p_best_new = pd.Series(p[out_index],index=[out_index])\n", - " else:\n", - " p_best_new = p_best\n", - " return p_best_new" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def moveleft(p,p_fit,p_best,rangeloss=0.1, diffshare=0.8): #diff_min=0.005):\n", - " # Starting from whatever point we end up with (either the slopepoint or a move to the right)\n", - " # We look left on the polyline and take the point for which the real line is largest (current point included)\n", - " # We move left if we stay within [a specific % loss of range] AND [a minimum % of current difference]\n", - " # i.e. we don't won't to go to low compared to the overall real line\n", - " # and we don't won't to move to a point that does not make a significant increase in AUC (i.e. difference between polyline and real line)\n", - " p_left = p[:p_best.index.values[0]]\n", - " p_best = p_left[p_left==p_left.max()]\n", - " p_diff = p-p_fit\n", - " p_range = p.max()-p.min()\n", - " s = p[(p >= p_best.values[0]-(rangeloss*p_range)) \n", - " & (p.index <= p_best.index.values[0]) \n", - " & (p_diff>=diffshare*p_diff[p_left.index[-1]])\n", - " ]\n", - " p_best_new = s[s.index == s.index.values.min()]\n", - " return p_best_new" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Execute functions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "points = best_models.auc_selection\n", - "points_fit = comparefit(p=points, g=2)\n", - "points_slope = slopepoint(p=points, p_fit=points_fit, thresh_ratio=0.2)\n", - "points_right = moveright(p=points, p_fit=points_fit, p_best=points_slope, n_steps=5, dampening=0.01)\n", - "points_left = moveleft(p=points, p_fit=points_fit, p_best=points_right, rangeloss=0.1, diffshare=0.8)\n", - "\n", - "optimal_nvars = points_left.index.values[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Inspect" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "%matplotlib inline\n", - "from pylab import rcParams\n", - "rcParams['figure.figsize'] = 10, 5\n", - "\n", - "plt.plot( points.index , points , color=\"blue\")\n", - "plt.plot( points_fit.index , points_fit , color=\"red\")\n", - "plt.plot( points_slope.index, points_slope,'o', color=\"lightgreen\", markersize=12)\n", - "plt.plot( points_right.index, points_right,'o', color=\"black\" , markersize=8)\n", - "plt.plot( points_left.index , points_left ,'o', color=\"gold\" , markersize=4)\n", - "\n", - "axes = plt.gca()\n", - "axes.set_ylim([0.45,1])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Cumulative gains/response" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define functions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Compute cumulative response/gains\n", - "def cumulatives(y,yhat,perc_as_int=False,dec=2):\n", - " nrows = len(y)\n", - " npositives = y.sum()\n", - " y_yhat = pd.DataFrame({\"y\":y, \"yhat\":yhat}).sort_values(by='yhat', ascending=False).reset_index(drop=True)\n", - " cresp = []\n", - " cgains = [0]\n", - " for stop in (np.linspace(0.01,1,100)*nrows).astype(int):\n", - " cresp.append(round(y_yhat.loc[:stop,'y'].mean()*max(100*int(perc_as_int),1),dec))\n", - " cgains.append(round(y_yhat.loc[:stop,'y'].sum()/npositives*max(100*int(perc_as_int),1),dec))\n", - " return cresp,cgains" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Execute functions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cresp_all = [None]\n", - "cgains_all = [None]\n", - "for i in range(1,len(best_models)+1):\n", - " out = cumulatives(y=y_selection\n", - " ,yhat=best_models.pred_selection[i][:,0]\n", - " ,perc_as_int=True\n", - " ,dec=2)\n", - " cresp_all.append(out[0]) \n", - " cgains_all.append(out[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Inspect" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "%matplotlib inline\n", - "from pylab import rcParams\n", - "rcParams['figure.figsize'] = 10, 5\n", - "\n", - "cmap = plt.get_cmap('hot')\n", - "colors = [cmap(i) for i in np.linspace(0, 1, n_steps)]\n", - "for i in range(1,len(best_models)):\n", - " plt.plot(range(1,101), cresp_all[i], color=colors[i-1])\n", - "plt.plot(range(1,101), cresp_all[-1], color=\"black\")\n", - " \n", - "axes = plt.gca()\n", - "axes.set_ylim([0,max(max(l) for l in np.array(cresp_all)[1:])])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "%matplotlib inline\n", - "from pylab import rcParams\n", - "rcParams['figure.figsize'] = 10, 5\n", - "\n", - "cmap = plt.get_cmap('hot')\n", - "colors = [cmap(i) for i in np.linspace(0, 1, n_steps)]\n", - "for i in range(1,len(best_models)):\n", - " plt.plot(range(0,101), cgains_all[i], color=colors[i-1])\n", - "plt.plot(range(0,101), cgains_all[-1], color=\"black\")\n", - " \n", - "axes = plt.gca()\n", - "axes.set_ylim([0,max(max(l) for l in np.array(cgains_all)[1:])])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Variable Importance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Compute variable importance based on correlation between predictor and prediction (on selection set)\n", - "def getImportance(model):\n", - " from scipy import stats\n", - " \n", - " predictors = [pred[2:] for pred in model.predictor_names]\n", - " pearcorr = []\n", - " for predictor in predictors:\n", - " pearsonr = stats.pearsonr(x_selection.loc[:,'D_'+predictor].values, model.pred_selection[:,0])\n", - " pearcorr.append(pearsonr[0].round(2))\n", - " df_result = pd.DataFrame({'variable':predictors,'importance':pearcorr}, columns=['variable','importance'])\n", - " return df_result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Execute function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "importance_all=[None]\n", - "for i in best_models.index:\n", - " importance_all.append(getImportance(best_models.loc[i,:]))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Inspect" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "scrolled": true - }, - "source": [ - "%matplotlib inline\n", - "from pylab import rcParams\n", - "rcParams['figure.figsize'] = 10, 5\n", - "\n", - "#nvars = optimal_nvars\n", - "nvars = len(best_models)\n", - "\n", - "fig, ax = plt.subplots()\n", - "predictors = importance_all[nvars].variable\n", - "y_pos = np.arange(len(predictors))\n", - "importance = importance_all[nvars].importance\n", - "\n", - "ax.barh(y_pos, importance, align='center',\n", - " color='darkblue', ecolor='black')\n", - "ax.set_yticks(y_pos)\n", - "ax.set_yticklabels(predictors)\n", - "ax.invert_yaxis()\n", - "ax.set_xlabel('Importance')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Coefficients" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Store all variable names + coefficients for every best model (with 1,2,3,... variables) \n", - "vars_out = []\n", - "coef_out = []\n", - "nmod_out = []\n", - "for i in best_models.index:\n", - " modout = best_models.loc[i,:]\n", - " vars_out_st = ['Intercept']+[var[2:] for var in modout.predictor_names]\n", - " vars_out.append(vars_out_st)\n", - " coef_out_st = list(modout.modelfit.intercept_)+list(+ modout.modelfit.coef_[0])\n", - " coef_out.append(coef_out_st)\n", - " nmod_out.append([i]*(i+1))\n", - " \n", - "vars_out = list(chain.from_iterable(vars_out))\n", - "coef_out = list(chain.from_iterable(coef_out))\n", - "nmod_out = list(chain.from_iterable(nmod_out))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_coeff = pd.DataFrame({'nstep':nmod_out,'varname':vars_out,'coeff':coef_out}, columns=['nstep','varname','coeff'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Export Files" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "nmods = len(best_models)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Auc curve" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filename = root+\"/data/modeling/\"+modelrun+\"_auccurve.csv\"\n", - "with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =';')\n", - " write.writerow([\"optimal\" ,optimal_nvars])\n", - " write.writerow([\"selected\",optimal_nvars])\n", - " write.writerow([\"variable\",\"train\", \"selection\",\"validation\"])\n", - " write.writerows([best_models.predictor_lastadd[i][2:]\n", - " , best_models.auc_train[i].round(3) \n", - " , best_models.auc_selection[i].round(3)\n", - " , best_models.auc_validation[i].round(3) ] for i in range(1,nmods+1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Cresp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "for v in range(1,nmods+1):\n", - " filename = root+\"/data/modeling/\"+modelrun+\"_cresp_\"+str(v)+\".csv\"\n", - " with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =';') \n", - " write.writerows([i+1, cresp_all[v][i]] for i in range(0,100))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Cgains" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "for v in range(1,nmods+1):\n", - " filename = root+\"/data/modeling/\"+modelrun+\"_cgains_\"+str(v)+\".csv\"\n", - " with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =';') \n", - " write.writerows([i, cgains_all[v][i]] for i in range(0,101))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Variable importance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for v in range(1,nmods+1):\n", - " filename = root+\"/data/modeling/\"+modelrun+\"_importance_\"+str(v)+\".csv\"\n", - " with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =';') \n", - " write.writerow(['variable','importance'])\n", - " write.writerows([importance_all[v].iloc[i,0],importance_all[v].iloc[i,1]] for i in range(v))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Model coefficients" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "out_path = root+\"/data/modeling/\"+modelrun+\"_modelcoeff.csv\"\n", - "df_coeff.to_csv(path_or_buf=out_path, sep=';', index=False, encoding='utf-8', line_terminator='\\n', quoting=csv.QUOTE_NONNUMERIC)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Log messages" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "log.append(\"-- Modeling phase completed --\"+\"\\n\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "log_file = open(root+\"/python/\"+modelrun+\"_modeling.log\",'w')\n", - "log_file.write('\\n'.join(log))\n", - "log_file.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stop script" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "print(\"ok\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# WIP" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Scoring all rows" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Scoring of all rows\n", - "import re\n", - "tic = time.time()\n", - "df_score = pd.DataFrame([])\n", - "df_score['ID'] = df_in['ID']\n", - "scores = []\n", - "for i in range(len(df_in)):\n", - " ### METHOD 1: using function\n", - " score = [optifit.predict_proba(df_in[optivars])[i,:][-1]]\n", - " ### METHOD 2: with coefficients (same method as in scoring)\n", - " #exponent = optiint + ((df_in[optivars].iloc[i,:])*(opticoef[0])).sum()\n", - " #score = [(math.exp(exponent)) / (1+math.exp(exponent))]\n", - " \n", - " scores.extend(score)\n", - " try:\n", - " zeros = re.findall('[0]+$',str(i))\n", - " if len(zeros[0])>=3:\n", - " print(i)\n", - " except:\n", - " a=1\n", - "df_score['score']=pd.Series(scores)\n", - "tac = time.time()\n", - "print((tac-tic)/60)\n", - "\n", - "\n", - "df_in.to_csv('df_mod.csv', sep=';', index=False, encoding='utf-8', line_terminator='\\n')\n", - "df_score.to_csv(path_or_buf='scores_modeling.csv', sep=';', index=False, encoding='utf-8', line_terminator='\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# /WIP" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.3" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/legacy_code/notes on backend.txt b/legacy_code/notes on backend.txt deleted file mode 100644 index 7a05ea6..0000000 --- a/legacy_code/notes on backend.txt +++ /dev/null @@ -1,4 +0,0 @@ -The notebooks are written so they can be transformed to a script (that goes in the WAMP local folder) without any reworking necessary -In jupyter notebook: File > Download as > .py file -or -Via cmd line: jupyter nbconvert --to Python univariate.ipynb \ No newline at end of file diff --git a/legacy_code/scorecode.py b/legacy_code/scorecode.py deleted file mode 100644 index 4a4dd31..0000000 --- a/legacy_code/scorecode.py +++ /dev/null @@ -1,131 +0,0 @@ -### Importing libraries & basetable to score -# Importing Libraries -import time -import math -import csv -import re -import pandas as pd -import numpy as np -# Importing Types -typevariables=['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country', 'TARGET', 'ID', 'scont_1', 'scont_2', 'scont_3', 'scont_4', 'scont_5', 'scont_6', 'scont_7', 'scont_8', 'scont_9', 'scont_10', 'scat_1', 'scat_2', 'scat_3', 'scat_4', 'scat_5', 'sflag_1', 'sflag_2', 'sflag_3', 'sflag_4', 'sflag_5'] -typetypes=['int', 'str', 'int', 'str', 'int', 'str', 'str', 'str', 'str', 'str', 'int', 'int', 'int', 'str', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'int', 'str', 'str', 'str', 'str', 'str', 'int', 'int', 'int', 'int', 'int'] -df_types=pd.DataFrame({'var':typevariables,'type':typetypes},columns=['var','type']) -df_types_copy = df_types.copy() -bool_mask = df_types_copy.loc[:,'type']!='bool' -df_types_copy.loc[bool_mask,'type'] = [getattr(__builtins__, type_str) for type_str in df_types_copy.loc[bool_mask,'type']] -df_types_copy.loc[bool_mask==False,'type'] = getattr(__builtins__, 'str') -types = df_types_copy.set_index('var').T.to_dict('records') -# Importing Basetable with similar typing as in univariate analysis -df_base = pd.read_csv('df_base.csv',header=0,sep=None,engine='python',converters=types[0]) - -### Creating dataframe containing model rules -modvariables=['Intercept', 'relationship', 'education', 'capital-gain', 'occupation', 'hours-per-week', 'marital-status', 'workclass'] -modcoefficients=[-7.089251945907992, 2.857566239388173, 3.9559266007760656, 3.805293440526549, 3.0368562075260153, 2.8679282172225298, 3.8409050507426072, 1.270430053023763] -df_modrules=pd.DataFrame({'varname':modvariables,'coeff':modcoefficients}) - -### Creating dataframe containing incidence translation rules -prepvariables=['relationship', 'relationship', 'relationship', 'relationship', 'relationship', 'relationship', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'education', 'capital-gain', 'capital-gain', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'occupation', 'hours-per-week', 'hours-per-week', 'hours-per-week', 'hours-per-week', 'marital-status', 'marital-status', 'marital-status', 'marital-status', 'marital-status', 'marital-status', 'marital-status', 'workclass', 'workclass', 'workclass', 'workclass', 'workclass', 'workclass', 'workclass'] -prepbins=['Husband', 'Wife', 'Not-in-family', 'Own-child', 'Unmarried', 'Other-relative', 'Some-college', '7th-8th', 'HS-grad', 'Bachelors', '5th-6th', 'Doctorate', 'Non-significants', 'Masters', 'Prof-school', '10th', '11th', '12th', '9th', '1st-4th', 'Preschool', '[..., 2105]', '(2105,...]', 'Exec-managerial', 'Non-significants', 'Sales', '?', 'Prof-specialty', 'Farming-fishing', 'Handlers-cleaners', 'Other-service', 'Protective-serv', 'Machine-op-inspct', 'Adm-clerical', 'Priv-house-serv', '(48,...]', '(40, 48]', '(35, 40]', '[..., 35]', 'Married-civ-spouse', 'Divorced', 'Never-married', 'Married-spouse-absent', 'Separated', 'Non-significants', 'Widowed', 'Private', 'Self-emp-not-inc', '?', 'Local-gov', 'Self-emp-inc', 'Non-significants', 'Federal-gov'] -prepincids=[0.4474003641513251, 0.4803571428571429, 0.10295278698878887, 0.015203145478374838, 0.056633663366336635, 0.04037685060565276, 0.18898367952522252, 0.0641025641025641, 0.16431451612903225, 0.42006504878659, 0.05303030303030303, 0.7353951890034365, 0.2429441062534588, 0.5435276305828918, 0.7382198952879581, 0.05128205128205128, 0.05584281282316442, 0.08433734939759037, 0.04702970297029703, 0.060869565217391314, 0.022727272727272728, 0.20500310476359446, 0.6513859275053305, 0.4710552431359577, 0.23353535353535354, 0.2739825581395349, 0.10049893086243764, 0.4519454605919521, 0.11946308724832215, 0.06824644549763033, 0.043478260869565216, 0.3169734151329243, 0.11971372804163954, 0.14123893805309734, 0.015503875968992246, 0.4302445038011095, 0.3498478922207736, 0.207983367983368, 0.08491107286288009, 0.4459652889604581, 0.10560344827586207, 0.044271796769022084, 0.09846153846153846, 0.07431551499348109, 0.4736842105263158, 0.07603092783505154, 0.2187813366365835, 0.2839756592292089, 0.1010028653295129, 0.2814526588845655, 0.554320987654321, 0.26436781609195403, 0.3900709219858156] -df_prep = pd.DataFrame({'var':prepvariables,'bin':prepbins,'incid':prepincids}, dtype=object) -df_prep.loc[:,'incid']=df_prep.loc[:,'incid'].astype('float64') - -### Grouping basetable predictors along their types and trimming basetable accordingly -predictors = list(df_modrules.loc[df_modrules.varname!='Intercept','varname'].values) -not_predictors = [column for column in df_base.columns if column not in predictors] -mask_FloatOrInt = (df_types.type=='int')|(df_types.type=='float') -numeric_headers=[var for var in df_types.loc[mask_FloatOrInt,'var'].values if var in predictors] -object_headers=[var for var in df_types.loc[df_types.type=='str','var'].values if var in predictors] -bool_headers=[var for var in df_types.loc[df_types.type=='bool','var'].values if var in predictors] -df_base = df_base[predictors+['ID']] - -### Preprocessing the basetable -# Strip quot function -def strip_quot(x_in): - try: - x_out = x_in.strip().strip('"').strip("'") - except: - x_out=x_in - return x_out -# Lower/upper function -def lower_upper(x_in): - if ((x_in.lower() == 'id')|(x_in.lower() == 'target')): - x_out = x_in.upper() - else: - x_out = x_in.lower() - return x_out -# maskmissing function in str/bool columns -def maskmissing(var): - crit1 = var.isnull() - modvar = pd.Series([str(value).strip() for value in var]) - crit2 = modvar==pd.Series(['']*len(var)) - return crit1 | crit2 -# Apply preprocessing functions -df_base = df_base.rename(columns=strip_quot) -df_base = df_base.rename(columns=lower_upper) -df_base = df_base.applymap(strip_quot) -for header in object_headers+bool_headers: - mask = maskmissing(df_base[header]) - df_base.loc[mask,header]='Missing' - -### Incidence replacement -# Recipient dataframe -df_out = pd.DataFrame() -df_out['ID']=df_base['ID'] -# Incidence replacement for string columns -for header in object_headers+bool_headers: - mask = df_prep.loc[:,'var']==header - bins = df_prep.loc[mask,'bin'] - incidences = df_prep.loc[mask,'incid'] - nonsig_bins = [] - nonsig_incidences = [] - if (bins == 'Non-significants').any(): - nonsig_bins = [binn for binn in df_base[header].unique() if binn not in list(bins)] - nonsig_incidences = list(incidences[bins=='Non-significants'])*len(nonsig_bins) - keys = list(bins) - keys.extend(nonsig_bins) - values = list(incidences) - values.extend(nonsig_incidences) - keys_and_values = zip(keys,values) - transdic = dict(keys_and_values) - items_to_translate = df_base[header] - df_out.loc[:,'D_'+header]= pd.Series([transdic[item] for item in items_to_translate]) -# Incidence replacement for numeric columns -for header in numeric_headers: - mask = df_prep.loc[:,'var']==header - bins = df_prep.loc[mask,'bin'] - incidences = df_prep.loc[mask,'incid'] - index_missing = bins.index[bins=='Missing'] - incidence_missing = incidences[index_missing] - upper_values = pd.Series([]) - for i,binn in enumerate(bins.values): - upper_value = binn.split(',')[-1] - try: - upper_value = re.findall('[0-9]+',upper_value)[0] - except: - upper_value = math.inf - upper_values[i] = upper_value - upper_values.index = bins.index - upper_values.drop(index_missing, inplace=True) - upper_values = upper_values.astype(float) - upper_values.sort_values(inplace=True) - upper_values_incidences = incidences[upper_values.index] - upper_values.reset_index(drop=True, inplace=True) - upper_values_incidences.reset_index(drop=True, inplace=True) - mask_npnan = df_base.loc[:,header].isnull() - lowest_memberships = upper_values.searchsorted(df_base.loc[:,header],side='left') - incidences_to_attribute = upper_values_incidences[lowest_memberships].reset_index(drop=True) - incidences_to_attribute[mask_npnan] = incidence_missing - df_out['D_'+header] = incidences_to_attribute - -### Scoring -df_scores = pd.DataFrame([]) -df_scores['ID'] = df_out['ID'] -scores = [] -intercept=-7.08925194591 -coefficients=np.array([2.857566239388173, 3.9559266007760656, 3.805293440526549, 3.0368562075260153, 2.8679282172225298, 3.8409050507426072, 1.270430053023763]) -productsums = (df_out['D_'+pd.Series(predictors)]*coefficients).sum(axis=1) -exponents = intercept + productsums -scores = exponents.apply(func=lambda x:(math.exp(x)) / (1+math.exp(x))) -df_scores['score']=scores - diff --git a/legacy_code/scoring[171005].ipynb b/legacy_code/scoring[171005].ipynb deleted file mode 100644 index 76723bf..0000000 --- a/legacy_code/scoring[171005].ipynb +++ /dev/null @@ -1,649 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Scoring" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Purpose of this script is to output (an)other script(s) with self-contained code to score out a basetable\n", - "\n", - "Call this script: the scriptmaker\n", - "\n", - "Call the created self-contained script: the scoring script" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import time\n", - "import math\n", - "import csv\n", - "import re\n", - "import pandas as pd\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Miscellaneous" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "log = []" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# When code is in script, we define the path of the script's parent folder location as the root directory\n", - "# From this root we can travel to the relevant folders with minimal adjustment\n", - "try:\n", - " root = os.path.dirname(os.path.realpath(__file__))\n", - " root = \"/\".join(root.split('\\\\')[:-1])\n", - " log.append('Dynamic paths'+'\\n')\n", - "except:\n", - " root = 'C:/wamp64/www/python_predictions_4/assets/scripts'\n", - " log.append('Static paths'+'\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read the data and create variables to be exported" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### 1A - Retrieve Modeltab info to find out for which modeltab we want a scoring script for\n", - "Intermediate step for 1C" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df_modeltab = pd.read_csv(root+'/data/univariate/modeltab_info.csv',sep=';', index_col=0, header=None).T\n", - "modeltabtoscore = df_modeltab.score[1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### 1B - Retrieve number of vars selected to identify which n-th model of the 'modeltab'-models we are interested in\n", - "Intermediate step for 1C" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "df_auccurve_path = root+\"/data/modeling/\"+modeltabtoscore+\"_auccurve.csv\"\n", - "df_auccurve = open(df_auccurve_path).read()\n", - "selected_nvars = int(re.findall(r\"selected;[0-9]+\",df_auccurve)[0].split(';')[-1])\n", - "selected_nvars = len(pd.read_csv(df_auccurve_path,skiprows=3, sep=';')) # USED FOR TESTING ALL VARS, TO BE DELETED !!!!!!!!!!!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### 1C - Retrieve model coefficients of the specific n-th model of the 'modeltab'-models\n", - "\n", - "Result is to be stored in text in the scoring script, to achieve self-containment" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "df_modelcoeff_path = root+\"/data/modeling/\"+modeltabtoscore+\"_modelcoeff.csv\"\n", - "df_modelcoeff = pd.read_csv(df_modelcoeff_path, sep=';')\n", - "mask = df_modelcoeff.nstep == int(selected_nvars)\n", - "df_modrules = df_modelcoeff.loc[mask,:] # TO BE STORED in SCORING SCRIPT -------------------------------------------------------" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### 2 - Retrieve Data types of the predictors\n", - "\n", - "Result is to be stored in text in the scoring script, to achieve self-containment" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "types_path = root+\"/python/data_types.csv\"\n", - "df_types = pd.read_csv(types_path, header=None)\n", - "df_types.columns=['var','type'] # TO BE STORED in SCORING SCRIPT ---------------------------------------------------------------" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### 3 - Univariate table output for deriving the translation from original VAR to discretisized D_VAR\n", - "Result is to be stored in text in the scoring script, to achieve self-containment" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "df_univariate_path = root+\"/data/univariate/df_univariate.csv\"\n", - "# we create str converters for all the B_variables coming from the univariate table\n", - "# we need this because bool & str variables (defined by ...types.csv) from the basetable are converted to objects\n", - "# and will have to be compared to the B_variables values, which better have the same type\n", - "# e.g. we have a varflag in our basetable which is converted to an object, but assume B_varflag is not converted and will be automatically read as float 1.0/0.0\n", - "# in our incidence replacement we will thus be comparing '1'/'0' with 1.0/0.0, which won't work \n", - "uni_iterable = [(variable,getattr(__builtins__, 'str')) for variable in 'B_'+df_modrules.varname[1:].values]\n", - "uni_dict = dict(uni_iterable)\n", - "df_uni = pd.read_csv(df_univariate_path, sep=\";\", converters=uni_dict)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "dcolumns = ['D_'+name for name in df_modrules.varname[1:]]\n", - "bcolumns = ['B_'+name for name in df_modrules.varname[1:]]\n", - "gvar = []\n", - "gincid = []\n", - "gbin = []\n", - "\n", - "for i in range(len(dcolumns)):\n", - " # Select B_varname and D_varname\n", - " # Then take unique combinations of B_var and D_var in the univariate dataframe\n", - " # These combinations give the incidence value to attribute to the (possibly discretisized/regrouped) variables\n", - " columns_set = dcolumns[i:i+1]+bcolumns[i:i+1]\n", - " df_dupli = df_uni.loc[:,columns_set].drop_duplicates()\n", - " n_occurences = len(df_dupli) \n", - " \n", - " gvar.extend([df_dupli.columns[0][2:]]*n_occurences)\n", - " gincid.extend(df_dupli.iloc[:,0].values)\n", - " gbin.extend(df_dupli.iloc[:,1].values)\n", - "\n", - " \n", - "df_prep = pd.DataFrame({'var':gvar,'bin':gbin,'incid':gincid} \n", - " ,columns=['var','bin','incid']) # TO BE STORED in SCORING SCRIPT --------------------------------" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Writing scoring scripts" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### for R" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "score_code = open(root+\"/Python/scorecode.R\",'w')\n", - "\n", - "score_code.write(\"### Importing libraries & basetable to score\\n\")\n", - "score_code.write(\"# Importing libraries\\n\")\n", - "score_code.write(\"#library(dplyr)\\n\")\n", - "score_code.write(\"# Importing Types\\n\")\n", - "score_code.write(\"typevariables=c\"+str([var for var in df_types.loc[:,'var']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"typetypes=c\"+str([vartype for vartype in df_types.loc[:,'type']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"df_types=data.frame(var=typevariables,type=typetypes, stringsAsFactors='False')\\n\")\n", - "score_code.write(\"df_types_copy = df_types\\n\")\n", - "score_code.write(\"df_types_copy$type[df_types_copy$type=='int'|df_types_copy$type=='float']='numeric'\\n\")\n", - "score_code.write(\"df_types_copy$type[df_types_copy$type=='str'|df_types_copy$type=='bool']='character'\\n\")\n", - "score_code.write(\"coltypes = df_types_copy$type\\n\")\n", - "score_code.write(\"names(coltypes) = df_types_copy$var\\n\")\n", - "score_code.write(\"# Importing Basetable (with similar typing as in univariate analysis)\\n\")\n", - "score_code.write(\"df_base = read.csv('df_base.csv', check.names='False', colClasses=coltypes )\\n\")\n", - "\n", - "score_code.write(\"### Creating dataframe containing model rules\\n\")\n", - "score_code.write(\"modvariables=c\"+str([var for var in df_modrules.loc[:,'varname']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"modcoefficients=c\"+str([coeff for coeff in df_modrules.loc[:,'coeff']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"df_modrules=data.frame(varname=modvariables,coeff=modcoefficients, stringsAsFactors='False')\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Creating dataframe containing incidence translation rules\\n\")\n", - "score_code.write(\"prepvariables=c\"+str([var for var in df_prep.loc[:,'var']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"prepbins=c\"+str([bin for bin in df_prep.loc[:,'bin']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"prepincids=c\"+str([bin for bin in df_prep.loc[:,'incid']]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"df_prep =data.frame(var=prepvariables,bin=prepbins,incid=prepincids, stringsAsFactors='False')\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Grouping basetable predictors along their types and trimming basetable accordingly\\n\")\n", - "score_code.write(\"predictors = df_modrules$varname[df_modrules$varname!='Intercept']\\n\")\n", - "score_code.write(\"not_predictors = subset(colnames(df_base),!(colnames(df_base) %in% predictors))\\n\")\n", - "score_code.write(\"mask_FloatOrInt = df_types$type=='int'|df_types$type=='float'\\n\")\n", - "score_code.write(\"numeric_headers = subset(df_types$var[mask_FloatOrInt], df_types$var[mask_FloatOrInt] %in% predictors)\\n\")\n", - "score_code.write(\"object_headers = subset(df_types$var[df_types$type=='str'], df_types$var[df_types$type=='str'] %in% predictors)\\n\")\n", - "score_code.write(\"bool_headers = subset(df_types$var[df_types$type=='bool'], df_types$var[df_types$type=='bool'] %in% predictors)\\n\")\n", - "score_code.write(\"df_base = df_base[c(predictors,'ID')]\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Preprocessing the basetable\\n\")\n", - "score_code.write(\"# Strip quot function\\n\")\n", - "score_code.write(\"strip_quot<-function(x){\\n\")\n", - "score_code.write(' x = gsub(\"')\n", - "score_code.write(\"'\")\n", - "score_code.write('\",\"\",x)\\n')\n", - "score_code.write(\" x = gsub('\")\n", - "score_code.write('\"')\n", - "score_code.write(\"','',x)\\n\") \n", - "score_code.write(\" x = trimws(x)\\n\")\n", - "score_code.write(\" return(x)\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"# Lower/upper function\\n\")\n", - "score_code.write(\"lower_upper<-function(x){\\n\")\n", - "score_code.write(\" if (tolower(x)=='id'|tolower(x)=='target'){\\n\")\n", - "score_code.write(\" x = toupper(x)\\n\")\n", - "score_code.write(\" }\\n\")\n", - "score_code.write(\" else {\\n\")\n", - "score_code.write(\" x = tolower(x)\\n\")\n", - "score_code.write(\" }\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"# maskmissing function in str/bool columns\\n\")\n", - "score_code.write(\"maskmissing<-function(var){\\n\")\n", - "score_code.write(\" crit1 = is.na(var)\\n\")\n", - "score_code.write(\" crit2 = var==''\\n\")\n", - "score_code.write(\" return(crit1|crit2)\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"# Apply preprocessing functions\\n\")\n", - "score_code.write(\"colnames(df_base) = sapply(colnames(df_base), lower_upper)\\n\")\n", - "score_code.write(\"colnames(df_base) = sapply(colnames(df_base), strip_quot)\\n\")\n", - "score_code.write(\"df_base[] = lapply(df_base, strip_quot)\\n\")\n", - "score_code.write(\"for (predictor in c(object_headers,bool_headers)){\\n\")\n", - "score_code.write(\" df_base[maskmissing(df_base[predictor]),predictor]='Missing'\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Incidence replacement\\n\")\n", - "score_code.write(\"# Recipient dataframe\\n\")\n", - "score_code.write(\"df_out = data.frame(ID=df_base$ID)\\n\")\n", - "score_code.write(\"# Incidence replacement for string columns\\n\")\n", - "score_code.write(\"for (header in c(object_headers,bool_headers)){\\n\")\n", - "score_code.write(\" mask = df_prep$var==header\\n\")\n", - "score_code.write(\" bins = df_prep[mask,'bin']\\n\")\n", - "score_code.write(\" incidences = df_prep[mask,'incid']\\n\")\n", - "score_code.write(\" nonsig_bins = c()\\n\")\n", - "score_code.write(\" nonsig_incidences = c()\\n\")\n", - "score_code.write(\" if (sum(bins == 'Non-significants')>0) {\\n\")\n", - "score_code.write(\" nonsig_bins = subset(unique(df_base[,header]), !(unique(df_base[,header]) %in% bins))\\n\")\n", - "score_code.write(\" nonsig_incidences = rep(incidences[bins=='Non-significants'],length(nonsig_bins))\\n\")\n", - "score_code.write(\" }\\n\")\n", - "score_code.write(\" keys = c(bins,nonsig_bins)\\n\")\n", - "score_code.write(\" values = c(incidences,nonsig_incidences)\\n\")\n", - "score_code.write(\" df_out[paste('D_',header, sep='')] = values[match(df_base[,header], keys)]\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"# Incidence replacement for numeric columns\\n\")\n", - "score_code.write(\"for (header in numeric_headers){\\n\")\n", - "score_code.write(\" mask = df_prep$var==header\\n\")\n", - "score_code.write(\" bins = df_prep[mask,'bin']\\n\")\n", - "score_code.write(\" incidences = df_prep[mask,'incid']\\n\")\n", - "score_code.write(\" index_missing = which(bins=='Missing')\\n\")\n", - "score_code.write(\" incidence_missing = incidences[index_missing]\\n\")\n", - "score_code.write(\" upper_values = c()\\n\")\n", - "score_code.write(\" last <- function(x) { return( x[length(x)] ) }\\n\")\n", - "score_code.write(\" for (binn in bins){\\n\")\n", - "score_code.write(\" upper_value = last(unlist(strsplit(binn,',')))\\n\")\n", - "score_code.write(\" upper_value = tryCatch(as.numeric(gsub('([0-9]+).*$', '\\\\\")\n", - "score_code.write(\"\\\\\")\n", - "score_code.write(\"1',upper_value)), warning=function(e) Inf)\\n\")\n", - "score_code.write(\" upper_values = c(upper_values,upper_value)\\n\")\n", - "score_code.write(\" }\\n\")\n", - "score_code.write(\" if(!identical(index_missing,integer(0))) upper_values = upper_values[-index_missing]\\n\")\n", - "score_code.write(\" if(!identical(index_missing,integer(0))) incidences = incidences[-index_missing]\\n\")\n", - "score_code.write(\" upper_values_incidences = incidences[order(upper_values)]\\n\")\n", - "score_code.write(\" upper_values = upper_values[order(upper_values)]\\n\")\n", - "#score_code.write(\" incidence_replaced_values = c()\\n\")\n", - "#score_code.write(\" for (original_value in as.numeric(df_base[,header])){\\n\")\n", - "#score_code.write(\" if (is.na(original_value)){\\n\")\n", - "#score_code.write(\" incidence_to_attribute = incidence_missing\\n\")\n", - "#score_code.write(\" }\\n\")\n", - "#score_code.write(\" else {\\n\")\n", - "#score_code.write(\" lowest_membership = min(which(original_value<=upper_values))\\n\")\n", - "#score_code.write(\" incidence_to_attribute = upper_values_incidences[lowest_membership]\\n\")\n", - "#score_code.write(\" }\\n\")\n", - "#score_code.write(\" incidence_replaced_values = c(incidence_replaced_values,incidence_to_attribute)\\n\")\n", - "#score_code.write(\" }\\n\")\n", - "#score_code.write(\" df_out[paste('D_',header, sep='')] = incidence_replaced_values\\n\")\n", - "score_code.write(\" mask_nan = is.na(df_base[,header])\\n\")\n", - "score_code.write(\" lowest_memberships = findInterval(as.numeric(df_base[,header]), upper_values * (1 + .Machine$double.eps)) + 1\\n\")\n", - "score_code.write(\" incidences_to_attribute = upper_values_incidences[lowest_memberships]\\n\")\n", - "score_code.write(\" incidences_to_attribute[mask_nan] = incidence_missing\\n\")\n", - "score_code.write(\" df_out[paste('D_',header, sep='')] = incidences_to_attribute\\n\")\n", - "score_code.write(\"}\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Scoring\\n\")\n", - "score_code.write(\"df_scores = data.frame(ID=as.numeric(as.character(df_out$ID)))\\n\")\n", - "score_code.write(\"scores = c()\\n\")\n", - "score_code.write(\"intercept=\"+str(df_modrules.coeff.values[0])+\"\\n\")\n", - "score_code.write(\"coefficients=c\"+str([coeff for coeff in df_modrules.coeff][1:]).replace(\"[\",\"(\").replace(\"]\",\")\")+\"\\n\")\n", - "score_code.write(\"productsums = rowSums(t(t(df_out[,paste('D_',predictors,sep='')])*coefficients))\\n\")\n", - "score_code.write(\"exponents = intercept + productsums\\n\")\n", - "score_code.write(\"scores = sapply(exponents, FUN = function(x) (exp(x)) / (1+exp(x)))\\n\")\n", - "score_code.write(\"df_scores['score']=scores\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### for Python" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "score_code = open(root+\"/Python/scorecode.py\",'w')\n", - "\n", - "score_code.write(\"### Importing libraries & basetable to score\\n\")\n", - "score_code.write(\"# Importing Libraries\\n\")\n", - "score_code.write(\"import time\\nimport math\\nimport csv\\nimport re\\nimport pandas as pd\\nimport numpy as np\\n\")\n", - "score_code.write(\"# Importing Types\\n\")\n", - "score_code.write(\"typevariables=\"+str([var for var in df_types.loc[:,'var']])+\"\\n\")\n", - "score_code.write(\"typetypes=\"+str([vartype for vartype in df_types.loc[:,'type']])+\"\\n\")\n", - "score_code.write(\"df_types=pd.DataFrame({'var':typevariables,'type':typetypes},columns=['var','type'])\\n\")\n", - "score_code.write(\"df_types_copy = df_types.copy()\\n\")\n", - "score_code.write(\"bool_mask = df_types_copy.loc[:,'type']!='bool'\\n\")\n", - "score_code.write(\"df_types_copy.loc[bool_mask,'type'] = [getattr(__builtins__, type_str) for type_str in df_types_copy.loc[bool_mask,'type']]\\n\")\n", - "score_code.write(\"df_types_copy.loc[bool_mask==False,'type'] = getattr(__builtins__, 'str')\\n\")\n", - "score_code.write(\"types = df_types_copy.set_index('var').T.to_dict('records')\\n\") \n", - "score_code.write(\"# Importing Basetable with similar typing as in univariate analysis\\n\")\n", - "score_code.write(\"df_base = pd.read_csv('df_base.csv',header=0,sep=None,engine='python',converters=types[0])\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Creating dataframe containing model rules\\n\")\n", - "score_code.write(\"modvariables=\"+str([var for var in df_modrules.loc[:,'varname']])+\"\\n\")\n", - "score_code.write(\"modcoefficients=\"+str([coeff for coeff in df_modrules.loc[:,'coeff']])+\"\\n\")\n", - "score_code.write(\"df_modrules=pd.DataFrame({'varname':modvariables,'coeff':modcoefficients})\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Creating dataframe containing incidence translation rules\\n\")\n", - "score_code.write(\"prepvariables=\"+str([var for var in df_prep.loc[:,'var']])+\"\\n\")\n", - "score_code.write(\"prepbins=\"+str([bin for bin in df_prep.loc[:,'bin']])+\"\\n\")\n", - "score_code.write(\"prepincids=\"+str([bin for bin in df_prep.loc[:,'incid']])+\"\\n\")\n", - "score_code.write(\"df_prep = pd.DataFrame({'var':prepvariables,'bin':prepbins,'incid':prepincids}, dtype=object)\\n\")\n", - "score_code.write(\"df_prep.loc[:,'incid']=df_prep.loc[:,'incid'].astype('float64')\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Grouping basetable predictors along their types and trimming basetable accordingly\\n\")\n", - "score_code.write(\"predictors = list(df_modrules.loc[df_modrules.varname!='Intercept','varname'].values)\\n\")\n", - "score_code.write(\"not_predictors = [column for column in df_base.columns if column not in predictors]\\n\")\n", - "score_code.write(\"mask_FloatOrInt = (df_types.type=='int')|(df_types.type=='float')\\n\")\n", - "score_code.write(\"numeric_headers=[var for var in df_types.loc[mask_FloatOrInt,'var'].values if var in predictors]\\n\")\n", - "score_code.write(\"object_headers=[var for var in df_types.loc[df_types.type=='str','var'].values if var in predictors]\\n\")\n", - "score_code.write(\"bool_headers=[var for var in df_types.loc[df_types.type=='bool','var'].values if var in predictors]\\n\")\n", - "score_code.write(\"df_base = df_base[predictors+['ID']]\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.write(\"### Preprocessing the basetable\\n\")\n", - "score_code.write(\"# Strip quot function\\n\")\n", - "score_code.write(\"def strip_quot(x_in):\\n\")\n", - "score_code.write(\" try:\\n\")\n", - "score_code.write(\" x_out = x_in.strip().strip('\")\n", - "score_code.write('\"')\n", - "score_code.write(\"').strip(\")\n", - "score_code.write('\"')\n", - "score_code.write(\"'\")\n", - "score_code.write('\")\\n')\n", - "score_code.write(\" except:\\n\")\n", - "score_code.write(\" x_out=x_in\\n\")\n", - "score_code.write(\" return x_out\\n\")\n", - "score_code.write(\"# Lower/upper function\\n\")\n", - "score_code.write(\"def lower_upper(x_in):\\n\")\n", - "score_code.write(\" if ((x_in.lower() == 'id')|(x_in.lower() == 'target')):\\n\")\n", - "score_code.write(\" x_out = x_in.upper()\\n\")\n", - "score_code.write(\" else:\\n\")\n", - "score_code.write(\" x_out = x_in.lower()\\n\")\n", - "score_code.write(\" return x_out\\n\")\n", - "score_code.write(\"# maskmissing function in str/bool columns\\n\")\n", - "score_code.write(\"def maskmissing(var):\\n\")\n", - "score_code.write(\" crit1 = var.isnull()\\n\")\n", - "score_code.write(\" modvar = pd.Series([str(value).strip() for value in var])\\n\")\n", - "score_code.write(\" crit2 = modvar==pd.Series(['']*len(var))\\n\")\n", - "score_code.write(\" return crit1 | crit2\\n\")\n", - "score_code.write(\"# Apply preprocessing functions\\n\")\n", - "score_code.write(\"df_base = df_base.rename(columns=strip_quot)\\n\")\n", - "score_code.write(\"df_base = df_base.rename(columns=lower_upper)\\n\")\n", - "score_code.write(\"df_base = df_base.applymap(strip_quot)\\n\")\n", - "score_code.write(\"for header in object_headers+bool_headers:\\n\")\n", - "score_code.write(\" mask = maskmissing(df_base[header])\\n\")\n", - "score_code.write(\" df_base.loc[mask,header]='Missing'\\n\")\n", - "score_code.write(\"\\n\")\n", - " \n", - "score_code.write(\"### Incidence replacement\\n\")\n", - "score_code.write(\"# Recipient dataframe\\n\")\n", - "score_code.write(\"df_out = pd.DataFrame()\\n\")\n", - "score_code.write(\"df_out['ID']=df_base['ID']\\n\")\n", - "score_code.write(\"# Incidence replacement for string columns\\n\")\n", - "score_code.write(\"for header in object_headers+bool_headers:\\n\")\n", - "score_code.write(\" mask = df_prep.loc[:,'var']==header\\n\")\n", - "score_code.write(\" bins = df_prep.loc[mask,'bin']\\n\")\n", - "score_code.write(\" incidences = df_prep.loc[mask,'incid']\\n\")\n", - "score_code.write(\" nonsig_bins = []\\n\")\n", - "score_code.write(\" nonsig_incidences = []\\n\")\n", - "score_code.write(\" if (bins == 'Non-significants').any():\\n\")\n", - "score_code.write(\" nonsig_bins = [binn for binn in df_base[header].unique() if binn not in list(bins)]\\n\")\n", - "score_code.write(\" nonsig_incidences = list(incidences[bins=='Non-significants'])*len(nonsig_bins)\\n\")\n", - "score_code.write(\" keys = list(bins)\\n\")\n", - "score_code.write(\" keys.extend(nonsig_bins)\\n\")\n", - "score_code.write(\" values = list(incidences)\\n\")\n", - "score_code.write(\" values.extend(nonsig_incidences)\\n\")\n", - "score_code.write(\" keys_and_values = zip(keys,values)\\n\")\n", - "score_code.write(\" transdic = dict(keys_and_values)\\n\")\n", - "score_code.write(\" items_to_translate = df_base[header] \\n\")\n", - "score_code.write(\" df_out.loc[:,'D_'+header]= pd.Series([transdic[item] for item in items_to_translate])\\n\")\n", - "score_code.write(\"# Incidence replacement for numeric columns\\n\")\n", - "score_code.write(\"for header in numeric_headers:\\n\")\n", - "score_code.write(\" mask = df_prep.loc[:,'var']==header\\n\")\n", - "score_code.write(\" bins = df_prep.loc[mask,'bin']\\n\")\n", - "score_code.write(\" incidences = df_prep.loc[mask,'incid']\\n\")\n", - "score_code.write(\" index_missing = bins.index[bins=='Missing']\\n\")\n", - "score_code.write(\" incidence_missing = incidences[index_missing]\\n\")\n", - "score_code.write(\" upper_values = pd.Series([])\\n\")\n", - "score_code.write(\" for i,binn in enumerate(bins.values):\\n\")\n", - "score_code.write(\" upper_value = binn.split(',')[-1]\\n\")\n", - "score_code.write(\" try:\\n\")\n", - "score_code.write(\" upper_value = re.findall('[0-9]+',upper_value)[0]\\n\")\n", - "score_code.write(\" except:\\n\")\n", - "score_code.write(\" upper_value = math.inf\\n\")\n", - "score_code.write(\" upper_values[i] = upper_value\\n\")\n", - "score_code.write(\" upper_values.index = bins.index\\n\")\n", - "score_code.write(\" upper_values.drop(index_missing, inplace=True)\\n\")\n", - "score_code.write(\" upper_values = upper_values.astype(float)\\n\")\n", - "score_code.write(\" upper_values.sort_values(inplace=True)\\n\")\n", - "score_code.write(\" upper_values_incidences = incidences[upper_values.index]\\n\")\n", - "score_code.write(\" upper_values.reset_index(drop=True, inplace=True)\\n\")\n", - "score_code.write(\" upper_values_incidences.reset_index(drop=True, inplace=True)\\n\")\n", - "#score_code.write(\" incidence_replaced_values = np.array([])\\n\")\n", - "#score_code.write(\" for original_value in df_base[header]:\\n\")\n", - "#score_code.write(\" lowest_membership = upper_values.index[original_value<=upper_values].min()\\n\")\n", - "#score_code.write(\" try:\\n\")\n", - "#score_code.write(\" incidence_to_attribute = upper_values_incidences[lowest_membership]\\n\")\n", - "#score_code.write(\" except:\\n\")\n", - "#score_code.write(\" if np.isnan(original_value):\\n\")\n", - "#score_code.write(\" incidence_to_attribute = incidence_missing\\n\")\n", - "#score_code.write(\" else:\\n\")\n", - "#score_code.write(\" incidence_to_attribute = np.nan\\n\")\n", - "#score_code.write(\" incidence_replaced_values = np.append(incidence_replaced_values,incidence_to_attribute)\\n\")\n", - "#score_code.write(\" df_out['D_'+header] = pd.Series(incidence_replaced_values)\\n\")\n", - "score_code.write(\" mask_npnan = df_base.loc[:,header].isnull()\\n\")\n", - "score_code.write(\" lowest_memberships = upper_values.searchsorted(df_base.loc[:,header],side='left')\\n\")\n", - "score_code.write(\" incidences_to_attribute = upper_values_incidences[lowest_memberships].reset_index(drop=True)\\n\")\n", - "score_code.write(\" incidences_to_attribute[mask_npnan] = incidence_missing\\n\")\n", - "score_code.write(\" df_out['D_'+header] = incidences_to_attribute\\n\")\n", - "score_code.write(\"\\n\") \n", - "\n", - " \n", - "score_code.write(\"### Scoring\\n\")\n", - "score_code.write(\"df_scores = pd.DataFrame([])\\n\")\n", - "score_code.write(\"df_scores['ID'] = df_out['ID']\\n\")\n", - "score_code.write(\"scores = []\\n\")\n", - "score_code.write(\"intercept=\"+str(df_modrules.coeff.values[0])+\"\\n\")\n", - "score_code.write(\"coefficients=np.array(\"+str([coeff for coeff in df_modrules.coeff][1:])+\")\\n\")\n", - "score_code.write(\"productsums = (df_out['D_'+pd.Series(predictors)]*coefficients).sum(axis=1)\\n\")\n", - "score_code.write(\"exponents = intercept + productsums\\n\")\n", - "score_code.write(\"scores = exponents.apply(func=lambda x:(math.exp(x)) / (1+math.exp(x)))\\n\")\n", - "score_code.write(\"df_scores['score']=scores\\n\")\n", - "score_code.write(\"\\n\")\n", - "\n", - "score_code.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### for Sas" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ok\n" - ] - } - ], - "source": [ - "print('ok')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/legacy_code/script_sprint2_.py b/legacy_code/script_sprint2_.py deleted file mode 100644 index dbf045d..0000000 --- a/legacy_code/script_sprint2_.py +++ /dev/null @@ -1,991 +0,0 @@ - -# coding: utf-8 - -# # Univariate analysis - -# ### General Imports - -# In[1]: - -import math -import csv -import warnings -import time -import os -import itertools -import scipy.integrate -import re - - -# In[2]: - -import numpy as np -import pandas as pd - - -# In[3]: - -from scipy import stats -from itertools import chain -from sklearn import metrics - - -# --- - -# ### Miscellaneous - -# In[4]: - -log = [] - - -# In[5]: - -# When code is in script, we define the path of the script's parent folder location as the 'root' directory -# From this 'root' we can travel to the relevant folders with minimal adjustment -try: - root = os.path.dirname(os.path.realpath(__file__)) - root = "/".join(root.split('\\')[:-1]) - log.append('Dynamic paths'+'\n') -except: - root = 'C:/wamp64/www/python_predictions_4/assets/scripts' - log.append('Static paths'+'\n') - - -# In[6]: - -# To allow pandas dataframes to display more columns -pd.set_option("display.max_columns",50) - - -# --- - -# ### Read data and organize - -# Basetable and its types - -# In[7]: - -# A Types csv file CAN be defined to be used to convert variables (of the basetable, see below) to the desired data types -# The Types csv files should include one column with variable names and one column with desired types (e.g. int,float,str,bool) -# If no Types csv file is provided no convertions will be forced. In that case 'Python' will guess the data type of each column -types_path = root+"/python/data_types.csv" -types_exist = True - -try: - df_types = pd.read_csv(types_path, header=None) - bool_mask = df_types[1]!='bool' - # Extract the functions based on the given type (e.g. 'str' -> str, 'int' -> int), for proper convertion - df_types.loc[bool_mask,1] = [getattr(__builtins__, type_str) for type_str in df_types.loc[bool_mask,1]] - # A type 'bool' is also attributed the function str, for convertion - df_types.loc[bool_mask==False,1] = getattr(__builtins__, 'str') - #types = df_types[bool_mask].set_index(0).T.to_dict('records') - types = df_types.set_index(0).T.to_dict('records') -except FileNotFoundError: - types = [dict()] - types_exist = False - - -# In[8]: - -# The basetable csv file should have column names as its first row -# The columns names should include 'TARGET', 'ID' -data_path = root+"/python/data.csv" - -df_in = pd.read_csv(data_path - ,header=0 - ,sep=None - ,engine='python' - ,converters=types[0]) - -# If no Types csv file was provided pd.read_csv guessed the types, we now output these types in a csv for re-use & later use -if types_exist == False: - filename = root+"/python/data_types.csv" - funtotype = lambda x:re.findall('[a-z]+',str(x))[0].replace('object','str') - with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =',') - write.writerows([column - ,funtotype(df_in[column].dtype)] for column in df_in.columns) - - -# In[9]: - -# Function to remove quotes from variable names and/or variable values -def strip_quot(x_in): - try: - x_out = x_in.strip().strip('"').strip("'") - except: - x_out=x_in - return x_out - -# Function to put 'id' and 'target' variable names in uppercase, all other variable names are put in lowercase -# This is coded as to visually differentiate predictors from other variables -# But another combination of upper/lower is possible as well, e.g. all variable names in uppercase -def lower_upper(x_in): - if ((x_in.lower() == 'id')|(x_in.lower() == 'target')): - x_out = x_in.upper() - else: - x_out = x_in.lower() - return x_out - -# Function to group variable names based on the data type of the variable -# Could as well use the types in Types.csv -def get_headers(dataframe,type): - return dataframe.select_dtypes(include=[type]).columns.values - - -# In[10]: - -# Clean up quotes from column names -df_in = df_in.rename(columns=strip_quot) - -# Perform uppercase/lowercase transformation to column names -df_in = df_in.rename(columns=lower_upper) - -# Clean up quotes from column values -df_in = df_in.applymap(strip_quot) - - -# In[11]: - -# Group variable (names) based on the respective data type of each variable -# With this information we know which variables are destined for equifrequency, regrouping or simply passing (see further) -other_headers = [n for n in ["TARGET","ID"]] -try: - bool_headers = [n for n in df_types.loc[bool_mask==False,0].values if n not in other_headers] -except: - bool_headers = [] -object_headers = [n for n in get_headers(df_in,'object') if n not in other_headers+bool_headers] -numeric_headers = [n for n in get_headers(df_in,'number') if n not in other_headers+bool_headers] - - -# Analysis settings - -# In[12]: - -# Import settings defined by the user -df_settings = pd.read_csv(root+'/python/analysis_settings.csv', sep=',', index_col=0, header=None).T - - -# --- - -# ### Partitioning -# Shuffle and sort on TARGET -df_in = df_in.iloc[np.random.permutation(len(df_in))].sort_values(by='TARGET', ascending=False).reset_index(drop=True) - -# Create dic of partitioning settings -partition_dic = {'train':df_settings.loc[:,'partitioning_train'] - ,'selection':df_settings.loc[:,'partitioning_selec'] - ,'validation':df_settings.loc[:,'partitioning_valid'] - } - -# Create a partition variable -partition = [] -sorted_target=df_in.TARGET #Just the target since it is allready sorted (see above) -for target in [sorted_target.iloc[0],sorted_target.iloc[-1]]: - target_length = (sorted_target==target).sum() - for part in partition_dic: - partition.extend( [part]*math.ceil(target_length*partition_dic[part][1]/100) ) - -# Attach partition variable to dataframe -df_in["PARTITION"] = partition[:len(df_in)] - -# Sampling based on analysis settings (if both sampling_settings are set to 100, all data is used) -sampling_settings = {1:df_settings.sampling_1, 0:df_settings.sampling_0} -drop_index = [] -for sample in sampling_settings: - if sampling_settings[sample].values<100: - sample_length = int(round((df_in.TARGET==sample).sum() * sampling_settings[sample]/100)) - for part in partition_dic: - part_length = int(round(sample_length * partition_dic[part] / 100)) - drop_index_part = df_in[(df_in.TARGET==sample) & (df_in.PARTITION==part)].index[part_length:] - drop_index.extend(drop_index_part) - #drop_index = df_in[df_in.TARGET==sample].index[sample_length:] -df_in.drop(drop_index,inplace=True) -df_in.reset_index(drop=True, inplace=True) - - -# --- - -# ### Output Container - -# In[15]: - -# Create output dataframe which will contain transformed variables -df_out = df_in.loc[:,["ID","TARGET","PARTITION"]].copy() - - -# --- - -# ### Preprocessing of continuous variables - -# Discretization function for Continuous variables - -# In[16]: - -### This function is a reworked version of pd.qcut to satisfy our particular needs -### Takes for var a continuous pd.Series as input and returns a pd.Series with bin-labels (e.g. [4,6[ ) -### Train takes a series/list of booleans (note: we define bins based on the training set) -### Autobins reduces the number of bins (starting from nbins) as a function of the number of missings -### Nbins is the wished number of bins -### Precision=0 results in integer bin-labels if possible -### twobins=True forces the function to output at least two bins -### catchLarge tests if some groups (or missing group) are very large, and if so catches and outputs two groups -#### note: catchLarge makes twobins irrelevant - -def eqfreq(var, train, autobins=True, nbins=10, precision=0, twobins=True, catchLarge=True): - - - # Test for large groups and if one exists pass them with two bins: Large_group,Other - if catchLarge: - catchPercentage=1-(1/nbins) - groupCount = var[train].groupby(by=var[train]).count() - maxGroupPerc = groupCount.max()/len(var[train]) - missingPerc = sum(var[train].isnull())/len(var[train]) - if maxGroupPerc>=catchPercentage: - largeGroup = groupCount.sort_values(ascending=False).index[0] - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned!=largeGroup]='Other' - cutpoints=None - info = (var.name+": One large group, outputting 2 groups") - return x_binned, cutpoints, info - elif missingPerc>=catchPercentage: - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned.isnull()]='Missing' - x_binned[x_binned!='Missing']='Other' - cutpoints=None - info = (var.name+": One large missing group, outputting 2 groups") - return x_binned, cutpoints, info - # Adapt number of bins as a function of number of missings - if autobins: - length = len(var[train]) - missing_total = var[train].isnull().sum() - missing_perten = missing_total/length*10 - nbins = max(round(10-missing_perten)*nbins/10 ,1) - # Store the name and index of the variable - name = var.name - series_index = var.index - # Transform var and train to a np.array and list respectively, which is needed for some particular function&methods - x = np.asarray(var) - train = list(train) - # First step in finding the bins is determining what the quantiles are (named as cutpoints) - # If the quantile lies between 2 points we use lin interpolation to determine it - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1),interpolation = 'linear') - # If the variable results only in 2 unique quantiles (due to skewness) increase number of quantiles until more than 2 bins can be formed - if twobins: - extrasteps = 1 - # Include a max. extrasteps to avoid infinite loop - while (len(cutpoints.unique())<=2) & (extrasteps<20): - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1+extrasteps),interpolation = 'linear') - extrasteps+=1 - # We store which rows of the variable x lies under/above the lowest/highest cutpoint - # Without np.errstate(): xcutpoints.max() can give if x contains nan values (missings) - # However the function will result in False in both >&< cases, which is a correct result, so the warning can be ignored - with np.errstate(invalid='ignore'): - under_lowestbin = x < cutpoints.min() - above_highestbin= x > cutpoints.max() - - - def _binnedx_from_cutpoints(x, cutpoints, precision, under_lowestbin, above_highestbin): - ### Attributes the correct bin ........................ - ### Function that, based on the cutpoints, seeks the lowest precision necessary to have meaningful bins - ### e.g. (5.5,5.5] ==> (5.51,5.54] - ### Attributes those bins to each value of x, to achieve a binned version of x - - # Store unique cutpoints (e.g. from 1,3,3,5 to 1,3,5) to avoid inconsistensies when bin-label making - # Indeed, bins [...,1], (1,3], (3,3], (3,5], (5,...] do not make much sense - # While, bins [...,1], (1,3], (3,5], (5,...] do make sense - unique_cutpoints = cutpoints.unique() - # If there are only 2 unique cutpoints (and thus only one bin will be returned), - # keep original values and code missings as 'Missing' - if len(unique_cutpoints) <= 2: - cutpoints = None - x_binned = pd.Series(x) - x_binned[x_binned.isnull()] = 'Missing' - info = (var.name+": Only one resulting bin, keeping original values instead") - return x_binned, cutpoints, info - # Store info on whether or not the number of resulting bins equals the desired number of bins - elif len(unique_cutpoints) < len(cutpoints): - info = (var.name+": Resulting # bins < whished # bins") - else: - info = (var.name+": Resulting # bins as desired") - # Finally, recode the cutpoints (which can have doubles) as the unique cutpoints - cutpoints = unique_cutpoints - - # Store missing values in the variable as a mask, and create a flag to test if there are any missing in the variable - na_mask = np.isnan(x) - has_nas = na_mask.any() - # Attribute to every x-value the index of the cutpoint (from the sorted cutpoint list) which is equal or higher than - # the x-value, effectively encompasing that x-value. - # e.g. for x=6 and for sorted_cutpoint_list=[0,3,5,8,...] the resulting_index=3 - ids = cutpoints.searchsorted(x, side='left') - # x-values equal to the lowest cutpoint will recieve a ids value of 0 - # but our code to attribute bins to x-values based on ids (see end of this subfunction) requires a min. value of 1 - ids[x == cutpoints[0]] = 1 - # Idem as previous: x-values below the lowest cutpoint should recieve a min. value of 1 - if under_lowestbin.any(): - ids[under_lowestbin] = 1 - # Similar as previous: x-values above the highest cutpoint should recieve the max. allowed ids - if above_highestbin.any(): - max_ids_allowed = ids[(above_highestbin == False) & (na_mask==False)].max() - ids[above_highestbin] = max_ids_allowed - # Maximal ids can now be defined if we neglect ids of missing values - max_ids = ids[na_mask==False].max() - - # Based on the cutpoints create bin-labels - # Iteratively go through each precision (= number of decimals) until meaningful bins are formed - # If theoretical bin is ]5.51689,5.83654] we will prefer ]5.5,5.8] as output bin - increases = 0 - original_precision = precision - while True: - try: - bins = _format_bins(cutpoints, precision) - except ValueError: - increases += 1 - precision += 1 - #if increases >= 5: - #warnings.warn("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - #print("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - else: - break - - # Make array of bins to allow vector-like attribution - bins = np.asarray(bins, dtype=object) - # If x has nas: for each na-value, set the ids-value to max_ids+1 - # this will allow na-values to be attributed the highest bin which we define right below - if has_nas: - np.putmask(ids, na_mask, max_ids+1) - # The highest bin is defined as 'Missing' - bins = np.append(bins,'Missing') - # ids-1 is used as index in the bin-labels list to attribute a bin-label to each x. Example: - # x=6 sorted_cutpoint_list=[0,3,5,8,...] ids=3 levels=[[0,3],(3,5],(5,8],...] - # The correct bin level for x is (5,8] which has index 2 which is equal to the ids-1 - x_binned = bins[ids-1] - return x_binned, cutpoints, info - - - def _format_bins(cutpoints, prec): - # Based on the quantile list create bins. Raise error if values are similar within one bin. - # On error _binnedx_from_cutpoints will increase precision - - fmt = lambda v: _format_label(v, precision=prec) - bins = [] - for a, b in zip(cutpoints, cutpoints[1:]): - fa, fb = fmt(a), fmt(b) - - if a != b and fa == fb: - raise ValueError('precision too low') - - formatted = '(%s, %s]' % (fa, fb) - bins.append(formatted) - - bins[0] = '[...,' + bins[0].split(",")[-1] - bins[-1] = bins[-1].split(",")[0] + ',...]' - return bins - - - def _format_label(x, precision): - # For a specific precision, returns the value formatted with the appropriate amount of numbers after comma and correct brackets - - if isinstance(x,float): - frac, whole = np.modf(x) - sgn = '-' if x < 0 else '' - whole = abs(whole) - if frac != 0.0: - val = '{0:.{1}f}'.format(frac, precision) - val = _trim_zeros(val) - if '.' in val: - return sgn + '.'.join(('%d' % whole, val.split('.')[1])) - else: - if '0' in val: - return sgn + '%0.f' % whole - else: - return sgn + '%0.f' % (whole+1) - else: - return sgn + '%0.f' % whole - else: - return str(x) - - - def _trim_zeros(x): - # Removes unnecessary zeros and commas - while len(x) > 1 and x[-1] == '0': - x = x[:-1] - if len(x) > 1 and x[-1] == '.': - x = x[:-1] - return x - - - x_binned, cutpoints, info = _binnedx_from_cutpoints(x, cutpoints, precision=precision, under_lowestbin=under_lowestbin, above_highestbin=above_highestbin) - x_binned = pd.Series(x_binned, index=series_index, name="B_"+name) - return x_binned, cutpoints, info - - -# # WIP - -# for n in numeric_headers: -# result = eqfreq(var=df_in[n] -# ,train=df_in["PARTITION"]=="train" -# ,autobins=True -# ,nbins=int(df_settings.discretization_nbins) -# ,precision=0 -# ,twobins=True -# ,catchLarge=True) -# print(n) -# print(result[0].unique()) -# print('\n') - -# # /WIP - -# Apply function to continuous variables - -# In[17]: - -tic = time.time() -# We loop only through the numeric variables -for n in numeric_headers: - # Perform the equifrequency function - result = eqfreq(var=df_in[n] - ,train=df_in["PARTITION"]=="train" - ,autobins=True - ,nbins=int(df_settings.discretization_nbins) - ,precision=0 - ,twobins=True - ,catchLarge=False) # TRUE OPTION STILL PRODUCES ERROR IN SORTNUMERIC function AND SCORING procedure !!!!!!!!! - df_out = pd.concat([df_out,result[0]], axis=1) - log.append(result[2]) -toc = time.time() -log.append("Discretisation: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Preprocessing of categorical variables - -# Function for labeling missing/empty values - -# In[18]: - -# Check which values of a var are empty strings or null values -def maskmissing(var): - # Check if values are null - crit1 = var.isnull() - # Check if values are empty strings - modvar = pd.Series([str(value).strip() for value in var]) - crit2 = modvar==pd.Series(['']*len(var)) - #crit2 = var==pd.Series(['']*len(var)) - #crit3 = var==pd.Series([' ']*len(var)) - return crit1 | crit2 - - -# Regrouping Function for nominal/ordinal variables - -# In[19]: - -# Regrouping function for categorical variables -# Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group -# The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or -# 'remaining groups average incidence' (if dummy=False) -# Groups with a pvalue above the threshold are relabled to a single group - -def regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): - - # Define the chi² test condition - # Groups that do not meet the condition are not analyzed and will be unconditionally relabled - def _chi2cond_(var=var,target=target,train=train): - varcounts = var[train].groupby(by=var).count() - train_inc = target[train].sum()/len(target[train]) - factor = max(train_inc, 1-train_inc) - analyze_mask = (varcounts*factor)>5 - analyze_groups = analyze_mask.index[analyze_mask].values - return analyze_groups - - # Compute overal incidence mean - incidence_mean = target[train].mean() - # Create container of which groups will be kept, compared to the groups which will be relabled - keepgroups = [] - # Cycle and test each group that meets the chi² condition - for group in _chi2cond_(): - # Container for target 0/1 observations of the group under scrutiny - obs_group = [] - # Counts of the target 0/1 occurences for the group under scrutiny - obs_group.append(((target[train]==0)&(var[train]==group)).sum()) - obs_group.append(((target[train]==1)&(var[train]==group)).sum()) - obs_group = np.array(obs_group) - # Container for target 0/1 observations of the remaining groups together - obs_other = [] - # Counts of the target 0/1 occurences for the remaining groups together - obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) - obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) - obs_other = np.array(obs_other) - # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence - # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups - if dummy: - obs_other_size = obs_other.sum() - obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) - obs_other[1]=( incidence_mean)*obs_other_size - obs = np.array([obs_group,obs_other]) - # Place at least 1 observation to avoid error in chi2 test - obs[obs==0] = 1 - # Perform chi² test - pval = stats.chi2_contingency(obs, correction=False)[1] - # If pval outperforms threshold, append the group in the keepgroups list - if pval<=pval_thresh: - keepgroups.append(group) - #elif group==keep: - # keepgroups.append(group) - # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list - if keep not in keepgroups: - keepgroups.append(keep) - # Makes a list of all groups not in the keepgroups list - regroup_mask = [val not in keepgroups for val in var.values] - var_regroup = var.copy() - # Rename those groups - var_regroup[regroup_mask] = rename - var_regroup.name = "B_"+var.name - info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) - return var_regroup, info - - -# Apply function to nominal/ordinal variables - -# In[20]: - -tic = time.time() -# We loop only through the categorical variables -for h in object_headers: - # We label missing and empty values for categorical variables as 'Missing' - # Note the interaction with the 'keep' parameter of the regroup function. - mask = maskmissing(df_in[h]) - df_in.loc[mask,h]='Missing' - # Perform regrouping function - result = regroup(var=df_in[h] - ,target=df_in.loc[:,'TARGET'] - ,train=df_in.PARTITION=='train' - ,pval_thresh=float(df_settings.regrouping_signif) - ,dummy=True - ,keep='Missing' - ,rename='Non-significants') - df_out = pd.concat([df_out,result[0]],axis=1) - log.append(result[1]) -toc = time.time() -log.append("Regrouping: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Preprocessing of boolean variables - -# Defining Function to pass variables as is - -# In[21]: - -# We could just rename them or put them with the regoup function, but for now let's keep consistent with the other functions -def passvar(var): - var_pass = var.copy() - var_pass.name = "B_"+var.name - info = ("Passing "+var.name) - return var_pass, info - - -# Executing function - -# In[22]: - -tic = time.time() -# We loop only through the boolean variables -for b in bool_headers: - # We label missing and empty values for boolean variables as 'Missing' - mask = maskmissing(df_in[b]) - df_in.loc[mask,b]='Missing' - # Perform the passvar function - result = passvar(var=df_in[b]) - df_out = pd.concat([df_out,result[0]],axis=1) - log.append(result[1]) -toc = time.time() -log.append("Passing: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Incidence Replacement - -# Function for incidence replacement - -# In[23]: - -def increp(b_var, target, train): - #get variable name - name = b_var.name - #get overall incidence - incidence_mean = target[train].mean() - #get incidence per group - incidences = target[train].groupby(b_var).mean() - #construct dataframe with incidences - idf = pd.DataFrame(incidences).reset_index() - #get values that are in the data but not in the labels - bin_labels = incidences.index - newgroups = list(set(b_var.unique()) ^ set(bin_labels)) - #if newgroups, add mean incidence to incidence dataframe for each new group - if len(newgroups)>0: - #make dataframe: - ngdf = pd.DataFrame(newgroups) - ngdf.columns = [name] - ngdf["TARGET"] = incidence_mean - #dataframe with incidences: - idf = idf.append(ngdf) - #dataframe with the variable - vdf = pd.DataFrame(b_var) - #discretized variable by merge - d_var = pd.merge(vdf,idf,how='left',on=name)["TARGET"] - return pd.Series(d_var, name="D_"+name[2:]) - - -# Apply function for incidence replacement - -# In[24]: - -# We define the columns destined for incidence replacement -headers_for_incidrep = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]=="B_"))] - - -# In[25]: - -tic = time.time() -# We loop only through the columns destined for incidence replacement -for n in headers_for_incidrep: - # Perform increp function - result = increp(b_var=df_out[n] - ,target=df_out.TARGET - ,train=df_out.PARTITION=="train") - df_out = pd.concat([df_out,result], axis=1) - log.append(n+ " processed") -toc = time.time() -log.append("Incidence replacement: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Calculate AUCS - -# Function for auc calculation - -# In[26]: - -def getauc(var, target, partition): - - y = np.array(target[partition]) - pred = np.array(var[partition]) - pred = pred.astype(np.float64) - fpr, tpr, thresholds = metrics.roc_curve(y,pred, pos_label=1) - - return metrics.auc(fpr, tpr) - - -# Applying function for auc calculation - -# In[27]: - -# We define the columns for which an AUC score should be computed -headers_for_auc = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]=="D_"))] - - -# In[28]: - -auc_list_all = [] -parts = ["train","selection"] -tic = time.time() -# We loop only through those columns for which an AUC score should be computed -for header in headers_for_auc: - auc_list_var = [header[2:]] - # We loop through the two sets ('train' and 'selection') for which an AUC score is needed - for part in parts: - # Perform getauc function - auc_value = getauc(var=df_out[header] - ,target=df_out.TARGET - ,partition=df_out.PARTITION==part) - auc_list_var.append(auc_value.round(2)) #We round auc values to 2 decimals - auc_list_all.append(auc_list_var) - log.append(header + " processed") -# We create a supplementary dataframe destined for Cobra input -df_auc = pd.DataFrame(auc_list_all,columns=['variable','AUC train','AUC test']) -toc = time.time() -log.append("Auc: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Preselection - -# In[29]: - -tic = time.time() -# We identify those variables for which the AUC score is above the user-defined threshold -auc_thresh = df_auc.loc[:,'AUC test'] > float(df_settings.preselection_auc) -# We identify those variables for which the AUC score difference between 'train' and 'selection' is within the user-defined ratio -auc_overtrain = (df_auc.loc[:,'AUC train']*100 - df_auc.loc[:,'AUC test']*100) < float(df_settings.preselection_overtrain) -# Only those variables passing the 2 criteria above are preselected -preselect = auc_thresh & auc_overtrain - - -# In[30]: - -# We create a supplementary dataframe destined for Cobra input -df_variable_selections = pd.DataFrame({'variable':df_auc.variable - ,'preselect':preselect.astype(int) - ,'Default':np.zeros(len(preselect)).astype(int) - ,'Alternative 1':np.zeros(len(preselect)).astype(int) - ,'Alternative 2':np.zeros(len(preselect)).astype(int) - ,'Alternative 3':np.zeros(len(preselect)).astype(int) - ,'Alternative 4':np.zeros(len(preselect)).astype(int) - ,'Alternative 5':np.zeros(len(preselect)).astype(int)} - ,columns=['variable' - ,'preselect' - ,'Default' - ,'Alternative 1' - ,'Alternative 2' - ,'Alternative 3' - ,'Alternative 4' - ,'Alternative 5']) - - -# In[31]: - -for i,var in enumerate(df_variable_selections.variable): - log.append(var+" "+np.array(['passed','filtered'])[df_variable_selections.preselect][i]) -toc = time.time() -log.append("Preselection: "+str(toc-tic)+" sec"+"\n") - - -# --- - -# ### Calculate Correlations - -# In[32]: - -# We define the columns for which a correlation score should be computed -headers_for_corr = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]=="D_"))] - - -# In[33]: - -train = df_out.PARTITION=="train" -tic = time.time() -dataforcorr = np.transpose(np.matrix(df_out.loc[train,headers_for_corr],dtype=float)) -with np.errstate(invalid='ignore', divide='ignore'): - mat_corr = np.corrcoef(dataforcorr) -toc = time.time() -log.append("Correlations: "+str(toc-tic)+" sec"+"\n") - - -# In[34]: - -df_corr = pd.DataFrame(mat_corr) -df_corr.columns = headers_for_corr -df_corr.index = headers_for_corr -df_corr.fillna(0, inplace=True) - -# --- - -# ### Export files - -# Table of all Auc values - -# In[35]: - -auc_path = root+'/data/univariate/aucs.csv' -df_auc = df_auc.sort_values(by=['AUC test','AUC train'], ascending=False).reset_index(drop=True) -df_auc.to_csv(path_or_buf=auc_path - ,sep=';' - ,index=False - ,encoding='utf-8' - ,line_terminator='\n') - - -# Tables of Incidences & Correlations per variable - -# In[36]: - -# Function for sorting cont.variables, whether or not they have undergone discritization -def sortnumeric(dataframe): - - lowestnumber = 0 - # If the variable was discretisized - if '[...' in [str(l)[:4] for l in dataframe.group.values]: - unsorted_labels = dataframe.group.values - label_items=[] - for label in unsorted_labels: - # For each bin label, retain the first value - label_items.append(label.split(",")[0].strip("[").strip("(")) - label_items=np.asarray(label_items) - # Special cases that are not numeric are given numbers - lowestnumber = label_items[(label_items!="...")&(label_items!="Missing")].astype('float64').min() - label_items[label_items=='...']= lowestnumber-1 - label_items[label_items=='Missing']= lowestnumber-2 - # argsort based on the numbers - rank = label_items.astype('float64').argsort() - return rank - - # If the variable wasn't discretisized, simply argsort on the numbers - else: - label_items = dataframe.group.values - if len(label_items)>1: - lowestnumber = label_items[label_items.astype('O')!="Missing"].astype('float64').min() - label_items[label_items.astype('O')=='Missing']= lowestnumber-2 - rank = label_items.astype('float64').argsort() - return rank - - -# In[37]: - -# Function for sorting cont.variables, whether or not they have undergone discritization -def sortnumeric_old(dataframe): - - # If the variable was discretisized - if dataframe.group.dtype=='object': #or# if np.array([str(unsorted_labels[i])[0] in ["[","(","M"] for i in range(0,len(unsorted_labels))]).all(): - unsorted_labels = dataframe.group.values - label_items=[] - for label in unsorted_labels: - # For each bin label, retain the first value - label_items.append(label.split(",")[0].strip("[").strip("(")) - label_items=np.asarray(label_items) - # Special cases that are not numeric are given numbers - lowestnumber = label_items[(label_items!="...")&(label_items!="Missing")].astype('float64').min() - label_items[label_items=='...']= lowestnumber-1 - label_items[label_items=='Missing']= lowestnumber-2 - # argsort based on the numbers - rank = label_items.astype('float64').argsort() - return rank - - # If the variable wasn't discretisized, simply argsort on the numbers - else: - rank = dataframe.group.values.argsort() - return rank - - -# In[38]: - -# Function for sorting cat. variables -def sortobject(dataframe): - # Sort dataframe on increasing incidence values - unsorted_incidences = dataframe.incidence.values - rank = unsorted_incidences.argsort() - return rank - - -# In[39]: - -n_decimals = 2 -average = round(df_out.TARGET[df_out.PARTITION=="train"].mean(),n_decimals) - -headers_to_output = list(df_auc['variable']) -for i,varname in enumerate(headers_to_output): - b_varname = 'B_'+varname - d_varname ='D_'+varname - #INCIDENCE CSV's - incidence_path = root+"/data/univariate/incidence_"+str(varname)+".csv" - groups_and_incidences = df_out.TARGET[df_out.PARTITION=='train'].groupby(df_out[b_varname]).mean() - n_groups= len(groups_and_incidences) - group = groups_and_incidences.index - incidence = groups_and_incidences.values.round(n_decimals) - size = df_out.TARGET[df_out.PARTITION=='train'].groupby(df_out[b_varname]).size().astype(float).values - df_incidence = pd.DataFrame( {'group':group - ,'incidence':incidence - ,'size':size - ,'average':average} - ,columns=['group','incidence','size','average']) - if varname in numeric_headers: - df_incidence = df_incidence.iloc[sortnumeric(df_incidence),:] - elif varname in object_headers: - df_incidence = df_incidence.iloc[sortobject(df_incidence),:] - else: - a=1 - #df_incidence = df_incidence.iloc[sortother(df_incidence),:] - df_incidence.to_csv(path_or_buf=incidence_path - ,sep=';' - ,index=False - ,encoding='utf-8' - ,line_terminator='\n') #quoting=csv.QUOTE_NONNUMERIC - - #CORRELATION CSV's - correlation_path = root+"/data/univariate/correlations_"+str(varname)+".csv" - Variable = [v.strip("D_") for v in df_corr[d_varname].index] - Correlation = abs(df_corr[d_varname].values).round(n_decimals) - Sign = np.array(["+","-"])[(df_corr[d_varname].values<0).astype(int)] - AUC = np.array([df_auc.loc[df_auc['variable']== v,'AUC test'].values[0] for v in Variable]).round(n_decimals) - df_correlation = pd.DataFrame({"Variable":Variable - ,"Correlation":Correlation - ,"Sign":Sign - ,"AUC": AUC} - ,columns=["Variable","Correlation","Sign","AUC"]) - df_correlation.sort_values(by='Correlation', ascending=False, inplace=True) - df_correlation = df_correlation.loc[df_correlation.Variable!=varname,:] - df_correlation.to_csv(path_or_buf=correlation_path - ,sep=';' - ,index=False - ,encoding='utf-8' - ,line_terminator='\n') # quoting=csv.QUOTE_NONNUMERIC - - -# Variable Preselections - -# In[ ]: - -selections_path = root+'/data/univariate/variable_selections.csv' -df_variable_selections.to_csv(path_or_buf=selections_path - ,sep=';' - ,index=False - ,encoding='utf-8' - ,line_terminator='\n') - - -# Result dataframe for Modeling input - -# In[ ]: - -out_path = root+"/data/univariate/df_univariate.csv" -df_out.to_csv(path_or_buf=out_path, sep=';', index=False, encoding='utf-8', line_terminator='\n', quoting=csv.QUOTE_NONNUMERIC) - - -# Modeltab reset - -# In[ ]: - -# Generate modeltab info -filename = root+"/data/univariate/modeltab_info.csv" -with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =';') - write.writerow(["key","value"]) - write.writerow(["run","Default"]) - write.writerow(["new","Alternative 1"]) - write.writerow(["new_template","Default"]) - write.writerow(["champ","Default"]) - write.writerow(["score","Default"]) - - -# Log messages - -# In[ ]: - -log.append("-- Univariate analysis completed --"+"\n") - - -# In[ ]: - -log_file = open(root+'/python/univariate.log','w') -log_file.write('\n'.join(log)) -log_file.close() - - -# --- - -# ### Stop script - -# In[ ]: - -print("ok") - - -# --- diff --git a/legacy_code/script_sprint3.py b/legacy_code/script_sprint3.py deleted file mode 100644 index 256e96b..0000000 --- a/legacy_code/script_sprint3.py +++ /dev/null @@ -1,662 +0,0 @@ - -# coding: utf-8 - -# # Modeling - -# ### General Imports - -# In[1]: - -import time -import math -import random -import csv -import os - - -# In[2]: - -import pandas as pd -import numpy as np -import statsmodels.api as sm -import matplotlib.pyplot as plt - - -# In[3]: - -from scipy import stats -from sklearn import metrics -from sklearn.linear_model import LogisticRegression -from itertools import chain - - -# --- - -# ### Miscellaneous - -# In[4]: - -log = [] - - -# In[5]: - -# When code is in script, we define the path of the script's parent folder location as the root directory -# From this root we can travel to the relevant folders with minimal adjustment -try: - root = os.path.dirname(os.path.realpath(__file__)) - root = "/".join(root.split('\\')[:-1]) - log.append('Dynamic paths'+'\n') -except: - root = 'C:/wamp64/www/python_predictions_4/assets/scripts' - log.append('Static paths'+'\n') - - -# In[6]: - -# To allow pandas dataframes to display more columns -pd.set_option("display.max_columns",50) - - -# --- - -# ### Read data and organize - -# Read-in univariate output with asssumed ID, TARGET, PARTITION and D_VARS - -# In[7]: - -df_univariate_path = root+"/data/univariate/df_univariate.csv" -df_in = pd.read_csv(df_univariate_path, sep=";") - - -# Reference X and Y for each partition individually - -# In[8]: - -dvars = [n for n in df_in.columns if n[:2] == 'D_'] - - -# In[9]: - -mask_train = df_in.PARTITION=="train" -mask_selection = df_in.PARTITION=="selection" -mask_validation = df_in.PARTITION=="validation" - - -# In[10]: - -y_train = df_in.loc[mask_train,'TARGET'] -y_selection = df_in.loc[mask_selection,'TARGET'] -y_validation = df_in.loc[mask_validation,'TARGET'] - - -# In[11]: - -x_train = df_in.loc[mask_train,dvars] -x_selection = df_in.loc[mask_selection,dvars] -x_validation = df_in.loc[mask_validation,dvars] - - -# Analysis settings - -# In[12]: - -df_settings = pd.read_csv(root+'/python/analysis_settings.csv', sep=',', index_col=0, header=None).T - - -# Modeltab info - -# In[13]: - -df_modeltab = pd.read_csv(root+'/data/univariate/modeltab_info.csv',sep=';', index_col=0, header=None).T -modelrun = df_modeltab.run[1] - - -# Variable selections - -# In[14]: - -df_selections = pd.read_csv(root+'/data/univariate/variable_selections.csv',sep=';') - - -# --- - -# ### Model making and recording - -# Define functions - -# In[15]: - -# Function to make logistic model on a predefined set of predictors + compute train AUC of resulting model -def processSubset(predictors_subset): - from sklearn.linear_model import LogisticRegression - from sklearn import metrics - # Fit model on predictors_subset and retrieve performance metric - model = LogisticRegression(fit_intercept=True, C=1e9, solver = 'liblinear') - modelfit = model.fit(y=y_train, X=x_train[predictors_subset]) - # Position of the TARGET==1 class - pos = [i for i,h in enumerate(modelfit.classes_) if h==1] - # Prediction probabilities for the TARGET==1 - y_pred = modelfit.predict_proba(x_train[predictors_subset])[:,pos] - auc = metrics.roc_auc_score(y_true=y_train, y_score=y_pred) - return {"modelfit":modelfit,"auc":auc,"predictor_names":predictors_subset,"predictor_lastadd":predictors_subset[-1]} - - -# In[16]: - -# Function for computing AUC of all sets (train, selection & validation) -def getAuc(df_without_auc): - import pandas as pd - from sklearn import metrics - df_with_auc = df_without_auc[:] - for x,y,part in [(x_train,y_train,'train'), - (x_selection,y_selection,'selection'), - (x_validation,y_validation,'validation')]: - pos = [i for i,h in enumerate(df_without_auc.modelfit.classes_) if h==1] - y_pred = df_without_auc.modelfit.predict_proba(x[df_without_auc['predictor_names']])[:,pos] - df_with_auc["auc_"+part] = metrics.roc_auc_score(y_true=y, y_score=y_pred) - df_with_auc["pred_"+part] = y_pred - return(df_with_auc) - - -# In[17]: - -# Forward selection function that uses processSubset and getAuc -def forward(current_predictors, pool_predictors, positive_only=True): - import pandas as pd - import numpy as np - tic = time.time() - - #Pull out predictors we still need to process - remaining_predictors = [p for p in pool_predictors if p not in current_predictors] - # If there are no more predictors left to use, raise an error we can easily identify as normal - if len(remaining_predictors)==0: - raise ValueError("No more predictors left to use","NormalStop") - - #Create a model for each combination of: current predictor(s) + one of the remaining predictors - #Keep track of the submodels and their performance - #If error skip to next and do not include in comparison table - results = [] - errorcount = 0 - for p in remaining_predictors: - try: - results.append(processSubset(current_predictors+[p])) - except: - errorcount += 1 - models = pd.DataFrame(results) - - # If we require all coefficients to be positive... - if positive_only: - #Create a flag for each submodel to test if all coefficients are positive - all_positive = pd.Series(None, index=models.index) - for i in range(0,len(models)): - all_positive[i] = (models.modelfit[i].coef_ >= 0 ).all() - - # if no model exist with only positive coefficients raise error we can easily identify as normal - if (all_positive==0).all(): - raise ValueError("No models with only positive coefficients","NormalStop") - - #Choose model with best performance and only positive coefficients - best_model = models.loc[models[all_positive==1].auc.argmax()] - best_model = getAuc(best_model) - - # If we don't require all coefficients to be positive... - else: - #Choose model with best performance - best_model = models.loc[models.auc.argmax()] - best_model = getAuc(best_model) - - - tac = time.time() - info = ("Processed " - + str(models.shape[0]) - + " models on " - + str(len(current_predictors)+1) - + " predictors in " - + str(round(tac-tic,2)) - +" sec with " - + str(errorcount) - +" errors") - - return best_model, info - - -# Create recipient vars - -# In[18]: - -best_models = pd.DataFrame(columns=["modelfit", - "predictor_names", - "predictor_lastadd", - "auc_train", - "auc_selection", - "auc_validation", - "pred_train", - "pred_selection", - "pred_validation"]) -predictors = [] - - -# Define number of steps depending on settings and total number of predictors - -# In[19]: - -step_setting = int(df_settings.modeling_nsteps) -n_steps = min(step_setting,len(x_train.columns)) - - -# Define which variables to pass, force and filter - -# In[20]: - -mask_pass = (df_selections.preselect == 1) & (df_selections[modelrun]==0) -varname_list_pass = 'D_'+df_selections.loc[mask_pass,'variable'] -length_pass = len(varname_list_pass) - -mask_force = (df_selections.preselect == 1) & (df_selections[modelrun]==1) -varname_list_force = 'D_'+df_selections.loc[mask_force,'variable'] -length_force = len(varname_list_force) - - -# Execute forward modeling process - -# In[21]: - -tic = time.time() -use_predictors = varname_list_force #x_train.columns -for i in range(1,n_steps+1): - try: - # Use predictors to be forced first. Once through the list, append the remaining variables to be passed. - use_predictors = varname_list_force.append(varname_list_pass[[i>length_force]*length_pass]).reset_index(drop=True) - result = forward(current_predictors=predictors - ,pool_predictors= use_predictors - ,positive_only=True) - best_models.loc[i] = result[0] - predictors = best_models.loc[i].predictor_names - log.append(result[1]) - except Exception as e: - # Normal errors (i.e. no more predictors to be used / no models with only positive coefficients) - if e.args[-1]=='NormalStop': - log.append("Stopped modeling at "+str(i)+" predictors: "+ e.args[-2]) - # Other unknown errors - else: - log.append("Stopped modeling at "+str(i)+" predictors: unknown error") - break -toc = time.time() -log.append("Forward selection modeling: " + str(round((toc-tic)/60,0)) + " min"+"\n") - - -# --- - -# ### Optimal model criterion - -# Define functions - -# In[22]: - -def comparefit(p,g=2): - # We fit a second degree (g=2) polyline through our auccurve - # This serves as a starting base for finding our optimal stopping point - import numpy as np - import pandas as pd - z = np.polyfit(p.index, p, g) - f = np.poly1d(z) - y_new = f(p.index) - return pd.Series(y_new,index=p.index) - - -# In[23]: - -def slopepoint(p,p_fit,thresh_ratio=0.2): - # We take the polyline from comparefit and look for the point of which the slope lies just below some percentage of the max. slope - slopes = [p_fit[i+1]-p_fit[i] for i in range(1,len(p_fit))] - slopes = pd.Series(slopes, index=range(1,len(p_fit))) - thresh = slopes.max()*thresh_ratio - p_best_index = (slopes[slopes>thresh])[-1:].index - p_best = p.loc[p_best_index] - return p_best - - -# In[24]: - -def moveright(p,p_fit,p_best,n_steps=5,dampening=0.01): - # We look nsteps right on the polyline (starting from the slopepoint) and take the point with largest difference with real line - # We move to that point if that difference is larger than some multiplication of the difference at the slopepoint - # That multiplication gets larger as current the current difference gets smaller with a certain amount of dampening. - # The rationale behind this is as follows: - # if the current difference is already large than the larger difference will definitely be noteworthy - # if however the current difference is near zero than there needs to be much larger difference to be noteworthy - in_index = p_best.index.values[0] - lower = (in_index-1) - upper = (in_index+n_steps-1) - p_diff = p[lower:upper]-p_fit[lower:upper] - out_index = p_diff.argmax() - factor = 1/abs(p_diff[in_index]) - if (p_diff[out_index]>p_diff[in_index]+(abs(p_diff[in_index])*factor*dampening)): - p_best_new = pd.Series(p[out_index],index=[out_index]) - else: - p_best_new = p_best - return p_best_new - - -# In[25]: - -def moveleft(p,p_fit,p_best,rangeloss=0.1, diffshare=0.8): #diff_min=0.005): - # Starting from whatever point we end up with (either the slopepoint or a move to the right) - # We look left on the polyline and take the point for which the real line is largest (current point included) - # We move left if we stay within [a specific % loss of range] AND [a minimum % of current difference] - # i.e. we don't won't to go to low compared to the overall real line - # and we don't won't to move to a point that does not make a significant increase in AUC (i.e. difference between polyline and real line) - p_left = p[:p_best.index.values[0]] - p_best = p_left[p_left==p_left.max()] - p_diff = p-p_fit - p_range = p.max()-p.min() - s = p[(p >= p_best.values[0]-(rangeloss*p_range)) - & (p.index <= p_best.index.values[0]) - & (p_diff>=diffshare*p_diff[p_left.index[-1]]) - ] - p_best_new = s[s.index == s.index.values.min()] - return p_best_new - - -# Execute functions - -# In[26]: - -points = best_models.auc_selection -points_fit = comparefit(p=points, g=2) -points_slope = slopepoint(p=points, p_fit=points_fit, thresh_ratio=0.2) -points_right = moveright(p=points, p_fit=points_fit, p_best=points_slope, n_steps=5, dampening=0.01) -points_left = moveleft(p=points, p_fit=points_fit, p_best=points_right, rangeloss=0.1, diffshare=0.8) - -optimal_nvars = points_left.index.values[0] - - -# Inspect - -# %matplotlib inline -# from pylab import rcParams -# rcParams['figure.figsize'] = 10, 5 -# -# plt.plot( points.index , points , color="blue") -# plt.plot( points_fit.index , points_fit , color="red") -# plt.plot( points_slope.index, points_slope,'o', color="lightgreen", markersize=12) -# plt.plot( points_right.index, points_right,'o', color="black" , markersize=8) -# plt.plot( points_left.index , points_left ,'o', color="gold" , markersize=4) -# -# axes = plt.gca() -# axes.set_ylim([0.45,1]) -# plt.show() - -# --- - -# ### Cumulative gains/response - -# Define functions - -# In[28]: - -# Compute cumulative response/gains -def cumulatives(y,yhat,perc_as_int=False,dec=2): - nrows = len(y) - npositives = y.sum() - y_yhat = pd.DataFrame({"y":y, "yhat":yhat}).sort_values(by='yhat', ascending=False).reset_index(drop=True) - cresp = [] - cgains = [0] - for stop in (np.linspace(0.01,1,100)*nrows).astype(int): - cresp.append(round(y_yhat.loc[:stop,'y'].mean()*max(100*int(perc_as_int),1),dec)) - cgains.append(round(y_yhat.loc[:stop,'y'].sum()/npositives*max(100*int(perc_as_int),1),dec)) - return cresp,cgains - - -# Execute functions - -# In[29]: - -cresp_all = [None] -cgains_all = [None] -for i in range(1,len(best_models)+1): - out = cumulatives(y=y_selection - ,yhat=best_models.pred_selection[i][:,0] - ,perc_as_int=True - ,dec=2) - cresp_all.append(out[0]) - cgains_all.append(out[1]) - - -# Inspect - -# %matplotlib inline -# from pylab import rcParams -# rcParams['figure.figsize'] = 10, 5 -# -# cmap = plt.get_cmap('hot') -# colors = [cmap(i) for i in np.linspace(0, 1, n_steps)] -# for i in range(1,len(best_models)): -# plt.plot(range(1,101), cresp_all[i], color=colors[i-1]) -# plt.plot(range(1,101), cresp_all[-1], color="black") -# -# axes = plt.gca() -# axes.set_ylim([0,max(max(l) for l in np.array(cresp_all)[1:])]) -# plt.show() - -# %matplotlib inline -# from pylab import rcParams -# rcParams['figure.figsize'] = 10, 5 -# -# cmap = plt.get_cmap('hot') -# colors = [cmap(i) for i in np.linspace(0, 1, n_steps)] -# for i in range(1,len(best_models)): -# plt.plot(range(0,101), cgains_all[i], color=colors[i-1]) -# plt.plot(range(0,101), cgains_all[-1], color="black") -# -# axes = plt.gca() -# axes.set_ylim([0,max(max(l) for l in np.array(cgains_all)[1:])]) -# plt.show() - -# --- - -# ### Variable Importance - -# Define function - -# In[32]: - -# Compute variable importance based on correlation between predictor and prediction (on selection set) -def getImportance(model): - from scipy import stats - - predictors = [pred[2:] for pred in model.predictor_names] - pearcorr = [] - for predictor in predictors: - pearsonr = stats.pearsonr(x_selection.loc[:,'D_'+predictor].values, model.pred_selection[:,0]) - pearcorr.append(pearsonr[0].round(2)) - df_result = pd.DataFrame({'variable':predictors,'importance':pearcorr}, columns=['variable','importance']) - return df_result - - -# Execute function - -# In[33]: - -importance_all=[None] -for i in best_models.index: - importance_all.append(getImportance(best_models.loc[i,:])) - - -# Inspect - -# %matplotlib inline -# from pylab import rcParams -# rcParams['figure.figsize'] = 10, 5 -# -# #nvars = optimal_nvars -# nvars = len(best_models) -# -# fig, ax = plt.subplots() -# predictors = importance_all[nvars].variable -# y_pos = np.arange(len(predictors)) -# importance = importance_all[nvars].importance -# -# ax.barh(y_pos, importance, align='center', -# color='darkblue', ecolor='black') -# ax.set_yticks(y_pos) -# ax.set_yticklabels(predictors) -# ax.invert_yaxis() -# ax.set_xlabel('Importance') -# plt.show() - -# --- - -# ### Model Coefficients - -# In[35]: - -# Store all variable names + coefficients for every best model (with 1,2,3,... variables) -vars_out = [] -coef_out = [] -nmod_out = [] -for i in best_models.index: - modout = best_models.loc[i,:] - vars_out_st = ['Intercept']+[var[2:] for var in modout.predictor_names] - vars_out.append(vars_out_st) - coef_out_st = list(modout.modelfit.intercept_)+list(+ modout.modelfit.coef_[0]) - coef_out.append(coef_out_st) - nmod_out.append([i]*(i+1)) - -vars_out = list(chain.from_iterable(vars_out)) -coef_out = list(chain.from_iterable(coef_out)) -nmod_out = list(chain.from_iterable(nmod_out)) - - -# In[36]: - -df_coeff = pd.DataFrame({'nstep':nmod_out,'varname':vars_out,'coeff':coef_out}, columns=['nstep','varname','coeff']) - - -# --- - -# ### Export Files - -# In[37]: - -nmods = len(best_models) - - -# Auc curve - -# In[38]: - -filename = root+"/data/modeling/"+modelrun+"_auccurve.csv" -with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =';') - write.writerow(["optimal" ,optimal_nvars]) - write.writerow(["selected",optimal_nvars]) - write.writerow(["variable","train", "selection","validation"]) - write.writerows([best_models.predictor_lastadd[i][2:] - , best_models.auc_train[i].round(3) - , best_models.auc_selection[i].round(3) - , best_models.auc_validation[i].round(3) ] for i in range(1,nmods+1)) - - -# Cresp - -# In[39]: - -for v in range(1,nmods+1): - filename = root+"/data/modeling/"+modelrun+"_cresp_"+str(v)+".csv" - with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =';') - write.writerows([i+1, cresp_all[v][i]] for i in range(0,100)) - - -# Cgains - -# In[40]: - -for v in range(1,nmods+1): - filename = root+"/data/modeling/"+modelrun+"_cgains_"+str(v)+".csv" - with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =';') - write.writerows([i, cgains_all[v][i]] for i in range(0,101)) - - -# Variable importance - -# In[41]: - -for v in range(1,nmods+1): - filename = root+"/data/modeling/"+modelrun+"_importance_"+str(v)+".csv" - with open(filename, 'w') as csvfile: - write=csv.writer(csvfile, delimiter =';') - write.writerow(['variable','importance']) - write.writerows([importance_all[v].iloc[i,0],importance_all[v].iloc[i,1]] for i in range(v)) - - -# Model coefficients - -# In[42]: - -out_path = root+"/data/modeling/"+modelrun+"_modelcoeff.csv" -df_coeff.to_csv(path_or_buf=out_path, sep=';', index=False, encoding='utf-8', line_terminator='\n', quoting=csv.QUOTE_NONNUMERIC) - - -# Log messages - -# In[43]: - -log.append("-- Modeling phase completed --"+"\n") - - -# In[44]: - -log_file = open(root+"/python/"+modelrun+"_modeling.log",'w') -log_file.write('\n'.join(log)) -log_file.close() - - -# --- - -# ### Stop script - -# In[45]: - -print("ok") - - -# --- - -# # WIP - -# Scoring all rows - -# # Scoring of all rows -# import re -# tic = time.time() -# df_score = pd.DataFrame([]) -# df_score['ID'] = df_in['ID'] -# scores = [] -# for i in range(len(df_in)): -# ### METHOD 1: using function -# score = [optifit.predict_proba(df_in[optivars])[i,:][-1]] -# ### METHOD 2: with coefficients (same method as in scoring) -# #exponent = optiint + ((df_in[optivars].iloc[i,:])*(opticoef[0])).sum() -# #score = [(math.exp(exponent)) / (1+math.exp(exponent))] -# -# scores.extend(score) -# try: -# zeros = re.findall('[0]+$',str(i)) -# if len(zeros[0])>=3: -# print(i) -# except: -# a=1 -# df_score['score']=pd.Series(scores) -# tac = time.time() -# print((tac-tic)/60) -# -# -# df_in.to_csv('df_mod.csv', sep=';', index=False, encoding='utf-8', line_terminator='\n') -# df_score.to_csv(path_or_buf='scores_modeling.csv', sep=';', index=False, encoding='utf-8', line_terminator='\n') - -# # /WIP diff --git a/legacy_code/script_sprint4_1.py b/legacy_code/script_sprint4_1.py deleted file mode 100644 index af1fce9..0000000 --- a/legacy_code/script_sprint4_1.py +++ /dev/null @@ -1,38 +0,0 @@ -import pandas as pd -import numpy as np - - - - -filename_modeltab = "C:\/wamp64\/www\/python_predictions_4\/assets\/scripts\/data\/univariate\/modeltab_info.csv" -filename_varsel = "C:\/wamp64\www\/python_predictions_4\/assets\/scripts\/data\/univariate\/variable_selections.csv" - -# Variable_selections.csv is the file that needs to be modified -df_varsel = pd.read_csv(filename_varsel, sep=";") - -# From modeltab_info.csv we get the information which column in variable_selections.csv needs to be modified -# as well as which model-tab_auccurve.csv needs to be imported to use as template-filler -df_tab = pd.read_csv(filename_modeltab, sep=";") -new = df_tab.loc[df_tab.key=="new", "value"].reset_index(drop=True)[0] -new_template = df_tab.loc[df_tab.key=="new_template", "value"].reset_index(drop=True)[0] - -# The correct model-tab_auccurve.csv is imported from which the variables to be forced are read -# This list can be deduced from the line "selected" and the column "variable" -template_list = [] -if new_template.upper()!="SCRATCH": - filename_auccurve = "C:\/wamp64\/www\/python_predictions_4\/assets\/scripts\/data\/modeling\/"+new_template+"_auccurve.csv" - df_usersel = pd.read_csv(filename_auccurve, sep=";", nrows =2, header=None, names=["option","value"]) - nvars = df_usersel.loc[df_usersel.option == "selected", "value"].reset_index(drop=True)[0] - df_template = pd.read_csv(filename_auccurve, sep=";", skiprows=2) - template_list = list(df_template.loc[:nvars-1, "variable"]) -mask_force= np.array([var in template_list for var in df_varsel.variable]) - - -# Apply template to variable_selections.csv -df_varsel.loc[:,[new]] = 0 -df_varsel.loc[mask_force,[new]] = 1 - -# Export to csv -df_varsel.to_csv(filename_varsel, sep=";", index=False) - -print("ok") diff --git a/legacy_code/script_sprint4_2.py b/legacy_code/script_sprint4_2.py deleted file mode 100644 index 5579a72..0000000 --- a/legacy_code/script_sprint4_2.py +++ /dev/null @@ -1,458 +0,0 @@ - -# coding: utf-8 - -# # Scoring - -# Purpose of this script is to output (an)other script(s) with self-contained code to score out a basetable -# -# Call this script: the scriptmaker -# -# Call the created self-contained script: the scoring script - -# ### Import libraries - -# In[1]: - -import time -import math -import csv -import re -import pandas as pd -import numpy as np - - -# --- - -# ### Miscellaneous - -# In[2]: - -log = [] - - -# In[3]: - -# When code is in script, we define the path of the script's parent folder location as the root directory -# From this root we can travel to the relevant folders with minimal adjustment -try: - root = os.path.dirname(os.path.realpath(__file__)) - root = "/".join(root.split('\\')[:-1]) - log.append('Dynamic paths'+'\n') -except: - root = 'C:/wamp64/www/python_predictions_4/assets/scripts' - log.append('Static paths'+'\n') - - -# --- - -# ### Read the data and create variables to be exported - -# ##### 1A - Retrieve Modeltab info to find out for which modeltab we want a scoring script for -# Intermediate step for 1C - -# In[4]: - -df_modeltab = pd.read_csv(root+'/data/univariate/modeltab_info.csv',sep=';', index_col=0, header=None).T -modeltabtoscore = df_modeltab.score[1] - - -# ##### 1B - Retrieve number of vars selected to identify which n-th model of the 'modeltab'-models we are interested in -# Intermediate step for 1C - -# In[5]: - -df_auccurve_path = root+"/data/modeling/"+modeltabtoscore+"_auccurve.csv" -df_auccurve = open(df_auccurve_path).read() -selected_nvars = int(re.findall(r"selected;[0-9]+",df_auccurve)[0].split(';')[-1]) -selected_nvars = len(pd.read_csv(df_auccurve_path,skiprows=3, sep=';')) # USED FOR TESTING ALL VARS, TO BE DELETED !!!!!!!!!!! - - -# ##### 1C - Retrieve model coefficients of the specific n-th model of the 'modeltab'-models -# -# Result is to be stored in text in the scoring script, to achieve self-containment - -# In[6]: - -df_modelcoeff_path = root+"/data/modeling/"+modeltabtoscore+"_modelcoeff.csv" -df_modelcoeff = pd.read_csv(df_modelcoeff_path, sep=';') -mask = df_modelcoeff.nstep == int(selected_nvars) -df_modrules = df_modelcoeff.loc[mask,:] # TO BE STORED in SCORING SCRIPT ------------------------------------------------------- - - -# ##### 2 - Retrieve Data types of the predictors -# -# Result is to be stored in text in the scoring script, to achieve self-containment - -# In[7]: - -types_path = root+"/python/data_types.csv" -df_types = pd.read_csv(types_path, header=None) -df_types.columns=['var','type'] # TO BE STORED in SCORING SCRIPT --------------------------------------------------------------- - - -# ##### 3 - Univariate table output for deriving the translation from original VAR to discretisized D_VAR -# Result is to be stored in text in the scoring script, to achieve self-containment - -# In[8]: - -df_univariate_path = root+"/data/univariate/df_univariate.csv" -# we create str converters for all the B_variables coming from the univariate table -# we need this because bool & str variables (defined by ...types.csv) from the basetable are converted to objects -# and will have to be compared to the B_variables values, which better have the same type -# e.g. we have a varflag in our basetable which is converted to an object, but assume B_varflag is not converted and will be automatically read as float 1.0/0.0 -# in our incidence replacement we will thus be comparing '1'/'0' with 1.0/0.0, which won't work -uni_iterable = [(variable,getattr(__builtins__, 'str')) for variable in 'B_'+df_modrules.varname[1:].values] -uni_dict = dict(uni_iterable) -df_uni = pd.read_csv(df_univariate_path, sep=";", converters=uni_dict) - - -# In[9]: - -dcolumns = ['D_'+name for name in df_modrules.varname[1:]] -bcolumns = ['B_'+name for name in df_modrules.varname[1:]] -gvar = [] -gincid = [] -gbin = [] - -for i in range(len(dcolumns)): - # Select B_varname and D_varname - # Then take unique combinations of B_var and D_var in the univariate dataframe - # These combinations give the incidence value to attribute to the (possibly discretisized/regrouped) variables - columns_set = dcolumns[i:i+1]+bcolumns[i:i+1] - df_dupli = df_uni.loc[:,columns_set].drop_duplicates() - n_occurences = len(df_dupli) - - gvar.extend([df_dupli.columns[0][2:]]*n_occurences) - gincid.extend(df_dupli.iloc[:,0].values) - gbin.extend(df_dupli.iloc[:,1].values) - - -df_prep = pd.DataFrame({'var':gvar,'bin':gbin,'incid':gincid} - ,columns=['var','bin','incid']) # TO BE STORED in SCORING SCRIPT -------------------------------- - - -# --- - -# ### Writing scoring scripts - -# ##### for R - -# In[10]: - -score_code = open(root+"/Python/scorecode.R",'w') - -score_code.write("### Importing libraries & basetable to score\n") -score_code.write("# Importing libraries\n") -score_code.write("#library(dplyr)\n") -score_code.write("# Importing Types\n") -score_code.write("typevariables=c"+str([var for var in df_types.loc[:,'var']]).replace("[","(").replace("]",")")+"\n") -score_code.write("typetypes=c"+str([vartype for vartype in df_types.loc[:,'type']]).replace("[","(").replace("]",")")+"\n") -score_code.write("df_types=data.frame(var=typevariables,type=typetypes, stringsAsFactors='False')\n") -score_code.write("df_types_copy = df_types\n") -score_code.write("df_types_copy$type[df_types_copy$type=='int'|df_types_copy$type=='float']='numeric'\n") -score_code.write("df_types_copy$type[df_types_copy$type=='str'|df_types_copy$type=='bool']='character'\n") -score_code.write("coltypes = df_types_copy$type\n") -score_code.write("names(coltypes) = df_types_copy$var\n") -score_code.write("# Importing Basetable (with similar typing as in univariate analysis)\n") -score_code.write("df_base = read.csv('df_base.csv', check.names='False', colClasses=coltypes )\n") - -score_code.write("### Creating dataframe containing model rules\n") -score_code.write("modvariables=c"+str([var for var in df_modrules.loc[:,'varname']]).replace("[","(").replace("]",")")+"\n") -score_code.write("modcoefficients=c"+str([coeff for coeff in df_modrules.loc[:,'coeff']]).replace("[","(").replace("]",")")+"\n") -score_code.write("df_modrules=data.frame(varname=modvariables,coeff=modcoefficients, stringsAsFactors='False')\n") -score_code.write("\n") - -score_code.write("### Creating dataframe containing incidence translation rules\n") -score_code.write("prepvariables=c"+str([var for var in df_prep.loc[:,'var']]).replace("[","(").replace("]",")")+"\n") -score_code.write("prepbins=c"+str([bin for bin in df_prep.loc[:,'bin']]).replace("[","(").replace("]",")")+"\n") -score_code.write("prepincids=c"+str([bin for bin in df_prep.loc[:,'incid']]).replace("[","(").replace("]",")")+"\n") -score_code.write("df_prep =data.frame(var=prepvariables,bin=prepbins,incid=prepincids, stringsAsFactors='False')\n") -score_code.write("\n") - -score_code.write("### Grouping basetable predictors along their types and trimming basetable accordingly\n") -score_code.write("predictors = df_modrules$varname[df_modrules$varname!='Intercept']\n") -score_code.write("not_predictors = subset(colnames(df_base),!(colnames(df_base) %in% predictors))\n") -score_code.write("mask_FloatOrInt = df_types$type=='int'|df_types$type=='float'\n") -score_code.write("numeric_headers = subset(df_types$var[mask_FloatOrInt], df_types$var[mask_FloatOrInt] %in% predictors)\n") -score_code.write("object_headers = subset(df_types$var[df_types$type=='str'], df_types$var[df_types$type=='str'] %in% predictors)\n") -score_code.write("bool_headers = subset(df_types$var[df_types$type=='bool'], df_types$var[df_types$type=='bool'] %in% predictors)\n") -score_code.write("df_base = df_base[c(predictors,'ID')]\n") -score_code.write("\n") - -score_code.write("### Preprocessing the basetable\n") -score_code.write("# Strip quot function\n") -score_code.write("strip_quot<-function(x){\n") -score_code.write(' x = gsub("') -score_code.write("'") -score_code.write('","",x)\n') -score_code.write(" x = gsub('") -score_code.write('"') -score_code.write("','',x)\n") -score_code.write(" x = trimws(x)\n") -score_code.write(" return(x)\n") -score_code.write("}\n") -score_code.write("# Lower/upper function\n") -score_code.write("lower_upper<-function(x){\n") -score_code.write(" if (tolower(x)=='id'|tolower(x)=='target'){\n") -score_code.write(" x = toupper(x)\n") -score_code.write(" }\n") -score_code.write(" else {\n") -score_code.write(" x = tolower(x)\n") -score_code.write(" }\n") -score_code.write("}\n") -score_code.write("# maskmissing function in str/bool columns\n") -score_code.write("maskmissing<-function(var){\n") -score_code.write(" crit1 = is.na(var)\n") -score_code.write(" crit2 = var==''\n") -score_code.write(" return(crit1|crit2)\n") -score_code.write("}\n") -score_code.write("# Apply preprocessing functions\n") -score_code.write("colnames(df_base) = sapply(colnames(df_base), lower_upper)\n") -score_code.write("colnames(df_base) = sapply(colnames(df_base), strip_quot)\n") -score_code.write("df_base[] = lapply(df_base, strip_quot)\n") -score_code.write("for (predictor in c(object_headers,bool_headers)){\n") -score_code.write(" df_base[maskmissing(df_base[predictor]),predictor]='Missing'\n") -score_code.write("}\n") -score_code.write("\n") - -score_code.write("### Incidence replacement\n") -score_code.write("# Recipient dataframe\n") -score_code.write("df_out = data.frame(ID=df_base$ID)\n") -score_code.write("# Incidence replacement for string columns\n") -score_code.write("for (header in c(object_headers,bool_headers)){\n") -score_code.write(" mask = df_prep$var==header\n") -score_code.write(" bins = df_prep[mask,'bin']\n") -score_code.write(" incidences = df_prep[mask,'incid']\n") -score_code.write(" nonsig_bins = c()\n") -score_code.write(" nonsig_incidences = c()\n") -score_code.write(" if (sum(bins == 'Non-significants')>0) {\n") -score_code.write(" nonsig_bins = subset(unique(df_base[,header]), !(unique(df_base[,header]) %in% bins))\n") -score_code.write(" nonsig_incidences = rep(incidences[bins=='Non-significants'],length(nonsig_bins))\n") -score_code.write(" }\n") -score_code.write(" keys = c(bins,nonsig_bins)\n") -score_code.write(" values = c(incidences,nonsig_incidences)\n") -score_code.write(" df_out[paste('D_',header, sep='')] = values[match(df_base[,header], keys)]\n") -score_code.write("}\n") -score_code.write("# Incidence replacement for numeric columns\n") -score_code.write("for (header in numeric_headers){\n") -score_code.write(" mask = df_prep$var==header\n") -score_code.write(" bins = df_prep[mask,'bin']\n") -score_code.write(" incidences = df_prep[mask,'incid']\n") -score_code.write(" index_missing = which(bins=='Missing')\n") -score_code.write(" incidence_missing = incidences[index_missing]\n") -score_code.write(" upper_values = c()\n") -score_code.write(" last <- function(x) { return( x[length(x)] ) }\n") -score_code.write(" for (binn in bins){\n") -score_code.write(" upper_value = last(unlist(strsplit(binn,',')))\n") -score_code.write(" upper_value = tryCatch(as.numeric(gsub('([0-9]+).*$', '\\") -score_code.write("\\") -score_code.write("1',upper_value)), warning=function(e) Inf)\n") -score_code.write(" upper_values = c(upper_values,upper_value)\n") -score_code.write(" }\n") -score_code.write(" if(!identical(index_missing,integer(0))) upper_values = upper_values[-index_missing]\n") -score_code.write(" if(!identical(index_missing,integer(0))) incidences = incidences[-index_missing]\n") -score_code.write(" upper_values_incidences = incidences[order(upper_values)]\n") -score_code.write(" upper_values = upper_values[order(upper_values)]\n") -#score_code.write(" incidence_replaced_values = c()\n") -#score_code.write(" for (original_value in as.numeric(df_base[,header])){\n") -#score_code.write(" if (is.na(original_value)){\n") -#score_code.write(" incidence_to_attribute = incidence_missing\n") -#score_code.write(" }\n") -#score_code.write(" else {\n") -#score_code.write(" lowest_membership = min(which(original_value<=upper_values))\n") -#score_code.write(" incidence_to_attribute = upper_values_incidences[lowest_membership]\n") -#score_code.write(" }\n") -#score_code.write(" incidence_replaced_values = c(incidence_replaced_values,incidence_to_attribute)\n") -#score_code.write(" }\n") -#score_code.write(" df_out[paste('D_',header, sep='')] = incidence_replaced_values\n") -score_code.write(" mask_nan = is.na(df_base[,header])\n") -score_code.write(" lowest_memberships = findInterval(as.numeric(df_base[,header]), upper_values * (1 + .Machine$double.eps)) + 1\n") -score_code.write(" incidences_to_attribute = upper_values_incidences[lowest_memberships]\n") -score_code.write(" incidences_to_attribute[mask_nan] = incidence_missing\n") -score_code.write(" df_out[paste('D_',header, sep='')] = incidences_to_attribute\n") -score_code.write("}\n") -score_code.write("\n") - -score_code.write("### Scoring\n") -score_code.write("df_scores = data.frame(ID=as.numeric(as.character(df_out$ID)))\n") -score_code.write("scores = c()\n") -score_code.write("intercept="+str(df_modrules.coeff.values[0])+"\n") -score_code.write("coefficients=c"+str([coeff for coeff in df_modrules.coeff][1:]).replace("[","(").replace("]",")")+"\n") -score_code.write("productsums = rowSums(t(t(df_out[,paste('D_',predictors,sep='')])*coefficients))\n") -score_code.write("exponents = intercept + productsums\n") -score_code.write("scores = sapply(exponents, FUN = function(x) (exp(x)) / (1+exp(x)))\n") -score_code.write("df_scores['score']=scores\n") -score_code.write("\n") - -score_code.close() - - -# ##### for Python - -# In[11]: - -score_code = open(root+"/Python/scorecode.py",'w') - -score_code.write("### Importing libraries & basetable to score\n") -score_code.write("# Importing Libraries\n") -score_code.write("import time\nimport math\nimport csv\nimport re\nimport pandas as pd\nimport numpy as np\n") -score_code.write("# Importing Types\n") -score_code.write("typevariables="+str([var for var in df_types.loc[:,'var']])+"\n") -score_code.write("typetypes="+str([vartype for vartype in df_types.loc[:,'type']])+"\n") -score_code.write("df_types=pd.DataFrame({'var':typevariables,'type':typetypes},columns=['var','type'])\n") -score_code.write("df_types_copy = df_types.copy()\n") -score_code.write("bool_mask = df_types_copy.loc[:,'type']!='bool'\n") -score_code.write("df_types_copy.loc[bool_mask,'type'] = [getattr(__builtins__, type_str) for type_str in df_types_copy.loc[bool_mask,'type']]\n") -score_code.write("df_types_copy.loc[bool_mask==False,'type'] = getattr(__builtins__, 'str')\n") -score_code.write("types = df_types_copy.set_index('var').T.to_dict('records')\n") -score_code.write("# Importing Basetable with similar typing as in univariate analysis\n") -score_code.write("df_base = pd.read_csv('df_base.csv',header=0,sep=None,engine='python',converters=types[0])\n") -score_code.write("\n") - -score_code.write("### Creating dataframe containing model rules\n") -score_code.write("modvariables="+str([var for var in df_modrules.loc[:,'varname']])+"\n") -score_code.write("modcoefficients="+str([coeff for coeff in df_modrules.loc[:,'coeff']])+"\n") -score_code.write("df_modrules=pd.DataFrame({'varname':modvariables,'coeff':modcoefficients})\n") -score_code.write("\n") - -score_code.write("### Creating dataframe containing incidence translation rules\n") -score_code.write("prepvariables="+str([var for var in df_prep.loc[:,'var']])+"\n") -score_code.write("prepbins="+str([bin for bin in df_prep.loc[:,'bin']])+"\n") -score_code.write("prepincids="+str([bin for bin in df_prep.loc[:,'incid']])+"\n") -score_code.write("df_prep = pd.DataFrame({'var':prepvariables,'bin':prepbins,'incid':prepincids}, dtype=object)\n") -score_code.write("df_prep.loc[:,'incid']=df_prep.loc[:,'incid'].astype('float64')\n") -score_code.write("\n") - -score_code.write("### Grouping basetable predictors along their types and trimming basetable accordingly\n") -score_code.write("predictors = list(df_modrules.loc[df_modrules.varname!='Intercept','varname'].values)\n") -score_code.write("not_predictors = [column for column in df_base.columns if column not in predictors]\n") -score_code.write("mask_FloatOrInt = (df_types.type=='int')|(df_types.type=='float')\n") -score_code.write("numeric_headers=[var for var in df_types.loc[mask_FloatOrInt,'var'].values if var in predictors]\n") -score_code.write("object_headers=[var for var in df_types.loc[df_types.type=='str','var'].values if var in predictors]\n") -score_code.write("bool_headers=[var for var in df_types.loc[df_types.type=='bool','var'].values if var in predictors]\n") -score_code.write("df_base = df_base[predictors+['ID']]\n") -score_code.write("\n") - -score_code.write("### Preprocessing the basetable\n") -score_code.write("# Strip quot function\n") -score_code.write("def strip_quot(x_in):\n") -score_code.write(" try:\n") -score_code.write(" x_out = x_in.strip().strip('") -score_code.write('"') -score_code.write("').strip(") -score_code.write('"') -score_code.write("'") -score_code.write('")\n') -score_code.write(" except:\n") -score_code.write(" x_out=x_in\n") -score_code.write(" return x_out\n") -score_code.write("# Lower/upper function\n") -score_code.write("def lower_upper(x_in):\n") -score_code.write(" if ((x_in.lower() == 'id')|(x_in.lower() == 'target')):\n") -score_code.write(" x_out = x_in.upper()\n") -score_code.write(" else:\n") -score_code.write(" x_out = x_in.lower()\n") -score_code.write(" return x_out\n") -score_code.write("# maskmissing function in str/bool columns\n") -score_code.write("def maskmissing(var):\n") -score_code.write(" crit1 = var.isnull()\n") -score_code.write(" modvar = pd.Series([str(value).strip() for value in var])\n") -score_code.write(" crit2 = modvar==pd.Series(['']*len(var))\n") -score_code.write(" return crit1 | crit2\n") -score_code.write("# Apply preprocessing functions\n") -score_code.write("df_base = df_base.rename(columns=strip_quot)\n") -score_code.write("df_base = df_base.rename(columns=lower_upper)\n") -score_code.write("df_base = df_base.applymap(strip_quot)\n") -score_code.write("for header in object_headers+bool_headers:\n") -score_code.write(" mask = maskmissing(df_base[header])\n") -score_code.write(" df_base.loc[mask,header]='Missing'\n") -score_code.write("\n") - -score_code.write("### Incidence replacement\n") -score_code.write("# Recipient dataframe\n") -score_code.write("df_out = pd.DataFrame()\n") -score_code.write("df_out['ID']=df_base['ID']\n") -score_code.write("# Incidence replacement for string columns\n") -score_code.write("for header in object_headers+bool_headers:\n") -score_code.write(" mask = df_prep.loc[:,'var']==header\n") -score_code.write(" bins = df_prep.loc[mask,'bin']\n") -score_code.write(" incidences = df_prep.loc[mask,'incid']\n") -score_code.write(" nonsig_bins = []\n") -score_code.write(" nonsig_incidences = []\n") -score_code.write(" if (bins == 'Non-significants').any():\n") -score_code.write(" nonsig_bins = [binn for binn in df_base[header].unique() if binn not in list(bins)]\n") -score_code.write(" nonsig_incidences = list(incidences[bins=='Non-significants'])*len(nonsig_bins)\n") -score_code.write(" keys = list(bins)\n") -score_code.write(" keys.extend(nonsig_bins)\n") -score_code.write(" values = list(incidences)\n") -score_code.write(" values.extend(nonsig_incidences)\n") -score_code.write(" keys_and_values = zip(keys,values)\n") -score_code.write(" transdic = dict(keys_and_values)\n") -score_code.write(" items_to_translate = df_base[header] \n") -score_code.write(" df_out.loc[:,'D_'+header]= pd.Series([transdic[item] for item in items_to_translate])\n") -score_code.write("# Incidence replacement for numeric columns\n") -score_code.write("for header in numeric_headers:\n") -score_code.write(" mask = df_prep.loc[:,'var']==header\n") -score_code.write(" bins = df_prep.loc[mask,'bin']\n") -score_code.write(" incidences = df_prep.loc[mask,'incid']\n") -score_code.write(" index_missing = bins.index[bins=='Missing']\n") -score_code.write(" incidence_missing = incidences[index_missing]\n") -score_code.write(" upper_values = pd.Series([])\n") -score_code.write(" for i,binn in enumerate(bins.values):\n") -score_code.write(" upper_value = binn.split(',')[-1]\n") -score_code.write(" try:\n") -score_code.write(" upper_value = re.findall('[0-9]+',upper_value)[0]\n") -score_code.write(" except:\n") -score_code.write(" upper_value = math.inf\n") -score_code.write(" upper_values[i] = upper_value\n") -score_code.write(" upper_values.index = bins.index\n") -score_code.write(" upper_values.drop(index_missing, inplace=True)\n") -score_code.write(" upper_values = upper_values.astype(float)\n") -score_code.write(" upper_values.sort_values(inplace=True)\n") -score_code.write(" upper_values_incidences = incidences[upper_values.index]\n") -score_code.write(" upper_values.reset_index(drop=True, inplace=True)\n") -score_code.write(" upper_values_incidences.reset_index(drop=True, inplace=True)\n") -#score_code.write(" incidence_replaced_values = np.array([])\n") -#score_code.write(" for original_value in df_base[header]:\n") -#score_code.write(" lowest_membership = upper_values.index[original_value<=upper_values].min()\n") -#score_code.write(" try:\n") -#score_code.write(" incidence_to_attribute = upper_values_incidences[lowest_membership]\n") -#score_code.write(" except:\n") -#score_code.write(" if np.isnan(original_value):\n") -#score_code.write(" incidence_to_attribute = incidence_missing\n") -#score_code.write(" else:\n") -#score_code.write(" incidence_to_attribute = np.nan\n") -#score_code.write(" incidence_replaced_values = np.append(incidence_replaced_values,incidence_to_attribute)\n") -#score_code.write(" df_out['D_'+header] = pd.Series(incidence_replaced_values)\n") -score_code.write(" mask_npnan = df_base.loc[:,header].isnull()\n") -score_code.write(" lowest_memberships = upper_values.searchsorted(df_base.loc[:,header],side='left')\n") -score_code.write(" incidences_to_attribute = upper_values_incidences[lowest_memberships].reset_index(drop=True)\n") -score_code.write(" incidences_to_attribute[mask_npnan] = incidence_missing\n") -score_code.write(" df_out['D_'+header] = incidences_to_attribute\n") -score_code.write("\n") - - -score_code.write("### Scoring\n") -score_code.write("df_scores = pd.DataFrame([])\n") -score_code.write("df_scores['ID'] = df_out['ID']\n") -score_code.write("scores = []\n") -score_code.write("intercept="+str(df_modrules.coeff.values[0])+"\n") -score_code.write("coefficients=np.array("+str([coeff for coeff in df_modrules.coeff][1:])+")\n") -score_code.write("productsums = (df_out['D_'+pd.Series(predictors)]*coefficients).sum(axis=1)\n") -score_code.write("exponents = intercept + productsums\n") -score_code.write("scores = exponents.apply(func=lambda x:(math.exp(x)) / (1+math.exp(x)))\n") -score_code.write("df_scores['score']=scores\n") -score_code.write("\n") - -score_code.close() - - -# ##### for Sas - -# In[12]: - -# ... - -print('ok') - -# --- diff --git a/legacy_code/script_sprint4_3.py b/legacy_code/script_sprint4_3.py deleted file mode 100644 index 6860046..0000000 --- a/legacy_code/script_sprint4_3.py +++ /dev/null @@ -1,25 +0,0 @@ -import pandas as pd -import os - -try: - root = os.path.dirname(os.path.realpath(__file__)) - root = "/".join(root.split('\\')[:-1]) -except: - root = 'C:\/wamp64\/www\/python_predictions_4\/assets\/scripts\/python' - -auc_path = root + '\/data\/univariate\/aucs.csv' -df_in = pd.read_csv(auc_path, sep=';') -df_sortqual = df_in.sort_values(by=['AUC test','AUC train'], ascending=False).reset_index(drop=True) -df_sortname = df_in.sort_values(by=['variable']).reset_index(drop=True).reset_index(drop=True) -if (df_in.variable == df_sortqual.variable).all(): - df_out = df_sortname -else: - df_out = df_sortqual - -df_out.to_csv(path_or_buf=auc_path - ,sep=';' - ,index=False - ,encoding='utf-8' - ,line_terminator='\n') - -print('ok') diff --git a/legacy_code/univariate[171005].ipynb b/legacy_code/univariate[171005].ipynb deleted file mode 100644 index 3556837..0000000 --- a/legacy_code/univariate[171005].ipynb +++ /dev/null @@ -1,1516 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Univariate analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### General Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import math\n", - "import csv\n", - "import warnings\n", - "import time\n", - "import os\n", - "import itertools\n", - "import scipy.integrate\n", - "import re" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from scipy import stats\n", - "from itertools import chain\n", - "from sklearn import metrics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Miscellaneous" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "log = []" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# When code is in script, we define the path of the script's parent folder location as the 'root' directory\n", - "# From this 'root' we can travel to the relevant folders with minimal adjustment\n", - "try:\n", - " root = os.path.dirname(os.path.realpath(__file__))\n", - " root = \"/\".join(root.split('\\\\')[:-1])\n", - " log.append('Dynamic paths'+'\\n')\n", - "except:\n", - " root = 'C:/wamp64/www/python_predictions_4/assets/scripts'\n", - " log.append('Static paths'+'\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# To allow pandas dataframes to display more columns\n", - "pd.set_option(\"display.max_columns\",50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read data and organize" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Basetable and its types" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# A Types csv file CAN be defined to be used to convert variables (of the basetable, see below) to the desired data types\n", - "# The Types csv files should include one column with variable names and one column with desired types (e.g. int,float,str,bool)\n", - "# If no Types csv file is provided no convertions will be forced. In that case 'Python' will guess the data type of each column \n", - "types_path = root+\"/python/data_types.csv\"\n", - "types_exist = True\n", - "\n", - "try: \n", - " df_types = pd.read_csv(types_path, header=None)\n", - " bool_mask = df_types[1]!='bool'\n", - " # Extract the functions based on the given type (e.g. 'str' -> str, 'int' -> int), for proper convertion \n", - " df_types.loc[bool_mask,1] = [getattr(__builtins__, type_str) for type_str in df_types.loc[bool_mask,1]]\n", - " # A type 'bool' is also attributed the function str, for convertion\n", - " df_types.loc[bool_mask==False,1] = getattr(__builtins__, 'str')\n", - " #types = df_types[bool_mask].set_index(0).T.to_dict('records')\n", - " types = df_types.set_index(0).T.to_dict('records')\n", - "except FileNotFoundError:\n", - " types = [dict()]\n", - " types_exist = False" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# The basetable csv file should have column names as its first row\n", - "# The columns names should include 'TARGET', 'ID'\n", - "data_path = root+\"/python/data.csv\"\n", - "\n", - "df_in = pd.read_csv(data_path\n", - " ,header=0\n", - " ,sep=None\n", - " ,engine='python'\n", - " ,converters=types[0])\n", - "\n", - "# If no Types csv file was provided pd.read_csv guessed the types, we now output these types in a csv for re-use & later use\n", - "if types_exist == False:\n", - " filename = root+\"/python/data_types.csv\"\n", - " funtotype = lambda x:re.findall('[a-z]+',str(x))[0].replace('object','str')\n", - " with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =',')\n", - " write.writerows([column\n", - " ,funtotype(df_in[column].dtype)] for column in df_in.columns)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# Function to remove quotes from variable names and/or variable values\n", - "def strip_quot(x_in):\n", - " try:\n", - " x_out = x_in.strip().strip('\"').strip(\"'\")\n", - " except:\n", - " x_out=x_in\n", - " return x_out\n", - "\n", - "# Function to put 'id' and 'target' variable names in uppercase, all other variable names are put in lowercase\n", - "# This is coded as to visually differentiate predictors from other variables\n", - "# But another combination of upper/lower is possible as well, e.g. all variable names in uppercase\n", - "def lower_upper(x_in):\n", - " if ((x_in.lower() == 'id')|(x_in.lower() == 'target')):\n", - " x_out = x_in.upper()\n", - " else:\n", - " x_out = x_in.lower()\n", - " return x_out\n", - "\n", - "# Function to group variable names based on the data type of the variable\n", - "# Could as well use the types in Types.csv\n", - "def get_headers(dataframe,type): \n", - " return dataframe.select_dtypes(include=[type]).columns.values" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# Clean up quotes from column names\n", - "df_in = df_in.rename(columns=strip_quot)\n", - "\n", - "# Perform uppercase/lowercase transformation to column names\n", - "df_in = df_in.rename(columns=lower_upper)\n", - "\n", - "# Clean up quotes from column values\n", - "df_in = df_in.applymap(strip_quot)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "# Group variable (names) based on the respective data type of each variable\n", - "# With this information we know which variables are destined for equifrequency, regrouping or simply passing (see further)\n", - "other_headers = [n for n in [\"TARGET\",\"ID\"]]\n", - "try:\n", - " bool_headers = [n for n in df_types.loc[bool_mask==False,0].values if n not in other_headers]\n", - "except:\n", - " bool_headers = []\n", - "object_headers = [n for n in get_headers(df_in,'object') if n not in other_headers+bool_headers]\n", - "numeric_headers = [n for n in get_headers(df_in,'number') if n not in other_headers+bool_headers]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Analysis settings" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "# Import settings defined by the user\n", - "df_settings = pd.read_csv(root+'/python/analysis_settings.csv', sep=',', index_col=0, header=None).T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Partitioning " - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Function to make partition sets (based on desired user settings) for both targets (0 & 1) \n", - "def partitionList(train_setting,selection_setting,validation_setting,sorted_target):\n", - " settings = {'train':train_setting,'selection':selection_setting,'validation':validation_setting}\n", - " parts = ['train','selection','validation']\n", - " result = []\n", - " for target in [sorted_target.iloc[0],sorted_target.iloc[-1]]:\n", - " target_length = (sorted_target==target).sum()\n", - " for part in parts:\n", - " result.extend( [part]*math.ceil(target_length*settings[part][1]/100) ) \n", - " return result" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "# Shuffle and sort on TARGET\n", - "df_in = df_in.iloc[np.random.permutation(len(df_in))].sort_values(by='TARGET', ascending=False).reset_index(drop=True)\n", - "\n", - "# Create partition based on analysis_setting.csv\n", - "partition = partitionList(train_setting=df_settings.loc[:,'partitioning_train']\n", - " ,selection_setting=df_settings.loc[:,'partitioning_selec']\n", - " ,validation_setting=df_settings.loc[:,'partitioning_valid']\n", - " ,sorted_target=df_in.TARGET) \n", - "\n", - "# Attach to dataframe\n", - "df_in[\"PARTITION\"] = partition[:len(df_in)]\n", - "\n", - "# Sampling based on analysis settings (if both sampling_settings are set to 100, all data is used)\n", - "sampling_settings = {1:df_settings.sampling_1, 0:df_settings.sampling_0}\n", - "if (int(sampling_settings[1])<100) | (int(sampling_settings[0])<100):\n", - " for sample in sampling_settings:\n", - " sample_length = int(round((df_in.TARGET==sample).sum() * sampling_settings[sample]/100))\n", - " drop_index = df_in[df_in.TARGET==sample].index[sample_length:]\n", - " df_in.drop(drop_index,inplace=True)\n", - " df_in.reset_index(drop=True, inplace=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Output Container" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Create output dataframe which will contain transformed variables\n", - "df_out = df_in.loc[:,[\"ID\",\"TARGET\",\"PARTITION\"]].copy()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preprocessing of continuous variables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Discretization function for Continuous variables" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "### This function is a reworked version of pd.qcut to satisfy our particular needs\n", - "### Takes for var a continuous pd.Series as input and returns a pd.Series with bin-labels (e.g. [4,6[ )\n", - "### Train takes a series/list of booleans (note: we define bins based on the training set)\n", - "### Autobins reduces the number of bins (starting from nbins) as a function of the number of missings\n", - "### Nbins is the wished number of bins\n", - "### Precision=0 results in integer bin-labels if possible\n", - "### twobins=True forces the function to output at least two bins\n", - "### catchLarge tests if some groups (or missing group) are very large, and if so catches and outputs two groups\n", - "#### note: catchLarge makes twobins irrelevant\n", - "\n", - "def eqfreq(var, train, autobins=True, nbins=10, precision=0, twobins=True, catchLarge=True):\n", - " \n", - " \n", - " # Test for large groups and if one exists pass them with two bins: Large_group,Other\n", - " if catchLarge:\n", - " catchPercentage=1-(1/nbins)\n", - " groupCount = var[train].groupby(by=var[train]).count()\n", - " maxGroupPerc = groupCount.max()/len(var[train])\n", - " missingPerc = sum(var[train].isnull())/len(var[train])\n", - " if maxGroupPerc>=catchPercentage:\n", - " largeGroup = groupCount.sort_values(ascending=False).index[0]\n", - " x_binned = var.copy()\n", - " x_binned.name = 'B_'+var.name\n", - " x_binned[x_binned!=largeGroup]='Other'\n", - " cutpoints=None\n", - " info = (var.name+\": One large group, outputting 2 groups\")\n", - " return x_binned, cutpoints, info\n", - " elif missingPerc>=catchPercentage:\n", - " x_binned = var.copy()\n", - " x_binned.name = 'B_'+var.name\n", - " x_binned[x_binned.isnull()]='Missing'\n", - " x_binned[x_binned!='Missing']='Other'\n", - " cutpoints=None\n", - " info = (var.name+\": One large missing group, outputting 2 groups\")\n", - " return x_binned, cutpoints, info\n", - " # Adapt number of bins as a function of number of missings\n", - " if autobins:\n", - " length = len(var[train])\n", - " missing_total = var[train].isnull().sum()\n", - " missing_perten = missing_total/length*10\n", - " nbins = max(round(10-missing_perten)*nbins/10 ,1)\n", - " # Store the name and index of the variable\n", - " name = var.name\n", - " series_index = var.index\n", - " # Transform var and train to a np.array and list respectively, which is needed for some particular function&methods\n", - " x = np.asarray(var)\n", - " train = list(train)\n", - " # First step in finding the bins is determining what the quantiles are (named as cutpoints)\n", - " # If the quantile lies between 2 points we use lin interpolation to determine it\n", - " cutpoints = var[train].quantile(np.linspace(0,1,nbins+1),interpolation = 'linear')\n", - " # If the variable results only in 2 unique quantiles (due to skewness) increase number of quantiles until more than 2 bins can be formed\n", - " if twobins:\n", - " extrasteps = 1\n", - " # Include a max. extrasteps to avoid infinite loop\n", - " while (len(cutpoints.unique())<=2) & (extrasteps<20):\n", - " cutpoints = var[train].quantile(np.linspace(0,1,nbins+1+extrasteps),interpolation = 'linear')\n", - " extrasteps+=1\n", - " # We store which rows of the variable x lies under/above the lowest/highest cutpoint \n", - " # Without np.errstate(): xcutpoints.max() can give if x contains nan values (missings)\n", - " # However the function will result in False in both >&< cases, which is a correct result, so the warning can be ignored\n", - " with np.errstate(invalid='ignore'):\n", - " under_lowestbin = x < cutpoints.min()\n", - " above_highestbin= x > cutpoints.max()\n", - "\n", - "\n", - " def _binnedx_from_cutpoints(x, cutpoints, precision, under_lowestbin, above_highestbin):\n", - " ### Attributes the correct bin ........................\n", - " ### Function that, based on the cutpoints, seeks the lowest precision necessary to have meaningful bins\n", - " ### e.g. (5.5,5.5] ==> (5.51,5.54]\n", - " ### Attributes those bins to each value of x, to achieve a binned version of x \n", - " \n", - " # Store unique cutpoints (e.g. from 1,3,3,5 to 1,3,5) to avoid inconsistensies when bin-label making\n", - " # Indeed, bins [...,1], (1,3], (3,3], (3,5], (5,...] do not make much sense\n", - " # While, bins [...,1], (1,3], (3,5], (5,...] do make sense\n", - " unique_cutpoints = cutpoints.unique()\n", - " # If there are only 2 unique cutpoints (and thus only one bin will be returned), \n", - " # keep original values and code missings as 'Missing'\n", - " if len(unique_cutpoints) <= 2:\n", - " cutpoints = None\n", - " x_binned = pd.Series(x)\n", - " x_binned[x_binned.isnull()] = 'Missing'\n", - " info = (var.name+\": Only one resulting bin, keeping original values instead\")\n", - " return x_binned, cutpoints, info\n", - " # Store info on whether or not the number of resulting bins equals the desired number of bins\n", - " elif len(unique_cutpoints) < len(cutpoints):\n", - " info = (var.name+\": Resulting # bins < whished # bins\")\n", - " else:\n", - " info = (var.name+\": Resulting # bins as desired\")\n", - " # Finally, recode the cutpoints (which can have doubles) as the unique cutpoints\n", - " cutpoints = unique_cutpoints\n", - " \n", - " # Store missing values in the variable as a mask, and create a flag to test if there are any missing in the variable\n", - " na_mask = np.isnan(x)\n", - " has_nas = na_mask.any()\n", - " # Attribute to every x-value the index of the cutpoint (from the sorted cutpoint list) which is equal or higher than\n", - " # the x-value, effectively encompasing that x-value.\n", - " # e.g. for x=6 and for sorted_cutpoint_list=[0,3,5,8,...] the resulting_index=3 \n", - " ids = cutpoints.searchsorted(x, side='left')\n", - " # x-values equal to the lowest cutpoint will recieve a ids value of 0\n", - " # but our code to attribute bins to x-values based on ids (see end of this subfunction) requires a min. value of 1\n", - " ids[x == cutpoints[0]] = 1\n", - " # Idem as previous: x-values below the lowest cutpoint should recieve a min. value of 1\n", - " if under_lowestbin.any():\n", - " ids[under_lowestbin] = 1\n", - " # Similar as previous: x-values above the highest cutpoint should recieve the max. allowed ids\n", - " if above_highestbin.any():\n", - " max_ids_allowed = ids[(above_highestbin == False) & (na_mask==False)].max()\n", - " ids[above_highestbin] = max_ids_allowed\n", - " # Maximal ids can now be defined if we neglect ids of missing values\n", - " max_ids = ids[na_mask==False].max()\n", - " \n", - " # Based on the cutpoints create bin-labels\n", - " # Iteratively go through each precision (= number of decimals) until meaningful bins are formed\n", - " # If theoretical bin is ]5.51689,5.83654] we will prefer ]5.5,5.8] as output bin\n", - " increases = 0\n", - " original_precision = precision\n", - " while True:\n", - " try:\n", - " bins = _format_bins(cutpoints, precision)\n", - " except ValueError:\n", - " increases += 1\n", - " precision += 1\n", - " #if increases >= 5:\n", - " #warnings.warn(\"Modifying precision from \"+str(original_precision)+\" to \"+str(precision)+\" to achieve discretization\")\n", - " #print(\"Modifying precision from \"+str(original_precision)+\" to \"+str(precision)+\" to achieve discretization\")\n", - " else:\n", - " break\n", - " \n", - " # Make array of bins to allow vector-like attribution\n", - " bins = np.asarray(bins, dtype=object)\n", - " # If x has nas: for each na-value, set the ids-value to max_ids+1\n", - " # this will allow na-values to be attributed the highest bin which we define right below\n", - " if has_nas:\n", - " np.putmask(ids, na_mask, max_ids+1)\n", - " # The highest bin is defined as 'Missing'\n", - " bins = np.append(bins,'Missing')\n", - " # ids-1 is used as index in the bin-labels list to attribute a bin-label to each x. Example:\n", - " # x=6 sorted_cutpoint_list=[0,3,5,8,...] ids=3 levels=[[0,3],(3,5],(5,8],...]\n", - " # The correct bin level for x is (5,8] which has index 2 which is equal to the ids-1\n", - " x_binned = bins[ids-1]\n", - " return x_binned, cutpoints, info\n", - " \n", - "\n", - " def _format_bins(cutpoints, prec):\n", - " # Based on the quantile list create bins. Raise error if values are similar within one bin.\n", - " # On error _binnedx_from_cutpoints will increase precision\n", - " \n", - " fmt = lambda v: _format_label(v, precision=prec)\n", - " bins = []\n", - " for a, b in zip(cutpoints, cutpoints[1:]):\n", - " fa, fb = fmt(a), fmt(b)\n", - " \n", - " if a != b and fa == fb:\n", - " raise ValueError('precision too low')\n", - " \n", - " formatted = '(%s, %s]' % (fa, fb)\n", - " bins.append(formatted)\n", - " \n", - " bins[0] = '[...,' + bins[0].split(\",\")[-1]\n", - " bins[-1] = bins[-1].split(\",\")[0] + ',...]'\n", - " return bins\n", - "\n", - "\n", - " def _format_label(x, precision):\n", - " # For a specific precision, returns the value formatted with the appropriate amount of numbers after comma and correct brackets\n", - " \n", - " if isinstance(x,float):\n", - " frac, whole = np.modf(x)\n", - " sgn = '-' if x < 0 else ''\n", - " whole = abs(whole)\n", - " if frac != 0.0:\n", - " val = '{0:.{1}f}'.format(frac, precision)\n", - " val = _trim_zeros(val)\n", - " if '.' in val:\n", - " return sgn + '.'.join(('%d' % whole, val.split('.')[1]))\n", - " else: \n", - " if '0' in val:\n", - " return sgn + '%0.f' % whole\n", - " else:\n", - " return sgn + '%0.f' % (whole+1)\n", - " else:\n", - " return sgn + '%0.f' % whole\n", - " else:\n", - " return str(x)\n", - "\n", - "\n", - " def _trim_zeros(x):\n", - " # Removes unnecessary zeros and commas\n", - " while len(x) > 1 and x[-1] == '0':\n", - " x = x[:-1]\n", - " if len(x) > 1 and x[-1] == '.':\n", - " x = x[:-1]\n", - " return x\n", - "\n", - "\n", - " x_binned, cutpoints, info = _binnedx_from_cutpoints(x, cutpoints, precision=precision, under_lowestbin=under_lowestbin, above_highestbin=above_highestbin)\n", - " x_binned = pd.Series(x_binned, index=series_index, name=\"B_\"+name)\n", - " return x_binned, cutpoints, info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# WIP" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "for n in numeric_headers:\n", - " result = eqfreq(var=df_in[n]\n", - " ,train=df_in[\"PARTITION\"]==\"train\"\n", - " ,autobins=True\n", - " ,nbins=int(df_settings.discretization_nbins)\n", - " ,precision=0\n", - " ,twobins=True\n", - " ,catchLarge=True)\n", - " print(n)\n", - " print(result[0].unique())\n", - " print('\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# /WIP" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Apply function to continuous variables" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "tic = time.time()\n", - "# We loop only through the numeric variables\n", - "for n in numeric_headers:\n", - " # Perform the equifrequency function\n", - " result = eqfreq(var=df_in[n]\n", - " ,train=df_in[\"PARTITION\"]==\"train\"\n", - " ,autobins=True\n", - " ,nbins=int(df_settings.discretization_nbins)\n", - " ,precision=0\n", - " ,twobins=True\n", - " ,catchLarge=False) # TRUE OPTION STILL PRODUCES ERROR IN SORTNUMERIC function AND SCORING procedure !!!!!!!!!\n", - " df_out = pd.concat([df_out,result[0]], axis=1)\n", - " log.append(result[2])\n", - "toc = time.time()\n", - "log.append(\"Discretisation: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preprocessing of categorical variables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Function for labeling missing/empty values" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Check which values of a var are empty strings or null values\n", - "def maskmissing(var):\n", - " # Check if values are null\n", - " crit1 = var.isnull()\n", - " # Check if values are empty strings\n", - " modvar = pd.Series([str(value).strip() for value in var])\n", - " crit2 = modvar==pd.Series(['']*len(var))\n", - " #crit2 = var==pd.Series(['']*len(var))\n", - " #crit3 = var==pd.Series([' ']*len(var))\n", - " return crit1 | crit2 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Regrouping Function for nominal/ordinal variables" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "# Regrouping function for categorical variables\n", - "# Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group\n", - "# The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or \n", - "# 'remaining groups average incidence' (if dummy=False)\n", - "# Groups with a pvalue above the threshold are relabled to a single group\n", - "\n", - "def regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'):\n", - " \n", - " # Define the chi² test condition\n", - " # Groups that do not meet the condition are not analyzed and will be unconditionally relabled\n", - " def _chi2cond_(var=var,target=target,train=train):\n", - " varcounts = var[train].groupby(by=var).count()\n", - " train_inc = target[train].sum()/len(target[train])\n", - " factor = max(train_inc, 1-train_inc)\n", - " analyze_mask = (varcounts*factor)>5\n", - " analyze_groups = analyze_mask.index[analyze_mask].values\n", - " return analyze_groups\n", - " \n", - " # Compute overal incidence mean\n", - " incidence_mean = target[train].mean()\n", - " # Create container of which groups will be kept, compared to the groups which will be relabled\n", - " keepgroups = []\n", - " # Cycle and test each group that meets the chi² condition\n", - " for group in _chi2cond_():\n", - " # Container for target 0/1 observations of the group under scrutiny\n", - " obs_group = []\n", - " # Counts of the target 0/1 occurences for the group under scrutiny\n", - " obs_group.append(((target[train]==0)&(var[train]==group)).sum())\n", - " obs_group.append(((target[train]==1)&(var[train]==group)).sum())\n", - " obs_group = np.array(obs_group)\n", - " # Container for target 0/1 observations of the remaining groups together\n", - " obs_other = []\n", - " # Counts of the target 0/1 occurences for the remaining groups together\n", - " obs_other.append(((target[train]==0)&(var[train]!=group)).sum())\n", - " obs_other.append(((target[train]==1)&(var[train]!=group)).sum())\n", - " obs_other = np.array(obs_other)\n", - " # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence\n", - " # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups\n", - " if dummy:\n", - " obs_other_size = obs_other.sum()\n", - " obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1)\n", - " obs_other[1]=( incidence_mean)*obs_other_size\n", - " obs = np.array([obs_group,obs_other])\n", - " # Place at least 1 observation to avoid error in chi2 test\n", - " obs[obs==0] = 1\n", - " # Perform chi² test\n", - " pval = stats.chi2_contingency(obs, correction=False)[1]\n", - " # If pval outperforms threshold, append the group in the keepgroups list\n", - " if pval<=pval_thresh:\n", - " keepgroups.append(group)\n", - " #elif group==keep:\n", - " # keepgroups.append(group)\n", - " # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list\n", - " if keep not in keepgroups:\n", - " keepgroups.append(keep)\n", - " # Makes a list of all groups not in the keepgroups list\n", - " regroup_mask = [val not in keepgroups for val in var.values]\n", - " var_regroup = var.copy()\n", - " # Rename those groups\n", - " var_regroup[regroup_mask] = rename\n", - " var_regroup.name = \"B_\"+var.name\n", - " info = (var.name+\": from \"+str(len(var.unique()))+\" to \"+str(len(var_regroup.unique())))\n", - " return var_regroup, info" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "Apply function to nominal/ordinal variables" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "tic = time.time()\n", - "# We loop only through the categorical variables\n", - "for h in object_headers:\n", - " # We label missing and empty values for categorical variables as 'Missing'\n", - " # Note the interaction with the 'keep' parameter of the regroup function.\n", - " mask = maskmissing(df_in[h])\n", - " df_in.loc[mask,h]='Missing'\n", - " # Perform regrouping function\n", - " result = regroup(var=df_in[h]\n", - " ,target=df_in.loc[:,'TARGET']\n", - " ,train=df_in.PARTITION=='train'\n", - " ,pval_thresh=float(df_settings.regrouping_signif)\n", - " ,dummy=True\n", - " ,keep='Missing'\n", - " ,rename='Non-significants')\n", - " df_out = pd.concat([df_out,result[0]],axis=1)\n", - " log.append(result[1])\n", - "toc = time.time()\n", - "log.append(\"Regrouping: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preprocessing of boolean variables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Defining Function to pass variables as is" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# We could just rename them or put them with the regoup function, but for now let's keep consistent with the other functions\n", - "def passvar(var):\n", - " var_pass = var.copy()\n", - " var_pass.name = \"B_\"+var.name\n", - " info = (\"Passing \"+var.name)\n", - " return var_pass, info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Executing function" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "tic = time.time()\n", - "# We loop only through the boolean variables\n", - "for b in bool_headers:\n", - " # We label missing and empty values for boolean variables as 'Missing'\n", - " mask = maskmissing(df_in[b])\n", - " df_in.loc[mask,b]='Missing'\n", - " # Perform the passvar function\n", - " result = passvar(var=df_in[b])\n", - " df_out = pd.concat([df_out,result[0]],axis=1)\n", - " log.append(result[1])\n", - "toc = time.time()\n", - "log.append(\"Passing: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "### Incidence Replacement" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "Function for incidence replacement" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def increp(b_var, target, train): \n", - " #get variable name\n", - " name = b_var.name\n", - " #get overall incidence \n", - " incidence_mean = target[train].mean()\n", - " #get incidence per group\n", - " incidences = target[train].groupby(b_var).mean()\n", - " #construct dataframe with incidences\n", - " idf = pd.DataFrame(incidences).reset_index()\n", - " #get values that are in the data but not in the labels\n", - " bin_labels = incidences.index\n", - " newgroups = list(set(b_var.unique()) ^ set(bin_labels))\n", - " #if newgroups, add mean incidence to incidence dataframe for each new group\n", - " if len(newgroups)>0:\n", - " #make dataframe:\n", - " ngdf = pd.DataFrame(newgroups)\n", - " ngdf.columns = [name]\n", - " ngdf[\"TARGET\"] = incidence_mean\n", - " #dataframe with incidences: \n", - " idf = idf.append(ngdf)\n", - " #dataframe with the variable\n", - " vdf = pd.DataFrame(b_var)\n", - " #discretized variable by merge\n", - " d_var = pd.merge(vdf,idf,how='left',on=name)[\"TARGET\"]\n", - " return pd.Series(d_var, name=\"D_\"+name[2:]) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Apply function for incidence replacement" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "# We define the columns destined for incidence replacement\n", - "headers_for_incidrep = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]==\"B_\"))]" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "tic = time.time()\n", - "# We loop only through the columns destined for incidence replacement\n", - "for n in headers_for_incidrep:\n", - " # Perform increp function\n", - " result = increp(b_var=df_out[n]\n", - " ,target=df_out.TARGET\n", - " ,train=df_out.PARTITION==\"train\")\n", - " df_out = pd.concat([df_out,result], axis=1)\n", - " log.append(n+ \" processed\")\n", - "toc = time.time()\n", - "log.append(\"Incidence replacement: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate AUCS" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Function for auc calculation" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def getauc(var, target, partition): \n", - " \n", - " y = np.array(target[partition])\n", - " pred = np.array(var[partition])\n", - " pred = pred.astype(np.float64)\n", - " fpr, tpr, thresholds = metrics.roc_curve(y,pred, pos_label=1)\n", - " \n", - " return metrics.auc(fpr, tpr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Applying function for auc calculation" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# We define the columns for which an AUC score should be computed\n", - "headers_for_auc = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]==\"D_\"))]" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [], - "source": [ - "auc_list_all = []\n", - "parts = [\"train\",\"selection\"]\n", - "tic = time.time()\n", - "# We loop only through those columns for which an AUC score should be computed\n", - "for header in headers_for_auc:\n", - " auc_list_var = [header[2:]]\n", - " # We loop through the two sets ('train' and 'selection') for which an AUC score is needed\n", - " for part in parts:\n", - " # Perform getauc function\n", - " auc_value = getauc(var=df_out[header]\n", - " ,target=df_out.TARGET\n", - " ,partition=df_out.PARTITION==part)\n", - " auc_list_var.append(auc_value.round(2)) #We round auc values to 2 decimals\n", - " auc_list_all.append(auc_list_var)\n", - " log.append(header + \" processed\")\n", - "# We create a supplementary dataframe destined for Cobra input \n", - "df_auc = pd.DataFrame(auc_list_all,columns=['variable','AUC train','AUC test'])\n", - "toc = time.time()\n", - "log.append(\"Auc: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preselection" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "tic = time.time()\n", - "# We identify those variables for which the AUC score is above the user-defined threshold\n", - "auc_thresh = df_auc.loc[:,'AUC test'] > float(df_settings.preselection_auc)\n", - "# We identify those variables for which the AUC score difference between 'train' and 'selection' is within the user-defined ratio\n", - "auc_overtrain = (df_auc.loc[:,'AUC train']*100 - df_auc.loc[:,'AUC test']*100) < float(df_settings.preselection_overtrain)\n", - "# Only those variables passing the 2 criteria above are preselected\n", - "preselect = auc_thresh & auc_overtrain" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [], - "source": [ - "# We create a supplementary dataframe destined for Cobra input \n", - "df_variable_selections = pd.DataFrame({'variable':df_auc.variable\n", - " ,'preselect':preselect.astype(int)\n", - " ,'Default':np.zeros(len(preselect)).astype(int)\n", - " ,'Alternative 1':np.zeros(len(preselect)).astype(int)\n", - " ,'Alternative 2':np.zeros(len(preselect)).astype(int)\n", - " ,'Alternative 3':np.zeros(len(preselect)).astype(int)\n", - " ,'Alternative 4':np.zeros(len(preselect)).astype(int)\n", - " ,'Alternative 5':np.zeros(len(preselect)).astype(int)}\n", - " ,columns=['variable'\n", - " ,'preselect'\n", - " ,'Default'\n", - " ,'Alternative 1'\n", - " ,'Alternative 2'\n", - " ,'Alternative 3'\n", - " ,'Alternative 4'\n", - " ,'Alternative 5'])" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "for i,var in enumerate(df_variable_selections.variable):\n", - " log.append(var+\" \"+np.array(['passed','filtered'])[df_variable_selections.preselect][i])\n", - "toc = time.time()\n", - "log.append(\"Preselection: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate Correlations" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [], - "source": [ - "# We define the columns for which a correlation score should be computed\n", - "headers_for_corr = [h for h in df_out.columns if ((h not in ['ID','TARGET','PARTITION']) & (h[:2]==\"D_\"))]" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [], - "source": [ - "train = df_out.PARTITION==\"train\"\n", - "tic = time.time()\n", - "dataforcorr = np.transpose(np.matrix(df_out.loc[train,headers_for_corr],dtype=float))\n", - "mat_corr = np.corrcoef(dataforcorr)\n", - "toc = time.time()\n", - "log.append(\"Correlations: \"+str(toc-tic)+\" sec\"+\"\\n\")" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [], - "source": [ - "df_corr = pd.DataFrame(mat_corr)\n", - "df_corr.columns = headers_for_corr\n", - "df_corr.index = headers_for_corr" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Export files" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Table of all Auc values" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "auc_path = root+'/data/univariate/aucs.csv'\n", - "df_auc = df_auc.sort_values(by=['AUC test','AUC train'], ascending=False).reset_index(drop=True)\n", - "df_auc.to_csv(path_or_buf=auc_path\n", - " ,sep=';'\n", - " ,index=False\n", - " ,encoding='utf-8'\n", - " ,line_terminator='\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tables of Incidences & Correlations per variable" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Function for sorting cont.variables, whether or not they have undergone discritization\n", - "def sortnumeric(dataframe):\n", - " \n", - " lowestnumber = 0\n", - " # If the variable was discretisized\n", - " if '[...' in [str(l)[:4] for l in dataframe.group.values]:\n", - " unsorted_labels = dataframe.group.values\n", - " label_items=[]\n", - " for label in unsorted_labels:\n", - " # For each bin label, retain the first value\n", - " label_items.append(label.split(\",\")[0].strip(\"[\").strip(\"(\"))\n", - " label_items=np.asarray(label_items)\n", - " # Special cases that are not numeric are given numbers\n", - " lowestnumber = label_items[(label_items!=\"...\")&(label_items!=\"Missing\")].astype('float64').min()\n", - " label_items[label_items=='...']= lowestnumber-1\n", - " label_items[label_items=='Missing']= lowestnumber-2\n", - " # argsort based on the numbers\n", - " rank = label_items.astype('float64').argsort()\n", - " return rank\n", - " \n", - " # If the variable wasn't discretisized, simply argsort on the numbers\n", - " else:\n", - " label_items = dataframe.group.values\n", - " if len(label_items)>1:\n", - " lowestnumber = label_items[label_items.astype('O')!=\"Missing\"].astype('float64').min()\n", - " label_items[label_items.astype('O')=='Missing']= lowestnumber-2\n", - " rank = label_items.astype('float64').argsort()\n", - " return rank" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [], - "source": [ - "# Function for sorting cont.variables, whether or not they have undergone discritization\n", - "def sortnumeric_old(dataframe):\n", - " \n", - " # If the variable was discretisized\n", - " if dataframe.group.dtype=='object': #or# if np.array([str(unsorted_labels[i])[0] in [\"[\",\"(\",\"M\"] for i in range(0,len(unsorted_labels))]).all():\n", - " unsorted_labels = dataframe.group.values\n", - " label_items=[]\n", - " for label in unsorted_labels:\n", - " # For each bin label, retain the first value\n", - " label_items.append(label.split(\",\")[0].strip(\"[\").strip(\"(\"))\n", - " label_items=np.asarray(label_items)\n", - " # Special cases that are not numeric are given numbers\n", - " lowestnumber = label_items[(label_items!=\"...\")&(label_items!=\"Missing\")].astype('float64').min()\n", - " label_items[label_items=='...']= lowestnumber-1\n", - " label_items[label_items=='Missing']= lowestnumber-2\n", - " # argsort based on the numbers\n", - " rank = label_items.astype('float64').argsort()\n", - " return rank\n", - " \n", - " # If the variable wasn't discretisized, simply argsort on the numbers\n", - " else:\n", - " rank = dataframe.group.values.argsort()\n", - " return rank" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Function for sorting cat. variables\n", - "def sortobject(dataframe):\n", - " # Sort dataframe on increasing incidence values\n", - " unsorted_incidences = dataframe.incidence.values\n", - " rank = unsorted_incidences.argsort()\n", - " return rank" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "n_decimals = 2\n", - "average = round(df_out.TARGET[df_out.PARTITION==\"train\"].mean(),n_decimals)\n", - "\n", - "headers_to_output = list(df_auc['variable'])\n", - "for i,varname in enumerate(headers_to_output):\n", - " b_varname = 'B_'+varname\n", - " d_varname ='D_'+varname\n", - " #INCIDENCE CSV's\n", - " incidence_path = root+\"/data/univariate/incidence_\"+str(varname)+\".csv\"\n", - " groups_and_incidences = df_out.TARGET[df_out.PARTITION=='train'].groupby(df_out[b_varname]).mean()\n", - " n_groups= len(groups_and_incidences)\n", - " group = groups_and_incidences.index\n", - " incidence = groups_and_incidences.values.round(n_decimals)\n", - " size = df_out.TARGET[df_out.PARTITION=='train'].groupby(df_out[b_varname]).size().astype(float).values\n", - " df_incidence = pd.DataFrame( {'group':group\n", - " ,'incidence':incidence\n", - " ,'size':size\n", - " ,'average':average}\n", - " ,columns=['group','incidence','size','average'])\n", - " if varname in numeric_headers:\n", - " df_incidence = df_incidence.iloc[sortnumeric(df_incidence),:]\n", - " elif varname in object_headers:\n", - " df_incidence = df_incidence.iloc[sortobject(df_incidence),:]\n", - " else:\n", - " a=1\n", - " #df_incidence = df_incidence.iloc[sortother(df_incidence),:]\n", - " df_incidence.to_csv(path_or_buf=incidence_path\n", - " ,sep=';'\n", - " ,index=False\n", - " ,encoding='utf-8'\n", - " ,line_terminator='\\n') #quoting=csv.QUOTE_NONNUMERIC\n", - " \n", - " #CORRELATION CSV's\n", - " correlation_path = root+\"/data/univariate/correlations_\"+str(varname)+\".csv\"\n", - " Variable = [v.strip(\"D_\") for v in df_corr[d_varname].index]\n", - " Correlation = abs(df_corr[d_varname].values).round(n_decimals)\n", - " Sign = np.array([\"+\",\"-\"])[(df_corr[d_varname].values<0).astype(int)]\n", - " AUC = np.array([df_auc.loc[df_auc['variable']== v,'AUC test'].values[0] for v in Variable]).round(n_decimals)\n", - " df_correlation = pd.DataFrame({\"Variable\":Variable\n", - " ,\"Correlation\":Correlation\n", - " ,\"Sign\":Sign\n", - " ,\"AUC\": AUC}\n", - " ,columns=[\"Variable\",\"Correlation\",\"Sign\",\"AUC\"]) \n", - " df_correlation.sort_values(by='Correlation', ascending=False, inplace=True)\n", - " df_correlation = df_correlation.loc[df_correlation.Variable!=varname,:]\n", - " df_correlation.to_csv(path_or_buf=correlation_path\n", - " ,sep=';'\n", - " ,index=False\n", - " ,encoding='utf-8'\n", - " ,line_terminator='\\n') # quoting=csv.QUOTE_NONNUMERIC" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Variable Preselections" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "selections_path = root+'/data/univariate/variable_selections.csv'\n", - "df_variable_selections.to_csv(path_or_buf=selections_path\n", - " ,sep=';'\n", - " ,index=False\n", - " ,encoding='utf-8'\n", - " ,line_terminator='\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Result dataframe for Modeling input" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [], - "source": [ - "out_path = root+\"/data/univariate/df_univariate.csv\"\n", - "df_out.to_csv(path_or_buf=out_path, sep=';', index=False, encoding='utf-8', line_terminator='\\n', quoting=csv.QUOTE_NONNUMERIC)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Modeltab reset" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Generate modeltab info\n", - "filename = root+\"/data/univariate/modeltab_info.csv\"\n", - "with open(filename, 'w') as csvfile:\n", - " write=csv.writer(csvfile, delimiter =';')\n", - " write.writerow([\"key\",\"value\"])\n", - " write.writerow([\"run\",\"Default\"])\n", - " write.writerow([\"new\",\"Alternative 1\"])\n", - " write.writerow([\"new_template\",\"Default\"])\n", - " write.writerow([\"champ\",\"Default\"])\n", - " write.writerow([\"score\",\"Default\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Log messages" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "log.append(\"-- Univariate analysis completed --\"+\"\\n\")" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [], - "source": [ - "log_file = open(root+'/python/univariate.log','w')\n", - "log_file.write('\\n'.join(log))\n", - "log_file.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stop script" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ok\n" - ] - } - ], - "source": [ - "print(\"ok\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.3" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} From d09bcdd7113317f4825b7745e70438df51340656 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 11:06:46 +0100 Subject: [PATCH 27/98] Update README.md --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2c025a6..50cb9ef 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,61 @@ # COBRA :snake: -**Cobra** here on GitHub is refactored web-based cobra originally developed by Guillaume. The goal is to wrap the back-end into easy to use Python package. +**Cobra** is a Python package that implements the Python Predictions methodology for predictive analytics. It consists of a main script/notebook that can be used to build and save a predictive model only by setting several parameters. The main scripts itself consists of several modules that can be used independently of one another to build custom scripts. -If you wish to modify the code, the best is to fork the repository or create another branch! +Note that this package is a refactored version of the back-end of the original web-based cobra, developed by _Guillaume Marion_ (former Python Predictions employee). -:heavy_exclamation_mark: Still lots of :bug: and under construction, keep that in mind:heavy_exclamation_mark: +:heavy_exclamation_mark: Be aware that there could still be :bug: in the code :heavy_exclamation_mark: -## What can Cobra 1.0 do: - * Transform given .csv to be ready to use for prediction modelling - * _Clense the headers, partition into train/selection/validation sets, sample, bins and regroups variables and add columns with incidence rate per categories._ +## Getting started + +These instructions will get you a copy of the project up and running on your local machine for usage, development and testing purposes. + +### What can cobra do? + + * Transform given pandas DataFrame to be ready to use for prediction modelling: partition into train/selection/validation sets, create bins from continuous variables, regroup categorical variables and add columns with incidence rate per category/bin. * Perform univariate selection based on AUC * Find best model by forward selection * Visualize the results * Allow iteration among each step for the analyst + +### Requirements + +This package requires the usual Python packages for data science: + +* numpy +* scipy +* matplotlib +* seaborn +* pandas +* scikit-learn + +These packages, along with their versions are listed in `requirements.txt` and `conda_env.txt`. To install these packages using pip, run + +``` +pip install requirements.txt +``` + +or using conda + +``` +conda install requirements.txt +``` + +### Installation + +As this package is an internal package that is not open-sourced, it is not available through `pip` or `conda`. As a result, the package has to be installed manually using the following steps: + + * Clone this repository. + * Open a shell that can execute python code and navigate to the folder where this repo was cloned in. + * Once you are in the folder, execute `python setup.py install`. + +### Usage + +TO DO + +## Development + +We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. + + -## Installation - * Clone this repository to your local PC (use GitHub Desktop). This assumes that the cloned repository will be in this directory `C:\Local\pers\Documents\GitHub\cobra` - * Open Powershell and navigate to that folder - * Once you are in the folder, execute `python setup.py install`. This is how the line should look like: - `PS C:\Local\pers\Documents\GitHub\cobra> python setup.py install` - * Restart kernel and you are ready to go - * For example of use, see the Jupyter Notebook in `examples` folder From 75eb75c3a8d64cf935ddd9445f57e24320010b36 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 15:45:41 +0100 Subject: [PATCH 28/98] Add (de)serializers to CategoricalDataProcessor --- .../categorical_data_processor.py | 57 +++++++++++++++++ .../test_categorical_data_processor.py | 61 +++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index a77851f..26d9964 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -51,6 +51,10 @@ class CategoricalDataProcessor(BaseEstimator): Whether contingency table should be scaled before chi^2.' """ + valid_keys = ["regroup", "regroup_name", "keep_missing", + "category_size_threshold", "p_value_threshold", + "scale_contingency_table", "forced_categories"] + def __init__(self, regroup: bool=True, regroup_name: str="Other", keep_missing: bool=True, category_size_threshold: int=5, @@ -69,6 +73,59 @@ def __init__(self, regroup: bool=True, regroup_name: str="Other", # dict to store fitted output in self._combined_categories_by_column = {} + def attributes_to_dict(self) -> dict: + """Return the attributes of CategoricalDataProcessor as a dictionary + + Returns + ------- + dict + Contains the attributes of CategoricalDataProcessor instance with + the attribute name as key + """ + params = self.get_params() + + params["_combined_categories_by_column"] = { + key: list(value) + for key, value in self._combined_categories_by_column.items() + } + + return params + + def set_attributes_from_dict(self, params: dict): + """Set instance attributes from a dictionary of values with key the + name of the attribute. + + Parameters + ---------- + params : dict + Contains the attributes of CategoricalDataProcessor with their + names as key. + + Raises + ------ + ValueError + In case _combined_categories_by_column is not of type dict + """ + _fitted_output = params.pop("_combined_categories_by_column", {}) + + if type(_fitted_output) != dict: + raise ValueError("_combined_categories_by_column is expected to " + "be a dict but is of type {} instead" + .format(type(_fitted_output))) + + # Clean out params dictionary to remove unknown keys (for safety!) + params = {key: params[key] for key in params if key in self.valid_keys} + + # We cannot turn this method into a classmethod as we want to make use + # of the following method from BaseEstimator: + self.set_params(**params) + + self._combined_categories_by_column = { + key: set(value) for key, value in _fitted_output.items() + } + + return self + def fit(self, data: pd.DataFrame, column_names: list, target_column: str): """Fit the CategoricalDataProcessor diff --git a/tests/preprocessing/test_categorical_data_processor.py b/tests/preprocessing/test_categorical_data_processor.py index 5155127..a918651 100644 --- a/tests/preprocessing/test_categorical_data_processor.py +++ b/tests/preprocessing/test_categorical_data_processor.py @@ -8,6 +8,67 @@ class TestCategoricalDataProcessor: + def test_attributes_to_dict(self): + + processor = CategoricalDataProcessor() + + combined_categories = ["a", "b", "c"] + processor._combined_categories_by_column = { + "variable": set(combined_categories) + } + + actual = processor.attributes_to_dict() + + expected = { + "regroup": True, + "regroup_name": "Other", + "keep_missing": True, + "category_size_threshold": 5, + "p_value_threshold": 0.001, + "scale_contingency_table": True, + "forced_categories": {}, + "_combined_categories_by_column": { + "variable": combined_categories + } + } + + assert actual == expected + + @pytest.mark.parametrize("attribute", + ["regroup", "regroup_name", "keep_missing", + "category_size_threshold", "p_value_threshold", + "scale_contingency_table", "forced_categories", + "_combined_categories_by_column"]) + def test_set_attributes_from_dict(self, attribute): + + processor = CategoricalDataProcessor() + + combined_categories = ["a", "b", "c"] + params = { + "regroup": True, + "regroup_name": "Other", + "keep_missing": True, + "category_size_threshold": 5, + "p_value_threshold": 0.001, + "scale_contingency_table": True, + "forced_categories": {}, + "_combined_categories_by_column": { + "variable": combined_categories + } + } + + expected = params[attribute] + + if attribute == "_combined_categories_by_column": + # list is transformed to a set in CategoricalDataProcessor + expected = {"variable": set(combined_categories)} + + processor.set_attributes_from_dict(params) + + actual = getattr(processor, attribute) + + assert actual == expected + @pytest.mark.parametrize("scale_contingency_table, expected", [(False, 0.013288667), (True, 0.434373)]) From 08eda64beefffd1a457ade8966687f215d03a5c3 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Dec 2019 16:10:32 +0100 Subject: [PATCH 29/98] Fix in unittest for CategoricalDataProcessor --- tests/preprocessing/test_categorical_data_processor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/preprocessing/test_categorical_data_processor.py b/tests/preprocessing/test_categorical_data_processor.py index a918651..a1ea39a 100644 --- a/tests/preprocessing/test_categorical_data_processor.py +++ b/tests/preprocessing/test_categorical_data_processor.py @@ -28,7 +28,7 @@ def test_attributes_to_dict(self): "scale_contingency_table": True, "forced_categories": {}, "_combined_categories_by_column": { - "variable": combined_categories + "variable": list(set(combined_categories)) } } @@ -70,8 +70,8 @@ def test_set_attributes_from_dict(self, attribute): assert actual == expected @pytest.mark.parametrize("scale_contingency_table, expected", - [(False, 0.013288667), - (True, 0.434373)]) + [(False, 0.01329), + (True, 0.43437)]) def test_compute_p_value(self, scale_contingency_table, expected): X = pd.Series(data=(["c1"]*70 + ["c2"]*20 + ["c3"]*10)) @@ -81,7 +81,7 @@ def test_compute_p_value(self, scale_contingency_table, expected): actual = (CategoricalDataProcessor ._compute_p_value(X, y, category, scale_contingency_table)) - assert pytest.approx(actual) == expected + assert pytest.approx(actual, abs=1e-5) == expected def test_get_small_categories(self): From 10cb556757c9770dc877296884a4cdf480004374 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 13:59:15 +0100 Subject: [PATCH 30/98] Update .gitignore --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9ef222c..37c9f41 100644 --- a/.gitignore +++ b/.gitignore @@ -103,5 +103,9 @@ ENV/ # mypy .mypy_cache/ -#VScode settins +# VScode settins .vscode + +# Other ignore files +*.pptx +*.ppt From cff611e74b4a761a3ebfb38fcf56b4e54d3c1ad7 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 13:59:56 +0100 Subject: [PATCH 31/98] Add fit_transform method to TargetEncoder --- cobra/preprocessing/kbins_discretizer.py | 2 +- cobra/preprocessing/target_encoder.py | 26 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 603bac5..5d126c9 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -307,7 +307,7 @@ def _transform_column(self, data: pd.DataFrame, data[column_name_bin] = pd.cut(x=data[column_name], bins=interval_idx) - # Rename bins so that the output has a proper format + # Rename bins so that the output has a proper format bin_labels = self._create_bin_labels(bins) data[column_name_bin] = (data[column_name_bin] diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index 328d431..ea63e13 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -217,6 +217,28 @@ def transform(self, data: pd.DataFrame, return data + def fit_transform(self, data: pd.DataFrame, + column_names: list, + target_column: str) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be encoded + column_names : list + Columns of data to be encoded + target_column : str + Column name of the target + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + self.fit(data, column_names, target_column) + return self.transform(data, column_names) + @staticmethod def _clean_column_name(column_name: str) -> str: """Clean column name string by removing "_bin" and adding "_enc" @@ -233,5 +255,9 @@ def _clean_column_name(column_name: str) -> str: """ if "_bin" in column_name: return column_name.replace("_bin", "") + "_enc" + elif "_processed" in column_name: + return column_name.replace("_processed", "") + "_enc" + elif "_cleaned" in column_name: + return column_name.replace("_cleaned", "") + "_enc" else: return column_name + "_enc" From b01f78d64c72ca1474aa3c11ba13000d8a7bfdbe Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 14:00:47 +0100 Subject: [PATCH 32/98] Update utils.py --- cobra/utils.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cobra/utils.py b/cobra/utils.py index ea09c64..e7057da 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -26,11 +26,6 @@ def get_column_datatypes(data: pd.DataFrame, dict Description """ - column_names = list(data.columns) - - # dummies variables: case they have only 2 values - vars_dummy = set([col for col in column_names - if len(data[col].unique()) == 2]) # categorical vars vars_cat = (set(data.dtypes[data.dtypes == object].index) @@ -41,9 +36,6 @@ def get_column_datatypes(data: pd.DataFrame, bool_arr_is_numeric = is_number(data.dtypes) vars_numeric = set(data.columns[bool_arr_is_numeric]) - # remove dummy variables from set - vars_numeric = vars_numeric.difference(vars_dummy) - # Remark: numeric variables can still be "categorical" # i.e. when they only contain some distinct values! # We only consider a variable continuous if they have more distinct values @@ -62,8 +54,7 @@ def get_column_datatypes(data: pd.DataFrame, if id_column_name: vars_cat = vars_cat.difference(set([id_column_name])) if target_column_name: - vars_dummy = vars_dummy.difference(set([target_column_name])) + vars_cat = vars_cat.difference(set([target_column_name])) - return {"numeric_variables": vars_numeric, - "categorical_variables": vars_cat, - "dummy_variables": vars_dummy} + return {"numeric_variables": list(vars_numeric), + "categorical_variables": list(vars_cat)} From 6cc18a9d53a62e94463786d64d990724ad8f7817 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 14:02:47 +0100 Subject: [PATCH 33/98] Change logic of CategoricalDataProcessor Instead of keeping a list per column of categories to merge, we now keep a list of categories to keep. This has the advantage that if a new category arises in a test set (or a future dataset to score), this will automatically be renamed to "Other" so that TargetEncoder can handle this. Unit tests were also updated for this change --- .../categorical_data_processor.py | 44 +++++++++++-------- .../test_categorical_data_processor.py | 33 +++++++------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index 26d9964..0ce21e9 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -71,7 +71,7 @@ def __init__(self, regroup: bool=True, regroup_name: str="Other", self.forced_categories = forced_categories # dict to store fitted output in - self._combined_categories_by_column = {} + self._cleaned_categories_by_column = {} def attributes_to_dict(self) -> dict: """Return the attributes of CategoricalDataProcessor as a dictionary @@ -84,9 +84,9 @@ def attributes_to_dict(self) -> dict: """ params = self.get_params() - params["_combined_categories_by_column"] = { + params["_cleaned_categories_by_column"] = { key: list(value) - for key, value in self._combined_categories_by_column.items() + for key, value in self._cleaned_categories_by_column.items() } return params @@ -104,12 +104,12 @@ def set_attributes_from_dict(self, params: dict): Raises ------ ValueError - In case _combined_categories_by_column is not of type dict + In case _cleaned_categories_by_column is not of type dict """ - _fitted_output = params.pop("_combined_categories_by_column", {}) + _fitted_output = params.pop("_cleaned_categories_by_column", {}) if type(_fitted_output) != dict: - raise ValueError("_combined_categories_by_column is expected to " + raise ValueError("_cleaned_categories_by_column is expected to " "be a dict but is of type {} instead" .format(type(_fitted_output))) @@ -120,7 +120,7 @@ def set_attributes_from_dict(self, params: dict): # of the following method from BaseEstimator: self.set_params(**params) - self._combined_categories_by_column = { + self._cleaned_categories_by_column = { key: set(value) for key, value in _fitted_output.items() } @@ -153,14 +153,14 @@ def fit(self, data: pd.DataFrame, column_names: list, "skipped in fitting" .format(column_name)) continue - combined_cats = self._fit_column(data, column_name, target_column) + cleaned_cats = self._fit_column(data, column_name, target_column) # Remove forced categories forced_cats = self.forced_categories.get(column_name, set()) - combined_cats = combined_cats.difference(forced_cats) + cleaned_cats = cleaned_cats.union(forced_cats) - # Add to _combined_categories_by_column for later use - self._combined_categories_by_column[column_name] = combined_cats + # Add to _cleaned_categories_by_column for later use + self._cleaned_categories_by_column[column_name] = cleaned_cats def _fit_column(self, data: pd.DataFrame, column_name: str, target_column) -> set: @@ -185,7 +185,10 @@ def _fit_column(self, data: pd.DataFrame, column_name: str, combined_categories = set() # replace missings and get unique categories as a list - X = CategoricalDataProcessor._replace_missings(data[column_name]) + X = (CategoricalDataProcessor + ._replace_missings(data[column_name]) + .astype(object)) + unique_categories = list(X.unique()) # get small categories and add them to the merged category list @@ -212,7 +215,7 @@ def _fit_column(self, data: pd.DataFrame, column_name: str, if self.keep_missing: combined_categories.discard("Missing") - return combined_categories + return set(unique_categories).difference(combined_categories) def transform(self, data: pd.DataFrame, column_names: list) -> pd.DataFrame: @@ -231,7 +234,7 @@ def transform(self, data: pd.DataFrame, data with additional discretized variables """ - if self.regroup and len(self._combined_categories_by_column) == 0: + if self.regroup and len(self._cleaned_categories_by_column) == 0: msg = ("{} instance is not fitted yet. Call 'fit' with " "appropriate arguments before using this method.") @@ -268,7 +271,7 @@ def _transform_column(self, data: pd.DataFrame, """ column_name_clean = column_name + "_processed" - data[column_name_clean] = data[column_name] + data[column_name_clean] = data[column_name].astype(object) # Fill missings first data[column_name_clean] = (CategoricalDataProcessor @@ -276,11 +279,14 @@ def _transform_column(self, data: pd.DataFrame, column_name_clean)) if self.regroup: - categories = self._combined_categories_by_column.get(column_name) + categories = self._cleaned_categories_by_column.get(column_name) if not categories: - log.warning("Column '{}' is not in fitted output " - "and will be skipped".format(column_name)) + # Log warning if categories is None, which indicates it is + # not in fitted output + if categories is None: + log.warning("Column '{}' is not in fitted output " + "and will be skipped".format(column_name)) return data data[column_name_clean] = (CategoricalDataProcessor @@ -425,4 +431,4 @@ def _replace_categories(data: pd.Series, categories: set) -> pd.Series: pd.Series Description """ - return data.apply(lambda x: x if x not in categories else "Other") + return data.apply(lambda x: x if x in categories else "Other") diff --git a/tests/preprocessing/test_categorical_data_processor.py b/tests/preprocessing/test_categorical_data_processor.py index a1ea39a..95ebc56 100644 --- a/tests/preprocessing/test_categorical_data_processor.py +++ b/tests/preprocessing/test_categorical_data_processor.py @@ -12,9 +12,9 @@ def test_attributes_to_dict(self): processor = CategoricalDataProcessor() - combined_categories = ["a", "b", "c"] - processor._combined_categories_by_column = { - "variable": set(combined_categories) + cleaned_categories = ["a", "b", "c"] + processor._cleaned_categories_by_column = { + "variable": set(cleaned_categories) } actual = processor.attributes_to_dict() @@ -27,8 +27,8 @@ def test_attributes_to_dict(self): "p_value_threshold": 0.001, "scale_contingency_table": True, "forced_categories": {}, - "_combined_categories_by_column": { - "variable": list(set(combined_categories)) + "_cleaned_categories_by_column": { + "variable": list(set(cleaned_categories)) } } @@ -38,12 +38,12 @@ def test_attributes_to_dict(self): ["regroup", "regroup_name", "keep_missing", "category_size_threshold", "p_value_threshold", "scale_contingency_table", "forced_categories", - "_combined_categories_by_column"]) + "_cleaned_categories_by_column"]) def test_set_attributes_from_dict(self, attribute): processor = CategoricalDataProcessor() - combined_categories = ["a", "b", "c"] + cleaned_categories = ["a", "b", "c"] params = { "regroup": True, "regroup_name": "Other", @@ -52,16 +52,16 @@ def test_set_attributes_from_dict(self, attribute): "p_value_threshold": 0.001, "scale_contingency_table": True, "forced_categories": {}, - "_combined_categories_by_column": { - "variable": combined_categories + "_cleaned_categories_by_column": { + "variable": cleaned_categories } } expected = params[attribute] - if attribute == "_combined_categories_by_column": + if attribute == "_cleaned_categories_by_column": # list is transformed to a set in CategoricalDataProcessor - expected = {"variable": set(combined_categories)} + expected = {"variable": set(cleaned_categories)} processor.set_attributes_from_dict(params) @@ -106,15 +106,16 @@ def test_replace_missings(self): pd.testing.assert_frame_equal(actual, expected) - @pytest.mark.parametrize("combined_categories, expected", - [({"c3", "c4"}, + @pytest.mark.parametrize("cleaned_categories, expected", + [({"c1", "c2"}, pd.Series(data=["c1", "c2", "Other", "Other"])), - ({}, pd.Series(data=["c1", "c2", "c3", "c4"]))]) - def test_replace_categories(self, combined_categories, expected): + ({"c1", "c2", "c3", "c4"}, + pd.Series(data=["c1", "c2", "c3", "c4"]))]) + def test_replace_categories(self, cleaned_categories, expected): data = pd.Series(data=["c1", "c2", "c3", "c4"]) actual = (CategoricalDataProcessor - ._replace_categories(data, combined_categories)) + ._replace_categories(data, cleaned_categories)) pd.testing.assert_series_equal(actual, expected) From 5e4a06dbccbdf89253666469d20a64c977daf577 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 14:05:46 +0100 Subject: [PATCH 34/98] Add PreProcessor class as facade for preprocessing The PreProcessor class unifies all the logic for preprocessing and combines all preprocessing steps (which can be found in separate classes). This means that the PreProcessor class is the main class to use from on. Furthermore, it also has functionality to store the preprocessing pipeline as a json file so that it can be easily reused and methods for train-test split. --- cobra/preprocessing/preprocessor.py | 508 +++++++++++++++++++++++ tests/preprocessing/test_preprocessor.py | 36 ++ 2 files changed, 544 insertions(+) create mode 100644 cobra/preprocessing/preprocessor.py create mode 100644 tests/preprocessing/test_preprocessor.py diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py new file mode 100644 index 0000000..7cf1959 --- /dev/null +++ b/cobra/preprocessing/preprocessor.py @@ -0,0 +1,508 @@ +""" +This module is a rework of the old cobra data_preparation.py. Here we will make +use of the classes for discretization, preprocessing of categorical variables +and incidence replacement. All of which will be employed to create a +preprocessing pipeline, which can be stored as a JSON file so that it can +easily be re-used for scoring. + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (implementation) +""" +# std lib imports +import json +from typing import Optional +import inspect +from datetime import datetime + +import logging +log = logging.getLogger(__name__) +# third party imports +import pandas as pd +from sklearn.model_selection import train_test_split +from sklearn.base import BaseEstimator +from sklearn.exceptions import NotFittedError +# custom imports +from cobra.preprocessing import KBinsDiscretizer +from cobra.preprocessing import TargetEncoder +from cobra.preprocessing import CategoricalDataProcessor + +import cobra.utils as utils + + +class PreProcessor(BaseEstimator): + + """Summary + + Attributes + ---------- + categorical_data_processor : CategoricalDataProcessor + Instance of CategoricalDataProcessor to do the prepocessing of + categorical variables + discretizer : KBinsDiscretizer + Instance of KBinsDiscretizer to do the prepocessing of continuous + variables by means of discretization + numeric_threshold : int + Threshold to decide whether a numeric variable is in fact a categorical + one based on the number of unique values of that variable + selection_pct : float + Percentage of data to add to selection dataset + serialization_path : str + path to save the pipeline to + stratify_split : bool + Whether or not to stratify the train-test split + target_encoder : TargetEncoder + Instance of TargetEncoder to do the incidence replacement + train_pct : float + Percentage of data to add to training dataset + validation_pct : float + Percentage of data to add to validation dataset + """ + + def __init__(self, + train_pct: float, + selection_pct: float, + validation_pct: float, + stratify_split: bool, + categorical_data_processor: CategoricalDataProcessor, + discretizer: KBinsDiscretizer, + target_encoder: TargetEncoder, + threshold_numeric_is_categorical: int=None, + serialization_path: str=None, + continuous_vars: list=[], + discrete_vars: list=[]): + + self.train_pct = train_pct + self.selection_pct = selection_pct + self.validation_pct = validation_pct + self.stratify_split = stratify_split + + self.numeric_threshold = threshold_numeric_is_categorical + + self.serialization_path = serialization_path + + self.categorical_data_processor = categorical_data_processor + self.discretizer = discretizer + self.target_encoder = target_encoder + + # placeholders for columns by datatype + # Included as constructor argument to get them from pipeline + self._is_fitted = False + self._continuous_vars = continuous_vars + self._discrete_vars = discrete_vars + + @classmethod + def from_params(cls, + train_pct: float, + selection_pct: float, + validation_pct: float, + stratify_split: bool=True, + threshold_numeric_is_categorical: int=None, + n_bins: int=10, + strategy: str="quantile", + closed: str="right", + auto_adapt_bins: bool=False, + starting_precision: int=0, + label_format: str="{} - {}", + change_endpoint_format: bool=False, + regroup: bool=True, + regroup_name: str="Other", + keep_missing: bool=True, + category_size_threshold: int=5, + p_value_threshold: float=0.001, + scale_contingency_table: bool=True, + forced_categories: dict={}, + weight: float=0.0, + serialization_path: Optional[str]=None): + """Constructor to instantiate PreProcessor from all the parameters + that can be set in all its required classes. + + Parameters + ---------- + train_pct : float + Percentage of data to add to training dataset + selection_pct : float + Percentage of data to add to selection dataset + validation_pct : float + Percentage of data to add to validation dataset + stratify_split : bool, optional + Whether or not to stratify the train-test split + threshold_numeric_is_categorical : int, optional + Threshold to decide whether a numeric variable is in fact a + categorical one based on the number of unique values of + that variable + n_bins : int, optional + Number of bins to produce. Raises ValueError if ``n_bins < 2``. + strategy : str, optional + Binning strategy. Currently only "uniform" and "quantile" + e.g. equifrequency is supported + closed : str, optional + Whether to close the bins (intervals) from the left or right + auto_adapt_bins : bool, optional + reduces the number of bins (starting from n_bins) as a function of + the number of missings + starting_precision : int, optional + Initial precision for the bin edges to start from, + can also be negative. Given a list of bin edges, the class will + automatically choose the minimal precision required to have proper + bins e.g. [5.5555, 5.5744, ...] will be rounded + to [5.56, 5.57, ...]. In case of a negative number, an attempt will + be made to round up the numbers of the bin edges + e.g. 5.55 -> 10, 146 -> 100, ... + label_format : str, optional + format string to display the bin labels + e.g. min - max, (min, max], ... + change_endpoint_format : bool, optional + Whether or not to change the format of the lower and upper bins + into "< x" and "> y" resp. + regroup : bool + Whether or not to regroup categories + regroup_name : str + New name of the non-significant regrouped variables + keep_missing : bool + Whether or not to keep missing as a separate category + category_size_threshold : int + minimal size of a category to keep it as a separate category + p_value_threshold : float + Significance threshold for regroupping. + forced_categories : dict + Map to prevent certain categories from being group into "Other" + for each colum - dict of the form {col:[forced vars]}. + scale_contingency_table : bool + Whether contingency table should be scaled before chi^2.' + weight : float, optional + Smoothing parameters (non-negative). The higher the value of the + parameter, the bigger the contribution of the overall mean. + When set to zero, there is no smoothing + (e.g. the pure target incidence is used). + serialization_path : str, optional + path to save the pipeline to + + Returns + ------- + PreProcessor + Description + """ + categorical_data_processor = CategoricalDataProcessor( + regroup, + regroup_name, + keep_missing, + category_size_threshold, + p_value_threshold, + scale_contingency_table, + forced_categories) + discretizer = KBinsDiscretizer(n_bins, strategy, closed, + auto_adapt_bins, + starting_precision, + label_format, + change_endpoint_format) + + target_encoder = TargetEncoder(weight) + + if not threshold_numeric_is_categorical: + threshold_numeric_is_categorical = n_bins + + return cls(train_pct, selection_pct, validation_pct, stratify_split, + categorical_data_processor, discretizer, target_encoder, + threshold_numeric_is_categorical, serialization_path) + + @classmethod + def from_pipeline(cls, pipeline_path: str): + """Summary + + Parameters + ---------- + pipeline_path : str + Description + + Returns + ------- + PreProcessor + Instance of PreProcessor instantiated from a stored pipeline + + Raises + ------ + ValueError + Description + """ + with open(pipeline_path, "r") as file: + pipeline = json.load(file) + + if not PreProcessor._is_valid_pipeline(pipeline): + raise ValueError("Invalid pipeline") # To do: specify error + + categorical_data_processor = CategoricalDataProcessor() + categorical_data_processor.set_attributes_from_dict( + pipeline["categorical_data_processor"] + ) + + discretizer = KBinsDiscretizer() + discretizer.set_attributes_from_dict(pipeline["discretizer"]) + + target_encoder = TargetEncoder() + target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) + + return cls(pipeline["train_pct"], pipeline["selection_pct"], + pipeline["validation_pct"], pipeline["stratify_split"], + categorical_data_processor, + discretizer, target_encoder, + pipeline["threshold_numeric_is_categorical"], + continuous_vars=pipeline["continuous_vars"], + discrete_vars=pipeline["discrete_vars"]) + + def fit(self, data: pd.DataFrame, target_column_name: str, + id_column_name: str=None): + """Fit the data to the preprocessing pipeline + + Parameters + ---------- + data : pd.DataFrame + Data to be preprocessed + target_column_name : str + Name of the target column + id_column_name : str, optional + Name of the id column + """ + # get list of all variables + var_list = self._get_variable_list(data, target_column_name, + id_column_name) + + data = (PreProcessor + .train_selection_validation_split(data, target_column_name, + self.train_pct, + self.selection_pct, + self.validation_pct, + self.stratify_split)) + + # Select train data to fit preprocessing pipeline + train_data = data[data["split"] == "train"] + + # Fit discretizer, categorical preprocessor & target encoder + # Note that in order to fit target_encoder, we first have to transform + # the data using the fitted discretizer & categorical_data_processor + if self._continuous_vars: + self.discretizer.fit(train_data, self._continuous_vars) + train_data = self.discretizer.transform(train_data, + self._continuous_vars) + + if self._discrete_vars: + self.categorical_data_processor.fit(train_data, + self._discrete_vars, + target_column_name) + train_data = (self.categorical_data_processor + .transform(train_data, self._discrete_vars)) + + self.target_encoder.fit(train_data, var_list, target_column_name) + + self._is_fitted = True # set fitted boolean to True + # serialize the pipeline to store the fitted output along with the + # various parameters that were used + self._serialize() + + def transform(self, data: pd.DataFrame, target_column_name: str, + id_column_name: str=None) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + target_column_name : str + Description + id_column_name : str, optional + Description + + Returns + ------- + pd.DataFrame + Description + """ + + if not self._is_fitted: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + var_lists = ([col + "_processed" for col in self._discrete_vars] + + [col + "_bin" for col in self._continuous_vars]) + + data = self.discretizer.transform(data, self._continuous_vars) + data = self.categorical_data_processor.transform(data, + self._discrete_vars) + + data = self.target_encoder.transform(data, var_lists) + + return data + + @staticmethod + def train_selection_validation_split(data: pd.DataFrame, + target_column_name: str, + train_pct: float=0.6, + selection_pct: float=0.2, + validation_pct: float=0.2, + stratify_split=True)->pd.DataFrame: + """Split dataset into train-selection-validation datasets and merge + them into one big DataFrame with an additional column "split" + indicating to which dataset the corresponding row belongs to. + + Parameters + ---------- + data : pd.DataFrame + Input dataset to split into train-selection and validation sets + target_column_name : str + Name of the target column + train_pct : float, optional + Percentage data to put in train set + selection_pct : float, optional + Percentage data to put in selection set + validation_pct : float, optional + Percentage data to put in validation set + stratify_split : bool, optional + Whether or not to stratify the train-test split + + Returns + ------- + pd.DataFrame + Description + """ + column_names = list(data.columns) + + predictors = [col for col in column_names if col != target_column_name] + + test_pct = selection_pct + validation_pct + + X = data[predictors] + y = data[target_column_name] + + stratify = None + if stratify_split: + stratify = y + + X_train, X_test, y_train, y_test = train_test_split(X, y, + test_size=test_pct, + random_state=42, + stratify=stratify) + + if stratify_split: + stratify = y_test + + X_sel, X_val, y_sel, y_val = train_test_split(X_test, y_test, + test_size=validation_pct, + random_state=42, + stratify=stratify) + + df_train = pd.DataFrame(X_train, columns=predictors) + df_train[target_column_name] = y_train + df_train["split"] = "train" + + df_selection = pd.DataFrame(X_sel, columns=predictors) + df_selection[target_column_name] = y_sel + df_selection["split"] = "selection" + + df_validation = pd.DataFrame(X_val, columns=predictors) + df_validation[target_column_name] = y_val + df_validation["split"] = "validation" + + return (pd.concat([df_train, df_selection, df_validation]) + .reset_index(drop=True)) + + def _get_variable_list(self, data: pd.DataFrame, + target_column_name: str, + id_column_name: str=None): + """Get list of variables and split into numeric and categorical + + Parameters + ---------- + data : pd.DataFrame + Data to be preprocessed + target_column_name : str + Name of the target column + id_column_name : str, optional + Name of the id column + """ + + columns_by_datatype = utils.get_column_datatypes(data, + target_column_name, + id_column_name, + self.numeric_threshold + ) + + self._continuous_vars = columns_by_datatype["numeric_variables"] + self._discrete_vars = columns_by_datatype["categorical_variables"] + + log.info("Numeric variables: {}".format(self._continuous_vars)) + log.info("Categorical variables:".format(self._discrete_vars)) + + var_list = ([col + "_processed" for col in self._discrete_vars] + + [col + "_bin" for col in self._continuous_vars]) + + if not var_list: + raise ValueError("Variable var_list is None or empty list") + + return var_list + + def _serialize(self) -> dict: + """Serialize the preprocessing pipeline by writing all its required + parameters to a JSON file. + + Returns + ------- + dict + Return the pipeline as a dictionary + """ + pipeline = { + "metadata": { + "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S") + } + } + + pipeline["train_pct"] = self.train_pct + pipeline["selection_pct"] = self.selection_pct + pipeline["validation_pct"] = self.validation_pct + pipeline["stratify_split"] = self.stratify_split + pipeline["threshold_numeric_is_categorical"] = self.numeric_threshold + + pipeline["categorical_data_processor"] = (self + .categorical_data_processor + .attributes_to_dict()) + + pipeline["discretizer"] = self.discretizer.attributes_to_dict() + pipeline["target_encoder"] = (self.target_encoder.attributes_to_dict()) + + pipeline["continuous_vars"] = self._continuous_vars + pipeline["discrete_vars"] = self._discrete_vars + + if self.serialization_path: + path = self.serialization_path + else: + path = "./pipeline_tmp.json" + + with open(path, "w") as file: + json.dump(pipeline, file) + + return pipeline + + @staticmethod + def _is_valid_pipeline(pipeline: dict) -> bool: + """Validate the loaded pipeline by checking if all required parameters + are present (and no others!). + + Parameters + ---------- + pipeline : dict + Loaded pipeline from json file + """ + keys = inspect.getfullargspec(PreProcessor.from_params).args + valid_keys = set([key for key in keys + if key not in ["cls", "serialization_path"]]) + + input_keys = set() + for key in pipeline: + if key in ["categorical_data_processor", "discretizer", + "target_encoder"]: + input_keys = input_keys.union(set(pipeline[key].keys())) + elif key != "metadata": + input_keys.add(key) + + return valid_keys == input_keys diff --git a/tests/preprocessing/test_preprocessor.py b/tests/preprocessing/test_preprocessor.py new file mode 100644 index 0000000..1548b9e --- /dev/null +++ b/tests/preprocessing/test_preprocessor.py @@ -0,0 +1,36 @@ +from contextlib import contextmanager +import pytest + +import numpy as np +import pandas as pd + +#from cobra.preprocessing.preprocessor import PreProcessor + + +@contextmanager +def does_not_raise(): + yield + + +class TestPreProcessor: + + def test_from_params(self): + pass + + def test_from_pipeline(self): + pass + + def test_fit(self): + pass + + def test_transform(self): + pass + + def test_train_selection_validation_split(self): + pass + + def test_serialize(self): + pass + + def test_is_valid_pipeline(self): + pass From c8f6c5c2ae356b46b0d3616dc94ed05c67c6df14 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 14:21:10 +0100 Subject: [PATCH 35/98] Bug fix in PreProcessor._is_valid_pipeline --- cobra/preprocessing/preprocessor.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 7cf1959..2cc841d 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -247,8 +247,8 @@ def from_pipeline(cls, pipeline_path: str): categorical_data_processor, discretizer, target_encoder, pipeline["threshold_numeric_is_categorical"], - continuous_vars=pipeline["continuous_vars"], - discrete_vars=pipeline["discrete_vars"]) + continuous_vars=pipeline["_continuous_vars"], + discrete_vars=pipeline["_discrete_vars"]) def fit(self, data: pd.DataFrame, target_column_name: str, id_column_name: str=None): @@ -470,8 +470,8 @@ def _serialize(self) -> dict: pipeline["discretizer"] = self.discretizer.attributes_to_dict() pipeline["target_encoder"] = (self.target_encoder.attributes_to_dict()) - pipeline["continuous_vars"] = self._continuous_vars - pipeline["discrete_vars"] = self._discrete_vars + pipeline["_continuous_vars"] = self._continuous_vars + pipeline["_discrete_vars"] = self._discrete_vars if self.serialization_path: path = self.serialization_path @@ -505,4 +505,7 @@ def _is_valid_pipeline(pipeline: dict) -> bool: elif key != "metadata": input_keys.add(key) - return valid_keys == input_keys + input_keys = sorted(list(input_keys)) + input_keys = [key for key in input_keys if not key.startswith("_")] + + return sorted(list(valid_keys)) == sorted(list(input_keys)) From 9986e050c4f9bf32787a4117c99704bf5529f19f Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 16:53:32 +0100 Subject: [PATCH 36/98] Bug fix in PreProcessor This bug caused an error on transform when PreProcessor is initialized from a pipeline (in which case _is_fitted was False, but should have been True). --- cobra/preprocessing/__init__.py | 4 +++- cobra/preprocessing/preprocessor.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cobra/preprocessing/__init__.py b/cobra/preprocessing/__init__.py index 2008235..e02ad4c 100644 --- a/cobra/preprocessing/__init__.py +++ b/cobra/preprocessing/__init__.py @@ -1,7 +1,9 @@ from .kbins_discretizer import KBinsDiscretizer from .target_encoder import TargetEncoder from .categorical_data_processor import CategoricalDataProcessor +from .preprocessor import PreProcessor __all__ = ['KBinsDiscretizer', 'TargetEncoder', - 'CategoricalDataProcessor'] + 'CategoricalDataProcessor', + 'PreProcessor'] diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 2cc841d..c0eb3ee 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -90,6 +90,8 @@ def __init__(self, self._is_fitted = False self._continuous_vars = continuous_vars self._discrete_vars = discrete_vars + if continuous_vars or discrete_vars: + self._is_fitted = True @classmethod def from_params(cls, @@ -244,8 +246,7 @@ def from_pipeline(cls, pipeline_path: str): return cls(pipeline["train_pct"], pipeline["selection_pct"], pipeline["validation_pct"], pipeline["stratify_split"], - categorical_data_processor, - discretizer, target_encoder, + categorical_data_processor, discretizer, target_encoder, pipeline["threshold_numeric_is_categorical"], continuous_vars=pipeline["_continuous_vars"], discrete_vars=pipeline["_discrete_vars"]) From f57804c68a7c0f37d54cbd99c14075203f48b9f9 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 8 Jan 2020 17:15:35 +0100 Subject: [PATCH 37/98] Update setup.py --- setup.py | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/setup.py b/setup.py index ceb8112..e04e832 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,19 @@ from distutils.core import setup + setup( - name = 'cobra', - packages = ['cobra'], - version = '1.0', - description = 'Library for fast model building', - author='Jan Benisek', - author_email = "jan.benisek@pythonpredictions.com", - url = "https://github.com/JanBenisek/COBRA", - download_url = "https://github.com/JanBenisek/COBRA", - keywords = ["Python", "cobra"], - classifiers = [ - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Development Status :: 1 - Beta", - "Environment :: Other Environment", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Text Processing :: Linguistic", - ], - long_description = 'Refactored Cobra project into a Python library.' - ) \ No newline at end of file + name="cobra", + version="0.1.0", + description="Python Prediction's methodology for predictive analytics", + packages=["cobra"], + url="https://github.com/PythonPredictions", + #long_description=long_description, # TO DO + #long_description_content_type="text/markdown", + install_requires=[ + "pandas>=0.25.1", + "numpy>=1.17.2", + "scipy>=1.2.0", + "scikit_learn>=0.22.1", + "matplotlib>=3.0.2", + "seaborn>=0.9.0"], + python_requires=">=3.5", +) From 3e8823775a7ef678809a78474934015af388eca8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 10 Jan 2020 21:18:50 +0100 Subject: [PATCH 38/98] Change API of PreProcessor for extra flexibility --- cobra/preprocessing/preprocessor.py | 271 ++++++++++++++++------------ 1 file changed, 153 insertions(+), 118 deletions(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index c0eb3ee..f17b267 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -14,6 +14,7 @@ from typing import Optional import inspect from datetime import datetime +import time import logging log = logging.getLogger(__name__) @@ -45,61 +46,32 @@ class PreProcessor(BaseEstimator): numeric_threshold : int Threshold to decide whether a numeric variable is in fact a categorical one based on the number of unique values of that variable - selection_pct : float - Percentage of data to add to selection dataset serialization_path : str path to save the pipeline to stratify_split : bool Whether or not to stratify the train-test split target_encoder : TargetEncoder Instance of TargetEncoder to do the incidence replacement - train_pct : float - Percentage of data to add to training dataset - validation_pct : float - Percentage of data to add to validation dataset """ - def __init__(self, - train_pct: float, - selection_pct: float, - validation_pct: float, - stratify_split: bool, - categorical_data_processor: CategoricalDataProcessor, + def __init__(self, categorical_data_processor: CategoricalDataProcessor, discretizer: KBinsDiscretizer, target_encoder: TargetEncoder, threshold_numeric_is_categorical: int=None, serialization_path: str=None, - continuous_vars: list=[], - discrete_vars: list=[]): - - self.train_pct = train_pct - self.selection_pct = selection_pct - self.validation_pct = validation_pct - self.stratify_split = stratify_split + is_fitted: bool=False): self.numeric_threshold = threshold_numeric_is_categorical - self.serialization_path = serialization_path - self.categorical_data_processor = categorical_data_processor - self.discretizer = discretizer - self.target_encoder = target_encoder + self._categorical_data_processor = categorical_data_processor + self._discretizer = discretizer + self._target_encoder = target_encoder - # placeholders for columns by datatype - # Included as constructor argument to get them from pipeline - self._is_fitted = False - self._continuous_vars = continuous_vars - self._discrete_vars = discrete_vars - if continuous_vars or discrete_vars: - self._is_fitted = True + self._is_fitted = is_fitted @classmethod def from_params(cls, - train_pct: float, - selection_pct: float, - validation_pct: float, - stratify_split: bool=True, - threshold_numeric_is_categorical: int=None, n_bins: int=10, strategy: str="quantile", closed: str="right", @@ -115,24 +87,13 @@ def from_params(cls, scale_contingency_table: bool=True, forced_categories: dict={}, weight: float=0.0, + threshold_numeric_is_categorical: int=None, serialization_path: Optional[str]=None): """Constructor to instantiate PreProcessor from all the parameters that can be set in all its required classes. Parameters ---------- - train_pct : float - Percentage of data to add to training dataset - selection_pct : float - Percentage of data to add to selection dataset - validation_pct : float - Percentage of data to add to validation dataset - stratify_split : bool, optional - Whether or not to stratify the train-test split - threshold_numeric_is_categorical : int, optional - Threshold to decide whether a numeric variable is in fact a - categorical one based on the number of unique values of - that variable n_bins : int, optional Number of bins to produce. Raises ValueError if ``n_bins < 2``. strategy : str, optional @@ -179,6 +140,10 @@ def from_params(cls, (e.g. the pure target incidence is used). serialization_path : str, optional path to save the pipeline to + threshold_numeric_is_categorical : int, optional + Threshold to decide whether a numeric variable is in fact a + categorical one based on the number of unique values of + that variable Returns ------- @@ -201,11 +166,7 @@ def from_params(cls, target_encoder = TargetEncoder(weight) - if not threshold_numeric_is_categorical: - threshold_numeric_is_categorical = n_bins - - return cls(train_pct, selection_pct, validation_pct, stratify_split, - categorical_data_processor, discretizer, target_encoder, + return cls(categorical_data_processor, discretizer, target_encoder, threshold_numeric_is_categorical, serialization_path) @classmethod @@ -244,64 +205,81 @@ def from_pipeline(cls, pipeline_path: str): target_encoder = TargetEncoder() target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) - return cls(pipeline["train_pct"], pipeline["selection_pct"], - pipeline["validation_pct"], pipeline["stratify_split"], - categorical_data_processor, discretizer, target_encoder, + return cls(categorical_data_processor, discretizer, target_encoder, pipeline["threshold_numeric_is_categorical"], - continuous_vars=pipeline["_continuous_vars"], - discrete_vars=pipeline["_discrete_vars"]) + pipeline["_is_fitted"]) - def fit(self, data: pd.DataFrame, target_column_name: str, - id_column_name: str=None): + def fit(self, train_data: pd.DataFrame, target_column_name: str, + id_column_name: str=None, + continuous_vars: list=[], discrete_vars: list=[]): """Fit the data to the preprocessing pipeline Parameters ---------- - data : pd.DataFrame + train_data : pd.DataFrame Data to be preprocessed target_column_name : str Name of the target column id_column_name : str, optional Name of the id column + continuous_vars : list, optional + list of continuous variables + discrete_vars : list, optional + list of discrete variables """ - # get list of all variables - var_list = self._get_variable_list(data, target_column_name, - id_column_name) - data = (PreProcessor - .train_selection_validation_split(data, target_column_name, - self.train_pct, - self.selection_pct, - self.validation_pct, - self.stratify_split)) + if not (continuous_vars or discrete_vars): + continuous_vars, discrete_vars = self._get_variable_list_by_type( + train_data, + target_column_name, + id_column_name) + + # get list of all variables + var_list = PreProcessor._get_variable_list(continuous_vars, + discrete_vars) - # Select train data to fit preprocessing pipeline - train_data = data[data["split"] == "train"] + log.info("Starting to fit pipeline") + start = time.time() # Fit discretizer, categorical preprocessor & target encoder # Note that in order to fit target_encoder, we first have to transform # the data using the fitted discretizer & categorical_data_processor - if self._continuous_vars: - self.discretizer.fit(train_data, self._continuous_vars) - train_data = self.discretizer.transform(train_data, - self._continuous_vars) - - if self._discrete_vars: - self.categorical_data_processor.fit(train_data, - self._discrete_vars, - target_column_name) - train_data = (self.categorical_data_processor - .transform(train_data, self._discrete_vars)) - - self.target_encoder.fit(train_data, var_list, target_column_name) + if continuous_vars: + begin = time.time() + self._discretizer.fit(train_data, continuous_vars) + log.info("Fitting KBinsDiscretizer took {} seconds" + .format(time.time() - begin)) + + train_data = self._discretizer.transform(train_data, + continuous_vars) + + if discrete_vars: + begin = time.time() + self._categorical_data_processor.fit(train_data, + discrete_vars, + target_column_name) + log.info("Fitting categorical_data_processor class took {} seconds" + .format(time.time() - begin)) + + train_data = (self._categorical_data_processor + .transform(train_data, discrete_vars)) + + begin = time.time() + self._target_encoder.fit(train_data, var_list, target_column_name) + log.info("Fitting TargetEncoder took {} seconds" + .format(time.time() - begin)) self._is_fitted = True # set fitted boolean to True # serialize the pipeline to store the fitted output along with the # various parameters that were used self._serialize() + log.info("Fitting and serializing pipeline took {} seconds" + .format(time.time() - start)) + def transform(self, data: pd.DataFrame, target_column_name: str, - id_column_name: str=None) -> pd.DataFrame: + id_column_name: str=None, continuous_vars: list=[], + discrete_vars: list=[]) -> pd.DataFrame: """Summary Parameters @@ -312,27 +290,56 @@ def transform(self, data: pd.DataFrame, target_column_name: str, Description id_column_name : str, optional Description + continuous_vars : list, optional + list of continuous variables + discrete_vars : list, optional + list of discrete variables Returns ------- pd.DataFrame Description + + Raises + ------ + NotFittedError + Description """ + start = time.time() + if not self._is_fitted: msg = ("This {} instance is not fitted yet. Call 'fit' with " "appropriate arguments before using this method.") raise NotFittedError(msg.format(self.__class__.__name__)) - var_lists = ([col + "_processed" for col in self._discrete_vars] - + [col + "_bin" for col in self._continuous_vars]) + if not (continuous_vars or discrete_vars): + continuous_vars, discrete_vars = self._get_variable_list_by_type( + data, + target_column_name, + id_column_name) + + # remove "split" column as this is the column + # making the train-selection-validation split + if "split" in discrete_vars: + discrete_vars.remove("split") + + # get list of all variables + var_list = PreProcessor._get_variable_list(continuous_vars, + discrete_vars) + + if continuous_vars: + data = self._discretizer.transform(data, continuous_vars) + + if discrete_vars: + data = self._categorical_data_processor.transform(data, + discrete_vars) - data = self.discretizer.transform(data, self._continuous_vars) - data = self.categorical_data_processor.transform(data, - self._discrete_vars) + data = self._target_encoder.transform(data, var_list) - data = self.target_encoder.transform(data, var_lists) + log.info("Transforming data took {} seconds" + .format(time.time() - start)) return data @@ -408,10 +415,39 @@ def train_selection_validation_split(data: pd.DataFrame, return (pd.concat([df_train, df_selection, df_validation]) .reset_index(drop=True)) - def _get_variable_list(self, data: pd.DataFrame, - target_column_name: str, - id_column_name: str=None): - """Get list of variables and split into numeric and categorical + @staticmethod + def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: + """Summary + + Parameters + ---------- + continuous_vars : list + Description + discrete_vars : list + Description + + Returns + ------- + list + Description + + Raises + ------ + ValueError + Description + """ + var_list = ([col + "_processed" for col in discrete_vars] + + [col + "_bin" for col in continuous_vars]) + + if not var_list: + raise ValueError("Variable var_list is None or empty list") + + return var_list + + def _get_variable_list_by_type(self, data: pd.DataFrame, + target_column_name: str, + id_column_name: str=None): + """Get two lists of variables (numeric and categorical) Parameters ---------- @@ -423,25 +459,29 @@ def _get_variable_list(self, data: pd.DataFrame, Name of the id column """ - columns_by_datatype = utils.get_column_datatypes(data, - target_column_name, - id_column_name, - self.numeric_threshold - ) + if not self.numeric_threshold: + raise ValueError("threshold_numeric_is_categorical is not allowed " + "to be None") - self._continuous_vars = columns_by_datatype["numeric_variables"] - self._discrete_vars = columns_by_datatype["categorical_variables"] + columns_by_datatype = utils.get_column_datatypes( + data, + target_column_name, + id_column_name, + self.numeric_threshold) - log.info("Numeric variables: {}".format(self._continuous_vars)) - log.info("Categorical variables:".format(self._discrete_vars)) + continuous_vars = columns_by_datatype["numeric_variables"] + discrete_vars = columns_by_datatype["categorical_variables"] - var_list = ([col + "_processed" for col in self._discrete_vars] - + [col + "_bin" for col in self._continuous_vars]) + log.info("Numeric variables: {}".format(continuous_vars)) + log.info("Categorical variables:".format(discrete_vars)) + + var_list = ([col + "_processed" for col in discrete_vars] + + [col + "_bin" for col in continuous_vars]) if not var_list: raise ValueError("Variable var_list is None or empty list") - return var_list + return continuous_vars, discrete_vars def _serialize(self) -> dict: """Serialize the preprocessing pipeline by writing all its required @@ -458,21 +498,16 @@ def _serialize(self) -> dict: } } - pipeline["train_pct"] = self.train_pct - pipeline["selection_pct"] = self.selection_pct - pipeline["validation_pct"] = self.validation_pct - pipeline["stratify_split"] = self.stratify_split - pipeline["threshold_numeric_is_categorical"] = self.numeric_threshold - pipeline["categorical_data_processor"] = (self - .categorical_data_processor + ._categorical_data_processor .attributes_to_dict()) - pipeline["discretizer"] = self.discretizer.attributes_to_dict() - pipeline["target_encoder"] = (self.target_encoder.attributes_to_dict()) + pipeline["discretizer"] = self._discretizer.attributes_to_dict() + pipeline["target_encoder"] = (self._target_encoder + .attributes_to_dict()) - pipeline["_continuous_vars"] = self._continuous_vars - pipeline["_discrete_vars"] = self._discrete_vars + pipeline["_is_fitted"] = True + pipeline["threshold_numeric_is_categorical"] = self.numeric_threshold if self.serialization_path: path = self.serialization_path From 32603a430af89483ce99e57b57049cf832be0416 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 16 Jan 2020 09:13:05 +0100 Subject: [PATCH 39/98] Clean up API of PreProcessor --- README.md | 9 +- cobra/preprocessing/preprocessor.py | 172 +++++++---------------- requirements.txt | 6 + tests/preprocessing/test_preprocessor.py | 12 +- 4 files changed, 67 insertions(+), 132 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 50cb9ef..130a724 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ These instructions will get you a copy of the project up and running on your loc ### What can cobra do? - * Transform given pandas DataFrame to be ready to use for prediction modelling: partition into train/selection/validation sets, create bins from continuous variables, regroup categorical variables and add columns with incidence rate per category/bin. + * Transform given pandas DataFrame to be ready to use for prediction modelling: partition into train/selection/validation sets, create bins from continuous variables, regroup categorical variables, replace missing values and add columns with incidence rate per category/bin. * Perform univariate selection based on AUC * Find best model by forward selection * Visualize the results @@ -47,7 +47,7 @@ As this package is an internal package that is not open-sourced, it is not avail * Clone this repository. * Open a shell that can execute python code and navigate to the folder where this repo was cloned in. - * Once you are in the folder, execute `python setup.py install`. + * Once you are in the folder, execute `python setup.py install` or `pip install .`. ### Usage @@ -55,7 +55,4 @@ TO DO ## Development -We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. - - - +We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to write or modify unit test for your changes! diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index f17b267..48d8948 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -43,9 +43,6 @@ class PreProcessor(BaseEstimator): discretizer : KBinsDiscretizer Instance of KBinsDiscretizer to do the prepocessing of continuous variables by means of discretization - numeric_threshold : int - Threshold to decide whether a numeric variable is in fact a categorical - one based on the number of unique values of that variable serialization_path : str path to save the pipeline to stratify_split : bool @@ -57,11 +54,9 @@ class PreProcessor(BaseEstimator): def __init__(self, categorical_data_processor: CategoricalDataProcessor, discretizer: KBinsDiscretizer, target_encoder: TargetEncoder, - threshold_numeric_is_categorical: int=None, serialization_path: str=None, is_fitted: bool=False): - self.numeric_threshold = threshold_numeric_is_categorical self.serialization_path = serialization_path self._categorical_data_processor = categorical_data_processor @@ -87,10 +82,9 @@ def from_params(cls, scale_contingency_table: bool=True, forced_categories: dict={}, weight: float=0.0, - threshold_numeric_is_categorical: int=None, serialization_path: Optional[str]=None): """Constructor to instantiate PreProcessor from all the parameters - that can be set in all its required classes. + that can be set in all its required (attribute) classes. Parameters ---------- @@ -140,10 +134,6 @@ def from_params(cls, (e.g. the pure target incidence is used). serialization_path : str, optional path to save the pipeline to - threshold_numeric_is_categorical : int, optional - Threshold to decide whether a numeric variable is in fact a - categorical one based on the number of unique values of - that variable Returns ------- @@ -167,16 +157,17 @@ def from_params(cls, target_encoder = TargetEncoder(weight) return cls(categorical_data_processor, discretizer, target_encoder, - threshold_numeric_is_categorical, serialization_path) + serialization_path) @classmethod def from_pipeline(cls, pipeline_path: str): - """Summary + """Constructor to instantiate PreProcessor from a (fitted) pipeline, + stored as a JSON file. Parameters ---------- pipeline_path : str - Description + path to the (fitted) pipeline Returns ------- @@ -206,37 +197,28 @@ def from_pipeline(cls, pipeline_path: str): target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) return cls(categorical_data_processor, discretizer, target_encoder, - pipeline["threshold_numeric_is_categorical"], pipeline["_is_fitted"]) - def fit(self, train_data: pd.DataFrame, target_column_name: str, - id_column_name: str=None, - continuous_vars: list=[], discrete_vars: list=[]): + def fit(self, train_data: pd.DataFrame, continuous_vars: list, + discrete_vars: list, target_column_name: str): """Fit the data to the preprocessing pipeline Parameters ---------- train_data : pd.DataFrame Data to be preprocessed - target_column_name : str - Name of the target column - id_column_name : str, optional - Name of the id column continuous_vars : list, optional list of continuous variables discrete_vars : list, optional list of discrete variables + target_column_name : str + Name of the target column """ - if not (continuous_vars or discrete_vars): - continuous_vars, discrete_vars = self._get_variable_list_by_type( - train_data, - target_column_name, - id_column_name) - # get list of all variables - var_list = PreProcessor._get_variable_list(continuous_vars, - discrete_vars) + preprocessed_variable_names = (PreProcessor + ._get_variable_list(continuous_vars, + discrete_vars)) log.info("Starting to fit pipeline") start = time.time() @@ -265,7 +247,8 @@ def fit(self, train_data: pd.DataFrame, target_column_name: str, .transform(train_data, discrete_vars)) begin = time.time() - self._target_encoder.fit(train_data, var_list, target_column_name) + self._target_encoder.fit(train_data, preprocessed_variable_names, + target_column_name) log.info("Fitting TargetEncoder took {} seconds" .format(time.time() - begin)) @@ -277,19 +260,14 @@ def fit(self, train_data: pd.DataFrame, target_column_name: str, log.info("Fitting and serializing pipeline took {} seconds" .format(time.time() - start)) - def transform(self, data: pd.DataFrame, target_column_name: str, - id_column_name: str=None, continuous_vars: list=[], - discrete_vars: list=[]) -> pd.DataFrame: + def transform(self, data: pd.DataFrame, continuous_vars: list, + discrete_vars: list) -> pd.DataFrame: """Summary Parameters ---------- data : pd.DataFrame Description - target_column_name : str - Description - id_column_name : str, optional - Description continuous_vars : list, optional list of continuous variables discrete_vars : list, optional @@ -314,20 +292,9 @@ def transform(self, data: pd.DataFrame, target_column_name: str, raise NotFittedError(msg.format(self.__class__.__name__)) - if not (continuous_vars or discrete_vars): - continuous_vars, discrete_vars = self._get_variable_list_by_type( - data, - target_column_name, - id_column_name) - - # remove "split" column as this is the column - # making the train-selection-validation split - if "split" in discrete_vars: - discrete_vars.remove("split") - - # get list of all variables - var_list = PreProcessor._get_variable_list(continuous_vars, - discrete_vars) + preprocessed_variable_names = (PreProcessor + ._get_variable_list(continuous_vars, + discrete_vars)) if continuous_vars: data = self._discretizer.transform(data, continuous_vars) @@ -336,7 +303,8 @@ def transform(self, data: pd.DataFrame, target_column_name: str, data = self._categorical_data_processor.transform(data, discrete_vars) - data = self._target_encoder.transform(data, var_list) + data = self._target_encoder.transform(data, + preprocessed_variable_names) log.info("Transforming data took {} seconds" .format(time.time() - start)) @@ -415,74 +383,6 @@ def train_selection_validation_split(data: pd.DataFrame, return (pd.concat([df_train, df_selection, df_validation]) .reset_index(drop=True)) - @staticmethod - def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: - """Summary - - Parameters - ---------- - continuous_vars : list - Description - discrete_vars : list - Description - - Returns - ------- - list - Description - - Raises - ------ - ValueError - Description - """ - var_list = ([col + "_processed" for col in discrete_vars] - + [col + "_bin" for col in continuous_vars]) - - if not var_list: - raise ValueError("Variable var_list is None or empty list") - - return var_list - - def _get_variable_list_by_type(self, data: pd.DataFrame, - target_column_name: str, - id_column_name: str=None): - """Get two lists of variables (numeric and categorical) - - Parameters - ---------- - data : pd.DataFrame - Data to be preprocessed - target_column_name : str - Name of the target column - id_column_name : str, optional - Name of the id column - """ - - if not self.numeric_threshold: - raise ValueError("threshold_numeric_is_categorical is not allowed " - "to be None") - - columns_by_datatype = utils.get_column_datatypes( - data, - target_column_name, - id_column_name, - self.numeric_threshold) - - continuous_vars = columns_by_datatype["numeric_variables"] - discrete_vars = columns_by_datatype["categorical_variables"] - - log.info("Numeric variables: {}".format(continuous_vars)) - log.info("Categorical variables:".format(discrete_vars)) - - var_list = ([col + "_processed" for col in discrete_vars] - + [col + "_bin" for col in continuous_vars]) - - if not var_list: - raise ValueError("Variable var_list is None or empty list") - - return continuous_vars, discrete_vars - def _serialize(self) -> dict: """Serialize the preprocessing pipeline by writing all its required parameters to a JSON file. @@ -507,7 +407,6 @@ def _serialize(self) -> dict: .attributes_to_dict()) pipeline["_is_fitted"] = True - pipeline["threshold_numeric_is_categorical"] = self.numeric_threshold if self.serialization_path: path = self.serialization_path @@ -545,3 +444,32 @@ def _is_valid_pipeline(pipeline: dict) -> bool: input_keys = [key for key in input_keys if not key.startswith("_")] return sorted(list(valid_keys)) == sorted(list(input_keys)) + + @staticmethod + def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: + """Summary + + Parameters + ---------- + continuous_vars : list + Description + discrete_vars : list + Description + + Returns + ------- + list + Description + + Raises + ------ + ValueError + Description + """ + var_list = ([col + "_processed" for col in discrete_vars] + + [col + "_bin" for col in continuous_vars]) + + if not var_list: + raise ValueError("Variable var_list is None or empty list") + + return var_list diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..784dea5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +pandas==0.25.1 +matplotlib==3.0.2 +scipy==1.2.0 +seaborn==0.9.0 +numpy==1.17.2 +scikit_learn==0.22.1 diff --git a/tests/preprocessing/test_preprocessor.py b/tests/preprocessing/test_preprocessor.py index 1548b9e..28432ae 100644 --- a/tests/preprocessing/test_preprocessor.py +++ b/tests/preprocessing/test_preprocessor.py @@ -1,10 +1,14 @@ from contextlib import contextmanager import pytest +from pytest_mock import mocker import numpy as np import pandas as pd -#from cobra.preprocessing.preprocessor import PreProcessor +from cobra.preprocessing import PreProcessor +from cobra.preprocessing import KBinsDiscretizer +from cobra.preprocessing import TargetEncoder +from cobra.preprocessing import CategoricalDataProcessor @contextmanager @@ -14,9 +18,6 @@ def does_not_raise(): class TestPreProcessor: - def test_from_params(self): - pass - def test_from_pipeline(self): pass @@ -29,6 +30,9 @@ def test_transform(self): def test_train_selection_validation_split(self): pass + def test_get_variable_list(self): + pass + def test_serialize(self): pass From b6060525ce48d3d9e29ebebe6bdeca2f941d8008 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 31 Jan 2020 16:35:53 +0100 Subject: [PATCH 40/98] Bug fix in preprocessor.train_selection_validation_split ratios --- cobra/preprocessing/preprocessor.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 48d8948..49fbb13 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -346,7 +346,13 @@ def train_selection_validation_split(data: pd.DataFrame, predictors = [col for col in column_names if col != target_column_name] + # for the first split, take sum of selection & validation pct as + # test pct test_pct = selection_pct + validation_pct + # To further split our test set into selection + validation set, + # we have to modify validation pct because we only have test_pct of + # the data available anymore for further splitting! + validation_pct_modif = validation_pct / test_pct X = data[predictors] y = data[target_column_name] @@ -363,10 +369,12 @@ def train_selection_validation_split(data: pd.DataFrame, if stratify_split: stratify = y_test - X_sel, X_val, y_sel, y_val = train_test_split(X_test, y_test, - test_size=validation_pct, - random_state=42, - stratify=stratify) + X_sel, X_val, y_sel, y_val = train_test_split( + X_test, y_test, + test_size=validation_pct_modif, + random_state=42, + stratify=stratify + ) df_train = pd.DataFrame(X_train, columns=predictors) df_train[target_column_name] = y_train From 24c38cbdd2f9dcb799e8d19087d051ac70a6aaca Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 25 Feb 2020 13:29:25 +0100 Subject: [PATCH 41/98] Rework README --- README.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 130a724..e61bd6b 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,30 @@ # COBRA :snake: -**Cobra** is a Python package that implements the Python Predictions methodology for predictive analytics. It consists of a main script/notebook that can be used to build and save a predictive model only by setting several parameters. The main scripts itself consists of several modules that can be used independently of one another to build custom scripts. +**Cobra** is a Python package that implements the Python Predictions methodology for predictive analytics. It consists of a main script/notebook that can be used to build and save a predictive model only by setting several parameters. The main scripts itself consists of several modules that can be used independently of one another to build custom scripts. -Note that this package is a refactored version of the back-end of the original web-based cobra, developed by _Guillaume Marion_ (former Python Predictions employee). +Note that this package is a refactored version of the back-end of the original web-based cobra. :heavy_exclamation_mark: Be aware that there could still be :bug: in the code :heavy_exclamation_mark: -## Getting started +## What can cobra do? -These instructions will get you a copy of the project up and running on your local machine for usage, development and testing purposes. - -### What can cobra do? - - * Transform given pandas DataFrame to be ready to use for prediction modelling: partition into train/selection/validation sets, create bins from continuous variables, regroup categorical variables, replace missing values and add columns with incidence rate per category/bin. + * Prepare a given pandas DataFrame for prediction modelling: + - partition into train/selection/validation sets + - create bins from continuous variables + - regroup categorical variables + - replace missing values and + - add columns with incidence rate per category/bin. * Perform univariate selection based on AUC + * Compute correlation matrix of predictors * Find best model by forward selection * Visualize the results * Allow iteration among each step for the analyst -### Requirements +## Getting started + +These instructions will get you a copy of the project up and running on your local machine for usage, development and testing purposes. Furthermore, this section includes some brief examples on how to use it. + +### Requirements This package requires the usual Python packages for data science: @@ -29,7 +35,7 @@ This package requires the usual Python packages for data science: * pandas * scikit-learn -These packages, along with their versions are listed in `requirements.txt` and `conda_env.txt`. To install these packages using pip, run +These packages, along with their versions are listed in `requirements.txt` and `conda_env.txt`. To install these packages using pip, run ``` pip install requirements.txt @@ -40,19 +46,19 @@ or using conda ``` conda install requirements.txt ``` - + ### Installation -As this package is an internal package that is not open-sourced, it is not available through `pip` or `conda`. As a result, the package has to be installed manually using the following steps: +As this package is an internal package that is not open-sourced, it is not available through `pip` or `conda`. As a result, the package has to be installed manually using the following steps: - * Clone this repository. + * Clone this repository. * Open a shell that can execute python code and navigate to the folder where this repo was cloned in. - * Once you are in the folder, execute `python setup.py install` or `pip install .`. + * Once you are in the folder, execute `python setup.py install` or `pip install .`. ### Usage TO DO -## Development +## Development We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to write or modify unit test for your changes! From ea4f3eff4044acb1d9fb18c1cc917f2c3f571c0d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 13 Mar 2020 16:10:46 +0100 Subject: [PATCH 42/98] Add model_building module --- cobra/model_building/univariate_selection.py | 124 +++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 cobra/model_building/univariate_selection.py diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py new file mode 100644 index 0000000..19a492f --- /dev/null +++ b/cobra/model_building/univariate_selection.py @@ -0,0 +1,124 @@ +""" +Module to perform univariate preselection and compute correlation amongst +predictors + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (current implementation) +- Jan Benisek (initial implementation) +""" +import pandas as pd +from sklearn.metrics import roc_auc_score + + +def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, + target_enc_selection_data: pd.DataFrame, + predictors: list, + target_column: str, + preselect_auc_threshold: float, + preselect_overtrain_threshold: float + ) -> pd.DataFrame: + """ Perform a preselection of predictors based on an AUC threshold of + a univariate model on a train and selection dataset and return a datframe + containing for each variable the train and selection AUC along with a + boolean "preselection" column. + + As the AUC just calculates the quality of a ranking, all monotonous + transformations of a given ranking (i.e. transformations that do not alter + the ranking itself) will lead to the same AUC. + Hence, pushing a categorical variable (incl. a binned continuous variable) + through a logistic regression will produce exactly the same ranking as + pushing it through incidence replacement (i.e. target encoding), + as it will produce the exact same output: a ranking of the categories on + the training set. + + Therefore, no univariate model is trained here as the target encoded train + and selection data is/must be used as inputs for this function. These will + be used as predicted scores to compute the AUC with against the target + + Args: + target_enc_train_data (pd.DataFrame): Train data + target_enc_selection_data (pd.DataFrame): Selection data + predictors (list): list of predictors (e.g. column names in the train + and selection data sets) + target_column (str): name of the target column + preselect_auc_threshold (float): Description + preselect_overtrain_threshold (float): Description + + Returns: + pd.DataFrame: DataFrame containing for each variable the train auc and + test auc allong with a boolean indicating whether or not it is selected + based on the criteria + """ + result = [] + + for predictor in predictors: + + cleaned_predictor = _clean_predictor_name(predictor) + + auc_train = roc_auc_score(target_enc_train_data[predictor], + target_enc_train_data[target_column]) + + auc_selection = roc_auc_score( + target_enc_selection_data[predictor], + target_enc_selection_data[target_column] + ) + + result.append({"predictor": cleaned_predictor, + "AUC train": auc_train, + "AUC selection": auc_selection}) + + df_auc = pd.DataFrame(result) + + # Filter based on min AUC + auc_thresh = df_auc.loc[:, "AUC selection"] > preselect_auc_threshold + + # Identify those variables for which the AUC difference between train + # and selection is within a user-defined ratio + auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) * 100 + < preselect_overtrain_threshold) + + df_auc["preselection"] = auc_thresh & auc_overtrain + + return df_auc + + +def compute_correlations(target_enc_train_data: pd.DataFrame, + predictors: list) -> pd.DataFrame: + """Given a DataFrame and a list of predictors, compute the correlations + amongst the predictors in the DataFrame + + Args: + target_enc_train_data (pd.DataFrame): data to compute correlation + matrix from + predictors (list): List of column names of the DataFrame between which + to compute correlations + + Returns: + pd.DataFrame: The correlation matrix of the training set + """ + + correlations = target_enc_train_data[predictors].corr() + + predictors_cleaned = [_clean_predictor_name(predictor) + for predictor in predictors] + + # Change index and columns with the cleaned version of the predictors + # e.g. change "var1_enc" with "var1" + correlations.columns = predictors_cleaned + correlations.index = predictors_cleaned + + return correlations + + +def _clean_predictor_name(predictor: str) -> str: + """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor + name to return a clean version of the predictor + + Args: + predictor (str): Description + + Returns: + str: Description + """ + return predictor.replace("_enc", "").replace("_bin", "") From 31165d71dfa153c75b53e3397efe5c5cab067d5d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 17 Mar 2020 15:25:18 +0100 Subject: [PATCH 43/98] Fix a bug in KBinsDiscretizer.fit method Bug caused None types to appear in the fitted output --- cobra/preprocessing/kbins_discretizer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 5d126c9..9ba9449 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -193,8 +193,9 @@ def fit(self, data: pd.DataFrame, column_names: list): bins = self._fit_column(data, column_name) - # Add to bins_by_column for later use - self._bins_by_column[column_name] = bins + if bins is not None: + # Add to bins_by_column for later use + self._bins_by_column[column_name] = bins def _fit_column(self, data: pd.DataFrame, column_name: str) -> List[tuple]: From 198563016cae60486af59d189ca9557ef190bc8f Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 09:13:08 +0100 Subject: [PATCH 44/98] Bug fix in PreProcessor.from_pipeline --- cobra/preprocessing/preprocessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 49fbb13..6890a39 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -197,7 +197,7 @@ def from_pipeline(cls, pipeline_path: str): target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) return cls(categorical_data_processor, discretizer, target_encoder, - pipeline["_is_fitted"]) + is_fitted=pipeline["_is_fitted"]) def fit(self, train_data: pd.DataFrame, continuous_vars: list, discrete_vars: list, target_column_name: str): From 9ebb1e02d6c8a2157895f58a0abe79c338c52bd7 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 09:17:47 +0100 Subject: [PATCH 45/98] Bug fix compute_univariate_preselection --- cobra/model_building/univariate_selection.py | 249 ++--- .../categorical_data_processor.py | 868 ++++++++-------- cobra/preprocessing/preprocessor.py | 966 +++++++++--------- 3 files changed, 1042 insertions(+), 1041 deletions(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index 19a492f..de8ef3f 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -1,124 +1,125 @@ -""" -Module to perform univariate preselection and compute correlation amongst -predictors - -Authors: -- Geert Verstraeten (methodology) -- Matthias Roels (current implementation) -- Jan Benisek (initial implementation) -""" -import pandas as pd -from sklearn.metrics import roc_auc_score - - -def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, - target_enc_selection_data: pd.DataFrame, - predictors: list, - target_column: str, - preselect_auc_threshold: float, - preselect_overtrain_threshold: float - ) -> pd.DataFrame: - """ Perform a preselection of predictors based on an AUC threshold of - a univariate model on a train and selection dataset and return a datframe - containing for each variable the train and selection AUC along with a - boolean "preselection" column. - - As the AUC just calculates the quality of a ranking, all monotonous - transformations of a given ranking (i.e. transformations that do not alter - the ranking itself) will lead to the same AUC. - Hence, pushing a categorical variable (incl. a binned continuous variable) - through a logistic regression will produce exactly the same ranking as - pushing it through incidence replacement (i.e. target encoding), - as it will produce the exact same output: a ranking of the categories on - the training set. - - Therefore, no univariate model is trained here as the target encoded train - and selection data is/must be used as inputs for this function. These will - be used as predicted scores to compute the AUC with against the target - - Args: - target_enc_train_data (pd.DataFrame): Train data - target_enc_selection_data (pd.DataFrame): Selection data - predictors (list): list of predictors (e.g. column names in the train - and selection data sets) - target_column (str): name of the target column - preselect_auc_threshold (float): Description - preselect_overtrain_threshold (float): Description - - Returns: - pd.DataFrame: DataFrame containing for each variable the train auc and - test auc allong with a boolean indicating whether or not it is selected - based on the criteria - """ - result = [] - - for predictor in predictors: - - cleaned_predictor = _clean_predictor_name(predictor) - - auc_train = roc_auc_score(target_enc_train_data[predictor], - target_enc_train_data[target_column]) - - auc_selection = roc_auc_score( - target_enc_selection_data[predictor], - target_enc_selection_data[target_column] - ) - - result.append({"predictor": cleaned_predictor, - "AUC train": auc_train, - "AUC selection": auc_selection}) - - df_auc = pd.DataFrame(result) - - # Filter based on min AUC - auc_thresh = df_auc.loc[:, "AUC selection"] > preselect_auc_threshold - - # Identify those variables for which the AUC difference between train - # and selection is within a user-defined ratio - auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) * 100 - < preselect_overtrain_threshold) - - df_auc["preselection"] = auc_thresh & auc_overtrain - - return df_auc - - -def compute_correlations(target_enc_train_data: pd.DataFrame, - predictors: list) -> pd.DataFrame: - """Given a DataFrame and a list of predictors, compute the correlations - amongst the predictors in the DataFrame - - Args: - target_enc_train_data (pd.DataFrame): data to compute correlation - matrix from - predictors (list): List of column names of the DataFrame between which - to compute correlations - - Returns: - pd.DataFrame: The correlation matrix of the training set - """ - - correlations = target_enc_train_data[predictors].corr() - - predictors_cleaned = [_clean_predictor_name(predictor) - for predictor in predictors] - - # Change index and columns with the cleaned version of the predictors - # e.g. change "var1_enc" with "var1" - correlations.columns = predictors_cleaned - correlations.index = predictors_cleaned - - return correlations - - -def _clean_predictor_name(predictor: str) -> str: - """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor - name to return a clean version of the predictor - - Args: - predictor (str): Description - - Returns: - str: Description - """ - return predictor.replace("_enc", "").replace("_bin", "") +""" +Module to perform univariate preselection and compute correlation amongst +predictors + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (current implementation) +- Jan Benisek (initial implementation) +""" +import pandas as pd +from sklearn.metrics import roc_auc_score + + +def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, + target_enc_selection_data: pd.DataFrame, + predictors: list, + target_column: str, + preselect_auc_threshold: float, + preselect_overtrain_threshold: float + ) -> pd.DataFrame: + """ Perform a preselection of predictors based on an AUC threshold of + a univariate model on a train and selection dataset and return a datframe + containing for each variable the train and selection AUC along with a + boolean "preselection" column. + + As the AUC just calculates the quality of a ranking, all monotonous + transformations of a given ranking (i.e. transformations that do not alter + the ranking itself) will lead to the same AUC. + Hence, pushing a categorical variable (incl. a binned continuous variable) + through a logistic regression will produce exactly the same ranking as + pushing it through incidence replacement (i.e. target encoding), + as it will produce the exact same output: a ranking of the categories on + the training set. + + Therefore, no univariate model is trained here as the target encoded train + and selection data is/must be used as inputs for this function. These will + be used as predicted scores to compute the AUC with against the target + + Args: + target_enc_train_data (pd.DataFrame): Train data + target_enc_selection_data (pd.DataFrame): Selection data + predictors (list): list of predictors (e.g. column names in the train + and selection data sets) + target_column (str): name of the target column + preselect_auc_threshold (float): Description + preselect_overtrain_threshold (float): Description + + Returns: + pd.DataFrame: DataFrame containing for each variable the train auc and + test auc allong with a boolean indicating whether or not it is selected + based on the criteria + """ + result = [] + + for predictor in predictors: + + cleaned_predictor = _clean_predictor_name(predictor) + + auc_train = roc_auc_score( + y_true=target_enc_train_data[target_column], + y_score=target_enc_train_data[predictor]) + + auc_selection = roc_auc_score( + y_true=target_enc_selection_data[target_column], + y_score=target_enc_selection_data[predictor] + ) + + result.append({"predictor": cleaned_predictor, + "AUC train": auc_train, + "AUC selection": auc_selection}) + + df_auc = pd.DataFrame(result) + + # Filter based on min AUC + auc_thresh = df_auc.loc[:, "AUC selection"] > preselect_auc_threshold + + # Identify those variables for which the AUC difference between train + # and selection is within a user-defined ratio + auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) * 100 + < preselect_overtrain_threshold) + + df_auc["preselection"] = auc_thresh & auc_overtrain + + return df_auc + + +def compute_correlations(target_enc_train_data: pd.DataFrame, + predictors: list) -> pd.DataFrame: + """Given a DataFrame and a list of predictors, compute the correlations + amongst the predictors in the DataFrame + + Args: + target_enc_train_data (pd.DataFrame): data to compute correlation + matrix from + predictors (list): List of column names of the DataFrame between which + to compute correlations + + Returns: + pd.DataFrame: The correlation matrix of the training set + """ + + correlations = target_enc_train_data[predictors].corr() + + predictors_cleaned = [_clean_predictor_name(predictor) + for predictor in predictors] + + # Change index and columns with the cleaned version of the predictors + # e.g. change "var1_enc" with "var1" + correlations.columns = predictors_cleaned + correlations.index = predictors_cleaned + + return correlations + + +def _clean_predictor_name(predictor: str) -> str: + """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor + name to return a clean version of the predictor + + Args: + predictor (str): Description + + Returns: + str: Description + """ + return predictor.replace("_enc", "").replace("_bin", "") diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index 0ce21e9..dd94a8b 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -1,434 +1,434 @@ -""" -This class implements the Python Prediction's way of dealing with -categorical data preprocessing. There are three steps involved here: -- An optional regrouping of the different categories based on category size - and significance of the category w.r.t. the target -- Missing value replacement with the additional category "Missing" -- Change of dtype to "category" (could potentially lead to memory optimization) - -Authors: -- Geert Verstraeten (methodology) -- Jan Benisek (implementation) -- Matthias Roels (implementation) -""" -# standard lib imports -import re -from typing import Optional - -import logging -log = logging.getLogger(__name__) - -# third party imports -import numpy as np -import pandas as pd -from scipy import stats - -from sklearn.base import BaseEstimator -from sklearn.exceptions import NotFittedError - - -class CategoricalDataProcessor(BaseEstimator): - """ - Regroups categories in categorical variables based on significance - with target variable. - - Attributes - ---------- - category_size_threshold : int - minimal size of a category to keep it as a separate category - forced_categories : dict - Map to prevent certain categories from being group into "Other" - for each colum - dict of the form {col:[forced vars]}. - keep_missing : bool - Whether or not to keep missing as a separate category - p_value_threshold : float - Significance threshold for regroupping. - regroup : bool - Whether or not to regroup categories - regroup_name : str - New name of the non-significant regrouped variables - scale_contingency_table : bool - Whether contingency table should be scaled before chi^2.' - """ - - valid_keys = ["regroup", "regroup_name", "keep_missing", - "category_size_threshold", "p_value_threshold", - "scale_contingency_table", "forced_categories"] - - def __init__(self, regroup: bool=True, regroup_name: str="Other", - keep_missing: bool=True, - category_size_threshold: int=5, - p_value_threshold: float=0.001, - scale_contingency_table: bool=True, - forced_categories: dict={}): - - self.regroup = regroup - self.regroup_name = regroup_name - self.keep_missing = keep_missing - self.category_size_threshold = category_size_threshold - self.p_value_threshold = p_value_threshold - self.scale_contingency_table = scale_contingency_table - self.forced_categories = forced_categories - - # dict to store fitted output in - self._cleaned_categories_by_column = {} - - def attributes_to_dict(self) -> dict: - """Return the attributes of CategoricalDataProcessor as a dictionary - - Returns - ------- - dict - Contains the attributes of CategoricalDataProcessor instance with - the attribute name as key - """ - params = self.get_params() - - params["_cleaned_categories_by_column"] = { - key: list(value) - for key, value in self._cleaned_categories_by_column.items() - } - - return params - - def set_attributes_from_dict(self, params: dict): - """Set instance attributes from a dictionary of values with key the - name of the attribute. - - Parameters - ---------- - params : dict - Contains the attributes of CategoricalDataProcessor with their - names as key. - - Raises - ------ - ValueError - In case _cleaned_categories_by_column is not of type dict - """ - _fitted_output = params.pop("_cleaned_categories_by_column", {}) - - if type(_fitted_output) != dict: - raise ValueError("_cleaned_categories_by_column is expected to " - "be a dict but is of type {} instead" - .format(type(_fitted_output))) - - # Clean out params dictionary to remove unknown keys (for safety!) - params = {key: params[key] for key in params if key in self.valid_keys} - - # We cannot turn this method into a classmethod as we want to make use - # of the following method from BaseEstimator: - self.set_params(**params) - - self._cleaned_categories_by_column = { - key: set(value) for key, value in _fitted_output.items() - } - - return self - - def fit(self, data: pd.DataFrame, column_names: list, - target_column: str): - """Fit the CategoricalDataProcessor - - Parameters - ---------- - data : pd.DataFrame - data used to compute the mapping to encode the categorical - variables with. - column_names : list - Columns of data to be processed - target_column : str - Column name of the target - """ - - if not self.regroup: - # We do not need to fit anything if regroup is set to False! - log.info("regroup was set to False, so no fitting is required") - return None - - for column_name in column_names: - - if column_name not in data.columns: - log.warning("DataFrame has no column '{}', so it will be " - "skipped in fitting" .format(column_name)) - continue - - cleaned_cats = self._fit_column(data, column_name, target_column) - - # Remove forced categories - forced_cats = self.forced_categories.get(column_name, set()) - cleaned_cats = cleaned_cats.union(forced_cats) - - # Add to _cleaned_categories_by_column for later use - self._cleaned_categories_by_column[column_name] = cleaned_cats - - def _fit_column(self, data: pd.DataFrame, column_name: str, - target_column) -> set: - """Compute which categories to regroup into "Other" for a particular - column - - Parameters - ---------- - data : pd.DataFrame - Description - column_name : str - Description - - Returns - ------- - list - list of categories to combine into a category "Other" - """ - y = data[target_column] - incidence = y.mean() - - combined_categories = set() - - # replace missings and get unique categories as a list - X = (CategoricalDataProcessor - ._replace_missings(data[column_name]) - .astype(object)) - - unique_categories = list(X.unique()) - - # get small categories and add them to the merged category list - small_categories = (CategoricalDataProcessor - ._get_small_categories( - X, - incidence, - self.category_size_threshold)) - combined_categories = combined_categories.union(small_categories) - - for category in unique_categories: - if category in small_categories: - continue - - pval = (CategoricalDataProcessor - ._compute_p_value(X, y, category, - self.scale_contingency_table)) - - # if not significant, add it to the list - if pval > self.p_value_threshold: - combined_categories.add(category) - - # Remove missing category from combined_categories if required - if self.keep_missing: - combined_categories.discard("Missing") - - return set(unique_categories).difference(combined_categories) - - def transform(self, data: pd.DataFrame, - column_names: list) -> pd.DataFrame: - """Summary - - Parameters - ---------- - data : pd.DataFrame - Data to be discretized - column_names : list - Columns of data to be discretized - - Returns - ------- - pd.DataFrame - data with additional discretized variables - """ - - if self.regroup and len(self._cleaned_categories_by_column) == 0: - msg = ("{} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - for column_name in column_names: - - if column_name not in data.columns: - log.warning("Unknown column '{}' will be skipped" - .format(column_name)) - continue - - data = self._transform_column(data, column_name) - - return data - - def _transform_column(self, data: pd.DataFrame, - column_name: str) -> pd.DataFrame: - """Given a DataFrame, a column name and a list of categories to - combine, create an additional column which combines these categories - into "Other" - - Parameters - ---------- - data : pd.DataFrame - Original data to be tranformed - column_name : str - name of the column to transform - - Returns - ------- - pd.DataFrame - original DataFrame with an added processed column - """ - - column_name_clean = column_name + "_processed" - data[column_name_clean] = data[column_name].astype(object) - - # Fill missings first - data[column_name_clean] = (CategoricalDataProcessor - ._replace_missings(data, - column_name_clean)) - - if self.regroup: - categories = self._cleaned_categories_by_column.get(column_name) - - if not categories: - # Log warning if categories is None, which indicates it is - # not in fitted output - if categories is None: - log.warning("Column '{}' is not in fitted output " - "and will be skipped".format(column_name)) - return data - - data[column_name_clean] = (CategoricalDataProcessor - ._replace_categories( - data[column_name_clean], - categories)) - - # change data to categorical - data[column_name_clean] = data[column_name_clean].astype("category") - - return data - - def fit_transform(self, data: pd.DataFrame, - column_names: list) -> pd.DataFrame: - """Summary - - Parameters - ---------- - data : pd.DataFrame - Data to be discretized - column_names : list - Columns of data to be discretized - - Returns - ------- - pd.DataFrame - data with additional discretized variables - """ - self.fit(data, column_names) - return self.transform(data, column_names) - - @staticmethod - def _get_small_categories(predictor_series: pd.Series, - incidence: float, - category_size_threshold: int) -> set: - """Fetch categories with a size below a certain threshold. - Note that we use an additional weighting with the overall incidence - - Parameters - ---------- - predictor_series : pd.Series - Description - incidence : float - global train incidence - category_size_threshold : int - minimal size of a category to keep it as a separate category - - Returns - ------- - set - List a categories with a count below a certain threshold - """ - category_counts = predictor_series.groupby(predictor_series).size() - factor = max(incidence, 1 - incidence) - - # Get all categories with a count below a threshold - bool_mask = (category_counts*factor) <= category_size_threshold - return set(category_counts[bool_mask].index.tolist()) - - @staticmethod - def _replace_missings(data: pd.DataFrame, - column_names: Optional[list]=None) -> pd.DataFrame: - """Replace missing values (incl empty strings) - - Parameters - ---------- - data : pd.DataFrame - data to replace missings in - column_names: list, optional - list of predictors to replace missings in - - Returns - ------- - list - list of unique values in the data - """ - # replace missings (incl. empty string) - regex = re.compile("^\\s+|\\s+$") - - temp = None - if column_names: - temp = data[column_names] - else: - temp = data.copy() - temp = temp.fillna("Missing") - temp = temp.replace(regex, "") - temp = temp.replace("", "Missing") - - return temp - - @staticmethod - def _compute_p_value(X: pd.Series, y: pd.Series, category: str, - scale_contingency_table: bool) -> float: - """Summary - - Parameters - ---------- - X : pd.Series - Description - y : pd.Series - Description - category : str - Description - scale_contingency_table : bool - Description - - Returns - ------- - float - Description - """ - df = pd.concat([X, y], axis=1) - df["other_categories"] = np.where(X == category, 0, 1) - - contigency_table = pd.crosstab(index=df['other_categories'], columns=y, - margins=False) - - # if true, we scale the "other" categories - if scale_contingency_table: - size_other_cats = contigency_table.iloc[1].sum() - incidence_mean = y.mean() - - contigency_table.iloc[1, 0] = (1-incidence_mean) * size_other_cats - contigency_table.iloc[1, 1] = incidence_mean * size_other_cats - contigency_table = contigency_table.values.astype(np.int64) - - return stats.chi2_contingency(contigency_table, correction=False)[1] - - @staticmethod - def _replace_categories(data: pd.Series, categories: set) -> pd.Series: - """replace categories in set with "Other" - - Parameters - ---------- - data : pd.Series - Description - categories : set - Description - - Returns - ------- - pd.Series - Description - """ - return data.apply(lambda x: x if x in categories else "Other") +""" +This class implements the Python Prediction's way of dealing with +categorical data preprocessing. There are three steps involved here: +- An optional regrouping of the different categories based on category size + and significance of the category w.r.t. the target +- Missing value replacement with the additional category "Missing" +- Change of dtype to "category" (could potentially lead to memory optimization) + +Authors: +- Geert Verstraeten (methodology) +- Jan Benisek (implementation) +- Matthias Roels (implementation) +""" +# standard lib imports +import re +from typing import Optional + +import logging +log = logging.getLogger(__name__) + +# third party imports +import numpy as np +import pandas as pd +from scipy import stats + +from sklearn.base import BaseEstimator +from sklearn.exceptions import NotFittedError + + +class CategoricalDataProcessor(BaseEstimator): + """ + Regroups categories in categorical variables based on significance + with target variable. + + Attributes + ---------- + category_size_threshold : int + minimal size of a category to keep it as a separate category + forced_categories : dict + Map to prevent certain categories from being group into "Other" + for each colum - dict of the form {col:[forced vars]}. + keep_missing : bool + Whether or not to keep missing as a separate category + p_value_threshold : float + Significance threshold for regroupping. + regroup : bool + Whether or not to regroup categories + regroup_name : str + New name of the non-significant regrouped variables + scale_contingency_table : bool + Whether contingency table should be scaled before chi^2.' + """ + + valid_keys = ["regroup", "regroup_name", "keep_missing", + "category_size_threshold", "p_value_threshold", + "scale_contingency_table", "forced_categories"] + + def __init__(self, regroup: bool=True, regroup_name: str="Other", + keep_missing: bool=True, + category_size_threshold: int=5, + p_value_threshold: float=0.001, + scale_contingency_table: bool=True, + forced_categories: dict={}): + + self.regroup = regroup + self.regroup_name = regroup_name + self.keep_missing = keep_missing + self.category_size_threshold = category_size_threshold + self.p_value_threshold = p_value_threshold + self.scale_contingency_table = scale_contingency_table + self.forced_categories = forced_categories + + # dict to store fitted output in + self._cleaned_categories_by_column = {} + + def attributes_to_dict(self) -> dict: + """Return the attributes of CategoricalDataProcessor as a dictionary + + Returns + ------- + dict + Contains the attributes of CategoricalDataProcessor instance with + the attribute name as key + """ + params = self.get_params() + + params["_cleaned_categories_by_column"] = { + key: list(value) + for key, value in self._cleaned_categories_by_column.items() + } + + return params + + def set_attributes_from_dict(self, params: dict): + """Set instance attributes from a dictionary of values with key the + name of the attribute. + + Parameters + ---------- + params : dict + Contains the attributes of CategoricalDataProcessor with their + names as key. + + Raises + ------ + ValueError + In case _cleaned_categories_by_column is not of type dict + """ + _fitted_output = params.pop("_cleaned_categories_by_column", {}) + + if type(_fitted_output) != dict: + raise ValueError("_cleaned_categories_by_column is expected to " + "be a dict but is of type {} instead" + .format(type(_fitted_output))) + + # Clean out params dictionary to remove unknown keys (for safety!) + params = {key: params[key] for key in params if key in self.valid_keys} + + # We cannot turn this method into a classmethod as we want to make use + # of the following method from BaseEstimator: + self.set_params(**params) + + self._cleaned_categories_by_column = { + key: set(value) for key, value in _fitted_output.items() + } + + return self + + def fit(self, data: pd.DataFrame, column_names: list, + target_column: str): + """Fit the CategoricalDataProcessor + + Parameters + ---------- + data : pd.DataFrame + data used to compute the mapping to encode the categorical + variables with. + column_names : list + Columns of data to be processed + target_column : str + Column name of the target + """ + + if not self.regroup: + # We do not need to fit anything if regroup is set to False! + log.info("regroup was set to False, so no fitting is required") + return None + + for column_name in column_names: + + if column_name not in data.columns: + log.warning("DataFrame has no column '{}', so it will be " + "skipped in fitting" .format(column_name)) + continue + + cleaned_cats = self._fit_column(data, column_name, target_column) + + # Remove forced categories + forced_cats = self.forced_categories.get(column_name, set()) + cleaned_cats = cleaned_cats.union(forced_cats) + + # Add to _cleaned_categories_by_column for later use + self._cleaned_categories_by_column[column_name] = cleaned_cats + + def _fit_column(self, data: pd.DataFrame, column_name: str, + target_column) -> set: + """Compute which categories to regroup into "Other" for a particular + column + + Parameters + ---------- + data : pd.DataFrame + Description + column_name : str + Description + + Returns + ------- + list + list of categories to combine into a category "Other" + """ + y = data[target_column] + incidence = y.mean() + + combined_categories = set() + + # replace missings and get unique categories as a list + X = (CategoricalDataProcessor + ._replace_missings(data[column_name]) + .astype(object)) + + unique_categories = list(X.unique()) + + # get small categories and add them to the merged category list + small_categories = (CategoricalDataProcessor + ._get_small_categories( + X, + incidence, + self.category_size_threshold)) + combined_categories = combined_categories.union(small_categories) + + for category in unique_categories: + if category in small_categories: + continue + + pval = (CategoricalDataProcessor + ._compute_p_value(X, y, category, + self.scale_contingency_table)) + + # if not significant, add it to the list + if pval > self.p_value_threshold: + combined_categories.add(category) + + # Remove missing category from combined_categories if required + if self.keep_missing: + combined_categories.discard("Missing") + + return set(unique_categories).difference(combined_categories) + + def transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + + if self.regroup and len(self._cleaned_categories_by_column) == 0: + msg = ("{} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + for column_name in column_names: + + if column_name not in data.columns: + log.warning("Unknown column '{}' will be skipped" + .format(column_name)) + continue + + data = self._transform_column(data, column_name) + + return data + + def _transform_column(self, data: pd.DataFrame, + column_name: str) -> pd.DataFrame: + """Given a DataFrame, a column name and a list of categories to + combine, create an additional column which combines these categories + into "Other" + + Parameters + ---------- + data : pd.DataFrame + Original data to be tranformed + column_name : str + name of the column to transform + + Returns + ------- + pd.DataFrame + original DataFrame with an added processed column + """ + + column_name_clean = column_name + "_processed" + data[column_name_clean] = data[column_name].astype(object) + + # Fill missings first + data[column_name_clean] = (CategoricalDataProcessor + ._replace_missings(data, + column_name_clean)) + + if self.regroup: + categories = self._cleaned_categories_by_column.get(column_name) + + if not categories: + # Log warning if categories is None, which indicates it is + # not in fitted output + if categories is None: + log.warning("Column '{}' is not in fitted output " + "and will be skipped".format(column_name)) + return data + + data[column_name_clean] = (CategoricalDataProcessor + ._replace_categories( + data[column_name_clean], + categories)) + + # change data to categorical + data[column_name_clean] = data[column_name_clean].astype("category") + + return data + + def fit_transform(self, data: pd.DataFrame, + column_names: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Data to be discretized + column_names : list + Columns of data to be discretized + + Returns + ------- + pd.DataFrame + data with additional discretized variables + """ + self.fit(data, column_names) + return self.transform(data, column_names) + + @staticmethod + def _get_small_categories(predictor_series: pd.Series, + incidence: float, + category_size_threshold: int) -> set: + """Fetch categories with a size below a certain threshold. + Note that we use an additional weighting with the overall incidence + + Parameters + ---------- + predictor_series : pd.Series + Description + incidence : float + global train incidence + category_size_threshold : int + minimal size of a category to keep it as a separate category + + Returns + ------- + set + List a categories with a count below a certain threshold + """ + category_counts = predictor_series.groupby(predictor_series).size() + factor = max(incidence, 1 - incidence) + + # Get all categories with a count below a threshold + bool_mask = (category_counts*factor) <= category_size_threshold + return set(category_counts[bool_mask].index.tolist()) + + @staticmethod + def _replace_missings(data: pd.DataFrame, + column_names: Optional[list]=None) -> pd.DataFrame: + """Replace missing values (incl empty strings) + + Parameters + ---------- + data : pd.DataFrame + data to replace missings in + column_names: list, optional + list of predictors to replace missings in + + Returns + ------- + list + list of unique values in the data + """ + # replace missings (incl. empty string) + regex = re.compile("^\\s+|\\s+$") + + temp = None + if column_names: + temp = data[column_names] + else: + temp = data.copy() + temp = temp.fillna("Missing") + temp = temp.replace(regex, "") + temp = temp.replace("", "Missing") + + return temp + + @staticmethod + def _compute_p_value(X: pd.Series, y: pd.Series, category: str, + scale_contingency_table: bool) -> float: + """Summary + + Parameters + ---------- + X : pd.Series + Description + y : pd.Series + Description + category : str + Description + scale_contingency_table : bool + Description + + Returns + ------- + float + Description + """ + df = pd.concat([X, y], axis=1) + df["other_categories"] = np.where(X == category, 0, 1) + + contigency_table = pd.crosstab(index=df['other_categories'], columns=y, + margins=False) + + # if true, we scale the "other" categories + if scale_contingency_table: + size_other_cats = contigency_table.iloc[1].sum() + incidence_mean = y.mean() + + contigency_table.iloc[1, 0] = (1-incidence_mean) * size_other_cats + contigency_table.iloc[1, 1] = incidence_mean * size_other_cats + contigency_table = contigency_table.values.astype(np.int64) + + return stats.chi2_contingency(contigency_table, correction=False)[1] + + @staticmethod + def _replace_categories(data: pd.Series, categories: set) -> pd.Series: + """replace categories in set with "Other" + + Parameters + ---------- + data : pd.Series + Description + categories : set + Description + + Returns + ------- + pd.Series + Description + """ + return data.apply(lambda x: x if x in categories else "Other") diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 6890a39..417ff02 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -1,483 +1,483 @@ -""" -This module is a rework of the old cobra data_preparation.py. Here we will make -use of the classes for discretization, preprocessing of categorical variables -and incidence replacement. All of which will be employed to create a -preprocessing pipeline, which can be stored as a JSON file so that it can -easily be re-used for scoring. - -Authors: -- Geert Verstraeten (methodology) -- Matthias Roels (implementation) -""" -# std lib imports -import json -from typing import Optional -import inspect -from datetime import datetime -import time - -import logging -log = logging.getLogger(__name__) -# third party imports -import pandas as pd -from sklearn.model_selection import train_test_split -from sklearn.base import BaseEstimator -from sklearn.exceptions import NotFittedError -# custom imports -from cobra.preprocessing import KBinsDiscretizer -from cobra.preprocessing import TargetEncoder -from cobra.preprocessing import CategoricalDataProcessor - -import cobra.utils as utils - - -class PreProcessor(BaseEstimator): - - """Summary - - Attributes - ---------- - categorical_data_processor : CategoricalDataProcessor - Instance of CategoricalDataProcessor to do the prepocessing of - categorical variables - discretizer : KBinsDiscretizer - Instance of KBinsDiscretizer to do the prepocessing of continuous - variables by means of discretization - serialization_path : str - path to save the pipeline to - stratify_split : bool - Whether or not to stratify the train-test split - target_encoder : TargetEncoder - Instance of TargetEncoder to do the incidence replacement - """ - - def __init__(self, categorical_data_processor: CategoricalDataProcessor, - discretizer: KBinsDiscretizer, - target_encoder: TargetEncoder, - serialization_path: str=None, - is_fitted: bool=False): - - self.serialization_path = serialization_path - - self._categorical_data_processor = categorical_data_processor - self._discretizer = discretizer - self._target_encoder = target_encoder - - self._is_fitted = is_fitted - - @classmethod - def from_params(cls, - n_bins: int=10, - strategy: str="quantile", - closed: str="right", - auto_adapt_bins: bool=False, - starting_precision: int=0, - label_format: str="{} - {}", - change_endpoint_format: bool=False, - regroup: bool=True, - regroup_name: str="Other", - keep_missing: bool=True, - category_size_threshold: int=5, - p_value_threshold: float=0.001, - scale_contingency_table: bool=True, - forced_categories: dict={}, - weight: float=0.0, - serialization_path: Optional[str]=None): - """Constructor to instantiate PreProcessor from all the parameters - that can be set in all its required (attribute) classes. - - Parameters - ---------- - n_bins : int, optional - Number of bins to produce. Raises ValueError if ``n_bins < 2``. - strategy : str, optional - Binning strategy. Currently only "uniform" and "quantile" - e.g. equifrequency is supported - closed : str, optional - Whether to close the bins (intervals) from the left or right - auto_adapt_bins : bool, optional - reduces the number of bins (starting from n_bins) as a function of - the number of missings - starting_precision : int, optional - Initial precision for the bin edges to start from, - can also be negative. Given a list of bin edges, the class will - automatically choose the minimal precision required to have proper - bins e.g. [5.5555, 5.5744, ...] will be rounded - to [5.56, 5.57, ...]. In case of a negative number, an attempt will - be made to round up the numbers of the bin edges - e.g. 5.55 -> 10, 146 -> 100, ... - label_format : str, optional - format string to display the bin labels - e.g. min - max, (min, max], ... - change_endpoint_format : bool, optional - Whether or not to change the format of the lower and upper bins - into "< x" and "> y" resp. - regroup : bool - Whether or not to regroup categories - regroup_name : str - New name of the non-significant regrouped variables - keep_missing : bool - Whether or not to keep missing as a separate category - category_size_threshold : int - minimal size of a category to keep it as a separate category - p_value_threshold : float - Significance threshold for regroupping. - forced_categories : dict - Map to prevent certain categories from being group into "Other" - for each colum - dict of the form {col:[forced vars]}. - scale_contingency_table : bool - Whether contingency table should be scaled before chi^2.' - weight : float, optional - Smoothing parameters (non-negative). The higher the value of the - parameter, the bigger the contribution of the overall mean. - When set to zero, there is no smoothing - (e.g. the pure target incidence is used). - serialization_path : str, optional - path to save the pipeline to - - Returns - ------- - PreProcessor - Description - """ - categorical_data_processor = CategoricalDataProcessor( - regroup, - regroup_name, - keep_missing, - category_size_threshold, - p_value_threshold, - scale_contingency_table, - forced_categories) - discretizer = KBinsDiscretizer(n_bins, strategy, closed, - auto_adapt_bins, - starting_precision, - label_format, - change_endpoint_format) - - target_encoder = TargetEncoder(weight) - - return cls(categorical_data_processor, discretizer, target_encoder, - serialization_path) - - @classmethod - def from_pipeline(cls, pipeline_path: str): - """Constructor to instantiate PreProcessor from a (fitted) pipeline, - stored as a JSON file. - - Parameters - ---------- - pipeline_path : str - path to the (fitted) pipeline - - Returns - ------- - PreProcessor - Instance of PreProcessor instantiated from a stored pipeline - - Raises - ------ - ValueError - Description - """ - with open(pipeline_path, "r") as file: - pipeline = json.load(file) - - if not PreProcessor._is_valid_pipeline(pipeline): - raise ValueError("Invalid pipeline") # To do: specify error - - categorical_data_processor = CategoricalDataProcessor() - categorical_data_processor.set_attributes_from_dict( - pipeline["categorical_data_processor"] - ) - - discretizer = KBinsDiscretizer() - discretizer.set_attributes_from_dict(pipeline["discretizer"]) - - target_encoder = TargetEncoder() - target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) - - return cls(categorical_data_processor, discretizer, target_encoder, - is_fitted=pipeline["_is_fitted"]) - - def fit(self, train_data: pd.DataFrame, continuous_vars: list, - discrete_vars: list, target_column_name: str): - """Fit the data to the preprocessing pipeline - - Parameters - ---------- - train_data : pd.DataFrame - Data to be preprocessed - continuous_vars : list, optional - list of continuous variables - discrete_vars : list, optional - list of discrete variables - target_column_name : str - Name of the target column - """ - - # get list of all variables - preprocessed_variable_names = (PreProcessor - ._get_variable_list(continuous_vars, - discrete_vars)) - - log.info("Starting to fit pipeline") - start = time.time() - - # Fit discretizer, categorical preprocessor & target encoder - # Note that in order to fit target_encoder, we first have to transform - # the data using the fitted discretizer & categorical_data_processor - if continuous_vars: - begin = time.time() - self._discretizer.fit(train_data, continuous_vars) - log.info("Fitting KBinsDiscretizer took {} seconds" - .format(time.time() - begin)) - - train_data = self._discretizer.transform(train_data, - continuous_vars) - - if discrete_vars: - begin = time.time() - self._categorical_data_processor.fit(train_data, - discrete_vars, - target_column_name) - log.info("Fitting categorical_data_processor class took {} seconds" - .format(time.time() - begin)) - - train_data = (self._categorical_data_processor - .transform(train_data, discrete_vars)) - - begin = time.time() - self._target_encoder.fit(train_data, preprocessed_variable_names, - target_column_name) - log.info("Fitting TargetEncoder took {} seconds" - .format(time.time() - begin)) - - self._is_fitted = True # set fitted boolean to True - # serialize the pipeline to store the fitted output along with the - # various parameters that were used - self._serialize() - - log.info("Fitting and serializing pipeline took {} seconds" - .format(time.time() - start)) - - def transform(self, data: pd.DataFrame, continuous_vars: list, - discrete_vars: list) -> pd.DataFrame: - """Summary - - Parameters - ---------- - data : pd.DataFrame - Description - continuous_vars : list, optional - list of continuous variables - discrete_vars : list, optional - list of discrete variables - - Returns - ------- - pd.DataFrame - Description - - Raises - ------ - NotFittedError - Description - """ - - start = time.time() - - if not self._is_fitted: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - preprocessed_variable_names = (PreProcessor - ._get_variable_list(continuous_vars, - discrete_vars)) - - if continuous_vars: - data = self._discretizer.transform(data, continuous_vars) - - if discrete_vars: - data = self._categorical_data_processor.transform(data, - discrete_vars) - - data = self._target_encoder.transform(data, - preprocessed_variable_names) - - log.info("Transforming data took {} seconds" - .format(time.time() - start)) - - return data - - @staticmethod - def train_selection_validation_split(data: pd.DataFrame, - target_column_name: str, - train_pct: float=0.6, - selection_pct: float=0.2, - validation_pct: float=0.2, - stratify_split=True)->pd.DataFrame: - """Split dataset into train-selection-validation datasets and merge - them into one big DataFrame with an additional column "split" - indicating to which dataset the corresponding row belongs to. - - Parameters - ---------- - data : pd.DataFrame - Input dataset to split into train-selection and validation sets - target_column_name : str - Name of the target column - train_pct : float, optional - Percentage data to put in train set - selection_pct : float, optional - Percentage data to put in selection set - validation_pct : float, optional - Percentage data to put in validation set - stratify_split : bool, optional - Whether or not to stratify the train-test split - - Returns - ------- - pd.DataFrame - Description - """ - column_names = list(data.columns) - - predictors = [col for col in column_names if col != target_column_name] - - # for the first split, take sum of selection & validation pct as - # test pct - test_pct = selection_pct + validation_pct - # To further split our test set into selection + validation set, - # we have to modify validation pct because we only have test_pct of - # the data available anymore for further splitting! - validation_pct_modif = validation_pct / test_pct - - X = data[predictors] - y = data[target_column_name] - - stratify = None - if stratify_split: - stratify = y - - X_train, X_test, y_train, y_test = train_test_split(X, y, - test_size=test_pct, - random_state=42, - stratify=stratify) - - if stratify_split: - stratify = y_test - - X_sel, X_val, y_sel, y_val = train_test_split( - X_test, y_test, - test_size=validation_pct_modif, - random_state=42, - stratify=stratify - ) - - df_train = pd.DataFrame(X_train, columns=predictors) - df_train[target_column_name] = y_train - df_train["split"] = "train" - - df_selection = pd.DataFrame(X_sel, columns=predictors) - df_selection[target_column_name] = y_sel - df_selection["split"] = "selection" - - df_validation = pd.DataFrame(X_val, columns=predictors) - df_validation[target_column_name] = y_val - df_validation["split"] = "validation" - - return (pd.concat([df_train, df_selection, df_validation]) - .reset_index(drop=True)) - - def _serialize(self) -> dict: - """Serialize the preprocessing pipeline by writing all its required - parameters to a JSON file. - - Returns - ------- - dict - Return the pipeline as a dictionary - """ - pipeline = { - "metadata": { - "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S") - } - } - - pipeline["categorical_data_processor"] = (self - ._categorical_data_processor - .attributes_to_dict()) - - pipeline["discretizer"] = self._discretizer.attributes_to_dict() - pipeline["target_encoder"] = (self._target_encoder - .attributes_to_dict()) - - pipeline["_is_fitted"] = True - - if self.serialization_path: - path = self.serialization_path - else: - path = "./pipeline_tmp.json" - - with open(path, "w") as file: - json.dump(pipeline, file) - - return pipeline - - @staticmethod - def _is_valid_pipeline(pipeline: dict) -> bool: - """Validate the loaded pipeline by checking if all required parameters - are present (and no others!). - - Parameters - ---------- - pipeline : dict - Loaded pipeline from json file - """ - keys = inspect.getfullargspec(PreProcessor.from_params).args - valid_keys = set([key for key in keys - if key not in ["cls", "serialization_path"]]) - - input_keys = set() - for key in pipeline: - if key in ["categorical_data_processor", "discretizer", - "target_encoder"]: - input_keys = input_keys.union(set(pipeline[key].keys())) - elif key != "metadata": - input_keys.add(key) - - input_keys = sorted(list(input_keys)) - input_keys = [key for key in input_keys if not key.startswith("_")] - - return sorted(list(valid_keys)) == sorted(list(input_keys)) - - @staticmethod - def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: - """Summary - - Parameters - ---------- - continuous_vars : list - Description - discrete_vars : list - Description - - Returns - ------- - list - Description - - Raises - ------ - ValueError - Description - """ - var_list = ([col + "_processed" for col in discrete_vars] - + [col + "_bin" for col in continuous_vars]) - - if not var_list: - raise ValueError("Variable var_list is None or empty list") - - return var_list +""" +This module is a rework of the old cobra data_preparation.py. Here we will make +use of the classes for discretization, preprocessing of categorical variables +and incidence replacement. All of which will be employed to create a +preprocessing pipeline, which can be stored as a JSON file so that it can +easily be re-used for scoring. + +Authors: +- Geert Verstraeten (methodology) +- Matthias Roels (implementation) +""" +# std lib imports +import json +from typing import Optional +import inspect +from datetime import datetime +import time + +import logging +log = logging.getLogger(__name__) +# third party imports +import pandas as pd +from sklearn.model_selection import train_test_split +from sklearn.base import BaseEstimator +from sklearn.exceptions import NotFittedError +# custom imports +from cobra.preprocessing import KBinsDiscretizer +from cobra.preprocessing import TargetEncoder +from cobra.preprocessing import CategoricalDataProcessor + +import cobra.utils as utils + + +class PreProcessor(BaseEstimator): + + """Summary + + Attributes + ---------- + categorical_data_processor : CategoricalDataProcessor + Instance of CategoricalDataProcessor to do the prepocessing of + categorical variables + discretizer : KBinsDiscretizer + Instance of KBinsDiscretizer to do the prepocessing of continuous + variables by means of discretization + serialization_path : str + path to save the pipeline to + stratify_split : bool + Whether or not to stratify the train-test split + target_encoder : TargetEncoder + Instance of TargetEncoder to do the incidence replacement + """ + + def __init__(self, categorical_data_processor: CategoricalDataProcessor, + discretizer: KBinsDiscretizer, + target_encoder: TargetEncoder, + serialization_path: str=None, + is_fitted: bool=False): + + self.serialization_path = serialization_path + + self._categorical_data_processor = categorical_data_processor + self._discretizer = discretizer + self._target_encoder = target_encoder + + self._is_fitted = is_fitted + + @classmethod + def from_params(cls, + n_bins: int=10, + strategy: str="quantile", + closed: str="right", + auto_adapt_bins: bool=False, + starting_precision: int=0, + label_format: str="{} - {}", + change_endpoint_format: bool=False, + regroup: bool=True, + regroup_name: str="Other", + keep_missing: bool=True, + category_size_threshold: int=5, + p_value_threshold: float=0.001, + scale_contingency_table: bool=True, + forced_categories: dict={}, + weight: float=0.0, + serialization_path: Optional[str]=None): + """Constructor to instantiate PreProcessor from all the parameters + that can be set in all its required (attribute) classes. + + Parameters + ---------- + n_bins : int, optional + Number of bins to produce. Raises ValueError if ``n_bins < 2``. + strategy : str, optional + Binning strategy. Currently only "uniform" and "quantile" + e.g. equifrequency is supported + closed : str, optional + Whether to close the bins (intervals) from the left or right + auto_adapt_bins : bool, optional + reduces the number of bins (starting from n_bins) as a function of + the number of missings + starting_precision : int, optional + Initial precision for the bin edges to start from, + can also be negative. Given a list of bin edges, the class will + automatically choose the minimal precision required to have proper + bins e.g. [5.5555, 5.5744, ...] will be rounded + to [5.56, 5.57, ...]. In case of a negative number, an attempt will + be made to round up the numbers of the bin edges + e.g. 5.55 -> 10, 146 -> 100, ... + label_format : str, optional + format string to display the bin labels + e.g. min - max, (min, max], ... + change_endpoint_format : bool, optional + Whether or not to change the format of the lower and upper bins + into "< x" and "> y" resp. + regroup : bool + Whether or not to regroup categories + regroup_name : str + New name of the non-significant regrouped variables + keep_missing : bool + Whether or not to keep missing as a separate category + category_size_threshold : int + minimal size of a category to keep it as a separate category + p_value_threshold : float + Significance threshold for regroupping. + forced_categories : dict + Map to prevent certain categories from being group into "Other" + for each colum - dict of the form {col:[forced vars]}. + scale_contingency_table : bool + Whether contingency table should be scaled before chi^2.' + weight : float, optional + Smoothing parameters (non-negative). The higher the value of the + parameter, the bigger the contribution of the overall mean. + When set to zero, there is no smoothing + (e.g. the pure target incidence is used). + serialization_path : str, optional + path to save the pipeline to + + Returns + ------- + PreProcessor + Description + """ + categorical_data_processor = CategoricalDataProcessor( + regroup, + regroup_name, + keep_missing, + category_size_threshold, + p_value_threshold, + scale_contingency_table, + forced_categories) + discretizer = KBinsDiscretizer(n_bins, strategy, closed, + auto_adapt_bins, + starting_precision, + label_format, + change_endpoint_format) + + target_encoder = TargetEncoder(weight) + + return cls(categorical_data_processor, discretizer, target_encoder, + serialization_path) + + @classmethod + def from_pipeline(cls, pipeline_path: str): + """Constructor to instantiate PreProcessor from a (fitted) pipeline, + stored as a JSON file. + + Parameters + ---------- + pipeline_path : str + path to the (fitted) pipeline + + Returns + ------- + PreProcessor + Instance of PreProcessor instantiated from a stored pipeline + + Raises + ------ + ValueError + Description + """ + with open(pipeline_path, "r") as file: + pipeline = json.load(file) + + if not PreProcessor._is_valid_pipeline(pipeline): + raise ValueError("Invalid pipeline") # To do: specify error + + categorical_data_processor = CategoricalDataProcessor() + categorical_data_processor.set_attributes_from_dict( + pipeline["categorical_data_processor"] + ) + + discretizer = KBinsDiscretizer() + discretizer.set_attributes_from_dict(pipeline["discretizer"]) + + target_encoder = TargetEncoder() + target_encoder.set_attributes_from_dict(pipeline["target_encoder"]) + + return cls(categorical_data_processor, discretizer, target_encoder, + is_fitted=pipeline["_is_fitted"]) + + def fit(self, train_data: pd.DataFrame, continuous_vars: list, + discrete_vars: list, target_column_name: str): + """Fit the data to the preprocessing pipeline + + Parameters + ---------- + train_data : pd.DataFrame + Data to be preprocessed + continuous_vars : list, optional + list of continuous variables + discrete_vars : list, optional + list of discrete variables + target_column_name : str + Name of the target column + """ + + # get list of all variables + preprocessed_variable_names = (PreProcessor + ._get_variable_list(continuous_vars, + discrete_vars)) + + log.info("Starting to fit pipeline") + start = time.time() + + # Fit discretizer, categorical preprocessor & target encoder + # Note that in order to fit target_encoder, we first have to transform + # the data using the fitted discretizer & categorical_data_processor + if continuous_vars: + begin = time.time() + self._discretizer.fit(train_data, continuous_vars) + log.info("Fitting KBinsDiscretizer took {} seconds" + .format(time.time() - begin)) + + train_data = self._discretizer.transform(train_data, + continuous_vars) + + if discrete_vars: + begin = time.time() + self._categorical_data_processor.fit(train_data, + discrete_vars, + target_column_name) + log.info("Fitting categorical_data_processor class took {} seconds" + .format(time.time() - begin)) + + train_data = (self._categorical_data_processor + .transform(train_data, discrete_vars)) + + begin = time.time() + self._target_encoder.fit(train_data, preprocessed_variable_names, + target_column_name) + log.info("Fitting TargetEncoder took {} seconds" + .format(time.time() - begin)) + + self._is_fitted = True # set fitted boolean to True + # serialize the pipeline to store the fitted output along with the + # various parameters that were used + self._serialize() + + log.info("Fitting and serializing pipeline took {} seconds" + .format(time.time() - start)) + + def transform(self, data: pd.DataFrame, continuous_vars: list, + discrete_vars: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + continuous_vars : list, optional + list of continuous variables + discrete_vars : list, optional + list of discrete variables + + Returns + ------- + pd.DataFrame + Description + + Raises + ------ + NotFittedError + Description + """ + + start = time.time() + + if not self._is_fitted: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + preprocessed_variable_names = (PreProcessor + ._get_variable_list(continuous_vars, + discrete_vars)) + + if continuous_vars: + data = self._discretizer.transform(data, continuous_vars) + + if discrete_vars: + data = self._categorical_data_processor.transform(data, + discrete_vars) + + data = self._target_encoder.transform(data, + preprocessed_variable_names) + + log.info("Transforming data took {} seconds" + .format(time.time() - start)) + + return data + + @staticmethod + def train_selection_validation_split(data: pd.DataFrame, + target_column_name: str, + train_pct: float=0.6, + selection_pct: float=0.2, + validation_pct: float=0.2, + stratify_split=True)->pd.DataFrame: + """Split dataset into train-selection-validation datasets and merge + them into one big DataFrame with an additional column "split" + indicating to which dataset the corresponding row belongs to. + + Parameters + ---------- + data : pd.DataFrame + Input dataset to split into train-selection and validation sets + target_column_name : str + Name of the target column + train_pct : float, optional + Percentage data to put in train set + selection_pct : float, optional + Percentage data to put in selection set + validation_pct : float, optional + Percentage data to put in validation set + stratify_split : bool, optional + Whether or not to stratify the train-test split + + Returns + ------- + pd.DataFrame + Description + """ + column_names = list(data.columns) + + predictors = [col for col in column_names if col != target_column_name] + + # for the first split, take sum of selection & validation pct as + # test pct + test_pct = selection_pct + validation_pct + # To further split our test set into selection + validation set, + # we have to modify validation pct because we only have test_pct of + # the data available anymore for further splitting! + validation_pct_modif = validation_pct / test_pct + + X = data[predictors] + y = data[target_column_name] + + stratify = None + if stratify_split: + stratify = y + + X_train, X_test, y_train, y_test = train_test_split(X, y, + test_size=test_pct, + random_state=42, + stratify=stratify) + + if stratify_split: + stratify = y_test + + X_sel, X_val, y_sel, y_val = train_test_split( + X_test, y_test, + test_size=validation_pct_modif, + random_state=42, + stratify=stratify + ) + + df_train = pd.DataFrame(X_train, columns=predictors) + df_train[target_column_name] = y_train + df_train["split"] = "train" + + df_selection = pd.DataFrame(X_sel, columns=predictors) + df_selection[target_column_name] = y_sel + df_selection["split"] = "selection" + + df_validation = pd.DataFrame(X_val, columns=predictors) + df_validation[target_column_name] = y_val + df_validation["split"] = "validation" + + return (pd.concat([df_train, df_selection, df_validation]) + .reset_index(drop=True)) + + def _serialize(self) -> dict: + """Serialize the preprocessing pipeline by writing all its required + parameters to a JSON file. + + Returns + ------- + dict + Return the pipeline as a dictionary + """ + pipeline = { + "metadata": { + "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S") + } + } + + pipeline["categorical_data_processor"] = (self + ._categorical_data_processor + .attributes_to_dict()) + + pipeline["discretizer"] = self._discretizer.attributes_to_dict() + pipeline["target_encoder"] = (self._target_encoder + .attributes_to_dict()) + + pipeline["_is_fitted"] = True + + if self.serialization_path: + path = self.serialization_path + else: + path = "./pipeline_tmp.json" + + with open(path, "w") as file: + json.dump(pipeline, file) + + return pipeline + + @staticmethod + def _is_valid_pipeline(pipeline: dict) -> bool: + """Validate the loaded pipeline by checking if all required parameters + are present (and no others!). + + Parameters + ---------- + pipeline : dict + Loaded pipeline from json file + """ + keys = inspect.getfullargspec(PreProcessor.from_params).args + valid_keys = set([key for key in keys + if key not in ["cls", "serialization_path"]]) + + input_keys = set() + for key in pipeline: + if key in ["categorical_data_processor", "discretizer", + "target_encoder"]: + input_keys = input_keys.union(set(pipeline[key].keys())) + elif key != "metadata": + input_keys.add(key) + + input_keys = sorted(list(input_keys)) + input_keys = [key for key in input_keys if not key.startswith("_")] + + return sorted(list(valid_keys)) == sorted(list(input_keys)) + + @staticmethod + def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: + """Summary + + Parameters + ---------- + continuous_vars : list + Description + discrete_vars : list + Description + + Returns + ------- + list + Description + + Raises + ------ + ValueError + Description + """ + var_list = ([col + "_processed" for col in discrete_vars] + + [col + "_bin" for col in continuous_vars]) + + if not var_list: + raise ValueError("Variable var_list is None or empty list") + + return var_list From dc2b1eaefd2f5f97629c1dd445e422ac2211051c Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 10:57:10 +0100 Subject: [PATCH 46/98] Add forward_selection submodule to model_building module --- cobra/model_building/__init__.py | 12 ++ cobra/model_building/forward_selection.py | 190 ++++++++++++++++++++++ cobra/model_building/models.py | 110 +++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 cobra/model_building/__init__.py create mode 100644 cobra/model_building/forward_selection.py create mode 100644 cobra/model_building/models.py diff --git a/cobra/model_building/__init__.py b/cobra/model_building/__init__.py new file mode 100644 index 0000000..63d074b --- /dev/null +++ b/cobra/model_building/__init__.py @@ -0,0 +1,12 @@ +from .univariate_selection import compute_univariate_preselection +from .univariate_selection import get_preselected_predictors +from .univariate_selection import compute_correlations + +from .models import LogisticRegressionModel +from .forward_selection import ForwardFeatureSelection + +__all__ = ['compute_univariate_preselection', + 'get_preselected_predictors', + 'compute_correlations', + 'LogisticRegressionModel', + 'ForwardFeatureSelection'] diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py new file mode 100644 index 0000000..fec7a8f --- /dev/null +++ b/cobra/model_building/forward_selection.py @@ -0,0 +1,190 @@ +import logging +log = logging.getLogger(__name__) + +import pandas as pd + +from cobra.model_building import LogisticRegressionModel as MLModel + + +class ForwardFeatureSelection: + + """Summary + + Attributes + ---------- + max_predictors : int + Description + model_name : str + Description + pos_only : bool + Description + """ + + def __init__(self, max_predictors: int=50, + model_name: str="logistic-regression", pos_only: bool=True): + + self.pos_only = pos_only + self.max_predictors = max_predictors + self.model_name = model_name + + self._fitted_models = [] + + def fit(self, data: pd.DataFrame, target_column_name: str, + predictors: list, forced_predictors: list=[], + excluded_predictors: list=[]): + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + target_column_name : str + Description + predictors : list + Description + forced_predictors : list, optional + Description + excluded_predictors : list, optional + Description + + Raises + ------ + ValueError + In case the number of forced predictors is larger than the maximum + number of allowed predictors in the model + """ + # prep predictor lists + filterd_predictors = [var for var in predictors + if (var not in excluded_predictors + and var not in forced_predictors)] + + # checks on predictor lists and self.max_predictors attr + if len(forced_predictors) > self.max_predictors: + raise ValueError("Size or forced_predictors cannot be bigger than " + "max_predictors") + elif len(forced_predictors) == self.max_predictors: + log.info("Size of forced_predictors equals max_predictors " + "only one model will be trained...") + # train model with all forced_predictors (only) + (self._fitted_models + .append(self._train_model(data[data["split"] == "train"], + target_column_name, + forced_predictors))) + else: + self._forward_selection(data, target_column_name, + filterd_predictors, + forced_predictors) + + def _forward_selection(self, data: pd.DataFrame, target_column_name: str, + predictors: list, forced_predictors: list=[]): + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + target_column_name : str + Description + predictors : list + Description + forced_predictors : list, optional + Description + """ + current_predictors = [] + + for step in range(1, self.max_predictors + 1): + if step <= len(forced_predictors): + # first, we go through forced predictors + candidate_predictors = list(set(forced_predictors) + .difference( + set(current_predictors))) + else: + candidate_predictors = [var for var in predictors + if var not in current_predictors] + + model = self._find_next_best_model(data[data["split"] == "train"], + target_column_name, + candidate_predictors, + current_predictors) + # if no new model was found, e.g. because there was no model with + # only positive coefficients, and all forced predictors were + # already tested (i.e. we are now looping through the other + # predictors) break out of the loop! + if (model is None) and (step > len(forced_predictors)): + break + + if model is not None: + self._fitted_models.append(model) + + if not self._fitted_models: + log.error("No models found in forward selection") + + def _find_next_best_model(self, data: pd.DataFrame, + target_column_name: str, + candidate_predictors: list, + current_predictors: list) -> MLModel: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + target_column_name : str + Description + candidate_predictors : list + Description + current_predictors : list + Description + + Returns + ------- + MLModel + Description + """ + # placeholders + best_model = None + best_auc = -1 + + for pred in candidate_predictors: + + # train model with additional predictor + model = self._train_model(data, target_column_name, + (current_predictors + [pred])) + # Evaluate model + auc_pred = model.evaluate(data[current_predictors + [pred]], + data[target_column_name]) + + if (self.pos_only and (not (model.get_coef() >= 0).all())): + continue + + # check if model is better than current best model + # and if yes, replace current best! + if (auc_pred >= best_auc): + best_auc = auc_pred + best_model = model + + return best_model + + def _train_model(self, data: pd.DataFrame, target_column_name: str, + predictors: list) -> MLModel: + """Summary + + Parameters + ---------- + data : pd.DataFrame + Description + target_column_name : str + Description + predictors : list + Description + + Returns + ------- + MLModel + Description + """ + model = MLModel() + + model.fit(data[predictors], data[target_column_name]) + + return model diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py new file mode 100644 index 0000000..0ba0398 --- /dev/null +++ b/cobra/model_building/models.py @@ -0,0 +1,110 @@ +import numpy as np +import pandas as pd +from sklearn.metrics import roc_auc_score +from sklearn.linear_model import LogisticRegression + + +class LogisticRegressionModel: + + """Wrapper around the LogisticRegression class, with additional methods + implemented such as evaluation (using auc), getting a list of coefficients, + a ditionary of coefficients per predictor, ... for convenience + + Attributes + ---------- + logit : LogisticRegression + scikit-learn logistic regression model + predictors : list + List of predictors used in the model + """ + + def __init__(self): + self.logit = LogisticRegression(fit_intercept=True, C=1e9, + solver='liblinear') + # placeholder to keep track of a list of predictors + self.predictors = [] + self._eval_metrics_by_split = {} + + def get_coef(self) -> np.array: + """Returns the model coefficients + + Returns + ------- + np.array + array of model coefficients + """ + return self.logit.coef_[0] + + def get_intercepts(self) -> float: + """Returns the intercept of the model + + Returns + ------- + float + intercept of the model + """ + return self.logit.intercept_[0] + + def get_coef_by_predictor(self) -> dict: + """Returns a map predictor -> coefficient + + Returns + ------- + dict + map predictor -> coefficient + """ + return dict(zip(self.predictors, self.logit.coef_[0])) + + def fit(self, X_train: pd.DataFrame, y_train: pd.Series): + """Fit the model + + Parameters + ---------- + X_train : pd.DataFrame + predictors of train data + y_train : pd.Series + target of train data + """ + self.predictors = X_train.columns + self.logit.fit(X_train, y_train) + + def score_model(self, X: pd.DataFrame) -> np.ndarray: + """Score a model on a (new) dataset + + Parameters + ---------- + X : pd.DataFrame + dataset of predictors to score the model + + Returns + ------- + np.ndarray + score of the model for each observation + """ + return self.logit.predict_proba(X)[:, 1] + + def evaluate(self, X: pd.DataFrame, y: pd.Series, + split: str="train") -> float: + """Evaluate the model on a given split (train, selection, validation) + of a data set (X, y) + + Parameters + ---------- + X : pd.DataFrame + dataset containing the predictor values for each observation + y : pd.Series + dataset containig the target of each observation + split : str, optional + split of the dataset (e.g. train-selection-validation) + + Returns + ------- + float + the performance score of the model (e.g. AUC) + """ + if self._eval_metrics_by_split.get(split) is None: + y_pred = self.score_model(X) + + self._eval_metrics_by_split[split] = roc_auc_score(y_true=y, + y_score=y_pred) + return self._eval_metrics_by_split[split] From 4a5195cc6028ab6a52f023fe34fa4e12358f6165 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 15:20:36 +0100 Subject: [PATCH 47/98] Add convenience function to univariate selection This function returns a list of preselection predictors --- cobra/model_building/univariate_selection.py | 23 ++++++++++++++++++++ cobra/preprocessing/preprocessor.py | 2 -- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index de8ef3f..1979ac8 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -84,6 +84,29 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, return df_auc +def get_preselected_predictors(df_auc: pd.DataFrame) -> list: + """Wrapper function to extract a list of predictors + from df_auc + + Parameters + ---------- + df_auc : pd.DataFrame + DataFrame containing for each variable the train auc and + test auc allong with a boolean indicating whether or not it is selected + based on the criteria + + Returns + ------- + list + list of preselected predictors + """ + predictor_list = (df_auc[df_auc["preselection"]] + .sort_values(by='AUC selection', ascending=False) + .predictor.tolist()) + + return [col + "_enc" for col in predictor_list] + + def compute_correlations(target_enc_train_data: pd.DataFrame, predictors: list) -> pd.DataFrame: """Given a DataFrame and a list of predictors, compute the correlations diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 417ff02..12ebcab 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -28,8 +28,6 @@ from cobra.preprocessing import TargetEncoder from cobra.preprocessing import CategoricalDataProcessor -import cobra.utils as utils - class PreProcessor(BaseEstimator): From 3d94f63afb4dec94365a522ee026bd682a6c9224 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 15:31:56 +0100 Subject: [PATCH 48/98] Clean up models.py --- cobra/model_building/models.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index 0ba0398..39c7a97 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -35,7 +35,7 @@ def get_coef(self) -> np.array: """ return self.logit.coef_[0] - def get_intercepts(self) -> float: + def get_intercept(self) -> float: """Returns the intercept of the model Returns @@ -65,7 +65,7 @@ def fit(self, X_train: pd.DataFrame, y_train: pd.Series): y_train : pd.Series target of train data """ - self.predictors = X_train.columns + self.predictors = list(X_train.columns) self.logit.fit(X_train, y_train) def score_model(self, X: pd.DataFrame) -> np.ndarray: @@ -81,7 +81,9 @@ def score_model(self, X: pd.DataFrame) -> np.ndarray: np.ndarray score of the model for each observation """ - return self.logit.predict_proba(X)[:, 1] + # We select predictor columns (self.predictors) here to + # ensure we have the proper predictors and the proper order!!! + return self.logit.predict_proba(X[self.predictors])[:, 1] def evaluate(self, X: pd.DataFrame, y: pd.Series, split: str="train") -> float: @@ -103,6 +105,7 @@ def evaluate(self, X: pd.DataFrame, y: pd.Series, the performance score of the model (e.g. AUC) """ if self._eval_metrics_by_split.get(split) is None: + y_pred = self.score_model(X) self._eval_metrics_by_split[split] = roc_auc_score(y_true=y, From f05f15479941b4f881d5b6fda81bbeb474b8b102 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 15:32:20 +0100 Subject: [PATCH 49/98] Add functions to explore result of forward_selection One function is to obtain the model of a particular step. The other function is to compute the performance (e.g. AUC) of the models in the different steps for train-selection-validation sets. The result of the later function is cached in each of the models to optimize recomputation. --- cobra/model_building/forward_selection.py | 140 ++++++++++++++++++---- 1 file changed, 117 insertions(+), 23 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index fec7a8f..7845d88 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -8,16 +8,19 @@ class ForwardFeatureSelection: - """Summary + """Perform forward feature selection for a given dataset using a given + model. Attributes ---------- max_predictors : int - Description + maximum number of predictors allowed in any model. This corresponds + more or less with the maximum number of steps in the forward feature + selection model_name : str - Description + name of the model to use for forward feature selection pos_only : bool - Description + whether or not the model coefficients should all be positive """ def __init__(self, max_predictors: int=50, @@ -29,7 +32,90 @@ def __init__(self, max_predictors: int=50, self._fitted_models = [] - def fit(self, data: pd.DataFrame, target_column_name: str, + def get_model_from_step(self, step: int) -> MLModel: + """Get fitted model from a particular step + + Parameters + ---------- + step : int + Particular step in the forward selection + + Returns + ------- + MLModel + Fitted model from the given step + + Raises + ------ + ValueError + in case step is larger than the number of available models + """ + if len(self._fitted_models) < step: + raise ValueError(f"No model available for step {step}") + + return self._fitted_models[step] + + def compute_model_performances(self, data: pd.DataFrame, + target_column_name: str) -> list: + """Compute for each model the performance for + train-selection-validation sets and return them along with a list + of predictors used in the model. + Note that the computation of the performance for each split is + cached inside the model itself, so it is inexpensive to perform + it multiple times! + + Parameters + ---------- + data : pd.DataFrame + dataset for which to compute performance of each model + target_column_name : str + name of the target column + + Returns + ------- + list + A list containing for each model the performance for + train-selection-validation sets as well as the set of predictors + used in this model + """ + results = [] + predictor_set = set([]) + for model in self._fitted_models: + # Evaluate model + performance_train = model.evaluate( + data[data["split"] == "train"], + data[data["split"] == "train"][target_column_name], + split="train" + ) + + performance_selection = model.evaluate( + data[data["split"] == "selection"], + data[data["split"] == "selection"][target_column_name], + split="selection" + ) + + performance_validation = model.evaluate( + data[data["split"] == "validation"], + data[data["split"] == "validation"][target_column_name], + split="validation" + ) + + last_added_predictor = (set(model.predictors) + .difference(predictor_set)) + + results.append({ + "predictors": model.predictors, + "last_added_predictor": list(last_added_predictor)[0], + "train_performance": performance_train, + "selection_performance": performance_selection, + "validation_performance": performance_validation + }) + + predictor_set = predictor_set.union(set(model.predictors)) + + return results + + def fit(self, train_data: pd.DataFrame, target_column_name: str, predictors: list, forced_predictors: list=[], excluded_predictors: list=[]): """Summary @@ -67,21 +153,22 @@ def fit(self, data: pd.DataFrame, target_column_name: str, "only one model will be trained...") # train model with all forced_predictors (only) (self._fitted_models - .append(self._train_model(data[data["split"] == "train"], + .append(self._train_model(train_data, target_column_name, forced_predictors))) else: - self._forward_selection(data, target_column_name, + self._forward_selection(train_data, target_column_name, filterd_predictors, forced_predictors) - def _forward_selection(self, data: pd.DataFrame, target_column_name: str, - predictors: list, forced_predictors: list=[]): + def _forward_selection(self, train_data: pd.DataFrame, + target_column_name: str, predictors: list, + forced_predictors: list=[]): """Summary Parameters ---------- - data : pd.DataFrame + train_data : pd.DataFrame Description target_column_name : str Description @@ -92,7 +179,9 @@ def _forward_selection(self, data: pd.DataFrame, target_column_name: str, """ current_predictors = [] - for step in range(1, self.max_predictors + 1): + max_steps = 1 + min(self.max_predictors, + len(predictors) + len(forced_predictors)) + for step in range(1, max_steps): if step <= len(forced_predictors): # first, we go through forced predictors candidate_predictors = list(set(forced_predictors) @@ -102,7 +191,7 @@ def _forward_selection(self, data: pd.DataFrame, target_column_name: str, candidate_predictors = [var for var in predictors if var not in current_predictors] - model = self._find_next_best_model(data[data["split"] == "train"], + model = self._find_next_best_model(train_data, target_column_name, candidate_predictors, current_predictors) @@ -114,12 +203,16 @@ def _forward_selection(self, data: pd.DataFrame, target_column_name: str, break if model is not None: + # Add new model predictors to the list of current predictors + current_predictors = list(set(current_predictors) + .union(set(model.predictors))) + self._fitted_models.append(model) if not self._fitted_models: log.error("No models found in forward selection") - def _find_next_best_model(self, data: pd.DataFrame, + def _find_next_best_model(self, train_data: pd.DataFrame, target_column_name: str, candidate_predictors: list, current_predictors: list) -> MLModel: @@ -127,7 +220,7 @@ def _find_next_best_model(self, data: pd.DataFrame, Parameters ---------- - data : pd.DataFrame + train_data : pd.DataFrame Description target_column_name : str Description @@ -143,35 +236,36 @@ def _find_next_best_model(self, data: pd.DataFrame, """ # placeholders best_model = None - best_auc = -1 + best_performance = -1 for pred in candidate_predictors: # train model with additional predictor - model = self._train_model(data, target_column_name, + model = self._train_model(train_data, target_column_name, (current_predictors + [pred])) # Evaluate model - auc_pred = model.evaluate(data[current_predictors + [pred]], - data[target_column_name]) + performance = (model + .evaluate(train_data[current_predictors + [pred]], + train_data[target_column_name])) if (self.pos_only and (not (model.get_coef() >= 0).all())): continue # check if model is better than current best model # and if yes, replace current best! - if (auc_pred >= best_auc): - best_auc = auc_pred + if (performance >= best_performance): + best_performance = performance best_model = model return best_model - def _train_model(self, data: pd.DataFrame, target_column_name: str, + def _train_model(self, train_data: pd.DataFrame, target_column_name: str, predictors: list) -> MLModel: """Summary Parameters ---------- - data : pd.DataFrame + train_data : pd.DataFrame Description target_column_name : str Description @@ -185,6 +279,6 @@ def _train_model(self, data: pd.DataFrame, target_column_name: str, """ model = MLModel() - model.fit(data[predictors], data[target_column_name]) + model.fit(train_data[predictors], train_data[target_column_name]) return model From ca199cc9038745c6d6c3d3e246a0df77b58fd907 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 18 Mar 2020 16:42:51 +0100 Subject: [PATCH 50/98] Modify forward_selection algorithm logic Change was made to make sure forced predictors could be included in models with only positive coefficients as much as possible. In the new implemtation, only when there is no possible combination of forced predictors (and other predictors) that lead to a model with only positive coefficients will some of the forced predictors be skipped. --- cobra/model_building/forward_selection.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index 7845d88..fc236ea 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -139,10 +139,9 @@ def fit(self, train_data: pd.DataFrame, target_column_name: str, In case the number of forced predictors is larger than the maximum number of allowed predictors in the model """ - # prep predictor lists + # remove excluded predictors from predictor lists filterd_predictors = [var for var in predictors - if (var not in excluded_predictors - and var not in forced_predictors)] + if var not in excluded_predictors] # checks on predictor lists and self.max_predictors attr if len(forced_predictors) > self.max_predictors: @@ -188,19 +187,14 @@ def _forward_selection(self, train_data: pd.DataFrame, .difference( set(current_predictors))) else: - candidate_predictors = [var for var in predictors + candidate_predictors = [var for var in (predictors + + forced_predictors) if var not in current_predictors] model = self._find_next_best_model(train_data, target_column_name, candidate_predictors, current_predictors) - # if no new model was found, e.g. because there was no model with - # only positive coefficients, and all forced predictors were - # already tested (i.e. we are now looping through the other - # predictors) break out of the loop! - if (model is None) and (step > len(forced_predictors)): - break if model is not None: # Add new model predictors to the list of current predictors From 8d87d32f668c3c15064207ed519d9a0a0b3c1dd9 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 19 Mar 2020 14:14:25 +0100 Subject: [PATCH 51/98] Remove old refactored modules and extend utils.py Removed old data_preparation.py and univariate_selection.py Add clean_predictor_name utility function to utils.py --- cobra/data_preparation.py | 749 ---------------------------------- cobra/univariate_selection.py | 131 ------ cobra/utils.py | 13 + 3 files changed, 13 insertions(+), 880 deletions(-) delete mode 100644 cobra/data_preparation.py delete mode 100644 cobra/univariate_selection.py diff --git a/cobra/data_preparation.py b/cobra/data_preparation.py deleted file mode 100644 index 4445bc7..0000000 --- a/cobra/data_preparation.py +++ /dev/null @@ -1,749 +0,0 @@ -''' -====================================================================================================================== ----------------------------------------------------- DATA PREPARATION ---------------------------------------------- -====================================================================================================================== -''' -import math -import numpy as np -import pandas as pd -from scipy import stats - -# To allow pandas dataframes to display more columns -pd.set_option("display.max_columns",50) - -class DataPreparation(object): - ''' - Class for DataPreparation - Loads, clean, partition, binn, regroup,replace (incidence) - ---------------------------------------------------- - Author: Jan Benisek, Python Predictions - Date: 14/02/2018 - ---------------------------------------------------- - ***PARAMETERS*** - :partition_train: Size of training set as int <0;1> - :partition_select: Size of selection set as int <0;1> - :partition_valid: Size of validation set as int <0;1> - :sampling_1: Size of sampling of target class - :sampling_0: Size of sampling of non-target class - :discret_nbins: ??? - :regroup_sign: Significance level for regrouping categorical variables - :rseed: Random seed for reproducibility (partitioning). None or a number - - ***ATTRIBUTES*** - :_headers_dict: Dict of 4 lists with header names (object, numeric, bool, other) - :_partitioning_settings: Dict with train/sel/valid sets with their size - :_sampling_settings: Dict with sampling settings (how many 1's and 0's we will take) - ---------------------------------------------------- - ''' - def __init__(self, data_path, data_types_path, partition_train, partition_select, partition_valid, - sampling_1, sampling_0, discret_nbins, regroup_sign, rseed): - ''' ***PARAMETERS*** ''' - self.data_path = data_path - self.data_types_path = data_types_path - self.partition_train = partition_train - self.partition_select = partition_select - self.partition_valid = partition_valid - self.sampling_1 = sampling_1 - self.sampling_0 = sampling_0 - self.discret_nbins = discret_nbins - self.regroup_sign = regroup_sign - self.rseed = rseed - ''' ***ATTRIBUTES*** ''' - # Instance attributes = Each instance has its own version self.XY - # Not everyone is initialized here - - # Class attributes = Shared accross all instances DataPreparation.XY - DataPreparation._partitioning_settings = {'train':self.partition_train, - 'selection':self.partition_select, - 'validation':self.partition_valid} - DataPreparation._sampling_settings = {1:self.sampling_1, - 0:self.sampling_0} - - #Set seed for testing - #partitioning will be affected - if rseed: - np.random.seed(rseed) - - - - def transform(self): - ''' - Method transforms given csv - Returns DF - ---------------------------------------------------- - data_path: path to the csv with data file to be transformed - data_types_path: path to the csv with data types of the above csv dataset - ---------------------------------------------------- - ''' - key_clmns = ["ID","TARGET","PARTITION"] - - ##Load csv - df_transformed, df_types = self._loadCSVs(self.data_path, self.data_types_path) - - ##Clean headers - df_transformed = self._cleanHeaders(df_transformed) - self._getHeaderNames(df_transformed, df_types, key_clmns) - - ##Partitioning - df_transformed = self._addPartitionColumn(df_transformed) - - ##Sample - df_transformed = self._sampling(df_transformed) - - ##Preprocessing - #Continuous 2 bins - df_cont = self._prepNumVars(df_transformed, key_clmns) - #Regrouping categoricals - df_cat = self._prepCatVars(df_transformed, key_clmns) - #Rename booleans - df_bool = self._prepBoolVars(df_transformed, key_clmns) - - ##Merge together Preprocessing DFs - df_prep = pd.concat([ - df_cont, - df_cat[list(set(df_cat.columns) - set(key_clmns))], - df_bool[list(set(df_bool.columns) - set(key_clmns))] - ], - axis=1) - - ##Replace groups by incidence rate - df_inc = self._replaceByIncidenceRate(df_prep, key_clmns) - - ##Merge Preprocessing and Incidence DFs - df_out = pd.concat([ - df_prep, - df_inc[list(set(df_inc.columns) - set(key_clmns))] - ], - axis=1) - - ##Cleaning - del df_transformed, df_cont, df_cat, df_bool, df_prep, df_inc - - return df_out - - def _loadCSVs(self, data_path, data_types_path): - ''' - Function loads csv and if no datatype csv is present, - guesses the datatypes. - Returns raw DataFrame - ---------------------------------------------------- - data_path: path to the data csv file - data_types_path: path to the datatypes csv file - ---------------------------------------------------- - Return also data types? watch out if it is not given! - ''' - #Loads Data types - types_exist = True - - #load data_types - try: - df_types = pd.read_csv(data_types_path, header=None) - df_types.columns = ['variable','data_type'] - except FileNotFoundError: - types_exist = False - df_types = pd.DataFrame() - - #load data - df = pd.read_csv(data_path, header=0, sep=None, engine='python') - - #change datatypes - if types_exist: - for row in df_types.itertuples(): #0:index, 1:variable, 2:data_type - if row[2] == 'int': - #Nan is stored as float, hence the dtype. - #Won't work when converting to int with nans - df[row[1]] = df[row[1]].astype(np.float64) - if row[2] in ['str', 'bool']: - df[row[1]] = df[row[1]].apply(str) - - return df, df_types - - def _cleanHeaders(self, df): - ''' - Method cleans headers in given DataFrame. - Returns cleaned DF - ---------------------------------------------------- - df: input dataframe to be modified - ---------------------------------------------------- - ''' - #Define functions - def strip_quot(x_in): - '''Function to remove quotes from variable names and/or variable values''' - try: - x_out = x_in.strip().strip('"').strip("'") - except: - x_out=x_in - return x_out - - def lower_upper(x_in): - '''Function to put 'id' and 'target' variable names in uppercase, - all other variable names are put in lowercase''' - if ((x_in.lower() == 'id')|(x_in.lower() == 'target')): - x_out = x_in.upper() - else: - x_out = x_in.lower() - return x_out - - #Apply functions - df = df.rename(columns=strip_quot) - df = df.rename(columns=lower_upper) - df = df.applymap(strip_quot) - - return df - - def _getHeaderNames(self, df, _df_types, key_clmns): - ''' - Method returns lists with header names (int and obj). - Does not return anything, only initialize the self._headers_dict variable for later use - ---------------------------------------------------- - df: input dataframe from which the headers are retrieved - _df_types: dataframe with types - key_columns: list with colum names of keys - ---------------------------------------------------- - ''' - #Define function - def get_headers(dataframe,type): - '''Function to group variable names based on the data type of the variable''' - return dataframe.select_dtypes(include=[type]).columns.values - - #Get header names into a list - other_headers = key_clmns[:2] - - if len(_df_types) != 0: - bool_mask = _df_types[_df_types['data_type'] != 'bool'] - try: - bool_headers = [n for n in _df_types.loc[bool_mask==False,0].values if n not in other_headers] - except: - bool_headers = [] - else: - bool_headers = [] - - object_headers = [n for n in get_headers(df,'object') if n not in other_headers + bool_headers] - numeric_headers = [n for n in get_headers(df,'number') if n not in other_headers + bool_headers] - - self._headers_dict = {'string':object_headers, 'numeric':numeric_headers, 'bool':bool_headers, 'other':other_headers} - - def _addPartitionColumn(self, df): - ''' - Method shuffle DF and create a column PARTITIONING with train/selection/validation categories - Returns DF with new column PARTITIONING - ---------------------------------------------------- - df: input dataframe which will be parittioned - ---------------------------------------------------- - ''' - #Shuffle and sort target - df = df.iloc[np.random.permutation(len(df))].sort_values(by='TARGET', ascending=False).reset_index(drop=True) - - partition = [] - sorted_target=df['TARGET'] #Just the target since it is allready sorted (see above) - for target in [sorted_target.iloc[0],sorted_target.iloc[-1]]: - target_length = (sorted_target==target).sum() - - for part, size in DataPreparation._partitioning_settings.items(): - partition.extend([part]*math.ceil(target_length*size)) - - df["PARTITION"] = partition[:len(df)] - - return df - - def _sampling(self, df): - ''' - Method takes sample for the dataframe. If no sampling is specified, all data are taken. - Returns sampled DF. - ---------------------------------------------------- - df: input dataframe which will be sampled - ---------------------------------------------------- - ''' - drop_index = [] - for target, size in DataPreparation._sampling_settings.items(): - if size < 1: - sample_length = int(round((df['TARGET']==target).sum() * size)) - - for part, size in DataPreparation._partitioning_settings.items(): - part_length = int(round(sample_length * size)) - drop_index_part = df[(df['TARGET']==target) & (df['PARTITION']==part)].index[part_length:] - drop_index.extend(drop_index_part) - - df.drop(drop_index,inplace=True) - df.reset_index(drop=True, inplace=True) - - return df - - def _prepNumVars(self, df, key_columns): - ''' - Method converts numerical variables into bins. 10 bins is always used - Returns DF with binned columns - ---------------------------------------------------- - df: input dataframe (with all variables!) - key_columns: list with colum names of keys - ---------------------------------------------------- - ''' - - df_out = df.loc[:,key_columns].copy() - - for clmn in self._headers_dict['numeric']: - result = DataPreparation.__eqfreq(var=df[clmn], - train=df["PARTITION"]=="train", - autobins=True, - nbins=self.discret_nbins, - precision=0, - twobins=True, - # TRUE OPTION STILL PRODUCES ERROR IN SORTNUMERIC function AND SCORING procedure !!!!!!!!! - catchLarge=False) - df_out = pd.concat([df_out,result[0]], axis=1) - - return df_out - - def _prepCatVars(self, df, key_columns): - ''' - Method regroup categorical variables based on significance. - If the incidence rate in particular group is not signicantly different - from the average incidence rate, then the variable is regrouped - (will be pushed to 'Non-significants' category) - Returns DF with regrouped categorical columns - ---------------------------------------------------- - df: input dataframe (with all variables!) - key_columns: list with colum names of keys - ---------------------------------------------------- - ''' - - df_out = df.loc[:,key_columns].copy() - - for clmn in self._headers_dict['string']: - # We label missing and empty values for categorical variables as 'Missing' - # Note the interaction with the 'keep' parameter of the regroup function. - mask = DataPreparation.__maskmissing(df[clmn]) - df.loc[mask,clmn]='Missing' - # Perform regrouping function - result = DataPreparation.__regroup(var=df[clmn], - target=df.loc[:,'TARGET'], - train=df['PARTITION']=='train', - pval_thresh=self.regroup_sign, - dummy=True, - keep='Missing', - rename='Non-significants') - df_out = pd.concat([df_out,result[0]],axis=1) - - return df_out - - def _prepBoolVars(self, df, key_columns): - ''' - Method just passes the variables. - In order to be consistent, there is this special method. Otherwise they could be renamed whenever - Returns DF with renamed bool variables - ---------------------------------------------------- - df: input dataframe (with all variables!) - key_columns: list with colum names of keys - ---------------------------------------------------- - ''' - - df_out = df.loc[:,key_columns].copy() - - def passvar(var): - var_pass = var.copy() - var_pass.name = "B_"+var.name - info = ("Passing "+var.name) - return var_pass, info - - for clmn in self._headers_dict['bool']: - # We label missing and empty values for boolean variables as 'Missing' - mask = DataPreparation.__maskmissing(df[clmn]) - df.loc[mask,clmn]='Missing' - # Perform the passvar function - result = passvar(var=df[clmn]) - df_out = pd.concat([df_out,result[0]],axis=1) - - return df_out - - def _replaceByIncidenceRate(self, df, key_columns): - ''' - Method to replace the groups with average incidence rate (the "secret sauce"). - The variables will start with "D_" - ---------------------------------------------------- - df: input dataframe (with all variables!) - key_columns: list with colum names of keys - ---------------------------------------------------- - ''' - df_out = df.loc[:,key_columns].copy() - - headers_for_incidrep = [h for h in df.columns if ((h not in key_columns) & (h[:2]=="B_"))] - - for clmn in headers_for_incidrep: - # Perform increp function - result = DataPreparation.__increp(b_var=df[clmn], - target=df['TARGET'], - train=df['PARTITION']=="train") - df_out = pd.concat([df_out,result], axis=1) - - return df_out - - ''' - ==================================================================== - ==================== AUXILIARY STATIC METHODS ==================== - ==================================================================== - ''' - - @staticmethod - def __eqfreq(var, train, autobins=True, nbins=10, precision=0, twobins=True, catchLarge=True): - ''' - Special method for binning continuous variables into bins - ---------------------------------------------------- - var: input pd.Serie with continuous columns - train: mask with rows which belongs to train - autobins: adapts number of bins - nbins: number of bins - precision: precision to form meaningful bins - twobins: if only two bins are found, iterate to find more - catchLarge: check when groups are too big - ---------------------------------------------------- - - This function is a reworked version of pd.qcut to satisfy our particular needs - - Takes for var a continuous pd.Series as input and returns a pd.Series with bin-labels (e.g. [4,6[ ) - - Train takes a series/list of booleans (note: we define bins based on the training set) - - Autobins reduces the number of bins (starting from nbins) as a function of the number of missings - - Nbins is the wished number of bins - - Precision=0 results in integer bin-labels if possible - - twobins=True forces the function to output at least two bins - - catchLarge tests if some groups (or missing group) are very large, and if so catches and outputs two groups - - note: catchLarge makes twobins irrelevant - ''' - - # Test for large groups and if one exists pass them with two bins: Large_group,Other - if catchLarge: - catchPercentage=1-(1/nbins) - groupCount = var[train].groupby(by=var[train]).count() - maxGroupPerc = groupCount.max()/len(var[train]) - missingPerc = sum(var[train].isnull())/len(var[train]) - if maxGroupPerc>=catchPercentage: - largeGroup = groupCount.sort_values(ascending=False).index[0] - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned!=largeGroup]='Other' - cutpoints=None - info = (var.name+": One large group, outputting 2 groups") - return x_binned, cutpoints, info - elif missingPerc>=catchPercentage: - x_binned = var.copy() - x_binned.name = 'B_'+var.name - x_binned[x_binned.isnull()]='Missing' - x_binned[x_binned!='Missing']='Other' - cutpoints=None - info = (var.name+": One large missing group, outputting 2 groups") - return x_binned, cutpoints, info - # Adapt number of bins as a function of number of missings - if autobins: - length = len(var[train]) - missing_total = var[train].isnull().sum() - missing_perten = missing_total/length*10 - nbins = max(round(10-missing_perten)*nbins/10 ,1) - # Store the name and index of the variable - name = var.name - series_index = var.index - # Transform var and train to a np.array and list respectively, which is needed for some particular function&methods - x = np.asarray(var) - train = list(train) - # First step in finding the bins is determining what the quantiles are (named as cutpoints) - # If the quantile lies between 2 points we use lin interpolation to determine it - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1),interpolation = 'linear') - # If the variable results only in 2 unique quantiles (due to skewness) increase number of quantiles until more than 2 bins can be formed - if twobins: - extrasteps = 1 - # Include a max. extrasteps to avoid infinite loop - while (len(cutpoints.unique())<=2) & (extrasteps<20): - cutpoints = var[train].quantile(np.linspace(0,1,nbins+1+extrasteps),interpolation = 'linear') - extrasteps+=1 - # We store which rows of the variable x lies under/above the lowest/highest cutpoint - # Without np.errstate(): xcutpoints.max() can give if x contains nan values (missings) - # However the function will result in False in both >&< cases, which is a correct result, so the warning can be ignored - with np.errstate(invalid='ignore'): - under_lowestbin = x < cutpoints.min() - above_highestbin= x > cutpoints.max() - - - def _binnedx_from_cutpoints(x, cutpoints, precision, under_lowestbin, above_highestbin): - ### Attributes the correct bin ........................ - ### Function that, based on the cutpoints, seeks the lowest precision necessary to have meaningful bins - ### e.g. (5.5,5.5] ==> (5.51,5.54] - ### Attributes those bins to each value of x, to achieve a binned version of x - - # Store unique cutpoints (e.g. from 1,3,3,5 to 1,3,5) to avoid inconsistensies when bin-label making - # Indeed, bins [...,1], (1,3], (3,3], (3,5], (5,...] do not make much sense - # While, bins [...,1], (1,3], (3,5], (5,...] do make sense - unique_cutpoints = cutpoints.unique() - # If there are only 2 unique cutpoints (and thus only one bin will be returned), - # keep original values and code missings as 'Missing' - if len(unique_cutpoints) <= 2: - cutpoints = None - x_binned = pd.Series(x) - x_binned[x_binned.isnull()] = 'Missing' - info = (var.name+": Only one resulting bin, keeping original values instead") - return x_binned, cutpoints, info - # Store info on whether or not the number of resulting bins equals the desired number of bins - elif len(unique_cutpoints) < len(cutpoints): - info = (var.name+": Resulting # bins < whished # bins") - else: - info = (var.name+": Resulting # bins as desired") - # Finally, recode the cutpoints (which can have doubles) as the unique cutpoints - cutpoints = unique_cutpoints - - # Store missing values in the variable as a mask, and create a flag to test if there are any missing in the variable - na_mask = np.isnan(x) - has_nas = na_mask.any() - # Attribute to every x-value the index of the cutpoint (from the sorted cutpoint list) which is equal or higher than - # the x-value, effectively encompasing that x-value. - # e.g. for x=6 and for sorted_cutpoint_list=[0,3,5,8,...] the resulting_index=3 - ids = cutpoints.searchsorted(x, side='left') - # x-values equal to the lowest cutpoint will recieve a ids value of 0 - # but our code to attribute bins to x-values based on ids (see end of this subfunction) requires a min. value of 1 - ids[x == cutpoints[0]] = 1 - # Idem as previous: x-values below the lowest cutpoint should recieve a min. value of 1 - if under_lowestbin.any(): - ids[under_lowestbin] = 1 - # Similar as previous: x-values above the highest cutpoint should recieve the max. allowed ids - if above_highestbin.any(): - max_ids_allowed = ids[(above_highestbin == False) & (na_mask==False)].max() - ids[above_highestbin] = max_ids_allowed - # Maximal ids can now be defined if we neglect ids of missing values - max_ids = ids[na_mask==False].max() - - # Based on the cutpoints create bin-labels - # Iteratively go through each precision (= number of decimals) until meaningful bins are formed - # If theoretical bin is ]5.51689,5.83654] we will prefer ]5.5,5.8] as output bin - increases = 0 - original_precision = precision - while True: - try: - bins = _format_bins(cutpoints, precision) - except ValueError: - increases += 1 - precision += 1 - #if increases >= 5: - #warnings.warn("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - #print("Modifying precision from "+str(original_precision)+" to "+str(precision)+" to achieve discretization") - else: - break - - # Make array of bins to allow vector-like attribution - bins = np.asarray(bins, dtype=object) - # If x has nas: for each na-value, set the ids-value to max_ids+1 - # this will allow na-values to be attributed the highest bin which we define right below - if has_nas: - np.putmask(ids, na_mask, max_ids+1) - # The highest bin is defined as 'Missing' - bins = np.append(bins,'Missing') - # ids-1 is used as index in the bin-labels list to attribute a bin-label to each x. Example: - # x=6 sorted_cutpoint_list=[0,3,5,8,...] ids=3 levels=[[0,3],(3,5],(5,8],...] - # The correct bin level for x is (5,8] which has index 2 which is equal to the ids-1 - x_binned = bins[ids-1] - return x_binned, cutpoints, info - - - def _format_bins(cutpoints, prec): - # Based on the quantile list create bins. Raise error if values are similar within one bin. - # On error _binnedx_from_cutpoints will increase precision - - fmt = lambda v: _format_label(v, precision=prec) - bins = [] - for a, b in zip(cutpoints, cutpoints[1:]): - fa, fb = fmt(a), fmt(b) - - if a != b and fa == fb: - raise ValueError('precision too low') - - formatted = '(%s, %s]' % (fa, fb) - bins.append(formatted) - - bins[0] = '[...,' + bins[0].split(",")[-1] - bins[-1] = bins[-1].split(",")[0] + ',...]' - return bins - - - def _format_label(x, precision): - # For a specific precision, returns the value formatted with the appropriate amount of numbers after comma and correct brackets - - if isinstance(x,float): - frac, whole = np.modf(x) - sgn = '-' if x < 0 else '' - whole = abs(whole) - if frac != 0.0: - val = '{0:.{1}f}'.format(frac, precision) - val = _trim_zeros(val) - if '.' in val: - return sgn + '.'.join(('%d' % whole, val.split('.')[1])) - else: - if '0' in val: - return sgn + '%0.f' % whole - else: - return sgn + '%0.f' % (whole+1) - else: - return sgn + '%0.f' % whole - else: - return str(x) - - - def _trim_zeros(x): - # Removes unnecessary zeros and commas - while len(x) > 1 and x[-1] == '0': - x = x[:-1] - if len(x) > 1 and x[-1] == '.': - x = x[:-1] - return x - - x_binned, cutpoints, info = _binnedx_from_cutpoints(x, cutpoints, precision=precision, under_lowestbin=under_lowestbin, above_highestbin=above_highestbin) - x_binned = pd.Series(x_binned, index=series_index, name="B_"+name) - return x_binned, cutpoints, info - - @staticmethod - def __maskmissing(df): - ''' - Method checks which values of a var are empty strings or null values - Returns DF mask - ---------------------------------------------------- - df: input dataframe - ---------------------------------------------------- - ''' - # Check if values are null - crit1 = df.isnull() - # Check if values are empty strings - modvar = pd.Series([str(value).strip() for value in df]) - crit2 = modvar==pd.Series(['']*len(df)) - return crit1 | crit2 - - @staticmethod - def __regroup(var,target,train,pval_thresh=0.01,dummy=True,keep='Missing',rename='Other'): - ''' - Method regroups categorical variables - Returns DF mask - ---------------------------------------------------- - var: input pd.Serie with cat column - target: pd.Serie with target variable - train: pd.Serie with parition variable - pval_thresh: threshold for regrouping - dummy: scale of booleans (?) - keep: keep specific groups (?) - rename: rename the insignificant category - ---------------------------------------------------- - - Each group is tested with a chi² for relevant incidence differences in comparison to a rest-group - - The rest group has the size of the remaining groups and an 'overall average incidence' (if dummy=True) or - - remaining groups average incidence' (if dummy=False) - - Groups with a pvalue above the threshold are relabled to a single group - ''' - - # Define the chi² test condition - # Groups that do not meet the condition are not analyzed and will be unconditionally relabled - def _chi2cond_(var=var,target=target,train=train): - varcounts = var[train].groupby(by=var).count() - train_inc = target[train].sum()/len(target[train]) - factor = max(train_inc, 1-train_inc) - analyze_mask = (varcounts*factor)>5 - analyze_groups = analyze_mask.index[analyze_mask].values - return analyze_groups - - # Compute overal incidence mean - incidence_mean = target[train].mean() - # Create container of which groups will be kept, compared to the groups which will be relabled - keepgroups = [] - # Cycle and test each group that meets the chi² condition - for group in _chi2cond_(): - # Container for target 0/1 observations of the group under scrutiny - obs_group = [] - # Counts of the target 0/1 occurences for the group under scrutiny - obs_group.append(((target[train]==0)&(var[train]==group)).sum()) - obs_group.append(((target[train]==1)&(var[train]==group)).sum()) - obs_group = np.array(obs_group) - # Container for target 0/1 observations of the remaining groups together - obs_other = [] - # Counts of the target 0/1 occurences for the remaining groups together - obs_other.append(((target[train]==0)&(var[train]!=group)).sum()) - obs_other.append(((target[train]==1)&(var[train]!=group)).sum()) - obs_other = np.array(obs_other) - # If dummy=True, we scale the two groups of target 0/1 occurences such that the incidence is equal to the overall incidence - # The size of the two groups of target 0/1 occurences is still equal to the size of the remaining groups - if dummy: - obs_other_size = obs_other.sum() - obs_other[0]=(1-incidence_mean)*obs_other_size # 0(1) index coincides with target = 0(1) - obs_other[1]=( incidence_mean)*obs_other_size - obs = np.array([obs_group,obs_other]) - # Place at least 1 observation to avoid error in chi2 test - obs[obs==0] = 1 - # Perform chi² test - pval = stats.chi2_contingency(obs, correction=False)[1] - # If pval outperforms threshold, append the group in the keepgroups list - if pval<=pval_thresh: - keepgroups.append(group) - #elif group==keep: - # keepgroups.append(group) - # If the specific group to be kept (e.g. 'Missing') didn't pass the test, append it to the keepgroups list - if keep not in keepgroups: - keepgroups.append(keep) - # Makes a list of all groups not in the keepgroups list - regroup_mask = [val not in keepgroups for val in var.values] - var_regroup = var.copy() - # Rename those groups - var_regroup[regroup_mask] = rename - var_regroup.name = "B_"+var.name - info = (var.name+": from "+str(len(var.unique()))+" to "+str(len(var_regroup.unique()))) - return var_regroup, info - - - @staticmethod - def __increp(b_var, target, train): - ''' - Method for incidence replacement - Returns replaced pd.Serie - ---------------------------------------------------- - b_var: input pd.Serie to be replaced - target: pd.Serie with target variable - train: pd.Serie with parition variable - ---------------------------------------------------- - ''' - - #get variable name - name = b_var.name - #get overall incidence - incidence_mean = target[train].mean() - #get incidence per group - incidences = target[train].groupby(b_var).mean() - #construct dataframe with incidences - idf = pd.DataFrame(incidences).reset_index() - #get values that are in the data but not in the labels - bin_labels = incidences.index - newgroups = list(set(b_var.unique()) ^ set(bin_labels)) - #if newgroups, add mean incidence to incidence dataframe for each new group - if len(newgroups)>0: - #make dataframe: - ngdf = pd.DataFrame(newgroups) - ngdf.columns = [name] - ngdf["TARGET"] = incidence_mean - #dataframe with incidences: - idf = idf.append(ngdf) - #dataframe with the variable - vdf = pd.DataFrame(b_var) - #discretized variable by merge - d_var = pd.merge(vdf,idf,how='left',on=name)["TARGET"] - return pd.Series(d_var, name="D_"+name[2:]) - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cobra/univariate_selection.py b/cobra/univariate_selection.py deleted file mode 100644 index a69ab74..0000000 --- a/cobra/univariate_selection.py +++ /dev/null @@ -1,131 +0,0 @@ -''' -====================================================================================================================== --------------------------------------------------- UNIVARIATE SELECTION -------------------------------------------- -====================================================================================================================== -''' -import numpy as np -import pandas as pd -from sklearn import metrics - -class UnivariateSelection(object): - ''' - Class for Univariate Selection. - Calculates AUC and correlation matrix - ---------------------------------------------------- - Author: Jan Benisek, Python Predictions - Date: 19/02/2018 - ---------------------------------------------------- - ***PARAMETERS*** - :preselect_auc: Minimal treshold for AUC selection - :preselect_overtrain: Threshold for difference between train and test performance - ---------------------------------------------------- - ''' - - def __init__(self, preselect_auc, preselect_overtrain): - ''' ***PARAMETERS*** ''' - self.preselect_auc = preselect_auc - self.preselect_overtrain = preselect_overtrain - - def fit(self, df): - ''' - Method fits (=performs) Univariate selection - Returns auc, correlation and list with filtered variables - ---------------------------------------------------- - df: transformed dataset - ---------------------------------------------------- - ''' - key_clmns = ["ID","TARGET","PARTITION"] - - ##AUC selection - df_auc = self._calcFilterAUC(df, key_clmns) - - ##Correlation - df_corr = self._calcCorr(df, key_clmns) - - return df_auc, df_corr - - - def _calcFilterAUC(self, df, key_clmns): - ''' - Method calculates AUC for train/test - Returns DF with AUC higher than given threshold, drops overfitted variables - and creates column signalizing if a variable has been preselected. - ---------------------------------------------------- - df: transformed dataset - key_clmns: list with key columns names - ---------------------------------------------------- - ''' - headers_for_auc = [h for h in df.columns if ((h not in key_clmns) & (h[:2]=="D_"))] - - def getauc(var, target, partition): - y = np.array(target[partition]) - pred = np.array(var[partition]) - pred = pred.astype(np.float64) - fpr, tpr, thresholds = metrics.roc_curve(y,pred, pos_label=1) - return metrics.auc(fpr, tpr) - - auc_list_all = [] - parts = ["train","selection"] - - for header in headers_for_auc: - auc_list_var = [header[2:]] - # We loop through the two sets ('train' and 'selection') for which an AUC score is needed - for part in parts: - auc_value = getauc(var=df[header] - ,target=df['TARGET'] - ,partition=df['PARTITION']==part) - auc_list_var.append(auc_value.round(3)) - - auc_list_all.append(auc_list_var) - - df_auc = pd.DataFrame(auc_list_all,columns=['variable','AUC train','AUC selection']) - - #Filter based on min AUC - auc_thresh = df_auc.loc[:,'AUC selection'] > self.preselect_auc - # We identify those variables for which the AUC score difference between 'train' and 'selection' is within the user-defined ratio - auc_overtrain = (df_auc.loc[:,'AUC train']*100 - df_auc.loc[:,'AUC selection']*100) < self.preselect_overtrain - - # List of variables which passed the two criteria - df_auc['preselection'] = auc_thresh & auc_overtrain - - return df_auc - - def _calcCorr(self, df, key_clmns): - ''' - Method calculates correlation on train set amongst the "D_" variables - Returns DF with correlations - ---------------------------------------------------- - df: transformed dataset - key_clmns: list with key columns names - ---------------------------------------------------- - ''' - headers_for_corr = [h for h in df.columns if ((h not in key_clmns) & (h[:2]=="D_"))] - - train = df['PARTITION']=="train" - dataforcorr = np.transpose(np.matrix(df.loc[train,headers_for_corr],dtype=float)) - with np.errstate(invalid='ignore', divide='ignore'): - mat_corr = np.corrcoef(dataforcorr) - - #Convert numpy to pandas - df_corr = pd.DataFrame(mat_corr) - df_corr.columns = headers_for_corr - df_corr.index = headers_for_corr - df_corr.fillna(0, inplace=True) - - return df_corr - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cobra/utils.py b/cobra/utils.py index e7057da..8c55b7d 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -58,3 +58,16 @@ def get_column_datatypes(data: pd.DataFrame, return {"numeric_variables": list(vars_numeric), "categorical_variables": list(vars_cat)} + + +def clean_predictor_name(predictor: str) -> str: + """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor + name to return a clean version of the predictor + + Args: + predictor (str): Description + + Returns: + str: Description + """ + return predictor.replace("_enc", "").replace("_bin", "") From defa4bdd97bbbab7d24baa3d4c55c65d98f712d7 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 19 Mar 2020 14:48:40 +0100 Subject: [PATCH 52/98] Move private function to utils --- cobra/model_building/univariate_selection.py | 24 +++++--------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index 1979ac8..60838e8 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -9,6 +9,7 @@ """ import pandas as pd from sklearn.metrics import roc_auc_score +import cobra.utils as utils def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, @@ -47,14 +48,14 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, Returns: pd.DataFrame: DataFrame containing for each variable the train auc and - test auc allong with a boolean indicating whether or not it is selected - based on the criteria + selection auc allong with a boolean indicating whether or not it is + selected based on the criteria """ result = [] for predictor in predictors: - cleaned_predictor = _clean_predictor_name(predictor) + cleaned_predictor = utils.clean_predictor_name(predictor) auc_train = roc_auc_score( y_true=target_enc_train_data[target_column], @@ -81,7 +82,7 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, df_auc["preselection"] = auc_thresh & auc_overtrain - return df_auc + return df_auc.sort_values(by='AUC selection', ascending=False) def get_preselected_predictors(df_auc: pd.DataFrame) -> list: @@ -124,7 +125,7 @@ def compute_correlations(target_enc_train_data: pd.DataFrame, correlations = target_enc_train_data[predictors].corr() - predictors_cleaned = [_clean_predictor_name(predictor) + predictors_cleaned = [utils.clean_predictor_name(predictor) for predictor in predictors] # Change index and columns with the cleaned version of the predictors @@ -133,16 +134,3 @@ def compute_correlations(target_enc_train_data: pd.DataFrame, correlations.index = predictors_cleaned return correlations - - -def _clean_predictor_name(predictor: str) -> str: - """Strip-off redundant suffix (e.g. "_enc" or "_bin") from the predictor - name to return a clean version of the predictor - - Args: - predictor (str): Description - - Returns: - str: Description - """ - return predictor.replace("_enc", "").replace("_bin", "") From aaff470f24fc16b854b75d5659366e04012f409b Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 19 Mar 2020 15:55:57 +0100 Subject: [PATCH 53/98] Add variable importance computation to models.py Moreover, split param in evaluation was made optional so that you can still evaluate the model on future datasets (for monitoring) without caching the result. --- cobra/model_building/forward_selection.py | 9 +++-- cobra/model_building/models.py | 49 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index fc236ea..674d3a4 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -85,19 +85,19 @@ def compute_model_performances(self, data: pd.DataFrame, performance_train = model.evaluate( data[data["split"] == "train"], data[data["split"] == "train"][target_column_name], - split="train" + split="train" # used for caching ) performance_selection = model.evaluate( data[data["split"] == "selection"], data[data["split"] == "selection"][target_column_name], - split="selection" + split="selection" # used for caching ) performance_validation = model.evaluate( data[data["split"] == "validation"], data[data["split"] == "validation"][target_column_name], - split="validation" + split="validation" # used for caching ) last_added_predictor = (set(model.predictors) @@ -240,7 +240,8 @@ def _find_next_best_model(self, train_data: pd.DataFrame, # Evaluate model performance = (model .evaluate(train_data[current_predictors + [pred]], - train_data[target_column_name])) + train_data[target_column_name], + split="train")) if (self.pos_only and (not (model.get_coef() >= 0).all())): continue diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index 39c7a97..de09015 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -1,7 +1,11 @@ +# third party imports import numpy as np import pandas as pd +from scipy import stats from sklearn.metrics import roc_auc_score from sklearn.linear_model import LogisticRegression +# custom imports +import cobra.utils as utils class LogisticRegressionModel: @@ -86,9 +90,11 @@ def score_model(self, X: pd.DataFrame) -> np.ndarray: return self.logit.predict_proba(X[self.predictors])[:, 1] def evaluate(self, X: pd.DataFrame, y: pd.Series, - split: str="train") -> float: - """Evaluate the model on a given split (train, selection, validation) - of a data set (X, y) + split: str=None) -> float: + """Evaluate the model on a given data set (X, y). The optional split + parameter is to indicate that the data set belongs to + (train, selection, validation), so that the computation on these sets + can be cached! Parameters ---------- @@ -104,10 +110,41 @@ def evaluate(self, X: pd.DataFrame, y: pd.Series, float the performance score of the model (e.g. AUC) """ - if self._eval_metrics_by_split.get(split) is None: + + if (split is None) or (split not in self._eval_metrics_by_split): y_pred = self.score_model(X) - self._eval_metrics_by_split[split] = roc_auc_score(y_true=y, - y_score=y_pred) + performance = roc_auc_score(y_true=y, y_score=y_pred) + + if split is None: + return performance + else: + self._eval_metrics_by_split[split] = performance + return self._eval_metrics_by_split[split] + + def compute_variable_importance(self, data: pd.DataFrame) -> dict: + """Compute the importance of each predictor in the model and return + it as a dictionary + + Parameters + ---------- + data : pd.DataFrame + data to score the model + + Returns + ------- + dict + Map of predictor -> importance + """ + + y_pred = self.score_model(data) + + return { + utils.clean_predictor_name(predictor): stats.pearsonr( + data[predictor], + y_pred + ) + for predictor in self.predictors + } From b2e4e93a8c4c2868f9dfa6ec9131dd697e379a7e Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 09:28:26 +0100 Subject: [PATCH 54/98] Delete old cobra files --- cobra/cobra.py | 501 --------------------------------------- cobra/model_selection.py | 302 ----------------------- 2 files changed, 803 deletions(-) delete mode 100644 cobra/cobra.py delete mode 100644 cobra/model_selection.py diff --git a/cobra/cobra.py b/cobra/cobra.py deleted file mode 100644 index cb5e6d0..0000000 --- a/cobra/cobra.py +++ /dev/null @@ -1,501 +0,0 @@ -''' -====================================================================================================================== ---------------------------------------------------------- COBRA ---------------------------------------------------- -====================================================================================================================== -''' -# -import cobra.data_preparation as dpc -import cobra.univariate_selection as us -import cobra.model_selection as ms -# -import seaborn as sns -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from string import ascii_lowercase - -class COBRA(object): - ''' - Wrapper class for all the child classes for easier usage - ---------------------------------------------------- - Author: Jan Benisek, Python Predictions - Date: 21/02/2018 - ---------------------------------------------------- - ***PARAMETERS*** - :data_path: Path to .csv file which contains the data - :data_types_path: Path to .csv files which contains the metadata - :partition_train: Size of training set as int <0;1> - :partition_select: Size of selection set as int <0;1> - :partition_valid: Size of validation set as int <0;1> - :sampling_1: Size of sampling of target class - :sampling_0: Size of sampling of non-target class - :discret_nbins: ??? - :regroup_sign: Significance level for regrouping categorical variables - :rseed: Random seed for reproducibility (partitioning). None or a number - - ***ATTRIBUTES*** - :_partition_dict: Dict with partitioned DFs X/Y train/selection/validation - :_headers_dict: Dict of 4 lists with header names (object, numeric, bool, other) - :_partitioning_settings: Dict with train/sel/valid sets with their size - ---------------------------------------------------- - __init__: contains variables which are established with the object. - If some of them is changed, then the whole process must be redone(call the class again), - because the model comparison wont make sense - transform: For the reasons before, transform has no parameters - fit_univariate: there I can change stuff when trying different modelling ideas - i.e. what variables will I get if AUC threshold is changed - fit_model: Here I want try many things, so the parametes are changeble in the method. - ''' - - def __init__(self, - data_path, - data_types_path, - partition_train=0.5, - partition_select=0.3, - partition_valid=0.2, - sampling_1=1, - sampling_0=1, - discret_nbins=5, - regroup_sign=0.001, - rseed=None): - - ''' ***PARAMETERS*** ''' - self.data_path = data_path - self.data_types_path = data_types_path - self.partition_train = partition_train - self.partition_select = partition_select - self.partition_valid = partition_valid - self.sampling_1 = sampling_1 - self.sampling_0 = sampling_0 - self.discret_nbins = discret_nbins - self.regroup_sign = regroup_sign - self.rseed = rseed - - - def transform(self): - ''' - Method transforms given csv - ---------------------------------------------------- - only self - ---------------------------------------------------- - ''' - dtrans = dpc.DataPreparation(self.data_path, - self.data_types_path, - self.partition_train, - self.partition_select, - self.partition_valid, - self.sampling_1, - self.sampling_0, - self.discret_nbins, - self.regroup_sign, - self.rseed) - - df_trans = dtrans.transform() - - self._headers_dict = dtrans._headers_dict - self._partitioning_settings = dtrans._partitioning_settings - - return df_trans - - - def fit_univariate(self, df_t, preselect_auc=0.53, preselect_overtrain=5): - ''' - Method transforms given csv - Returns univariate selection and correlation matrix - ---------------------------------------------------- - df_t: dataframe with transformed data - ---------------------------------------------------- - ''' - - unisel = us.UnivariateSelection(preselect_auc, - preselect_overtrain) - df_sel, df_corr = unisel.fit(df_t) - - return df_sel, df_corr - - def fit_model(self, df_t, df_us, modeling_nsteps=30, forced_vars=None, excluded_vars=None, name=None, verbose=False, positive_only=True): - ''' - Method fits and finds best model - Returns dataframe with all the info - forward selection, AUC, importance... - ---------------------------------------------------- - df_t: dataframe with transformed data - df_us: dataframe with univariate selection - modeling_nsteps: how many steps in modelling - forced_vars: list with variables to be forced in the model - excluded_vars: list with variables to be excluded in the model - name: name of the model - verbose: whether immediate steps of the procedure should be printed to the console - positive_only: whether only positive coeficients should be considered (recommended to stay so) - ---------------------------------------------------- - ''' - modsel = ms.ModelSelection(verbose=verbose, positive_only=positive_only) - - df_models = modsel.fit(df_t, - df_us, - modeling_nsteps=modeling_nsteps, - forced_vars=forced_vars, - excluded_vars=excluded_vars, - name=name) - - self._partition_dict = modsel._partition_dict - - return df_models - - def summary(self, df_t): - ''' - Method describes dataset (=prints summary). - Now just few simple things, make printing fancy and add more. - ------------------------------------------------------------ - df_t: dataframe with transformed data - ------------------------------------------------------------ - ''' - print('----------------- SUMMARY -----------------'.format()) - print('Dataset has {} rows and {} columns.'.format(len(df_t), len(df_t.columns))) - print('Train set has {} rows'.format(len(df_t[df_t['PARTITION'] == 'train']))) - print('Selection set has {} rows'.format(len(df_t[df_t['PARTITION'] == 'selection']))) - print('Validation set has {} rows'.format(len(df_t[df_t['PARTITION'] == 'validation']))) - print('Overall incidence rate is {0:0.2f}%'.format(df_t['TARGET'].mean()*100)) - - num_of_missings = df_t[df_t == 'Missing'].count().sum() - all_elements = len(df_t) * len(df_t.columns) - - print('{0:0.2f}% records in the dataset are missing.'.format((num_of_missings/all_elements)*100)) - print('-------------------------------------------'.format()) - - - ''' - ==================================================================== - ================ STATIC METHODS FOR VISUALIZATION ================ - ==================================================================== - ''' - @staticmethod - def plotPredictorQuality(df, dim=(12,8)): - ''' - Method plots Univarite quality of predictors - Returns plot - ---------------------------------------------------- - df: dataframe with univariate selection - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - df_uq = df[['variable','AUC train','AUC selection']][df['preselection'] == True].sort_values(by='AUC train', ascending=False) - df_uq.columns = ['variable name','AUC train','AUC selection'] - df_uq = pd.melt(df_uq, id_vars=['variable name'], value_vars=['AUC train', 'AUC selection'], var_name='partition', value_name='AUC') - - #---------------------------------- - #------- Plot the bars ---------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - - ax = sns.barplot(x="AUC", y="variable name", hue="partition", data=df_uq) - ax.set_title('Univariate Quality of Predictors') - plt.show() - - @staticmethod - def plotCorrMatrix(df, dim=(12,8)): - ''' - Method plots Correlation matrix among predictors - Returns plot - ---------------------------------------------------- - df: dataframe with correlation data - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - fig, ax = plt.subplots(figsize=dim) - ax = sns.heatmap(df, cmap='Blues') - ax.set_title('Correlation Matrix') - plt.show() - - @staticmethod - def plotIncidence(df, variable, dim=(12,8)): - ''' - Method plots Incidence plot on train partition - Returns plot - ---------------------------------------------------- - df: dataframe with cleaned, binned, partitioned and prepared data - variable: variable for which the incidence plot will be shown` - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - def masterOfOrder(x): - ''' - Function converts interval or string (category) to a number, so the incidence plot can be orderd. - In case of interval -> '(151, 361]' to integer 151. - In case of string -> order is alphabetical - Missings and Non-significants are always put at the end - - Parameters - ---------- - x: value to be converted - - Output - ------ - Order of given value - ''' - x_split = x.split(',')[0] - replace_strings = (('...', '0'),('Missing','999999999999'), ('Non-significants','999999999999')) - for repl_str in replace_strings: - x_split = x_split.replace(repl_str[0], repl_str[1]) - x_split = x_split.strip("()[]") - - try: - order = float(x_split) - except: - LETTERS = {letter: index for index, letter in enumerate(ascii_lowercase, start=1)} - order = LETTERS[x[0].lower()] - - return order - - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - #Set up the variable and dataframe - var_prefix = 'B_' + variable - df_plt = df[['TARGET', var_prefix]][df['PARTITION'] == 'train'].copy() - - #Aggregate the data - avg_inc_rate = df_plt['TARGET'].mean() - - aggregations = { - 'bin_inc_rate': 'mean', - 'bin_size': 'count' - } - df_plt = df_plt.groupby(var_prefix, as_index=False)['TARGET'].agg(aggregations) - df_plt['avg_inc_rate'] = avg_inc_rate - - #create a sort column and sort by it - df_plt['sort_by'] = df_plt[var_prefix].apply(lambda x: masterOfOrder(x)) - df_plt.sort_values(by='sort_by', ascending=True, inplace=True) - df_plt.reset_index(inplace=True) - - #---------------------------------- - #----- Plot the incidence ------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - ##First Axis - #Bin size - y_pos = np.arange(len(df_plt[var_prefix])) - plt.bar(y_pos, df_plt['bin_size'].values.tolist(), align='center', color="cornflowerblue") - plt.xticks(y_pos, df_plt[var_prefix]) - plt.ylabel('Bin Size') - plt.xlabel(variable + ' Bins') - - max_inc = max(df_plt['bin_inc_rate']) - - ##Second Axis - ax2 = ax.twinx() - #incidence rate per bin - plt.plot(df_plt['bin_inc_rate'], color="darkorange", marker=".", markersize=20, linewidth=3, label='incidence rate per bin') - plt.plot(df_plt['avg_inc_rate'], color="dimgrey", linewidth=4, label='average incidence rate') - ax2.plot(np.nan, "cornflowerblue", linewidth=6, label = 'bin size') #dummy line to have label on second axis from first - ax2.set_yticks(np.arange(0, max_inc+0.05, 0.05)) - ax2.set_yticklabels(['{:3.1f}%'.format(x*100) for x in ax2.get_yticks()]) - plt.ylabel('Incidence') - - ##Set Axis - sns.despine(ax=ax, right=True, left=True) - sns.despine(ax=ax2, left=True, right=False) - ax2.spines['right'].set_color('white') - - #remove white line from second grid axes - #the white lines are reguler, Spyder sometimes fails to visualize it (try to export the pic!) - ax2.grid(False) - - ##Description - fig.suptitle('Incidence Plot - ' + variable, fontsize=20, y=1.02) - ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=1, mode="expand", borderaxespad=0.) - plt.show() - - @staticmethod - def plotAUC(df, dim=(12,8)): - ''' - Method plots AUC for train/selection/validation and number of selected variables - Returns plot - ---------------------------------------------------- - df: dataframe with models performance - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - AUC on optimal number of vars - ''' - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - df_plt = df[['last_var_added','auc_train','auc_selection','auc_validation']] - df_plt.columns = ['variable name', 'AUC train','AUC selection','AUC validation'] - - highest_auc = np.round(max(max(df_plt['AUC train']), - max(df_plt['AUC selection']), - max(df_plt['AUC validation'])), 1) - - #---------------------------------- - #-------- Plot the AUC ---------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - - plt.plot(df_plt['AUC train'], marker=".", markersize=20, linewidth=3, label='AUC train') - plt.plot(df_plt['AUC selection'], marker=".", markersize=20, linewidth=3, label='AUC selection') - plt.plot(df_plt['AUC validation'], marker=".", markersize=20, linewidth=3, label='AUC validation') - #Set x/yticks - ax.set_xticks(np.arange(len(df_plt['variable name'])+1)) - ax.set_xticklabels(df_plt['variable name'].tolist(), rotation = 40, ha='right') - ax.set_yticks(np.arange(0.5, highest_auc+0.02, 0.05)) - #Make Pretty - ax.legend(loc='lower right') - fig.suptitle('Multivariate Model AUC - ' + df.name, fontsize=20) - plt.ylabel('AUC') - plt.show() - - @staticmethod - def plotVariableImportance(df, step=None, dim=(12,8)): - ''' - Method plots variable importance for given model - Returns plot - ---------------------------------------------------- - df: dataframe with models performance - step: for which model the importance will be shown - (the best if not specified) - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - Importance on optimal number of vars - ''' - plt.style.use('seaborn-darkgrid') - - #If step not specified, give model with highes performance - if step == None: - step = df['step'].iloc[df['auc_validation'].idxmax()] - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - #dict_plt = df['importance'].iloc[model_row] - dict_plt = df['importance'][df['step'] == step] - df_plt = pd.DataFrame.from_dict(dict_plt.iloc[0], orient='index') - df_plt.reset_index(level=0, inplace=True) - df_plt.columns = ['variable name','importance'] - df_plt.sort_values(by='importance', ascending=False, inplace=True) - - - #---------------------------------- - #------- Plot the bars ---------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - ax = sns.barplot(x="importance", y="variable name", data=df_plt) - ax.set_title('Variable Importance in model ' + df.name) - ax.set_title('Variable importance in model {}, step {}.'.format(df.name, step)) - plt.show() - - @staticmethod - def plotCumulatives(model_list, df_trans, dim=(12,8)): - ''' - Method plots cumulative response and gains in one plot for multiple models - Returns plot - ---------------------------------------------------- - model_list: list of tuples with model DF and step number (which model to take from the DF) - df_trans: dataframe with cleaned, binned, partitioned and prepared data - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - Max 5 models is allowed, on train partition - ''' - plt.style.use('seaborn-darkgrid') - - if len(model_list) >5: - raise ValueError('The maximum number of input models is 5') - - colors = ['cornflowerblue','forestgreen','firebrick','darkmagneta','orange'] - - avg_incidence = df_trans['TARGET'][df_trans['PARTITION'] == 'train'].mean() - - #---------------------------------- - #------- Plot the data ---------- - #---------------------------------- - fig, (ax_cresp, ax_cgains) = plt.subplots(1, 2, sharey=False, figsize=dim) - # - #Cumulative Response - # - for i, model in enumerate(model_list): - #------ Prepare the data -------- - cum_resp = model[0]['cum_response'][model[0]['step'] == model[1]].tolist()[0] - #------ Plot line for each model -------- - ax_cresp.plot(cum_resp, color=colors[i], linewidth=3, label='cumulative response - ' + model[0].name) - - ax_cresp.axhline(y=np.round(avg_incidence*100), color="darkorange", linewidth=3, ls="--", label='average incidence rate') - ax_cresp.set_title('Cumulative Response', fontsize=20) - #Format axes - ax_cgains.set_xlim([0,100]) - ax_cgains.set_ylim([0,100]) - #Format ticks - ax_cresp.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cresp.get_yticks()]) - ax_cresp.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cresp.get_xticks()]) - #Legend - ax_cresp.legend(loc='upper right') - # - #Cumulative Gains - # - for i, model in enumerate(model_list): - #------ Prepare the data -------- - cum_gains = model[0]['cum_gains'][model[0]['step'] == model[1]].tolist()[0] - #------ Plot line for each model -------- - ax_cgains.plot(cum_gains, color=colors[i], linewidth=3, label='cumulative gains - ' + model[0].name) - - ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, ls="--", color="darkorange", label='random selection') - ax_cgains.set_title('Cumulative Gains', fontsize=20) - #Format axes - ax_cgains.set_xlim([0,100]) - ax_cgains.set_ylim([0,100]) - #Format ticks - ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_yticks()]) - ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_xticks()]) - #Legend - ax_cgains.legend(loc='lower right') - - #Make pretty - plt.tight_layout() - - plt.show() - - @staticmethod - def plotAUCComparison(model_list, dim=(12,8)): - ''' - Method plots AUC comarison on train/selection/validation - Returns plot - ---------------------------------------------------- - model_list: list of tuples with model DF and step number (which model to take from the DF) - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - df_plt = pd.DataFrame() - for model, step in model_list: - df_aux = pd.DataFrame(model[['auc_train','auc_selection','auc_validation']][model['step'] == step]) - df_aux['model'] = model.name - df_plt = pd.concat([df_plt, df_aux]) - - df_plt.reset_index(inplace=True, drop=True) - - df_plt.columns = ['AUC train','AUC selection','AUC validation','model'] - df_plt = pd.melt(df_plt, id_vars=['model'], value_vars=['AUC train','AUC selection','AUC validation'], - var_name='partition', value_name='AUC') - - #---------------------------------- - #------- Plot the bars ---------- - #---------------------------------- - fig, ax = plt.subplots(figsize=(12,8)) - - ax = sns.barplot(x="AUC", y="partition", hue="model", data=df_plt) - - ax.set_xlim([0,1.2]) - ax.set_title('AUC comparison') - plt.show() \ No newline at end of file diff --git a/cobra/model_selection.py b/cobra/model_selection.py deleted file mode 100644 index f986051..0000000 --- a/cobra/model_selection.py +++ /dev/null @@ -1,302 +0,0 @@ -''' -====================================================================================================================== --------------------------------------------------- MODEL SELECTION -------------------------------------------- -====================================================================================================================== -''' -import pandas as pd -import numpy as np -from scipy import stats -from sklearn import metrics -from sklearn.linear_model import LogisticRegression - -class ModelSelection(object): - ''' - Class for Model Selection - Finds best model using forward selection - ---------------------------------------------------- - Author: Jan Benisek, Python Predictions - Date: 19/02/2018 - ---------------------------------------------------- - ***PARAMETERS*** - :modeling_nsteps: how many variables will be used for modelling - :forced_vars: Force variables to be used in forward selection - :excluded_vars: List with variables to be excluded - :verbose: Whether more info about the ouput should be printed - - ***ATTRIBUTES*** - :_partition_dict: Dict with partitioned DFs X/Y train/selection/validation - :_optimal_nvars: Optimal number of variables - :positive_only: Whether all coeficients should be positivr - ---------------------------------------------------- - ''' - - def __init__(self, verbose, positive_only): - self.verbose = verbose - self.positive_only = positive_only - - - def fit(self, df_trans, df_unisel, modeling_nsteps, forced_vars, excluded_vars, name): - ''' - Method fits (=performs) Model Selection - Returns DF with model performance and list - ---------------------------------------------------- - df_trans: transformed dataset - df_unisel: dataframe with univariate selection - modeling_nsteps: how many variables will be used for modelling - forced_vars: variables forced to be used in the modelling, list - excluded_vars: variables to be excluded - ---------------------------------------------------- - ''' - - self.modeling_nsteps = modeling_nsteps - - ##Create partition - self._partition_dict = self._getTrainSelectValidXY(df_trans) - - ##Perform forward selection - df_fsel = self._forwardSelection(df_unisel, forced_vars, excluded_vars) - - ##Cumulative respone/gain and adds it into df_fsel - self._cumulatives(df_fsel) - - ##Calclates importance and adds it into df_fsel - self._calcImportance(df_fsel) - - ##Give name - df_fsel.name = name - - return df_fsel - - def _getTrainSelectValidXY(self, df): - ''' - Method split given DF into train/test/validation set in respect to X and Y. - Returns dictionary with DFs - ---------------------------------------------------- - df: transformed dataset - ---------------------------------------------------- - ''' - - dvars = [n for n in df.columns if n[:2] == 'D_'] - - mask_train = df['PARTITION']=="train" - mask_selection = df['PARTITION']=="selection" - mask_validation = df['PARTITION']=="validation" - - y_train = df.loc[mask_train,'TARGET'] - y_selection = df.loc[mask_selection,'TARGET'] - y_validation = df.loc[mask_validation,'TARGET'] - - x_train = df.loc[mask_train,dvars] - x_selection = df.loc[mask_selection,dvars] - x_validation = df.loc[mask_validation,dvars] - - dict_out = {'y_train':y_train, 'y_selection':y_selection, 'y_validation':y_validation, - 'x_train':x_train, 'x_selection':x_selection, 'x_validation':x_validation} - - return dict_out - - def _forwardSelection(self, df_sel, forced_vars, excluded_vars): - ''' - Method performs forward selection - Returns DF with performance - ---------------------------------------------------- - df_sel: DF with selection from Univariate Selection - forced_vars: list with varibels forced to be in the forward selection - excluded_vars: list with variables to be excluded - positive_only: whether or not all coefs in logit should be positive - ---------------------------------------------------- - ''' - if not excluded_vars: - excluded_vars = [] - - if not forced_vars: - forced_vars = [] - - #Sort - df_sel = df_sel.sort_values(by='AUC selection', ascending=False) - - #Build list of variables to be used for Forward selection - preselected_vars = df_sel['variable'][df_sel['preselection'] == True].tolist() - preselected_vars = [var for var in preselected_vars if var not in forced_vars+excluded_vars] - all_vars = ['D_' + var for var in forced_vars + preselected_vars] - - df_forward_selection = pd.DataFrame(None,columns=[ - 'step', - 'coef', - 'intercept', - 'all_coefs_positive', - 'auc_train', - 'auc_selection', - 'auc_validation', - 'predictors_subset', - 'last_var_added', - 'auc_train_rank', - 'selected_model', - 'pred_training', - 'pred_selection', - 'pred_validation', - 'first_rank' - ]) - - - f_position_forced = lambda i, forced, all_vars: len(forced) if i <= len(forced) else len(all_vars) - - n_steps = min(self.modeling_nsteps + 1,len(all_vars)) - predictors = [] - row = 0 - - #----------------------------------------------------------------------------------- - #------------------------------- ITERATE FOR EVERY STEP -------------------------- - #----------------------------------------------------------------------------------- - for step in range(1,n_steps): - - pos = f_position_forced(step, forced_vars, all_vars) - remaining_predictors = [var for var in all_vars[:pos] if var not in predictors] - - #----------------------------------------------------------------------------------- - #-------------------------------- FOR EVERY COMBINATION -------------------------- - #----------------------------------------------------------------------------------- - for predictor in remaining_predictors: - predictors_subset = predictors + [predictor] - #Train - train model - logit = LogisticRegression(fit_intercept=True, C=1e9, solver = 'liblinear') - logit.fit(y=self._partition_dict['y_train'], X=self._partition_dict['x_train'][predictors_subset]) - - #Train - predict and AUC - y_pred_train = logit.predict_proba(self._partition_dict['x_train'][predictors_subset]) - AUC_train = metrics.roc_auc_score(y_true=self._partition_dict['y_train'], y_score=y_pred_train[:,1]) - - #Selection - predict and AUC - y_pred_selection = logit.predict_proba(self._partition_dict['x_selection'][predictors_subset]) - AUC_selection = metrics.roc_auc_score(y_true=self._partition_dict['y_selection'], y_score=y_pred_selection[:,1]) - - #Validation - predict and AUC - y_pred_validation = logit.predict_proba(self._partition_dict['x_validation'][predictors_subset]) - AUC_validation = metrics.roc_auc_score(y_true=self._partition_dict['y_validation'], y_score=y_pred_validation[:,1]) - - #check if coefs are positive - all_coefs_positive = (logit.coef_[0] >= 0).all() - - #Update DF - df_forward_selection.loc[row] = [ - step, #Step - logit.coef_, #coef - logit.intercept_, #intercept - all_coefs_positive, #all_coefs_positive - AUC_train, #auc_train - AUC_selection, #auc_selection - AUC_validation, #auc_validation - predictors_subset, #predictors_subset - predictors_subset[-1], #last_var_added - 0, #auc_train_rank - False, #selected_model - y_pred_train, #pred_training - y_pred_selection, #pred_selection - y_pred_validation, #pred_validation - 0 #first_rank - ] - row +=1 - - #Only positive coefs - if self.positive_only: - all_coefs_negative = len(df_forward_selection[(df_forward_selection['all_coefs_positive'] == True) & (df_forward_selection['step'] == step)]) == 0 - - if all_coefs_negative: - if self.verbose: - - print('No models with only positive coefficients, following step skipped.') - print(df_forward_selection[(df_forward_selection['all_coefs_positive'] == True) & (df_forward_selection['step'] == step)]) - #Skip the next steps and go the next iteration - #The fitted models are not of interest if the user explicitly - # says positive_only=True - continue - - ''' - Previous solution, I kept it FYI - if len(df_forward_selection[(df_forward_selection['all_coefs_positive'] == True) & (df_forward_selection['step'] == step)]) == 0: - raise ValueError("No models with only positive coefficients","NormalStop") - ''' - - ##Find best model - #Sort AUC by size (if two with same performance, ranked by first appearence) - df_forward_selection['auc_train_rank'] = df_forward_selection.groupby('step')['auc_train'].rank(ascending=False, method='first') - - #Find model where AUC is highest AND all coefs are positive - convert to boolean flag - df_forward_selection['first_rank'] = df_forward_selection[df_forward_selection['all_coefs_positive'] == True].groupby(['step'])['auc_train_rank'].transform(min) - df_forward_selection['selected_model'] = (df_forward_selection['first_rank'] == df_forward_selection['auc_train_rank']) - else: - ##Highest AUC, regardless of coefs - df_forward_selection['auc_train_rank'] = df_forward_selection.groupby('step')['auc_train'].rank(ascending=False, method='first') - df_forward_selection['selected_model'] = (df_forward_selection.groupby(['step'])['auc_train_rank'].transform(min) == df_forward_selection['auc_train_rank']) - - ##Add next predictor - add_variable = df_forward_selection.loc[(df_forward_selection['selected_model'] == True) & (df_forward_selection['step'] == step), 'last_var_added'].iloc[0] - predictors.append(add_variable) - - #Return only DF with selected models - clmns_out = ['step', 'coef', 'intercept', 'auc_train', 'auc_selection', 'auc_validation', - 'predictors_subset', 'last_var_added','pred_training','pred_selection','pred_validation'] - - df_out = df_forward_selection[clmns_out][df_forward_selection['selected_model'] == True] - - - #Reset index - otherwise lots of nasty errors later - df_out.reset_index(inplace=True, drop=True) - - return df_out - - def _cumulatives(self, df): - ''' - Method calculates cumulative gains/response - Returns nothing, adds cgains/response into the dataframe - ---------------------------------------------------- - df: df with best models - ---------------------------------------------------- - ''' - - def cumulatives(y,yhat,perc_as_int=False,dec=2): - nrows = len(y) - npositives = y.sum() - y_yhat = pd.DataFrame({"y":y, "yhat":yhat}).sort_values(by='yhat', ascending=False).reset_index(drop=True) - cresp = [] - cgains = [0] - for stop in (np.linspace(0.01,1,100)*nrows).astype(int): - cresp.append(round(y_yhat.loc[:stop,'y'].mean()*max(100*int(perc_as_int),1),dec)) - cgains.append(round(y_yhat.loc[:stop,'y'].sum()/npositives*max(100*int(perc_as_int),1),dec)) - return cresp,cgains - - cresp_all = [] - cgains_all = [] - - for i in range(0,len(df)): - - out = cumulatives(y=self._partition_dict['y_selection'], - yhat=df.iloc[i]['pred_selection'][:,1], - perc_as_int=True, - dec=2) - cresp_all.append(out[0]) - cgains_all.append(out[1]) - - #Add it to the models dataframe - df['cum_response'] = cresp_all - df['cum_gains'] = cgains_all - - def _calcImportance(self, df): - ''' - Method calculates importance of each variable - Returns nothing, adds importnace into the dataframe - ---------------------------------------------------- - df: df with best models - ---------------------------------------------------- - ''' - importance_all = [] - for row in df.index: - importance_dict = {} - for pr in df.iloc[row,:]['predictors_subset']: - corr = stats.pearsonr(self._partition_dict['x_selection'].loc[:,pr].values, df.iloc[row,:]['pred_selection'][:,1]) - importance_dict[pr[2:]] = corr[0] - - importance_all.append(importance_dict) - - #Add it to the models dataframe - df['importance'] = importance_all From 4770ced89c9326d5e1ece382b61788962d3334a1 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 10:31:14 +0100 Subject: [PATCH 55/98] Improve model perf comp code of forward selection Add a splits argument to compute_model_performances so that we can compute the result on several splits, even if we do not know in advance which ones. --- cobra/model_building/forward_selection.py | 45 ++++++++++------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index 674d3a4..543334c 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -56,7 +56,9 @@ def get_model_from_step(self, step: int) -> MLModel: return self._fitted_models[step] def compute_model_performances(self, data: pd.DataFrame, - target_column_name: str) -> list: + target_column_name: str, + splits: list=["train", "selection", + "validation"]) -> list: """Compute for each model the performance for train-selection-validation sets and return them along with a list of predictors used in the model. @@ -70,6 +72,8 @@ def compute_model_performances(self, data: pd.DataFrame, dataset for which to compute performance of each model target_column_name : str name of the target column + splits : list, optional + list of splits to compute performance on Returns ------- @@ -81,36 +85,27 @@ def compute_model_performances(self, data: pd.DataFrame, results = [] predictor_set = set([]) for model in self._fitted_models: - # Evaluate model - performance_train = model.evaluate( - data[data["split"] == "train"], - data[data["split"] == "train"][target_column_name], - split="train" # used for caching - ) - - performance_selection = model.evaluate( - data[data["split"] == "selection"], - data[data["split"] == "selection"][target_column_name], - split="selection" # used for caching - ) - - performance_validation = model.evaluate( - data[data["split"] == "validation"], - data[data["split"] == "validation"][target_column_name], - split="validation" # used for caching - ) last_added_predictor = (set(model.predictors) .difference(predictor_set)) - - results.append({ + tmp = { "predictors": model.predictors, - "last_added_predictor": list(last_added_predictor)[0], - "train_performance": performance_train, - "selection_performance": performance_selection, - "validation_performance": performance_validation + "last_added_predictor": list(last_added_predictor)[0] + } + + # Evaluate model on each data set split, + # e.g. train-selection-validation + tmp.update({ + f"{split}_performance": model.evaluate( + data[data["split"] == split], + data[data["split"] == split][target_column_name], + split=split # parameter used for caching + ) + for split in splits }) + results.append(tmp) + predictor_set = predictor_set.union(set(model.predictors)) return results From 9d4f90a7ab5c98a7753fc342d8fbaeebe6c001da Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 10:54:14 +0100 Subject: [PATCH 56/98] Modify processor.train_selection_validation_split Changed train_selection_validation_split to allow the validation_pct to be 0.0 (i.e. has no validation set) --- cobra/preprocessing/preprocessor.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 417ff02..cc33367 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -234,7 +234,6 @@ def fit(self, train_data: pd.DataFrame, continuous_vars: list, train_data = self._discretizer.transform(train_data, continuous_vars) - if discrete_vars: begin = time.time() self._categorical_data_processor.fit(train_data, @@ -366,6 +365,20 @@ def train_selection_validation_split(data: pd.DataFrame, random_state=42, stratify=stratify) + df_train = pd.DataFrame(X_train, columns=predictors) + df_train[target_column_name] = y_train + df_train["split"] = "train" + + # If there is no validation percentage, return train-selection sets + # only + if validation_pct == 0.0: + df_selection = pd.DataFrame(X_test, columns=predictors) + df_selection[target_column_name] = y_test + df_selection["split"] = "selection" + + return (pd.concat([df_train, df_selection]) + .reset_index(drop=True)) + if stratify_split: stratify = y_test @@ -376,10 +389,6 @@ def train_selection_validation_split(data: pd.DataFrame, stratify=stratify ) - df_train = pd.DataFrame(X_train, columns=predictors) - df_train[target_column_name] = y_train - df_train["split"] = "train" - df_selection = pd.DataFrame(X_sel, columns=predictors) df_selection[target_column_name] = y_sel df_selection["split"] = "selection" From 1c49c9fd0a11d00e672436907c9f6cd083b1b13d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 11:37:37 +0100 Subject: [PATCH 57/98] Update preprocssing modules to fix pandas warnings Also fixed bug in KBinsDiscretizer serializer so that constant columns are now included as None in serialization. --- .../categorical_data_processor.py | 21 +++++----- cobra/preprocessing/kbins_discretizer.py | 17 ++++---- tests/preprocessing/test_preprocessor.py | 40 ------------------- 3 files changed, 20 insertions(+), 58 deletions(-) delete mode 100644 tests/preprocessing/test_preprocessor.py diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index dd94a8b..5b3a4fc 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -271,12 +271,14 @@ def _transform_column(self, data: pd.DataFrame, """ column_name_clean = column_name + "_processed" - data[column_name_clean] = data[column_name].astype(object) + data.loc[:, column_name_clean] = data[column_name].astype(object) # Fill missings first - data[column_name_clean] = (CategoricalDataProcessor - ._replace_missings(data, - column_name_clean)) + data.loc[:, column_name_clean] = (CategoricalDataProcessor + ._replace_missings( + data, + column_name_clean + )) if self.regroup: categories = self._cleaned_categories_by_column.get(column_name) @@ -289,13 +291,14 @@ def _transform_column(self, data: pd.DataFrame, "and will be skipped".format(column_name)) return data - data[column_name_clean] = (CategoricalDataProcessor - ._replace_categories( - data[column_name_clean], - categories)) + data.loc[:, column_name_clean] = (CategoricalDataProcessor + ._replace_categories( + data[column_name_clean], + categories)) # change data to categorical - data[column_name_clean] = data[column_name_clean].astype("category") + data.loc[:, column_name_clean] = (data[column_name_clean] + .astype("category")) return data diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 9ba9449..4ad153d 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -125,7 +125,7 @@ def attributes_to_dict(self) -> dict: params = self.get_params() params["_bins_by_column"] = { - key: [list(tup) for tup in value] + key: [list(tup) for tup in value] if value else None for key, value in self._bins_by_column.items() } @@ -193,9 +193,8 @@ def fit(self, data: pd.DataFrame, column_names: list): bins = self._fit_column(data, column_name) - if bins is not None: - # Add to bins_by_column for later use - self._bins_by_column[column_name] = bins + # Add to bins_by_column for later use + self._bins_by_column[column_name] = bins def _fit_column(self, data: pd.DataFrame, column_name: str) -> List[tuple]: @@ -273,7 +272,7 @@ def transform(self, data: pd.DataFrame, # can be None for a column with a constant value! bins = self._bins_by_column[column_name] - if bins: + if bins is not None: data = self._transform_column(data, column_name, bins) return data @@ -305,14 +304,14 @@ def _transform_column(self, data: pd.DataFrame, column_name_bin = column_name + "_bin" # use pd.cut to compute bins - data[column_name_bin] = pd.cut(x=data[column_name], - bins=interval_idx) + data.loc[:, column_name_bin] = pd.cut(x=data[column_name], + bins=interval_idx) # Rename bins so that the output has a proper format bin_labels = self._create_bin_labels(bins) - data[column_name_bin] = (data[column_name_bin] - .cat.rename_categories(bin_labels)) + data.loc[:, column_name_bin] = (data[column_name_bin] + .cat.rename_categories(bin_labels)) if data[column_name_bin].isnull().sum() > 0: diff --git a/tests/preprocessing/test_preprocessor.py b/tests/preprocessing/test_preprocessor.py deleted file mode 100644 index 28432ae..0000000 --- a/tests/preprocessing/test_preprocessor.py +++ /dev/null @@ -1,40 +0,0 @@ -from contextlib import contextmanager -import pytest -from pytest_mock import mocker - -import numpy as np -import pandas as pd - -from cobra.preprocessing import PreProcessor -from cobra.preprocessing import KBinsDiscretizer -from cobra.preprocessing import TargetEncoder -from cobra.preprocessing import CategoricalDataProcessor - - -@contextmanager -def does_not_raise(): - yield - - -class TestPreProcessor: - - def test_from_pipeline(self): - pass - - def test_fit(self): - pass - - def test_transform(self): - pass - - def test_train_selection_validation_split(self): - pass - - def test_get_variable_list(self): - pass - - def test_serialize(self): - pass - - def test_is_valid_pipeline(self): - pass From 64bff7994c73dd6b0c17e7588d8a34db246b03de Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 12:00:29 +0100 Subject: [PATCH 58/98] Bug fix in models.compute_variable_importance Changed output from tuple (Pearson's corr coeff, p-value) to only the Pearson's corr coef --- cobra/model_building/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index de09015..53f8c3c 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -145,6 +145,6 @@ def compute_variable_importance(self, data: pd.DataFrame) -> dict: utils.clean_predictor_name(predictor): stats.pearsonr( data[predictor], y_pred - ) + )[0] for predictor in self.predictors } From fd2d3bccbc97435136b83f9f71c44e942fce818e Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 20 Mar 2020 13:59:21 +0100 Subject: [PATCH 59/98] Add usage section to README --- README.md | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e61bd6b..cc7293b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Note that this package is a refactored version of the back-end of the original w - add columns with incidence rate per category/bin. * Perform univariate selection based on AUC * Compute correlation matrix of predictors - * Find best model by forward selection + * Find the suitable variables using forward feature selection * Visualize the results * Allow iteration among each step for the analyst @@ -53,12 +53,104 @@ As this package is an internal package that is not open-sourced, it is not avail * Clone this repository. * Open a shell that can execute python code and navigate to the folder where this repo was cloned in. - * Once you are in the folder, execute `python setup.py install` or `pip install .`. + * Once you are in the folder, execute `python setup.py install` or `pip install .` (preferred). ### Usage -TO DO +This section contains detailed examples for each step. We assume the data for model building is available in a pandas DataFrame called `basetable`. + +```python +from cobra.preprocessing import PreProcessor + +# Prepare data +# create instance of PreProcessor from parameters +# (many options possible, see source code for docs) +path = "path/to/store/preprocessing/pipeline/as/json/file/for/later/re-use/" +preprocessor = PreProcessor.from_params(serialization_path=path) + +# split data into train-selection-validation set +# in the result, an additional column "split" will be created +# containing each of those values +basetable = preprocessor.train_selection_validation_split( + basetable, + target_column_name=target_column_name, + train_pct=0.6, selection_pct=0.2, + validation_pct=0.2) + +# create list containing the column names of the discrete resp. +# continiuous variables +continuous_vars = [] +discrete_vars = [] + +# fit the pipeline (will automatically be stored to "path" variable) +preprocessor.fit(basetable[basetable["split"]=="train"], + continuous_vars=continuous_vars, + discrete_vars=discrete_vars, + target_column_name=target_column_name) + +# When you want to reuse the pipeline the next time, simply run +# preprocessor = PreProcessor.from_pipeline(path) and you're good to go! + +# transform the data (e.g. perform discretisation, incidence replacement, ...) +basetable = preprocessor.transform(basetable, + continuous_vars=continuous_vars, + discrete_vars=discrete_vars) +``` + +Once the preprocessing pipeline is fitted and applied to your data, it is time for the actual modelling. In this part of the process, +we first start with the _univariate preselection_: + +```python +from cobra.model_building import univariate_selection + +# perform univariate selection on preprocessed predictors: +df_auc = univariate_selection.compute_univariate_preselection( + target_enc_train_data=basetable[basetable["split"] == "train"], + target_enc_selection_data=basetable[basetable["split"] == "selection"], + predictors=preprocessed_predictors, + target_column=target_column_name, + preselect_auc_threshold=0.5, + preselect_overtrain_threshold=5) + +# compute correlations between preprocessed predictors: +df_corr = (univariate_selection + .compute_correlations(basetable[basetable["split"] == "train"], + preprocessed_predictors)) + +# get a list of predictors selection by the univariate selection +preselected_predictors = (univariate_selection + .get_preselected_predictors(df_auc)) +``` + +After a preselection is done on the predictors, we can start the model building itself using _forward feature selection_ to choose the right set of predictors: + +```python +from cobra.model_building import ForwardFeatureSelection + +forward_selection = ForwardFeatureSelection(max_predictors=30, + pos_only=True) + +# fit the forward feature selection on the train data +# has optional parameters to force and/or exclude certain predictors +forward_selection.fit(basetable[basetable["split"] == "train"], + target_column_name, + preselected_predictors) + +# compute model performance (e.g. AUC for train-selection-validation) +performances = (forward_selection + .compute_model_performances(basetable, target_column_name)) + +# After plotting the performances and selecting the model, +# we can extract this model from the forward_selection class: +model = forward_selection.get_model_from_step(5) + +# Note that model has 6 variables (python lists start with index 0), +# which can be obtained as follows: +final_predictors = model.predictors +# We can also compute the importance of each predictor in the model (dict): +variable_importance = model.compute_variable_importance(transformed_data) +``` ## Development -We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to write or modify unit test for your changes! +We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to write or modify unit test for your changes if they are related to preprocessing! From 26cadd5ef8369e195ea9f1ba3d7e4f23ec8e9858 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 23 Mar 2020 14:08:47 +0100 Subject: [PATCH 60/98] Clean up of repo --- LICENSE | 21 ------------------- datasets/analysis_settings.csv | 10 --------- datasets/data_types.csv | 36 --------------------------------- datasets/titanic.xls | Bin 296960 -> 0 bytes examples/testing.py | 19 ----------------- 5 files changed, 86 deletions(-) delete mode 100644 LICENSE delete mode 100644 datasets/analysis_settings.csv delete mode 100644 datasets/data_types.csv delete mode 100644 datasets/titanic.xls delete mode 100644 examples/testing.py diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 269bf5f..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Jan Benisek - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/datasets/analysis_settings.csv b/datasets/analysis_settings.csv deleted file mode 100644 index f374672..0000000 --- a/datasets/analysis_settings.csv +++ /dev/null @@ -1,10 +0,0 @@ -partitioning_train,50 -partitioning_selec,30 -partitioning_valid,20 -sampling_1,100 -sampling_0,100 -discretization_nbins,5 -regrouping_signif,0.001 -preselection_auc,0.53 -preselection_overtrain,5 -modeling_nsteps,30 diff --git a/datasets/data_types.csv b/datasets/data_types.csv deleted file mode 100644 index 895323b..0000000 --- a/datasets/data_types.csv +++ /dev/null @@ -1,36 +0,0 @@ -age,int -workclass,str -fnlwgt,int -education,str -education-num,int -marital-status,str -occupation,str -relationship,str -race,str -sex,str -capital-gain,int -capital-loss,int -hours-per-week,int -native-country,str -TARGET,int -ID,int -scont_1,int -scont_2,int -scont_3,int -scont_4,int -scont_5,int -scont_6,int -scont_7,int -scont_8,int -scont_9,int -scont_10,int -scat_1,str -scat_2,str -scat_3,str -scat_4,str -scat_5,str -sflag_1,int -sflag_2,int -sflag_3,int -sflag_4,int -sflag_5,int diff --git a/datasets/titanic.xls b/datasets/titanic.xls deleted file mode 100644 index acbf5ece74e71f6f952f87cf933470858e181183..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 296960 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
  • 1W2@SD5e;2=q5LxcG%88EQKNXdT32PIO6hWaFx%Ql(iiu!6VT5Z`D)3nYinoX;!9@>j)(<FKH87 z8l+vf$=0->_L9DCmn(wSN_in!DOUuy#Y(cg=#a3jH1M|<)%o(0%2y03E4Gg1hE1fb zZdVP??W&h(#Vc0cUaDegt{-X(m1aXfRGZDJ|FE#IxL{SaYGYA9 zlouP7CCK1j`}R(2cYQCZlp76f0qt9@TS;Z9x>Q%!i%>XOFVyS07m_Rg&xW?@UHH5O`d zUe#KoR-68~v8Z`As*Q!%sNAg7R9I#En$`tF&wS-mSt>6yimR(zUyBQc0qG+wvD(Oi zY!d_8HD6hi_4`y8%icp}pu~A=&)2l3%o8h}=wf0d&fLm?;Ps-JL&9`WA zwmbl++NdsRoZ7-tWl8)zVJ+>iUPqx?K z4be(t1Ul=Z7N0a?j%F|0y2m&+V#+O?AV{MIWlAgDZSWz3ZXagG7Y*OvIY^I@X4f~k zWZV6uH9F~XV^a{;R9v}E0)mPye7!Zt8B}g3R(XyJUo>j$)^2)^-G&vkUMrREcep(dumdsx%iHUAY_(S`j@~1pEOZ|Y_N1Qz7JMEcce*1ucJ&hny0jOw8HnTqlQD0A%+a`VS zvx8=*1pxrF&?|B(p8)TzH^BlV9F$SAzUAZTfE5S@pP2xw!@;&gwy-_fk=@*a2U|{u zQl;%Z>5Nc#h=70?`X1Ph;O2!Y&u%v1YAo6m7b}hW6>G6;p-S&Q z7q0N7MPD5tsaBi14!8ktw{F$A^s9Bdld5dQlJ1jgQ+wj$G|QC$TW&CM1+ClqWTC!j z4HlV@I5uuIabWxtezR#dny^P~^2Sb5t}iZL-b+@KzK-w_xn;{+O&1#bm}ZD*Pb)v&GgX49(JPv6)kfLefcUxBy0 zV{BnvTwc7GL|6Gvz)_8oEfcf<}>b^qTMGmY{9I zaEyA(Xro`>E$%g%VE{WtCyW`$y3_)7&9Rju6GQsFZK+Ew6mOy^(hFF|4u=ABcO6 zci!rDiob4TJ>a@wR*7TGK{PTLw8jIegIe6BofHB~3ot^n3)**h1h{fZJd^jJ=HhGKSN)?r|knGnl`oKwS336P4(Et zMM`KzRn(R8=|0X0c)96xNi@8`CFV3&tZDM6eLmVu5ALT@gz`nOS;RG~@(@pfd^C^| z?jPzux0%WcC@U0Aa&`F~nC#J8{UdI;o({=on)S}pT*Iz2&@$gbn9z=meKNsxO3S6+fBa zQH#Au=J?yKqBFI*;nYScpaH-IT%4NCgRRNV%7Tp0SttX-DukqT@U!S8+plh9kkK1Z zLR!HLNqlXtw4zLO3Fw9aT!$S-{%8e|LGYh1K>wk53mBAHu76wl^_z5Og8^j_z9 z%vH=$;>2rJ{k}d;b>V;nvfycOFV(d;zYMTbU56?RCoy_C1rx6I!@U{j019HOE|k)B zFqYoaYT-xX`AWInykYJK5lEMSh~}toUXi(f99xvivcK6M1{enCRWrWWH{(apH|=v+ z6!%f5KP>B;*(zu`W|6ynCvybmF3!AEPArABb3v zakhqK%F*1RovaFs>}}U>7P@wS5>044IG+%r={~bL9qp+G;EvxA_uv$T3{$nTD(Wa* zoItt4F79L}0|??H2rPzTsons7m#gp$ageuvUWEHNOYY+v zEw>bnt7&berycRk) z)!IU1Q7RzR`RMYcqn6yfwBQ=?TD9&9n$ zTOUlo)!0lV|3Wq-OeU>0Y$S;AKEdYVlGe7cFl{R_Z8*c!7Ld9tiwo6>9ixn5hMp#N z6`N!g%f-^vl&VoH-=tWeG&9U%Gq)yGt7TL52uyC+sbS%nvC=e}ANIYG%F1*psBV_Z zvLT@PU+~oeR@l#iIjFV-1w|Q9xLIJBKv+nCkYJ`}@D{vx2EEKHz))vPlZV+O5n7>O z_GCEW#RJjq_b$$}-i4~+gZo{$MW+(s^Ho@~;fQJ-vNX9!whvM{Kdj($3P-|ak3s?x zu?=^uOJ4)K!!~t2Btv~V97)UUhswsyAVSAID2jN7Sk0$M_*wUa``XkGaCBpN*la)M zaRr9n?PCl-9%t|PID6TEd+V@Ii`{H|fb%d@zlR08cuJg{!&#a-db+iS zCy+7fM>&%ywRP45o;&G9lw2QRh=$gn^9MwaE?Dvo+(Eby!qrZAij)N0ka-RA$ znfj2gdfAQF)TWmX5Be;ut!o1xv*=agjzP&GZl?sY9%j4GZcP8Pt<4=8!X;vSlu9bf z-m$*=BMtT-h2Fo_mtz}MgAAwPoLhr6k^k|Iz-iZa3TEY~bhm$q|M7(y;2s}2>Ja>{Ay zs}h9yJ+8Tot%ET_p%OlJVAQP?mLSX^I0x<>Ff9`qaY$7JkLC`yws`=91o&@!Uf*Sp z*v23TPBiq1yPe_Jv%by(cX93HFghkiw=&6TDQ-84-ALNttGM@UPN3d3y}pje8U&mw zc3!d$Kj4X-j-_BRg6>%2+#$=@ka!GSk8kQHA8u7HA+8nhp~CWLffk0V>Pw+tIrfAp zMlM1#3qOOGVB(zQP-YR}=kn(GxWA}U$^rK&tC=airYq>qYiv{8`6SsuppaYbXe3E~m!6#{1>VYLxGaLl zy$Np?78t0`T~6)f6NjidDB2fX*fW*bY;>xTzd*FPz41E=Fqxa`WO4w|LiMskB>hPz z!tWL!z}?4dS&5Cux;TS2s`h^`O}j_A54s#`&QCH}mA&*BsM=BCSV95uj|P5wDQc6P z6bFYbX;k-6R)7u>vE}lw5zkUs4Ri`z8<0hu#1F&`+VX?DB%s`rE-RX=WiI7Cx}e98p%AXf)(X+3U(qNF3qg~cf5-BsJ`6!R zt8#)wY0SxYHQ!$E0>W09;12Z19MT~zlS;a?>F2e{{1*3wq7h6p2kjI}oM(ryyYv=L zh)arG(H8b5Xyq-Ke!lbDDMH)@=e$tOh%@z*;F=MNI}iXO!}zETr8f;|Fuf6xQVKGT zCaN!NeQe-26`B`Qc4rDGLBVvwz2Ccu5{SqZR;7@@&Gc=nBiM3!D8E|;1TJiN2d|Cv zSSJxxJsx}2sTNG-auY?E$s1FiaFPN$!yBA*`sJYN&W~&G5Zz^t15u^60uexugR_%9 z(lHCz7bL}&4uN16Itdh*&7|W=AE^B%$I;{x7P7Sjhanlvv095P>pF zw z*<@16_6W{Quu#Sf4fyl>AhrQjT3+3Al14o~*eAH!MPx{=Sj%9=FC8(t800yCZdANNzzMh? zzc@7>p9Oo5;SFJa-3;zeL_NYQhV}TpK8IUz_d@!SVjGYQ#sPJIPu`@V2DpY3oZ$F@ zh|XS2s`CAt`XJzyE#;n!Lz54z%mXztFa(%g#iBv6 zx{ji|_d1U;UU*A4b^%(q(jpz z8q(^l@~cc4Gu0T22{q7GgwG5r7AdPuoh~HT0v5-}6BRyUiDAlO=DP4^MZMGLps&8V z{e%AFJoIPJl=^1;JLpIg3H#7}3pfUg&L+xQ!E3^%_${YV&n_&|$EGR_O_3w_`pE6! zzVUg2Z!K2GUfMV0s)h`D05q|ji%c%TDWzM0*`ne$0t&?QHVu2Y=CBL^C`p+>}%7& zW(y7x&|{FIW2bOdG7K0Q14yDZQY6GEh*siob*4FDfX)F8a@D5OOWR$kow?01Rz`bZ zfc`*m4%R*I)4tNIFPD-#NU`sN;#B2!w|~SJ`*2RiY3aP;S%E1huos*u`57o|*9URP zCqWm_u6hBLsh_zPYG|zPF%t-8a1UJ^4DdtrHL*_zsJ6Og8)~YO0b^|;R|nc`;$HFH z%oJd~Pk;(%pny(VoPr*```tc*jTR>wpHqC?EIKly?)T4f(7UP)4bxx*{{U9(%ljal zIx3NW!a21O21k)_3g+4>R`%yHDus1LnrRpVitqQwM|NYg5ooQu9aQR+omnrebVUwA zm?U)nW_SV1nehlc-8&sw7-Gw?&(25;V>4TccNM6pgx}DI7C#2Y0U&#$x3cq822M)f z(&$bQ@|zr-r{taXq5vKh8h7=7%ux-1KId@0Y5coAptmBm`-KUF zP-Lk(G^Nd~dy2m2G>F-9RKWpSY!{uP5PX0RA=KH4hgpCYD*&2J@+Pb^rYzV(cS|Ck z_66D!#gJJ8`~pVUHyu4W%HTNnWvr$3V0^*ehXzio@VT;Y2gwd(%x34i7Gg0?+u<;i z#x8Fp;20wX#AiF8PNS2*1kauEuz5ub0na{{jsvk!)K!>eU7cYZgmKQ4_~-!=SAi3; z$Tslk0a0O5C=|7Gw+6I)4**Poy1VmL3lhvlA>|A{#&RZj(0@E(wNCpC^bl8(+$W~x zvHc)8-gRMDat;uHQtDn&EH%=;fTbKrH1qlKHVjrj_}|3|Z*;`vj9`TxO6Oz!1CX4@ z*QO`)3b5+2ojB(_>fA)l^ymi2@(=@9@W|5GFiE%L1Z=n2s)wCI2>v~MJRV8CLckd( zbEshGMrRCb^sJ@E)D_hs;m)lUh4`p)gi|t zEDsa}Qu09IJfWE9>be8xKjOZK;h%7fgNw>+IdItMyz>r1E`SIDbQ0ybBY+i>Dq=K@ z*Bxf|NP!c6?)=O~tN`S9`XN9)ut8{xz>X1nN%oRR)8UcvZXp#2+GI?@%Bf-AG%7=%)s!RdjqcSwg5VI4-j2Wk`49x&FH#imS3z^Ox$cPOXAU2GiSQtaF^AOOs} z>F|{8^tCwJSZS^T+(@5*`Ug^R68Nr_c-w&f++Y+(pM=#ztiPO>LQ#HDfnLXbH+0#Rb8Uixd9yQRlRjkDRdy5r zgnB>px0{iB4Xy|TB0LyT89lq`Ww?)sqkcB)gTxzqwqgy|=1TwzqW zf-_!@;8zGdpW9;WIS5E>vztBUEV{FkHZx}Q@B?5IpA`Bn=tFBytbdX;@e`Ew%v*=e z!VmAQ6ebIM0{vPc*d9y4_aUYMf^|*z)bF2J9E5a6YuE)%D)iY)#jB2|O2tMM3psru z*G!?MfEG&UWur%=K@lw~vzFmF1hNl{K zx(?0tW+<7nUI{CnEoNlJ`lQbQ1WMkcGh^ zTAiuZiakq1L@vpr!4DUrEHCT}8B^}1NKOT2#sz_mDcVCxp5@F`*=oR2GWq z`oPQfxEp4{aYNsWY0B{(lWB#EWt^WodL-51|`}V zE-&;2ZRo%#Ps(hq6w-s8aP76U_&2~Su67tmeMzf(1^^O^5v4@Lfp$n41$3V8!{Fw3 zfZTDCmc|XG?6SLB0harqz3GtXFwbr{hl{tI_PE9GlV8XMVWV1+DG8?&+_~SATGtm7 z3>o*(E#FsIL9T-^*4L=H@*_wuDWzGz$C5VE6Zio;rI#pfvUkxvLIf6eP1@N+0`29P zZ(zsv;q6ZGE7Qt}hCIok;1*g=(FQC|q1^0O5Gpnbl5KoIOX`@6MNFCkvTzr>EBZN< zE#tn%(5|rerYvY&Q5)GsE^CLKiZ>W2!aP6`cv$>q z!>uWJYP>$)1rFi3{jI1l;GpK!%;r*kv=_Eu>*eG{q+|by&H(<%c8h8WfagM3i$fSp zOiqbpuLeexLO&QkT-LTb+z5F&d*fys1msB}9S36$mv_uWNGHcSN158+Nqt^s&5 zUmS+m6gdJ32d>nleN;mn-Y0=(L|_Rl+*a6~;5h=QJu<*qK-C?P*EB$}<<@4;CK>7? zpA#+)9|rQZiIaAOzN-dJGSXL^Q#7MK)8b47%PiMtyQPjzLn(=(B5*`TY6F=>`^z1N zV<=}JGGl)A1ovKn9P=yZ7kS=Um2v-_R2fO(^wTHC{>aGZG!mmB#kq9gT}3J_?}gC0 z{C3#MIrDBDA5JT%0^Vqy2=if;PMV|Ejp6aHbIUt}yZ2K%gmg}d+4EQq;>rJthn-Hc zGbu#2(#Hd711E^&f^1?6pO;FdAxZqs`S(19O~*&AjUV32{8J|bLR}dUR_aPcu-dTH z3=IW5lD9x1VQnjFH&xxPTaJRV@(-+7E`pV+K5kc)?qDTkZm^b%A!EWKqpVh!lGR!n zK0gFael4jIYiLafptGnqeqV~AiuE%4J`)GIxIjl1ch~UHM&cJO{86EewM$)FmLlL>q(lij!>C>(wjPVUuQNi{5#2p?-PxOP9ZEEX}Sl zkK=5}pRroU>(Ga-+7SBPhGtB! zgj{}@eO1N_LQ)$|wrY~303 zAHyGieV-fR>*q*x*mAiVW8~saXEc)AhkX2ZT3kYe+Z^IcBzYfSNHI!?kyiySBDDsK zU@785|$yulBk7M0rS=v41(v|dh;XP2vZp&3P}JMW1l3Chd^n2P3p9)cZMjO zNWufGV>Fc)nulk~;C7Uv022!eWWpiBXkgPi(J7McR=GRh<^!_Q_B!X8ID=)XW0C}F z;64=8M~p_cJx`y2BAd%d%cOrJ z7X((r5lj{-KZNECRaiAMFMzcbLzg*QmVd+1F)RnXnIOkrdVoWZ0*OJ#7Cztm{nHD6 zzL#RH5X~T4VG;7|6X=Fx$g1*6+cw3{o^fOg0omd=av5T#&|cf0BE*uRJKH1Tg$z5#TSpOKb427GE;_c)NaIbz|_sUr#)SxGy@&J!_kpbpdTX0>ZMsQ8gWBXQKVq*-hJJz7fQ)U z2tD7kwob~iAtH~1%xTz~LIX*?5oizg;Q-q3wZn{t4h1NK_M-pfCQFB#zv%~v;44!4 zb0i>*ZG00^-4no#_nIFwg!%$4)oKqUWb0HUpzv*sH2LW~>GatvlK?ns5YPL{!y)D4 z@QDyEhY7798;_Kt1ki5^XJJ?Q(G?jS7fYrGpM1tl0#rHK~HO5{jZ!60^vc0AwXT_8G(J~&f;%K0vri~CFh{tu(8K79-;ELPFuwdRw zvvUO#Z43_08lGMW?cx*$B_ZJ7JCHMqco@yvU?lODQ**v(>GY?DUB z`*F!t?Jo#Oi@hwTBtsr3Xl@y?wI9T!-Sp@Pe>0XxX)zhI*@0Zw-<+vrDTP|sRi&(s zB%R7?Fi96Gg#z6|mLw$GT7vT@=W3>6O_nyjxiBEQF6GLasTc5YOlJ@aauq$xdlqo_ zh?75)^=Sh+?I`8J6FTZCtdMu+fKxsJ#B*hM#B{D?1ypk8%y$XJ(ph$BWms~8cL>fD z78%XFVq!y?%+rK@>#$W}zOi?L!q8JV_oH>FYbIBCP{c%XS;hErl3UXJKMYzEcO6gI zDSd$!!Ba1`2zEXS@Gjz7C3JtX|E9_Ez8%7INi1i;;GO{mT81__xCoku94kY3$l)aznstSpl4TvThk->CAgZKbd_?faw*l(_ z`i~LEbs2jXMDZf`>w$DaFi&KE^^nHN1a zs9oD54u-R68^QMz8#lARfEe+S7t)d7>HUNv>jw+eWLss>U-pDtg2QC%66MKOl2kZw zy#m8pY|>MK>|U)D(tbubFv^~c{9q(QK*AX~ZsA5I&y8SO5Ht#P+vEge7i0i3V#Y%{ z(cWY`zdM$>Tlk3@6Nhk)$c#D4kGwt}t3f{DJgQT;kyqJTQTBGNbOJG=63#lQV&jp3 zCgG+6E|(-%G6$f|g8@2aw&qPxK(uM5a6mdx@F13IGqU488K{nBurCyEj8DBJ_v^Zc zEXGDk!U<;?omeX8oHiTphbas_xUex|DWe2eOu#_nKvrF21<#Cq-Lko3Xpb~Mpp}}3 zvI4HzCQ77AhYAjreSUISop@uQI6mTPXRD{H^&8SpI4k@n&A(0C2&RQ#XT3RF)ue8c zoP;khq45oi@~t&=hScyZCGk-~dl`%&2Z}@20>Qm-K^Q6Br;HqJiM8ZZHbJWHWP{?{K8BG!HGLSit)-y!p?9P03dOA(Xu12M4M`8 zvC$bHW{*1~|0;k$2mOMZkZ>(D3(O+W@Kso&WFs5O7a!B4iDYwMyxcBcBqSy|Nn&vN z96H&&!zrVR34in`rV{&3vX2nVLZy&z_ur1tbK{WqN?xgARe@PJJEpO=s*dGVF{t^Y zH7%Kq3srn%_t;uGGDEWvlSFf8lQhm4Hss8?lEM-ADELGlb`H7JD3hv|K8)&_Q-Q)- zRnAe;6=p-jalx>By-SFln1L1I!2_*;s1laVd?wFt8S6rpZSykMlLDd2dg6@Z)j+@k zKcM#LT1>d5KY3LRdygy@H|$UFkj06HNmmmeQ(`oLC_$FbY1|VIr`N7!50@@jax;pJqgC5C+^)M;c6HlCqVoTd7 z*V;qZr)%W5WDCQx8d0Qp!0`Fh2ceFY6)WAkL)vUudE(vB9AE_6R0!Q!iq~A&_Pb^s zC4Ma2?ucKPo6+-ZJB-nU(#i0v=49lG9qxO&EqP^9vx7uf91gg^+Gtxr|Ap%cjF@mxBpGN$pn3(r#jZP|~|)voNfbrQ#$Jv(;A? zU!YY(e_1MI)4#V#;;Z&e6}4+-)UX`Q?Ikm9SRU(k)AB@DrLe~aVkwj@Z}y63fHqAk zJt>v*vS)Y>6=wOU)EKjExt$x<#I)VCmu8**+(=>>wAH?4D`?r$T9&D~RbGHZF4vnt zVu@7VUy&FsK(ySCT7QU(U5Iev9p{1!B#;sZZdaQKh2CEKyi~MuO@ld>Vy)+?b8nk4iPsxKi1Y-U#jCWY z2>oj*_pDt;+goadoN>m{fCo5(OKLu$pP@PDf0xj=8Nyn%1nS8j;nT=pzU~afvow2gY z*lX;yw|7Sg`(aa*hDH1i z*OzHf1CA6R!Cu0Nka=IRf7=4wGScnzT2Ue#oDe_5_0Hx@02^k{gd|3?aMA!5C-h(+ z48?N&ge1ji|3iXdOacf!jyr2}wh>~?ibG1@!T!Wlt(plSa~mMYTB?DhaC>@Uut{w1vOmj%3#3v!6{S^2_+nja-+DwjY5F<2hA&Z;Dwgr4+_j}_*Xb=>@@X^;h0TnAcf{?wQ zJkT=YKA=IeRA*anavT)iH<@hce}FB#w?{}ff8&>a3_jS$Oav0g1U26@msE& zMrEKQ37rGuVJUfylpTp9M}3<#y@`FB<42j&q57$|607Ah;u?4Z6 z=r2#6slbFYd_aaeT-|cGbMoPd%nf_pi0%Zzb=2K1RZx&?Z}NoQZdH*wlHyi(L@Wrx zeL?X)*|JGaY>+W@i^FfO5Z{zWKCY}-3^f2?L;R`qkX^n23P|ZV?_*YYjAUvrNg)}fQQF}KO{L~msI$a}L1ZFH7FTjCWTH|-yaS}~r(*G4g5zbj>~KM4 zBfNpJgL&}-n#j>gY1KdguS)V#^Asw$EQSr9-LpO>Ap}A+Nxyhg2VLL; zq%AyOxmi_KOpR=y@ubMyZW1ATVD4-y7Avngdv_>(ayoPlNHB1JbcYW}s#m1}PC$ns zK`&)k|8fR`WdTt?XulGht?IIT@C z@fIU))U6KWWMMia=o9i%bPV0ILU5nIl0|+>$qko3SB9W`#C*t*GDry@j3iBss6(3A zc<}dS-Y{8^cMloPs!b);-{C!(CT=@$;v~hd%ZJE{nss>t(cV3VXumM2tS>5Od}ybX zXC%~b)MHV-DgP&Zy5x!4m&^^xy0FG%rBhnEa)q?);bVE~@Wp~9U1uT<$$SlSRw`58 zkY|kcD4xoo0Tg*SWcRBbhYZNSwzD1@8KRxTLtIA`8iy#f2Ya0vG0Vc}8)70@sg@EPvVtVyF4* zrD}pjSZsh0I>iBuAzAa~?t`eh3nv5vFDIAvEq7+L!5A_XC9f%;Aq3`j zs>fY`X=@oXJQC>n2{)Js%@%T2J3Hj!ojmyhBb>`A=0m}O-bC*2sE64tWJM_TW;)WnIm>XcdUTd-ncH8bs^S3xIc(So%;awCO9zNiLA_`xRaZ5qsQ$-k@kUBH4dE|vTEV+KP{-zL6PL>L+gzeBP-bG;UNgB>E(_uHn5}!PKA%Wp;^sEok9<3}?lA{MF!W3k zgTU9#>}{S43Qjuy3mwjU#Nmlj6e8P*TkI5rxj9{Zojgm(<#BfE6*NQ6M_H|Rb71o& z7i_&5w}%%!+}#s@UUvuP2Y=(EUhv+mD9>e?)RvsHc9K7RQsyxZ^mW`8=BX*yWA~~w z7gnA|slhG<2hiAsd1dk*S6!Mz-tN%w?WDHgKEF8IdSCj%XNG^WUIy_AxYulxarjJc zuNWQHNa2kU<{&C;KE^f_?zH8yM7hHIEar(Coqg@9v##9vbIF&#)VSiKg{3PBukJqd z5ip4@hW3nMJz3I^Rm!Xfy~k)T z64n!eX)&>9RqVyTa+Q~6l4Wl5cCw;-ubz~R1yDKD(2IRF%f3pATftlp_IVauyq4=6 zx4riE?T31<#(vqp?GNhNw>1_KzNuSVBVfu0YaaJ)<*4P^Bf3fsFauL<~`27EY>xqby{9*+P>NggW3kYFxV(Vg_JJVXbcYy>4hA< z_}8p0#t-#0P|PYDnl{Z>?4h9WVBtb@ zNex-)ttyW#8Q;Z&J28##fS;cd_jzieAkTq95YOx_aY zK_!1TQOSoJ+~!+K;~WjQZffqM9>e7ghtQjEO6w_?784z~LJ(k~f3vkgm0+S1%m;iw zswcZi2e84UzJOA5pCLsfVCF2eo3w1yC>Gmy@c4YUO^)17igrWnyMJS~C?ObS z(l7Vuq=iHyf9(XbwsIPou)f8+u`msikk;`_;t^a4P>LpMb>7m`rdu5z$_;UDh?irF zf!yl5iy*gzzKp@9NW&&2Y@zxLR#3=04;N?3I<<9yY*mzKQWz~BRpmOO>M@ET@6Ch* zv0Z2j*pBU2xh-PdKCff&@&Fgpbg@N4-kvH&oDE{>yPVXtHBs0#_dtYQ`K=1IISJF^`g4Y6ACb#xqB1^q&pn)PLG48%czgMusV z9pw`}lVNXQQyb70#RQ2)C8`NRdLNrQHad@qIwn^j>|>$Iw^QU{T2?QvhDAnnhP!q zreol!f^X<$JXk&^5CL3@1j$2czfQVdBN995W8$!57ki`ZsQ)Fkjd&sDsG-1oODVBr zDh0<=)9lKtP{DDN)&YKZ<9oz!KTYfT~PjlUhTEJx*&1^da*cnVCwm z9w{;;k&}t-EvdIdWK29Xo{!w~NN5A{2G;w&oKobCtmi-`by{X2M*WcVuUfX{d{TCA z`B@8@eG57EraYQLK!G?r9FrTwo4QLFgo=0PdH;c%Cv;(NRdH?LS1=-c{4|tD4X%?( zdy>hmO0hdV4W#U*!W_)U`SD{;*s9El#j2qGd=kJBoRtzO{VXPwDg%V{n?f{Y1JVcp zV4_E#I5u*tykn0SS;JgehaR*eS0^uxgh?nm9&(f^hE!w)?>@_<<{wP*Vr`*;Lxl^) z(Fgf$4^GSf6U8n;IQnT$xngO;RP$9kNm8(_fzs*N$#c{+?gPlyX1TDA`_KS$a4ZR zShU?NcURiyCa|U#A|To(kTsZtMOIG^uo z(z?ZGc@3Wxs*?G;cu^pIgG>nErck~RpJU;|VS6G-n5jIx$uW;3%GaLn z0hi}V(y~e8f#4_=#&kl&?T2fKK44P8TPz@e)+B5uVW?L-J?7XFFoUXS;srAK-z?ls1To3@AW zXVdoR!BstgZb8TBwHHl`pg?A;ZY(wzDgv16T|%U~t8`FinC_v9u)g#$+7x1Xqb|bC z2==OPSi(|xffvEvmyCkLwM|V|Q!IJZZip;ilIC1%t1d0LE~cs%fDJ%|Z2M%9!moOQ z*u&Hn`0Y!nEt=%ZbVG1;V-ZEW;10a(6`U&X^j2jh(1>NkVgTcB(KH&~8?WRvtI*s_ zdME`xw_9w7nI(8|$0M=PP%P%VX!`ydmK1azyz}E49=+?^yxe1@an&#$v#k(zGkP$X z^lvCUY$Ft{F1=E-Ud`jMo62ioTDK5B4!q-&yVVAIg4Wk=SBdXmXjIjuUNzSA^slM? zECE1;2GSh*i@b_A_jqfMg9pMDgRp+E#p(xXE^^q}h$ui7O@p>nE7uGignSMi)PvTB z4X9WU_|&+I6;YX~q9rXHm1RU~S2Lo?49^bJVZQPuH8O=~{Zc=kcR&+q3K*m^V697K zKOD-BAmo%@k$*TdaW5+imwIYmQDob~A^2oxRW?o_z|_fJxJS}2H_8{HlAWmn@4_#5*nvjL@`d?YZM}&ec3JOxw=0#TZM2`9L>U|SsDF6DQ9nrGmt=60 zj3{TzM=f=aBKx%H28?UgU)D-R09Lk(m{~ssF%Gu z%s=1Idv=C7RGS_ev_Zmyp_%pt=F&E=M?L8c67ECOa1K$JFMOaI?VwUQUgdGrOah%u z46KEX@(c9RV2oRrzwL~PE5YIQOztPl#Z(RG`SPh3+p_n0iU&^P zbX+*F_31E!TtIUv zKXVXpN9={B{aY!=#^H1NbH57G&{&Jck}fpXwNRIR2Y$`{8yZUuxA(vjohcTA;qw4F+5nSbT2YDrG1}rzDKTbAh!jS~)={6oe#z~Zqu97vn zC}mE8oV|ygK@hGy+#K!0L!Lbf%2y&N2?obbey$*zhg_Z6V{VVhn>{Ito37q-dovwQ zVpK7DpejQKDwImHwqmj$OT8YShsG63xuBJHJlDBc1pQdqd_y*J#nd94hH@AQm3Knv z!}Oi55?FpC)ukS#H(WJ&u8uCIi@CaGJ7P88NMRHR#8eVwY?mgNC@ z_YI!Yh}lq?4T{m*aE5>Xm<&rmm#S@-oru3$Bu8jpSHA;uZkBXZ#MpIIrNl?f?6=N2$B715j@!_$`*+)r`#s=yK zV+{Gh?bj+X?p;4bS<4*&Bx?!FdH@LBv@)`;RDRb3`KilFK z*>ic=RV#~N?*rwq8e1^(a+@m_w3A`_;HBR`RlXeYj4G4ZLGQaKUB^Iz&FKOb-`66ho17>I^v@&%u{(6f;u6(c-Rkg!V`3 zCelan{52|f=v0AqQQw#HvTTw-TMr3a#1>}^D+RGGaN*l>5S#Mmmy(v)6vdq?&dbzM zk_bezq7xCtlk_>-8!#gGelaIRokq{l#)f-(I@)+%vgSgNEbZpxLqCT&9taD-EmsbML__uZL7IzjV45M*oA=OYRw{ICdhmhKKis_5j8&LuXtK4uJeo@(>7El9#O5e+;<)v z=)2_Jo)88e1rAWD=2v_TwTMAOWI|~fB}YLgE>FyL9U``tEb(jRAyjn@*B+i4{kL-}PdB@3sV z2y3R={HR3K0u5oLXtHlb5Er%3f?F1Z)h~(~-y6lY?2qZewH~ zv^Jutv5liQC7x@MJ_DIUH)Cx?$j#xZhKR_GW(Np5??2!+6Jdvci#D$b5<1#*+qvp7 zXdoRP+?>XsNkeDtL}KNg<98S;LxU*r3v?g6G>9_C1;2|dVd(T%u!#Y4%I?19eM{kJ z$5c!96IL&SL#A%?YY6wO00T~wgNG^FDnZDyX`LMW!8xy4xr};?l8YhtQf@6S`YgSB zx(eqDPaeO5*i>60uy&1~*b3ixf{JbA26vdL8gV_;x`dg=(xaiWL#n;^TZsN0U_7a9 zolsSl%bcGR(jF(L*S{Se8P^D&GS zuX%ou$5~Y&(=E%{x?IzJw_3HhYKw$kwzRM9#U>W2ebS~x<3o(x$9uTj!Go44C@d{s zACN40jN1nxrTv~97<%MMPgdbNxjqwk%;cn>MfUaLKps*^zwLJ!h1Ik&p0$N9_V+k_S`Hcjk8ow_q2i_>#N0*SBr&hWYnegXi3V0SyHE zDgI%}!_GT>8RpUPwz`Lb=@P<5Ft{V#c*s_P?S#PlbnJZ*Bq#5VX%#CSq206x&q)+- zeB~oQIJH7Cv&zd0kAlq!eGst1L>$!vS1*)b@jRo`sCn7?&nv+C;08I^gx3+au>uc z0k?PPmFy-aZ>f*VDLA$1F_1-M!DCE+B8s*gj10plt67stQcjvc2UNCGm zS>~Zxd}$^8?AIce~(^<@_?;0b_*}bSlRBf9l>IP%#AqCKlgM+`7~T28foY-dCOwaQ9ID zf2jN?_5OtHD)^K2v_+wgae`qw&NB@A)j}I!b#BsBX;hifb&kVkY;wtAf)S-khV6Qdn`}EaU4Em#K(JA;Fj!B8hZ-72nf= z{D_!pah|u`&^1ha*Pf*7qW{2I&XSqoxayC1*@?i@u`hQ;orgA>k|$sT-3R=GV1Oxi z8C!226zUn?^;NFF?hLEb)rMvAj*0bXoyY>rg}0gVrnj;aa@=(u+H9*CL;}@@tjo_4 zPcLwg%5x~5o!^{kl+l3 z6Ss?=O_|g>71M>{faMZby-^p(ia1L=96$?z1ggPOK-oMqM-g#CTP+l)U6_i9TKFoR zk_H853-Lv&%pD=a6EN+_PLGs~f!*iDt1nHJ0wyme z8K7=w$@Y!~WKHDFNSmTR14dJ6Fy)iuZa{jI1BV`t=ieZrjLmbn^ixPHHWD_xfo0yO? zbaNc!VZ{xAPXZt7>3#{c1fNk_$KLL8DoFGPd&9l4^(JfFlxrxNM&LGrLB24exELb~ zgR!_b%Tr_*Q+$<-SwMi!!o3}g zS@ByHYj#NaBRZg*eAFl(%_QlWxMkWU4p6yn!K7`vI)X3kin12d-JtjfTw>fTQ=oR5 zPSubOij(~?<^!`+XpaeDk(h*;fX^W||NMxMv4-PeI8oS2j+dE}$Jj=%5TrDV;s)(z=t zA+mPLw(f!JLP8iou4OrNsddgO2PyRZ!Xx`1eb&jdGoZnrMXc^TP& z?2qTTFMDq|{rT3oi~PjU9{W{id^D42h$-*rMen|J;VrOa`z$??51}$!&Bg(Yel^jwA|P24$Z20XsUCZHd*k*eV8x z)jl;x0(!u~CK>>?WK7?{Oo--wyNA}@WFL(8M@K^z(jrs8at>{+K+%gBNiXX#^?K4` z>rDgLRMvjhm0R_5Ljd0d#ATY`3H+RsT3PD-0bGr>+~!v~7kTQbt$R=<6F`8V@e7D4 zA>)m%ZF{wp`e4%w#5tJtKs55MFq=sO>%!&i4dQAb-P{h7YF#kFB-kIGjMIc{n(iOL znL>svc*5It4tBa^IbpnGsD`Hl8g^2yhDjR+2%Pr4!A&a8&taAP#^HM4ae*W$*;|}& z;A~2PyFry8y$>6P*HwCq!_@`k3#icLZVHZR91y*z65_tW*S(xq7@uqRzrQX8D+9eJ z=Leozxppqf& zw4gm`b&oT`Y``%EQ-}v=HAcR8(mLj{wW+O`0RMJi%x{rW9BCc-m?a)^nJlNVD!)(N z=UNIUQi#A4;FxSq;OFC!Nv7#L?+~~WXR0h)J*78B{1(IMAk?lfBi1e17awLEX$-Wu z)Nr*L0Qfh^#<89$M43Cp-H2y50U26)!4m}+E*qbil!535PlP&6Vv}TM;s@-KFc9LK zmcqyDn(bK3#00PcRA#d$m}@Bnwq~>04Xnr^j-| zGmgBRa{!r$e1k4v%<-T`{JXhFh|*Cpd5T2@j^2S};1|acTmHXkdk^p~iYM@UlTbp3 zPz)^u2oQQNDo7v@dJzy*6bKM{4M-prHZXJm zME5;g>QIl%v7^DM~3*186%V=U=>z9d;wp8N(82I?p4EmYKJknes~1? zVpy~iv=|MZftS;&UHg_$1<*rk8q&~c8(b9wB7Vg@jsyEf!Z%}jw4+zg;5IIK;dFXP z5J9`pxFS)3)Fd$+>PW~A8wqxR^L2vu1HGYM93nsD9GLmW^y5g#!LP8Pl(# z_>wPdz*R#|3FpNN#NgJ?9dA)55P|7PST2Us_@2(w75Z4HiFIuQ4gs5BF?fB!z62?> z4g_TP78*UlvuZuMLd^#@=m5t(mN5?S_rT-`>d4?azHd02&ZzdJUR91YIkefu6h)&>K+I^kj?0Pu!z7{f!> z8(IfKT$?t~;OIr=;kls00lneMr(iQ$%c^zHTsR!B+t)ahYq$$tCNvFv5kWm1tn-<4 zum{c8>w|BX;b5x=x7xLUt$sAv5Z|E=&CR?$m&TF!3{8E=DY&uIBazUdz{8Zl4Lg9Q zd%{V1OpM{7y?&Mz1{6#XiR*&k#7XW}Ab8KDyZX&k6|m^cA(A*Obxb$ z9u0B_SlSsIG_)<4{H0$Oi%x4w*6b37X|O^AD1IO>ETRJss?uXTROw>tKr3k< z4Lv*0G4rK7mE83uJ?@E)^nMLXlFsXD!{IfP@Jeu$>7ulmx<-$V^J^#Z`t*c04kuMH zo$bjX47zYw(-+Y-ocI~tsaLmd{7|1(H7vjgR{*aufTty6V2T{rzJ4!Q z@5S_~sMCJX(jnoX$zIgxOq~bb915lvn_#HX9^W(^+8AC!3jK)Lh{4=ZwJG4}S8KCb zV{!F%c)}1$99s2gvDOeuzyw`;@tsC`%2Rij1>IZV@UBf%rlgv>c^fi5w%H)2FTOh+ zT@Aio73LAc;&rWHby2uzX3!@_)!@j~fPs`l5_f~#P8}g0p>Ev|>G^8alx3r?dbM?mI(WbWFo=k04P^x~ z6*#Zav!nSY(XDy;Q}CmPkj3#m6l~7$SURl4Mm=r=h$yh!6cq1FrO#Yfcnt_)uqbaR zH!>O@VQSp4eJ8zal#a`NVLc>z*9nPo#*gb40tf-dO`kSn#u@-&D$!b8b1zB~l# z*0=&h!wxWO+4lq@1aT-sSHd@j;I>YXcsOY`%!`71QsC%)X&b!60S|_4m-AG*CfwFp zr?3nFe%}Q;d#Hv%D%r4AyUy*+1CRQ49@w`br(STWtGo9MZvb4dFH06o5@~h1U2chF%X0;Ou5TvjJZtC@2vM!B@rGPY}i% z_2}BG8x+WVj|s!e-QW&SB?|j`GsQ7*J!3$q=<<7*YvYX;JOZUJ`navX!AIWVg&gf+ zH3+I3#N>jO(5P3BZWzN+tL8wIXzG5bockNDbGg0}8V`FAVmP%UciklAXbJ_9wipP^*Im~F znLNA$8CslPV^v>iRY<~crBbNU!h-{JdPh~5;kJY4f=kdoM$6hgTzzUTJ%u(G-nYWq zc%Ale0~=ow-6W=^Yp4jzk4Ix1g82)vJee5=Pw+TFYY3t~AzU9zvEoiIx#F7`Fl55l z1GJ9GUmU;BNd$bK(`!A#49`^wnm@cRqY5NSp;!utj_nA&B6y$KhXY-MXn0vAoDAAv z5AavBm8=2P?k>=B@zD-rAjmUdU<}#-pLEN86A0kC;eq_l&TimHkjjEL&{zV-X1p6l zN!JElk;b--OA+^Mha<-(m@b>-iSuE6vn0r26vs&e$l=vRkP~qCo(p%>&mVJ}6Nebx62{EA(9)G!!r&Ss zHC!{yABQ)^^o4i{Qt*F}vc~zH9y+f8gZQ??UN8!d#doEf?BBf=^u<9aG#p3EI1=cI z7OjNy9N;d70f%4=gHOcAgvPZMP5{f6^ooVHgLfL>0c6N!E48F`L!2y?1!m14XlGEO zfO}{B)%KJZ0t_cc;WRl1?^D8|d*Z5cy=eM`vm}lgEz_DFfCY^VfXb~_lALM;rgB&JJY-vY4I&>~@Y1;JR~1%Y8KEE(0Cs2AmlOv@rOM z#uq2Kzez5Q@ECZbyL^~nE0|1wTW^Ne3-r`ZCh00PCw&J3tDX?R!KNQ9QEd5I)s`)z;l2liU}mtL5BgR70xtM~4F#9f z@|+KHk!UzZOrL0&>4OWGc)n2sE>NcsbQoG>A{x3W_ZLxddiAZUvq_D4q+p&(gr1D^jKDWdAf$zC;8?Wk6HF$8?9I1~NgYVCx?;h^ZaB zCAQZ#(8+kWabs?S?TywY%aG{X5MWf@y zcH`Iov4f$5Lren&;S4O#Rv4P#8xQy{Is~@1UD0R>zeg7ht++!6oJH2Y4@7j&ccLi& zqUE8Wr*Jlt2f>^M8Z5wlgq}TmwT3j>`2r}ODD}!xyr4W2;pL+xd_X=caUX)s!PyA< zbR7zBz(cXg_z4ZipaV6_G2^XQUXvOTAXCTbc6DOlE<7}SNIJ|$JZgfiP_hBl!NGJD zDHf)_#k6gUU1xP)p-D#-m_QlR11tk6G-$vqO2hY)^)QRJcBAQp?zqQY5$L))JX#fv z&sgeQjh4hl3;coJt6+&}{se6dpXuGpaIEma)zyWq1()GEpA zzllW$gbA2%OUdW|zVIAMYgCs^@C7(`f^YDo1qjDj>W7*moE_SGb-b;mLnOpK$UCq@ zrx|ZByrJ4j^{#y>-E)s<>qPgVd2kS#oy{gP!?nSsp)Pn&fbk_J5NPzS(9Ez_371JX z99s6p+@VG*Z58s6fT&@Ajj`>Ql_|7Ycu)SL0ZtyYy zGaxZ81NU;!ive3#Yxu(Hegxe0Xc-d=Nhz!#2iZSoFqBxqY4n|V>;Yc=KS=ANgLW#GGHF+zkM3F>9Aa3HunC--+@MGLbRLKnI48FYJdPhr%c8+uL7(|Ky@SSw_nIL) zt_@f2x>15%H7-mKchMlG7)|LM1k>Y6rsD*cs}tR}S9>v_)2;-8s!lYxgYWi~9%bVF zDbN9|$&W9*N6)JdceQ%pgODHw^=ct5$OhdI4Dz5wf8yQ-iV_<+0>FI{urAu6GZqYB zUod!gnCcA31 z0h0^x)a|y%Magh89EM$k*RTx5b(rh4s)l!g^_A$sq`r9EE5G*tnvU!Lq{6qK^_2?3VOmDFD5!^o#VOT3QPrpS#{MEe8;z^_I!AgY;51q zsu0qg4d{pRm`)k4DoyF>7K1kp>gXyEnvbd?-JlhBhV}a(K-KB$%*@uo7YqyQpwh~u z@NRYEE7-kJWiz%5;zp1^=7%=C)&v_39AfdNmo=%*Y}LJK0mLbUW4M+sP< zw;VmhR2)|3H%BU24AzuM4Ewhv@M1kTyJZhL5DfB0ThvT!~U8;MsI2wL0@7!OTXxjeWy&$m5|}M3%rr z9`ck<5Hctva_llU)p0q+n4S;^-QPjqafL4G10e*ohEiTn47>H=9_zKfLiPs{7Q-^+ ze|p^H3_pxZ&<-c?%qzrgf4NL-7Dcpm0ZsURs_XpdxX+-x6NL zhgSz+G^{pdw3;y4zbZd9Lr=K3huak}JtVd@)ZF#_Z+vYF-+Tfy!rKQrL0^P@Lv-gd zcy!lxIJ*1-q<}D!B-*(n)DRZ)iRpxcL7;^aC9B*d=ut5BKc+`nv6YZ%0%WM(OgF=E zGO<&PgJN=9c#9q0L5JhQ8y58|f?-#Ej}iJpJ!zblro=KQJUr93t)3|D+QIXE4cZG9 zKLjHgU*d^OUFe0xkO)(^s56aD_<~Y!lE`+jC<9*{4P+ZYMFqOoytt`-OSo-AJw5=2 z>xU7b=^>Vx#6a_x+kx8Qp)Bo{4Z4D>fKgyv2$u&_*kR-kN$h*&pD2^+2p0}vQFSY> zR63Zh_-!ervO!F34ysfTJKuh&)O3ib@bVIM9(s`XkVeb|uOD7Xsc>kM*Wgy(U}!WQ ztH5!fRs9D)C&AC}t16Wbdg=^OO09vP&*A3;xV%f%l==>O*e%uJ9MF1~*MyJ;xo(a+ zO2vaKm#wSRSr~ids;ATNWhV*h;BA&_vs{QR)eBRco)*5#WgKq|`l# zYpc5`1!9$|yHefZe9^H=?Sk`N>jmdbs?^VYlzIy1%kiyJLqKtj1}L>IHSi6Dh4SI} zYX>Wp1&&{Im{Q|F50k%F>Kz=g|8S+Q!u3c00LO#t&p!f=1?NpWQYoB+vf@Y36`cR> z&yYvL^*{avbOG0$e5_Ipz%D(~()G^k~3UXaO7(c+UI|*8=Cyyad?b{6m(4PQec4 zmnk(AIHHy-)dTcVZY9jj27TmQ4c!&!;~D%60iLseD3u88&}WTObK&PJ_?Z{<^mwgO zS%I^~I;DOBeQntQ=LY+v-3-SD&Ouw1S_*u(w<#3_T$OjiOxlb}b=U=d0({kWgUvv1 z#rMGV0B7dCpnuR?vVEXu&|97TO7#WqWd}eP;6GCjDzz8*a~+242e>mFQ7SiZpM#%w z;peoYpbOAv^W$(H(C4fZpmVs6J}1GK;W|D$1=k1s>rcaRfWN~TI6m-KI1Baw{#@sP z1@!$4e)_?6Y&fsfKKMEI0vrqUU-u$#g5OQLtkl9ZN`+m6M^~Yry>dgTzrY{P-UeI2 z^^Cm--v|Bz55PZx`^-bo0dV_2hV~b#)WN4p{Rw*B`w#dcaJ75^*9sg3Un!Ld&bRK3 zQgh%uJ>SE%fd2CN`;q=~CH7OPKyQ@;{ZwJl)86EMYBlJmV=6z@80=XhwV%okdbys) zPyG)1xDw>2=7Szu1!jVOAmBf62>Ro%CIm*LoZ#B>ePe%Bzg9q^3@V92aZYvba+ zt@ic2D`o4QTiSTUpe(`VqvxfW_|Jwdo5ITukE=0aa)Wu_-tN}6_Noe_M)$}udhYR8 z<@^tn?wvR8ZCvU@y*|Ad{9weGayRxxp1BpUc18BjEBtlq<;1Y-qyN}Yu}s}1H6sIu ztd1Xh^5MdlQTsAgF0y}pzUM{e7926_&==bqz0ADgZnK!u$4W1}9QrK9PeD6Yq#V)w zNZZiNg>P^Dc>0Kk7fyfCY15@qt48%N)9hf~ZSOO*cwX^t`V$8SHXR5WdH+5jfxhOQ zUT`NK^n@?i08^^w&?CkcErS0K{pCo99mP@(e>`mSkYfWrJ-#D2Pw%4hMn@K%GeBJn zAJ{He)(cr5T&TAvN%D<>L+3tz@q2}L%YJ-$ZTy|kOp_NS*>OFx;(>P8dRN#waPYoi za~I?ZY}%*W_jA6zu%`K@`p4#!KU6&Hyy|O<{Th_!hjaBdHCR@;^|5_vkJqevp;XJT zfeXfTZ+<4n#Oax@mZ^Q>d8P(u%C=bc%ffx{SMMlxaC*IM1D6-NRW(tUvg2#Fzu9uu z!lE}CZXUfbX3@!zqifc^JpC3f#e1P0`%cI-1TGL_n*twKFih+mQ`Gf8%#DeLlo&lE z<-t11cZ8;mxHe$cH>1y$ZM8AHYRgpzmTml3MNZ99Y0u2#GcKIDbz?}`!&fq#no=v( z7lo!KO*Jy+P>~=0eA2(rf~*VASI@SqP^W8qGTy$|CwSi?f4%C1f_ansm$SqFIJnE_>mU-0f>w&H!G*0a zp*Uhc{Nl)u7osw3`QTN>z)RWRkA2f>MA@^ergdIDW$4)&`?{YFX*w=@`=8ou%9`Fk z&xe_k&uf2g-qEIM=5-Do)a>}yHW$X18TKZ{fRbZxW&ZZ!r9vOh&C~JoxyN2y?*I4Y z6EC8U-zxYb_x)@)hNjQ-=f!99#y+^(XXoy)>1!`u*zw)0Yae|zv(HcAKXyOxb&J26 zoqPLa$boj%hL+o&`s|+mN!MJg|HHYuEB96__S1iP%Iqlq)sz)smHq3EYu|n9%=AyP z9&0(T(V~pGtF$~b;?qQ3y4KFydg!!SJ@5ThWZH}Hsk;Xs7?iF~lH;Yz6w1)Mf1?S3 zTdw-mncAvK(;^2;w#gUSzxle!?PZR-StJ>VDDH2U6~? zma%l(5)}^SdtUy^;x)1FlSk~zyR6CMQ~lOloZ&z7SYYGHJ&p}{HLcBp2W>x3H9q9a z{@*;f)Ztuw$9IdfPQ9?@P3As{Yrg$9>wn)>j0?{KzdwDKDL39kc$| z@Apg3?re`9eS)n!Auz`Ir6m5`{iGIuAVv-y1ewnqe6yyy>>LYGVtk(KWBV2u)^!VPK{gsu+Op^1K;0UedW8i$5Rj8wQTm2 zXAc$(EI;tovnfa3zqvLd@#VTlXQj?DHZbvNG`W{Mju^>>0jq$`>gktE5bk zVQ|+UA50HSb@h4T*=4>;I%V6&adGiyX`;LPy>yW0)Vb6R~h zF1CHTki$2AAMmX8vKw7;_Uts}V<3@J+ODxPW7rro28 ztzGh0k(^OqrP&)EJ+;oz*x$-!ud*a(=CP4;MmLW7Hf->wLY)%jJQdX`^WI$Nutoyd*%6xBK$OohE<)44>-p|GF zl$nw>*^(x;UX@P!!MEqr+|5;D%!trxWoEqop?LOlG1V&lFndy}k3+jJY*q1kwfeL7 zuHQE_)pzj+Kdv%8GNk^d(wDvoEOUIo+O!vwe0?ha(&SZ|P0Bant7f|nF1)gBaNV?x z3k0m{y0CAR>m}z6sB`k|i`mnA&+GMR*L*wsb$qn6#>+An2iA|@*e>MC&TdCKm09uP z)!SxUFTLOVUDu7jUD?uM`Oqw-zUq?k#H9`AH&$!<=U?e7--{>|e)&#^7l&t6`1y6Q z@JAf{T-ir~Y_g z=}ylJKkXTkeMR?IJHIS)V8Mw-<;w*vemZu>FFQ_rU;WscSNHQ5`eymrx~EP&8yok> zfb>D@utew4 zNB18Jo0V%$zu+{-g4>?TU+qZbjXJTHzM6M(X1>T-w}%}6^4P+%?|$A@H8xf62j?SN zeVxDE!>NB<{PNWBv6*LmabQ~fxQf}LN-oS^Rx+(u6o$LR0Gq+Ov>|B&va={ z#1%YN<5Xxa|9q+Qe0t#514m9ow3?T5WYhKjgP$B8+i~!cO&xEB-fJ`VY3sq&D%aas zCRgrh(+6)~@MZMoMF)$fo-m

    U$wWZ=}C7`tZ1>fi({<|1C!C8M-6I%pO?|6(3Y~ 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 diff --git a/examples/testing.py b/examples/testing.py deleted file mode 100644 index 0345284..0000000 --- a/examples/testing.py +++ /dev/null @@ -1,19 +0,0 @@ -#%% -import pandas as pd -import numpy as np -import sys -sys.path.insert(0,"C:/Local/pers/Documents/GitHub/Cobra") - -ROOT = "C:/Local/pers/Documents/GitHub/Cobra/" - -#%% -df_data = pd.read_csv(ROOT + "datasets/titanic_data.csv") - -#%% -from cobra.preprocessing import KBinsDiscretizer - -KBD = KBinsDiscretizer() -df_prep = KBD.fit_transform(data=df_data, column_names=['Age','Fare']) - -#%% -from cobra.preprocessing import TargetEncoder \ No newline at end of file From 946a500718050ccefc77c242f7643fb17b641ae8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Mar 2020 09:07:48 +0100 Subject: [PATCH 61/98] Change docstring format in univariate_selection.py --- cobra/model_building/univariate_selection.py | 64 +++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index afc96d5..72620bd 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -18,7 +18,7 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, preselect_auc_threshold: float, preselect_overtrain_threshold: float ) -> pd.DataFrame: - """ Perform a preselection of predictors based on an AUC threshold of + """Perform a preselection of predictors based on an AUC threshold of a univariate model on a train and selection dataset and return a datframe containing for each variable the train and selection AUC along with a boolean "preselection" column. @@ -33,18 +33,29 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, Therefore, no univariate model is trained here as the target encoded train and selection data is/must be used as inputs for this function. These will be used as predicted scores to compute the AUC with against the target - Args: - target_enc_train_data (pd.DataFrame): Train data - target_enc_selection_data (pd.DataFrame): Selection data - predictors (list): list of predictors (e.g. column names in the train - and selection data sets) - target_column (str): name of the target column - preselect_auc_threshold (float): Description - preselect_overtrain_threshold (float): Description - Returns: - pd.DataFrame: DataFrame containing for each variable the train auc and - selection auc allong with a boolean indicating whether or not it is - selected based on the criteria + + Parameters + ---------- + target_enc_train_data : pd.DataFrame + Train data + target_enc_selection_data : pd.DataFrame + Selection data + predictors : list + list of predictors (e.g. column names in the train + target_column : str + name of the target column + preselect_auc_threshold : float + Description + preselect_overtrain_threshold : float + Description + and selection data sets) + + Returns + ------- + pd.DataFrame + DataFrame containing for each variable the train auc and + selection auc allong with a boolean indicating whether or not it is + selected based on the criteria """ result = [] @@ -81,8 +92,8 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, def get_preselected_predictors(df_auc: pd.DataFrame) -> list: - """Wrapper function to extract a list of predictors - from df_auc + """Wrapper function to extract a list of predictors from df_auc + Parameters ---------- df_auc : pd.DataFrame @@ -105,13 +116,20 @@ def compute_correlations(target_enc_train_data: pd.DataFrame, predictors: list) -> pd.DataFrame: """Given a DataFrame and a list of predictors, compute the correlations amongst the predictors in the DataFrame - Args: - target_enc_train_data (pd.DataFrame): data to compute correlation - matrix from - predictors (list): List of column names of the DataFrame between which - to compute correlations - Returns: - pd.DataFrame: The correlation matrix of the training set + + Parameters + ---------- + target_enc_train_data : pd.DataFrame + data to compute correlation + predictors : list + List of column names of the DataFrame between which + matrix from + to compute correlations + + Returns + ------- + pd.DataFrame + The correlation matrix of the training set """ correlations = target_enc_train_data[predictors].corr() @@ -124,4 +142,4 @@ def compute_correlations(target_enc_train_data: pd.DataFrame, correlations.columns = predictors_cleaned correlations.index = predictors_cleaned - return correlations \ No newline at end of file + return correlations From e94f17d809afb20ca49b9e4727de6b12db1e4768 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 24 Mar 2020 09:39:55 +0100 Subject: [PATCH 62/98] Add docstrings to forward_selection.py --- cobra/model_building/forward_selection.py | 50 +++++++++++++---------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index 543334c..c9d4807 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -113,20 +113,20 @@ def compute_model_performances(self, data: pd.DataFrame, def fit(self, train_data: pd.DataFrame, target_column_name: str, predictors: list, forced_predictors: list=[], excluded_predictors: list=[]): - """Summary + """Fit the forward feature selection estimator Parameters ---------- data : pd.DataFrame - Description + Data on which to fit the model target_column_name : str - Description + Name of the target column predictors : list - Description + List of predictors on which to train the estimator forced_predictors : list, optional - Description + List of predictors to force in the estimator excluded_predictors : list, optional - Description + List of predictors to exclude from the estimator Raises ------ @@ -158,18 +158,21 @@ def fit(self, train_data: pd.DataFrame, target_column_name: str, def _forward_selection(self, train_data: pd.DataFrame, target_column_name: str, predictors: list, forced_predictors: list=[]): - """Summary + """Perform the forward feature selection algoritm to compute a list + of models (with increasing performance?). The length of the list, + i.e. the number of models is bounded by the max_predictors class + attribute. Parameters ---------- train_data : pd.DataFrame - Description + Data on which to fit the model target_column_name : str - Description + Name of the target column predictors : list - Description + List of predictors on which to train the models forced_predictors : list, optional - Description + List of predictors to force in the models """ current_predictors = [] @@ -205,23 +208,26 @@ def _find_next_best_model(self, train_data: pd.DataFrame, target_column_name: str, candidate_predictors: list, current_predictors: list) -> MLModel: - """Summary + """Given a list of current predictors which are already to selected to + be include in the model, Find amongst a list candidate predictors + the predictor to add to the selected list so that the resulting model + has the best performance. Parameters ---------- train_data : pd.DataFrame - Description + Data on which to fit the model target_column_name : str - Description + Name of the target column candidate_predictors : list - Description + List of candidate predictors to test current_predictors : list - Description + List of predictors on which to train the models Returns ------- MLModel - Description + Best performing model """ # placeholders best_model = None @@ -251,21 +257,21 @@ def _find_next_best_model(self, train_data: pd.DataFrame, def _train_model(self, train_data: pd.DataFrame, target_column_name: str, predictors: list) -> MLModel: - """Summary + """Train the model with a given set of predictors Parameters ---------- train_data : pd.DataFrame - Description + Data on which to fit the model target_column_name : str - Description + Name of the target column predictors : list - Description + List of predictors on which to train the models Returns ------- MLModel - Description + trained model """ model = MLModel() From 3b6523585e8a690675e39cc3925dae8e7c400321 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 08:42:26 +0100 Subject: [PATCH 63/98] Add default args to univariate_selection --- cobra/model_building/univariate_selection.py | 21 ++++++++++---------- setup.py | 5 +++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index 72620bd..6b4dc38 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -15,8 +15,8 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, target_enc_selection_data: pd.DataFrame, predictors: list, target_column: str, - preselect_auc_threshold: float, - preselect_overtrain_threshold: float + preselect_auc_threshold: float=0.053, + preselect_overtrain_threshold: float=0.05 ) -> pd.DataFrame: """Perform a preselection of predictors based on an AUC threshold of a univariate model on a train and selection dataset and return a datframe @@ -41,14 +41,14 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, target_enc_selection_data : pd.DataFrame Selection data predictors : list - list of predictors (e.g. column names in the train + list of predictors (e.g. column names in the train set and selection + data sets) target_column : str name of the target column - preselect_auc_threshold : float - Description - preselect_overtrain_threshold : float - Description - and selection data sets) + preselect_auc_threshold : float, optional + threshold on AUC to select predictor + preselect_overtrain_threshold : float, optional + threshold on the difference between train and selection AUC Returns ------- @@ -83,12 +83,13 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, # Identify those variables for which the AUC difference between train # and selection is within a user-defined ratio - auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) * 100 + auc_overtrain = ((df_auc["AUC train"] - df_auc["AUC selection"]) < preselect_overtrain_threshold) df_auc["preselection"] = auc_thresh & auc_overtrain - return df_auc.sort_values(by='AUC selection', ascending=False) + return (df_auc.sort_values(by='AUC selection', ascending=False) + .reset_index()) def get_preselected_predictors(df_auc: pd.DataFrame) -> list: diff --git a/setup.py b/setup.py index e04e832..20ac799 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,11 @@ from distutils.core import setup + setup( name="cobra", - version="0.1.0", + version="1.0.0", description="Python Prediction's methodology for predictive analytics", - packages=["cobra"], + packages=["cobra", "cobra.preprocessing", "cobra.model_building"], url="https://github.com/PythonPredictions", #long_description=long_description, # TO DO #long_description_content_type="text/markdown", From e2635aea2ff4d706d5344efb9bae81e4878918f8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 09:15:49 +0100 Subject: [PATCH 64/98] Change output of forward_selection.compute_model_performances --- cobra/model_building/forward_selection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cobra/model_building/forward_selection.py b/cobra/model_building/forward_selection.py index c9d4807..7924e79 100644 --- a/cobra/model_building/forward_selection.py +++ b/cobra/model_building/forward_selection.py @@ -58,7 +58,8 @@ def get_model_from_step(self, step: int) -> MLModel: def compute_model_performances(self, data: pd.DataFrame, target_column_name: str, splits: list=["train", "selection", - "validation"]) -> list: + "validation"] + ) -> pd.DataFrame: """Compute for each model the performance for train-selection-validation sets and return them along with a list of predictors used in the model. @@ -108,7 +109,7 @@ def compute_model_performances(self, data: pd.DataFrame, predictor_set = predictor_set.union(set(model.predictors)) - return results + return pd.DataFrame(results) def fit(self, train_data: pd.DataFrame, target_column_name: str, predictors: list, forced_predictors: list=[], From 741ab490b24cbc6d4fa9825e950f9a9b8d91c3bb Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 10:01:45 +0100 Subject: [PATCH 65/98] Clean PreProcessor docstrings --- README.md | 4 +- cobra/preprocessing/preprocessor.py | 85 +++++++++++++++++++---------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index cc7293b..9dd035c 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ preprocessor = PreProcessor.from_params(serialization_path=path) basetable = preprocessor.train_selection_validation_split( basetable, target_column_name=target_column_name, - train_pct=0.6, selection_pct=0.2, - validation_pct=0.2) + train_prop=0.6, selection_prop=0.2, + validation_prop=0.2) # create list containing the column names of the discrete resp. # continiuous variables diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 81c7677..8299b8f 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -205,9 +205,9 @@ def fit(self, train_data: pd.DataFrame, continuous_vars: list, ---------- train_data : pd.DataFrame Data to be preprocessed - continuous_vars : list, optional + continuous_vars : list list of continuous variables - discrete_vars : list, optional + discrete_vars : list list of discrete variables target_column_name : str Name of the target column @@ -259,26 +259,26 @@ def fit(self, train_data: pd.DataFrame, continuous_vars: list, def transform(self, data: pd.DataFrame, continuous_vars: list, discrete_vars: list) -> pd.DataFrame: - """Summary + """Transform the data by applying the preprocessing pipeline to the it Parameters ---------- data : pd.DataFrame - Description - continuous_vars : list, optional + Data to be preprocessed + continuous_vars : list list of continuous variables - discrete_vars : list, optional + discrete_vars : list list of discrete variables Returns ------- pd.DataFrame - Description + Transformed (preprocessed) data Raises ------ NotFittedError - Description + In case PreProcessor was not fitted first """ start = time.time() @@ -308,12 +308,33 @@ def transform(self, data: pd.DataFrame, continuous_vars: list, return data + def fit_transform(self, train_data: pd.DataFrame, continuous_vars: list, + discrete_vars: list, target_column_name: str): + """Fit the data to the preprocessing pipeline and transform the data + + Parameters + ---------- + train_data : pd.DataFrame + Data to be preprocessed + continuous_vars : list + list of continuous variables + discrete_vars : list + list of discrete variables + target_column_name : str + Name of the target column + """ + + self.fit(train_data, continuous_vars, discrete_vars, + target_column_name) + + return self.transform(train_data, continuous_vars, discrete_vars) + @staticmethod def train_selection_validation_split(data: pd.DataFrame, target_column_name: str, - train_pct: float=0.6, - selection_pct: float=0.2, - validation_pct: float=0.2, + train_prop: float=0.6, + selection_prop: float=0.2, + validation_prop: float=0.2, stratify_split=True)->pd.DataFrame: """Split dataset into train-selection-validation datasets and merge them into one big DataFrame with an additional column "split" @@ -325,11 +346,11 @@ def train_selection_validation_split(data: pd.DataFrame, Input dataset to split into train-selection and validation sets target_column_name : str Name of the target column - train_pct : float, optional + train_prop : float, optional Percentage data to put in train set - selection_pct : float, optional + selection_prop : float, optional Percentage data to put in selection set - validation_pct : float, optional + validation_prop : float, optional Percentage data to put in validation set stratify_split : bool, optional Whether or not to stratify the train-test split @@ -339,17 +360,22 @@ def train_selection_validation_split(data: pd.DataFrame, pd.DataFrame Description """ + + if train_prop + selection_prop + validation_prop > 1: + raise ValueError("The sum of train_prop, selection_prop and " + "validation_prop cannot be larger than 1") + column_names = list(data.columns) predictors = [col for col in column_names if col != target_column_name] # for the first split, take sum of selection & validation pct as # test pct - test_pct = selection_pct + validation_pct + test_prop = selection_prop + validation_prop # To further split our test set into selection + validation set, - # we have to modify validation pct because we only have test_pct of + # we have to modify validation pct because we only have test_prop of # the data available anymore for further splitting! - validation_pct_modif = validation_pct / test_pct + validation_prop_modif = validation_prop / test_prop X = data[predictors] y = data[target_column_name] @@ -358,10 +384,12 @@ def train_selection_validation_split(data: pd.DataFrame, if stratify_split: stratify = y - X_train, X_test, y_train, y_test = train_test_split(X, y, - test_size=test_pct, - random_state=42, - stratify=stratify) + X_train, X_test, y_train, y_test = train_test_split( + X, y, + test_size=test_prop, + random_state=42, + stratify=stratify + ) df_train = pd.DataFrame(X_train, columns=predictors) df_train[target_column_name] = y_train @@ -369,7 +397,7 @@ def train_selection_validation_split(data: pd.DataFrame, # If there is no validation percentage, return train-selection sets # only - if validation_pct == 0.0: + if validation_prop == 0.0: df_selection = pd.DataFrame(X_test, columns=predictors) df_selection[target_column_name] = y_test df_selection["split"] = "selection" @@ -382,7 +410,7 @@ def train_selection_validation_split(data: pd.DataFrame, X_sel, X_val, y_sel, y_val = train_test_split( X_test, y_test, - test_size=validation_pct_modif, + test_size=validation_prop_modif, random_state=42, stratify=stratify ) @@ -462,24 +490,25 @@ def _is_valid_pipeline(pipeline: dict) -> bool: @staticmethod def _get_variable_list(continuous_vars: list, discrete_vars: list) -> list: - """Summary + """merge lists of continuous_vars and discrete_vars and add suffix + "_bin" resp. "_processed" to the predictors Parameters ---------- continuous_vars : list - Description + list of continuous variables discrete_vars : list - Description + list of discrete variables Returns ------- list - Description + Merged list of predictors with proper suffixes added Raises ------ ValueError - Description + in case both lists are empty """ var_list = ([col + "_processed" for col in discrete_vars] + [col + "_bin" for col in continuous_vars]) From 8d2a970425c31d6b2179a53624073ed4d73d22cf Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 10:07:07 +0100 Subject: [PATCH 66/98] Delete scripts module --- cobra/scripts/__init__.py | 0 cobra/scripts/export_pigs.py | 143 ----------------------------------- 2 files changed, 143 deletions(-) delete mode 100644 cobra/scripts/__init__.py delete mode 100644 cobra/scripts/export_pigs.py diff --git a/cobra/scripts/__init__.py b/cobra/scripts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cobra/scripts/export_pigs.py b/cobra/scripts/export_pigs.py deleted file mode 100644 index 086f342..0000000 --- a/cobra/scripts/export_pigs.py +++ /dev/null @@ -1,143 +0,0 @@ -# third party lib imports -import pandas as pd -# custom imports -import cobra.utils as utils -from cobra.preprocessing.kbins_discretizer import KBinsDiscretizer - - -def preprocess_categoricals(data: pd.DataFrame, - categorical_columns: list) -> pd.DataFrame: - - for column_name in categorical_columns: - - # change data to categorical - data[column_name] = data[column_name].astype("category") - - # check for null values - if data[column_name].isnull().sum() > 0: - - # Add an additional category - data[column_name].cat.add_categories(["Missing"], inplace=True) - - # Replace NULL with "Missing" - # Otherwise these will be ignored in groupby - data[column_name].fillna("Missing", inplace=True) - - return data - - -def compute_pig_table(data: pd.DataFrame, - column_name: str, - target_column_name: str, - id_column_name: str) -> pd.DataFrame: - """Compute the pig table of a given predictor for a given target - - Parameters - ---------- - data : pd.DataFrame - input data from which to compute the pig table - column_name : str - predictor name of which to compute the pig table - target_column_name : str - name of the target variable - id_column_name : str - name of the id column (used to count population size) - - Returns - ------- - pd.DataFrame - pig table as a DataFrame - """ - avg_incidence = data[target_column_name].mean() - - # group by the binned variable, compute the incidence - # (=mean of the target for the given bin) and compute the bin size - # (e.g. COUNT(id_column_name)). After that, rename the columns - res = (data.groupby(column_name) - .agg({target_column_name: "mean", id_column_name: "size"}) - .reset_index() - .rename(columns={column_name: "label", - target_column_name: "incidence", - id_column_name: "pop_size"})) - - # add the column name to a variable column - # add the average incidence - # replace population size by a percentage of total population - res["variable"] = column_name - res["avg_incidence"] = avg_incidence - res["pop_size"] = res["pop_size"]/len(data.index) - - # make sure to always return the data with the proper column order - column_order = ["variable", "label", "pop_size", - "avg_incidence", "incidence"] - - return res[column_order] - - -def generate_pig_tables(data: pd.DataFrame, - id_column_name: str, - target_column_name: str, - n_bins: int, - strategy: str, - label_format: str) -> pd.DataFrame: - """Summary - - Parameters - ---------- - data : pd.DataFrame - basetable to compute PIG tables of - id_column_name : str - column name of the id (e.g. customernumber) - target_column_name : str - column name of the target - n_bins : int - Number of bins to produce after discretization - strategy : str - Binning strategy. Currently only "uniform" and "quantile" - e.g. equifrequency is supported - label_format : str - format string to display the bin labels e.g. min - max, (min, max], ... - - Returns - ------- - pd.DataFrame - DataFrame containing a PIG table for all predictors - """ - - # Based on the data, get column names by datatype - # threshold to decide whether a numeric column should be considered - # a categorical variable (if the number of distinct values is smaller - # or equal to the number of requested bins) - categorical_threshold = n_bins - columns_by_type = utils.get_column_datatypes(data, id_column_name, - target_column_name, - categorical_threshold) - - # process continuous variables - discretizer = KBinsDiscretizer(n_bins=n_bins, - strategy=strategy, - label_format=label_format) - - # Transform the data - data = discretizer.fit_transform(data, - columns_by_type["numeric_variables"]) - - # Process categorical and dummy variables - categorical_vars = columns_by_type["categorical_variables"] - dummy_vars = columns_by_type["dummy_variables"] - relevant_columns = set(categorical_vars).union(set(dummy_vars)) - - data = preprocess_categoricals(data, list(relevant_columns)) - - # Get relevant columns, e.g. the ones that are transformed - # into categorical dtypes by the preprocessing steps - relevant_columns = set(data.dtypes[data.dtypes == "category"].index) - - pigs = [compute_pig_table(data, column_name, target_column_name, - id_column_name) - for column_name in sorted(relevant_columns) - if column_name not in [id_column_name, target_column_name]] - - output = pd.concat(pigs) - - return output From 234aba8551c03e05450a24ea8a50d5b56339e71f Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 10:07:20 +0100 Subject: [PATCH 67/98] Add evaluation module with PIGs script --- cobra/evaluation/__init__.py | 5 ++ cobra/evaluation/pigs_tables.py | 107 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 cobra/evaluation/__init__.py create mode 100644 cobra/evaluation/pigs_tables.py diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py new file mode 100644 index 0000000..c7d6820 --- /dev/null +++ b/cobra/evaluation/__init__.py @@ -0,0 +1,5 @@ +from .pig_tables import generate_pig_tables, compute_pig_table, plot_pig_graph + +__all__ = ['generate_pig_tables', + 'compute_pig_table', + 'plot_pig_graph'] diff --git a/cobra/evaluation/pigs_tables.py b/cobra/evaluation/pigs_tables.py new file mode 100644 index 0000000..19305d1 --- /dev/null +++ b/cobra/evaluation/pigs_tables.py @@ -0,0 +1,107 @@ +# third party imports +import pandas as pd +#import matplotlib.pyplot as plt +#import seaborn as sns + + +def generate_pig_tables(data: pd.DataFrame, + id_column_name: str, + target_column_name: str, + preprocessed_predictors: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + basetable to compute PIG tables of + id_column_name : str + column name of the id (e.g. customernumber) + target_column_name : str + column name of the target + predictors: list + list of preprocessed predictor names + + Returns + ------- + pd.DataFrame + DataFrame containing a PIG table for all predictors + """ + + # Based on the data, get column names by datatype + # threshold to decide whether a numeric column should be considered + # a categorical variable (if the number of distinct values is smaller + # or equal to the number of requested bins) + + pigs = [compute_pig_table(data, column_name, target_column_name, + id_column_name) + for column_name in sorted(preprocessed_predictors) + if column_name not in [id_column_name, target_column_name]] + + output = pd.concat(pigs) + + return output + + +def compute_pig_table(data: pd.DataFrame, + column_name: str, + target_column_name: str, + id_column_name: str) -> pd.DataFrame: + """Compute the pig table of a given predictor for a given target + + Parameters + ---------- + data : pd.DataFrame + input data from which to compute the pig table + column_name : str + predictor name of which to compute the pig table + target_column_name : str + name of the target variable + id_column_name : str + name of the id column (used to count population size) + + Returns + ------- + pd.DataFrame + pig table as a DataFrame + """ + avg_incidence = data[target_column_name].mean() + + # group by the binned variable, compute the incidence + # (=mean of the target for the given bin) and compute the bin size + # (e.g. COUNT(id_column_name)). After that, rename the columns + res = (data.groupby(column_name) + .agg({target_column_name: "mean", id_column_name: "size"}) + .reset_index() + .rename(columns={column_name: "label", + target_column_name: "incidence", + id_column_name: "pop_size"})) + + # add the column name to a variable column + # add the average incidence + # replace population size by a percentage of total population + res["variable"] = column_name + res["avg_incidence"] = avg_incidence + res["pop_size"] = res["pop_size"]/len(data.index) + + # make sure to always return the data with the proper column order + column_order = ["variable", "label", "pop_size", + "avg_incidence", "incidence"] + + return res[column_order] + + +def plot_pig_graph(pig_table: pd.DataFrame, + dim: tuple=(12, 8), + save_path: str=None): + """Create the Predictor Insights Graphs from a PIG table + + Parameters + ---------- + pig_table : pd.DataFrame + Description + dim : tuple, optional + Tuple with width and lentgh of the plot + save_path : str, optional + path to store the plot on disk + """ + pass From baf792f438ad4fc251897fa405f6b2cfeb84726a Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 10:10:07 +0100 Subject: [PATCH 68/98] Add plotting functions to evaluation module --- cobra/evaluation/model_evaluator.py | 354 +++++++++++++++++++++++++ cobra/evaluation/performance_curves.py | 35 +++ cobra/evaluation/predictor_quality.py | 85 ++++++ 3 files changed, 474 insertions(+) create mode 100644 cobra/evaluation/model_evaluator.py create mode 100644 cobra/evaluation/performance_curves.py create mode 100644 cobra/evaluation/predictor_quality.py diff --git a/cobra/evaluation/model_evaluator.py b/cobra/evaluation/model_evaluator.py new file mode 100644 index 0000000..a0addd9 --- /dev/null +++ b/cobra/evaluation/model_evaluator.py @@ -0,0 +1,354 @@ +""" +Created on Fri Apr 12 09:36:37 2019 +@author: AP_JBENEK +""" +import numpy as np +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt + +from sklearn.metrics import precision_score +from sklearn.metrics import recall_score +from sklearn.metrics import f1_score +from sklearn.metrics import accuracy_score +from sklearn.metrics import roc_curve +from sklearn.metrics import confusion_matrix +from sklearn.metrics import roc_auc_score + + +class Evaluator(): + + def __init__(self, y_true, y_pred_p, threshold=0.5, lift_at=0.1): + self.y_true = y_true + self.y_pred_p = y_pred_p # As probability + self.lift_at = lift_at + self.threshold = threshold + + #Convert to bool + self.y_pred_b = np.array([0 if pred <= self.threshold else 1 + for pred in self.y_pred_p]) + + def plotROCCurve(self, save_pth=None, desc=None): + ''' + Plot ROC curve and print best cutoff value + + Parameters + ---------- + y_true: True values of target y + proba: Predicted values of target y, probabilities + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + fpr, tpr, thresholds = roc_curve(self.y_true, self.y_pred_p) + + #--------------------------- + #Calculate AUC + #-------------------------- + out_perfo = self.evaluation() + score = out_perfo['AUC'] + + fig, ax = plt.subplots(figsize=(8, 5)) + ax.plot(fpr, tpr, color='darkorange', lw=2, + label='ROC curve (area = {s:.3})'.format(s=score)) + ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') + ax.set_xlabel('False Positive Rate', fontsize=15) + ax.set_ylabel('True Positive Rate', fontsize=15) + ax.legend(loc="lower right") + ax.set_title('ROC Curve {}' .format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + #Best cutoff value + #i want value where FPR is highest and FPR is lowest + #https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python + i = np.arange(len(tpr)) + roc = pd.DataFrame({'tf': pd.Series(tpr-(1-fpr), index=i), + 'threshold': pd.Series(thresholds, index=i)}) + roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] + + best_cutoff = list(roc_t['threshold']) + print(f'Best cutoff value for probability is: {best_cutoff[0]}') + + def plotConfusionMatrix(self, labels=None, color='Reds', save_pth=None, desc=None): + ''' + Plot Confusion matrix with performance measures + + Parameters + ---------- + y_test: True values of target y + pred: Predicted values of target y, boolean + labels: labels for the matrix, if empty, values from y_test_ are used + color: Color of the matrix, its a cmap, so many values possible + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if labels is None: + labels = [str(lab) for lab in np.unique(self.y_true)] + + if desc is None: + desc = '' + + cm = confusion_matrix(self.y_true, self.y_pred_b) + + fig, ax = plt.subplots(figsize=(8,5)) + ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, + xticklabels=labels, yticklabels=labels) + ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + out_perfo = self.evaluation() + + # If we mark customer as a churner, how often we are correct + print('Precision: {s:.3}'.format(s=out_perfo['precision'])) + # Overall performance + print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) + # How many churners can the model detect + print('Recall: {s:.3}'.format(s=out_perfo['recall'])) + # 2 * (precision * recall) / (precision + recall) + print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) + # 2 * (precision * recall) / (precision + recall) + print('Lift at top {l}%: {s:.3}' + .format(l=self.lift_at*100, s=out_perfo['lift'])) + # 2 * (precision * recall) / (precision + recall) + print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) + + def plotCumulativeGains(self, save_pth=None, desc=None): + ''' + Functions plot cumulative gains + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + #--------------------------- + #Calculate cumulative gains + #-------------------------- + nrows = len(self.y_true) + npositives = self.y_true.sum() + df_y_pred = (pd.DataFrame({"y": self.y_true, "y_pred": self.y_pred_p}) + .sort_values(by='y_pred', ascending=False) + .reset_index(drop=True)) + cgains = [0] + for stop in (np.linspace(0.01, 1, 100) * nrows).astype(int): + cgains.append(round(df_y_pred.loc[:stop, 'y'].sum()/npositives*max(100, 1), 2)) + + #--------------------------- + #Plot it + #--------------------------- + plt.style.use('seaborn-darkgrid') + fig, ax_cgains = plt.subplots(figsize=(8, 5)) + ax_cgains.plot(cgains, color='blue', linewidth=3, + label='cumulative gains') + ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, + ls="--", color="darkorange", label='random selection') + ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) + + ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) + #Format axes + ax_cgains.set_xlim([0, 100]) + ax_cgains.set_ylim([0, 100]) + #Format ticks + ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) + for x in ax_cgains.get_yticks()]) + ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) + for x in ax_cgains.get_xticks()]) + #Legend + ax_cgains.legend(loc='lower right') + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotLift(self, desc=None, save_pth=None): + ''' + Method plots lift per decile + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- +# inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + plt.style.use('seaborn-darkgrid') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0, nrows, 1)] + + plt.bar(x_labels[::-1], lifts, align='center', color="cornflowerblue") + plt.ylabel('lift', fontsize=15) + plt.xlabel('decile', fontsize=15) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color='darkorange', linestyle='--', + xmin=0.1, xmax=0.9, linewidth=3, label='Baseline') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeResponse(self, desc=None, save_pth=None): + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- + inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + lifts = np.array(lifts)*inc_rate*100 + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8, 5)) + #plt.style.use('seaborn-darkgrid') + plt.style.use('default') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0, nrows, 1)] + + plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") + plt.ylabel('response (%)', fontsize=16) + plt.xlabel('decile', fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', + xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative response {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def evaluation(self): + ''' + Convenient function, returns various performance measures in a dict + + Parameters + ---------- + y_true: true values + y_pred: predictions as booleans + + Output + ------ + Returns dictionary with the measures + ''' + + dict_perfo = {'precision': precision_score(self.y_true, self.y_pred_b), + 'accuracy': accuracy_score(self.y_true, self.y_pred_b), + 'recall': recall_score(self.y_true, self.y_pred_b), + 'F1': f1_score(self.y_true, self.y_pred_b, + average=None)[1], + 'lift': np.round(Evaluator + .liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=self.lift_at), + 2), + 'AUC': roc_auc_score(self.y_true, self.y_pred_p) + } + return dict_perfo + + @staticmethod + def liftCalculator(y_true, y_pred, lift_at=0.05, **kwargs): + ''' + Calculates lift given two arrays on specified level + + Parameters + ---------- + y_true: numpy array with true values + y_pred: numpy array with predictions (probabilities) + lift_at: lift at what top percentage + + Output + ------ + Scalar value, lift. + + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, + 10000 loops each) + ''' + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_), 1) + y_pred_ = y_pred_.reshape(len(y_pred_), 1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum('ij->j', y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] + .reshape(stop, 1)) + + #Calculate lift (einsum is very fast way of summing, + # needs specific shape) + inc_in_top_n = np.einsum('ij->j', data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence, 2)[0] + + return lift diff --git a/cobra/evaluation/performance_curves.py b/cobra/evaluation/performance_curves.py new file mode 100644 index 0000000..9ce9be4 --- /dev/null +++ b/cobra/evaluation/performance_curves.py @@ -0,0 +1,35 @@ +# third party imports +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt + + +def plot_performance_curves(model_performances: list, + dim: tuple=(12, 8)): + + df_plt = pd.DataFrame(model_performances) + + highest_auc = np.round(max(max(df_plt['train_performance']), + max(df_plt['selection_performance']), + max(df_plt['validation_performance'])), 1) + + fig, ax = plt.subplots(figsize=dim) + + plt.plot(df_plt['train_performance'], marker=".", markersize=20, + linewidth=3, label='AUC train') + plt.plot(df_plt['selection_performance'], marker=".", markersize=20, + linewidth=3, label='AUC selection') + plt.plot(df_plt['validation_performance'], marker=".", markersize=20, + linewidth=3, label='AUC validation') + # Set x/yticks + ax.set_xticks(np.arange(len(df_plt['last_added_predictor']) + 1)) + ax.set_xticklabels(df_plt['last_added_predictor'].tolist(), + rotation=40, ha='right') + ax.set_yticks(np.arange(0.5, highest_auc + 0.02, 0.05)) + #Make Pretty + ax.legend(loc='lower right') + fig.suptitle('Performance curves - forward feature selection', + fontsize=20) + plt.ylabel('Model performance') + plt.show() diff --git a/cobra/evaluation/predictor_quality.py b/cobra/evaluation/predictor_quality.py new file mode 100644 index 0000000..f2a2ea3 --- /dev/null +++ b/cobra/evaluation/predictor_quality.py @@ -0,0 +1,85 @@ +# third party imports +import pandas as pd + +import matplotlib.pyplot as plt +import seaborn as sns + + +def plot_variable_importance(importance_by_variable: dict, + title: str=None, + dim: tuple=(12, 8)): + """Plot variable importance of a given model + + Parameters + ---------- + importance_by_variable : dict + Map of predictor -> importance + title : str, optional + Title of the plot + dim : tuple, optional + tuple with width and lentgh of the plot + """ + df = pd.DataFrame.from_dict(importance_by_variable, + orient='index').reset_index() + + df.columns = ["predictor", "importance"] + + df = df.sort_values(by="importance", ascending=False) + + # plot data + fig, ax = plt.subplots(figsize=dim) + ax = sns.barplot(x="importance", y="predictor", data=df) + if title: + ax.set_title(title) + else: + ax.set_title("Variable importance") + plt.show() + + +def plot_predictor_quality(df_auc: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot univariate quality of the predictors + + Parameters + ---------- + df_auc : pd.DatFrame + Contains for each variable the train auc and selection auc allong with + a boolean indicating whether or not it is selected based on the + criteria + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + plt.style.use('seaborn-darkgrid') + + df = (df_auc[df_auc["preselection"]] + .sort_values(by='AUC train', ascending=False)) + + df = pd.melt(df, id_vars=["predictor"], + value_vars=["AUC train", "AUC selection"], + var_name="partition", + value_name="AUC") + + # plots + fig, ax = plt.subplots(figsize=dim) + + ax = sns.barplot(x="AUC", y="predictor", hue="partition", data=df) + ax.set_title('Univariate Quality of Predictors') + plt.show() + + +def plot_correlation_matrix(df_corr: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot correlation matrix amongst the predictors + + Parameters + ---------- + df_corr : pd.DataFrame + Correlation matrix + dim : tuple, optional + tuple with width and lentgh of the plot + """ + fig, ax = plt.subplots(figsize=dim) + ax = sns.heatmap(df_corr, cmap='Blues') + ax.set_title('Correlation Matrix') + plt.show() From b6dac15cec4f810eb934ba38aef232b67ab1c222 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 11:16:51 +0100 Subject: [PATCH 69/98] Fix a bug in univariate_selection.compute_univariate_preselection output --- cobra/model_building/univariate_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/model_building/univariate_selection.py b/cobra/model_building/univariate_selection.py index 6b4dc38..ced16ac 100644 --- a/cobra/model_building/univariate_selection.py +++ b/cobra/model_building/univariate_selection.py @@ -89,7 +89,7 @@ def compute_univariate_preselection(target_enc_train_data: pd.DataFrame, df_auc["preselection"] = auc_thresh & auc_overtrain return (df_auc.sort_values(by='AUC selection', ascending=False) - .reset_index()) + .reset_index(drop=True)) def get_preselected_predictors(df_auc: pd.DataFrame) -> list: From 1b6a058e68fcad6647a25fe797907a4683bcc15d Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 11:17:22 +0100 Subject: [PATCH 70/98] Bug fix in evaluation __init__.py --- cobra/evaluation/__init__.py | 2 +- cobra/evaluation/pigs_tables.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py index c7d6820..3172704 100644 --- a/cobra/evaluation/__init__.py +++ b/cobra/evaluation/__init__.py @@ -1,4 +1,4 @@ -from .pig_tables import generate_pig_tables, compute_pig_table, plot_pig_graph +from .pigs_tables import generate_pig_tables, compute_pig_table, plot_pig_graph __all__ = ['generate_pig_tables', 'compute_pig_table', diff --git a/cobra/evaluation/pigs_tables.py b/cobra/evaluation/pigs_tables.py index 19305d1..ccd64e9 100644 --- a/cobra/evaluation/pigs_tables.py +++ b/cobra/evaluation/pigs_tables.py @@ -3,6 +3,8 @@ #import matplotlib.pyplot as plt #import seaborn as sns +import cobra.utils as utils + def generate_pig_tables(data: pd.DataFrame, id_column_name: str, @@ -79,7 +81,7 @@ def compute_pig_table(data: pd.DataFrame, # add the column name to a variable column # add the average incidence # replace population size by a percentage of total population - res["variable"] = column_name + res["variable"] = utils.clean_predictor_name(column_name) res["avg_incidence"] = avg_incidence res["pop_size"] = res["pop_size"]/len(data.index) From a0f9ee79a9f63e9403f9d56ad8c59ba3f2620d05 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 11:17:40 +0100 Subject: [PATCH 71/98] Modified example notebook to new version --- examples/examples.ipynb | 2421 ++++++++++++--------------------------- 1 file changed, 751 insertions(+), 1670 deletions(-) diff --git a/examples/examples.ipynb b/examples/examples.ipynb index fd8f6fd..4611637 100644 --- a/examples/examples.ipynb +++ b/examples/examples.ipynb @@ -6,30 +6,22 @@ "source": [ "\n", "\n", - "# COBRA 1.0 as a Python library\n", + "# COBRA v1.0.0\n", "----------------------------------------------------------------------------------------------------------\n", - "**Autor**: Jan Benisek\n", + "**Autors**: Jan Benisek, Matthias Roels, Geert Verstraeten\n", "\n", - "**Date**: 05/03/2018\n", + "**Date**: 25/03/2020\n", "\n", - "**Purpose**: Show transformed COBRA 1.0 code into Python library for easy use\n", + "**Purpose**: Example usage of COBRA 1.0.0\n", "\n", - "**Requirements**: Python 3.6 (Conda distribution), COBRA library (to be specified)\n", + "**Requirements**: Python 3.6, COBRA library (installation, see README)\n", "\n", "----------------------------------------------------------------------------------------------------------\n", - "\n", - "**BEFORE YOU START**\n", - " * The dataset must contains a column name _ID_, which is a unique identifier of a row\n", - " * The column with target must be named _TARGET_\n", - " * .csv should be comma delimited (although Pandas will try to guess the sepator)\n", - " * Make sure you have the latest version of COBRA (follow the instructions on the main page of the repository)\n", - " \n", " \n", "## List of content\n", " * Data transformation\n", " * Univariate selection\n", - " * Model building\n", - " * Model comparison" + " * Model building" ] }, { @@ -38,24 +30,8 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas as pd\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\")\n", - "\n", - "#####################\n", - "import cobra.cobra as c\n", - "#####################\n", - "\n", - "data_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data.csv'\n", - "data_types_path = 'C:/Local/pers/Documents/GitHub/COBRA/datasets/data_types.csv'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data transformation\n", - "The first step is, as in the original version, to import and process the data." + "%reload_ext autoreload\n", + "%autoreload 2" ] }, { @@ -64,29 +40,34 @@ "metadata": {}, "outputs": [], "source": [ - "build = c.COBRA(data_path, #Path to .csv file which contains the data\n", - " data_types_path, #Path to .csv files which contains the metadata\n", - " partition_train=0.5, #Size of training set as int <0;1>\n", - " partition_select=0.3, #Size of selection set as int <0;1>\n", - " partition_valid=0.2, #Size of validation set as int <0;1>\n", - " sampling_1=1, #Size of sampling of target class (ie. 0.5 = take 50% of 1s)\n", - " sampling_0=1, #Size of sampling of non-target class (ie. 0.5 = take 50% of 0s)\n", - " discret_nbins=5, #Number of bins when binning continuous variables\n", - " regroup_sign=0.001, #Threshold for regrouping cat. variables (p-value)\n", - " rseed=0) #Random seed for reproduction\n", - "df_transformed = build.transform()" + "# third party imports \n", + "import numpy as np\n", + "import pandas as pd \n", + "\n", + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "# Custom imports\n", + "import sys\n", + "sys.path.append(\"/mnt/c/Users/matroe/Documents/workspace/cobra\")\n", + "from cobra.preprocessing import PreProcessor\n", + "from cobra.model_building import univariate_selection\n", + "from cobra.model_building import ForwardFeatureSelection\n", + "from cobra.evaluation import generate_pig_tables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Below you can see first 10 rows of the transformed dataframe." + "## Data Loading\n", + "The first step is to load a dataset into a pandas DataFrame. This step is not part of COBRA as we want to keep the flexibility the load from different data sources" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -110,806 +91,339 @@ " \n", " \n", " \n", - " ID\n", - " TARGET\n", - " PARTITION\n", - " B_age\n", - " B_fnlwgt\n", - " B_education-num\n", - " B_capital-gain\n", - " B_capital-loss\n", - " B_hours-per-week\n", - " B_scont_1\n", - " B_scont_2\n", - " B_scont_3\n", - " B_scont_4\n", - " B_scont_5\n", - " B_scont_6\n", - " B_scont_7\n", - " B_scont_8\n", - " B_scont_9\n", - " B_scont_10\n", - " B_sflag_1\n", - " B_sflag_2\n", - " B_sflag_3\n", - " B_sflag_4\n", - " B_sflag_5\n", - " B_marital-status\n", + " age\n", + " workclass\n", + " fnlwgt\n", + " education\n", + " education-num\n", + " marital-status\n", + " occupation\n", + " relationship\n", + " race\n", + " sex\n", " ...\n", - " D_sex\n", - " D_native-country\n", - " D_hours-per-week\n", - " D_scont_2\n", - " D_sflag_2\n", - " D_scat_4\n", - " D_scont_4\n", - " D_sflag_1\n", - " D_scont_1\n", - " D_workclass\n", - " D_scont_9\n", - " D_scat_1\n", - " D_capital-loss\n", - " D_capital-gain\n", - " D_sflag_3\n", - " D_scat_2\n", - " D_fnlwgt\n", - " D_scont_7\n", - " D_sflag_4\n", - " D_relationship\n", - " D_education\n", - " D_scont_5\n", - " D_age\n", - " D_scont_10\n", - " D_race\n", + " scat_1\n", + " scat_2\n", + " scat_3\n", + " scat_4\n", + " scat_5\n", + " sflag_1\n", + " sflag_2\n", + " sflag_3\n", + " sflag_4\n", + " sflag_5\n", " \n", " \n", " \n", " \n", " 0\n", - " 6640.0\n", - " 1.0\n", - " train\n", - " (41, 51]\n", - " (195948, 260560]\n", - " (13,...]\n", - " [..., 1797]\n", - " (1258,...]\n", - " (35, 40]\n", - " [..., 2]\n", - " (2, 4]\n", - " (9, 12]\n", - " (16,...]\n", - " [..., 5]\n", - " (7, 12]\n", - " (21, 28]\n", - " [..., 9]\n", - " [..., 9]\n", - " (30, 40]\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " Married-civ-spouse\n", + " 39\n", + " State-gov\n", + " 77516\n", + " Bachelors\n", + " 13\n", + " Never-married\n", + " Adm-clerical\n", + " Not-in-family\n", + " White\n", + " Male\n", " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.230408\n", - " 0.240833\n", - " 0.239292\n", - " 0.248492\n", - " 0.245857\n", - " 0.246575\n", - " 0.557160\n", - " 0.220824\n", - " 0.239292\n", - " 0.512613\n", - " 0.205332\n", - " 0.236874\n", - " 0.239292\n", - " 0.224519\n", - " 0.243842\n", - " 0.236838\n", - " 0.444804\n", - " 0.756410\n", - " 0.235711\n", - " 0.380231\n", - " 0.231227\n", - " 0.254518\n", + " A\n", + " C\n", + " C\n", + " A\n", + " A\n", + " 0\n", + " 1\n", + " 0\n", + " 0\n", + " 0\n", " \n", " \n", " 1\n", - " 8393.0\n", - " 1.0\n", - " train\n", - " (26, 33]\n", - " (195948, 260560]\n", - " (13,...]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (40, 50]\n", - " [..., 2]\n", - " (2, 4]\n", - " (6, 9]\n", - " [..., 5]\n", - " (21,...]\n", - " (18, 25]\n", - " [..., 7]\n", - " (9, 17]\n", - " (37,...]\n", - " (20, 30]\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " Never-married\n", + " 50\n", + " Self-emp-not-inc\n", + " 83311\n", + " Bachelors\n", + " 13\n", + " Married-civ-spouse\n", + " Exec-managerial\n", + " Husband\n", + " White\n", + " Male\n", " ...\n", - " 0.113317\n", - " 0.242674\n", - " 0.395141\n", - " 0.230408\n", - " 0.240833\n", - " 0.239292\n", - " 0.237532\n", - " 0.245857\n", - " 0.246575\n", - " 0.217724\n", - " 0.237834\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.236874\n", - " 0.239292\n", - " 0.224519\n", - " 0.231617\n", - " 0.236838\n", - " 0.101233\n", - " 0.560060\n", - " 0.250251\n", - " 0.182973\n", - " 0.236608\n", - " 0.254518\n", + " B\n", + " B\n", + " A\n", + " B\n", + " A\n", + " 1\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", " \n", " \n", " 2\n", - " 32199.0\n", - " 1.0\n", - " train\n", - " (51,...]\n", - " (156845, 195948]\n", - " (9, 10]\n", - " (1797,...]\n", - " [..., 1258]\n", - " [..., 35]\n", - " (4,...]\n", - " [..., 2]\n", - " [..., 3]\n", - " (12, 16]\n", - " (11, 16]\n", - " (18, 25]\n", - " [..., 7]\n", - " (24, 33]\n", - " (37,...]\n", - " [..., 10]\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " Married-civ-spouse\n", + " 38\n", + " Private\n", + " 215646\n", + " HS-grad\n", + " 9\n", + " Divorced\n", + " Handlers-cleaners\n", + " Not-in-family\n", + " White\n", + " Male\n", " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.082494\n", - " 0.239825\n", - " 0.237778\n", - " 0.239292\n", - " 0.244302\n", - " 0.232815\n", - " 0.241358\n", - " 0.557160\n", - " 0.237834\n", - " 0.239292\n", - " 0.226278\n", - " 0.646965\n", - " 0.236874\n", - " 0.239292\n", - " 0.251229\n", - " 0.231617\n", - " 0.241786\n", - " 0.444804\n", - " 0.182432\n", - " 0.239166\n", - " 0.310733\n", - " 0.241169\n", - " 0.254518\n", + " A\n", + " C\n", + " A\n", + " A\n", + " F\n", + " 0\n", + " 1\n", + " 0\n", + " 1\n", + " 0\n", " \n", " \n", " 3\n", - " 16168.0\n", - " 1.0\n", - " train\n", - " (41, 51]\n", - " (260560,...]\n", - " [..., 9]\n", - " (1797,...]\n", - " [..., 1258]\n", - " (40, 50]\n", - " [..., 2]\n", - " (6, 8]\n", - " (9, 12]\n", - " (8, 12]\n", - " (11, 16]\n", - " (12, 18]\n", - " (28,...]\n", - " (9, 17]\n", - " (37,...]\n", - " (30, 40]\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", + " 53\n", + " Private\n", + " 234721\n", + " 11th\n", + " 7\n", " Married-civ-spouse\n", + " Handlers-cleaners\n", + " Husband\n", + " Black\n", + " Male\n", " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.395141\n", - " 0.235776\n", - " 0.240833\n", - " 0.239292\n", - " 0.234490\n", - " 0.245857\n", - " 0.246575\n", - " 0.217724\n", - " 0.237834\n", - " 0.239292\n", - " 0.226278\n", - " 0.646965\n", - " 0.236874\n", - " 0.239292\n", - " 0.230596\n", - " 0.243021\n", - " 0.241786\n", - " 0.444804\n", - " 0.162138\n", - " 0.239166\n", - " 0.380231\n", - " 0.231227\n", - " 0.254518\n", + " B\n", + " B\n", + " C\n", + " A\n", + " B\n", + " 0\n", + " 0\n", + " 1\n", + " 0\n", + " 0\n", " \n", " \n", " 4\n", - " 6469.0\n", - " 1.0\n", - " train\n", - " (33, 41]\n", - " (195948, 260560]\n", - " (13,...]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (40, 50]\n", - " [..., 2]\n", - " (8,...]\n", - " (12,...]\n", - " (8, 12]\n", - " [..., 5]\n", - " [..., 7]\n", - " [..., 7]\n", - " (24, 33]\n", - " (9, 18]\n", - " [..., 10]\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " Married-civ-spouse\n", - " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.395141\n", - " 0.252790\n", - " 0.237778\n", - " 0.239292\n", - " 0.234490\n", - " 0.232815\n", - " 0.246575\n", - " 0.217724\n", - " 0.246939\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.241725\n", - " 0.239292\n", - " 0.224519\n", - " 0.231617\n", - " 0.236838\n", - " 0.444804\n", - " 0.560060\n", - " 0.235711\n", - " 0.312165\n", - " 0.241169\n", - " 0.254518\n", - " \n", - " \n", - " 5\n", - " 101.0\n", - " 1.0\n", - " train\n", - " (51,...]\n", - " (105659, 156845]\n", - " (13,...]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (35, 40]\n", - " [..., 2]\n", - " [..., 2]\n", - " (6, 9]\n", - " (5, 8]\n", - " (5, 11]\n", - " (12, 18]\n", - " (7, 14]\n", - " (17, 24]\n", - " (28, 37]\n", - " (40,...]\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " Married-civ-spouse\n", - " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.239825\n", - " 0.237778\n", - " 0.239292\n", - " 0.230127\n", - " 0.245857\n", - " 0.246575\n", - " 0.217724\n", - " 0.248562\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.241725\n", - " 0.239292\n", - " 0.264947\n", - " 0.236933\n", - " 0.241786\n", - " 0.444804\n", - " 0.560060\n", - " 0.234372\n", - " 0.310733\n", - " 0.246743\n", - " 0.254518\n", - " \n", - " \n", - " 6\n", - " 22547.0\n", - " 1.0\n", - " train\n", - " (41, 51]\n", - " [..., 105659]\n", - " (10, 13]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (35, 40]\n", - " (3, 4]\n", - " (4, 6]\n", - " [..., 3]\n", - " [..., 5]\n", - " (5, 11]\n", - " (18, 25]\n", - " (14, 21]\n", - " [..., 9]\n", - " [..., 9]\n", - " [..., 10]\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " Married-civ-spouse\n", - " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.237765\n", - " 0.240833\n", - " 0.239292\n", - " 0.237532\n", - " 0.232815\n", - " 0.230504\n", - " 0.217724\n", - " 0.220824\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.241725\n", - " 0.239292\n", - " 0.225179\n", - " 0.241300\n", - " 0.241786\n", - " 0.444804\n", - " 0.246997\n", - " 0.234372\n", - " 0.380231\n", - " 0.241169\n", - " 0.254518\n", - " \n", - " \n", - " 7\n", - " 1319.0\n", - " 1.0\n", - " train\n", - " (33, 41]\n", - " (105659, 156845]\n", - " (10, 13]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (35, 40]\n", - " (4,...]\n", - " [..., 2]\n", - " [..., 3]\n", - " [..., 5]\n", - " (5, 11]\n", - " (12, 18]\n", - " [..., 7]\n", - " (17, 24]\n", - " (28, 37]\n", - " (20, 30]\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " Married-civ-spouse\n", - " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.239825\n", - " 0.240833\n", - " 0.239292\n", - " 0.237532\n", - " 0.232815\n", - " 0.241358\n", - " 0.273360\n", - " 0.248562\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.241725\n", - " 0.239292\n", - " 0.264947\n", - " 0.231617\n", - " 0.236838\n", - " 0.444804\n", - " 0.415358\n", - " 0.234372\n", - " 0.312165\n", - " 0.236608\n", - " 0.254518\n", - " \n", - " \n", - " 8\n", - " 653.0\n", - " 1.0\n", - " train\n", - " (33, 41]\n", - " (260560,...]\n", - " (13,...]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (35, 40]\n", - " (4,...]\n", - " (6, 8]\n", - " (9, 12]\n", - " (5, 8]\n", - " (11, 16]\n", - " (7, 12]\n", - " (14, 21]\n", - " [..., 9]\n", - " (37,...]\n", - " (10, 20]\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", + " 28\n", + " Private\n", + " 338409\n", + " Bachelors\n", + " 13\n", " Married-civ-spouse\n", + " Prof-specialty\n", + " Wife\n", + " Black\n", + " Female\n", " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.235776\n", - " 0.240833\n", - " 0.239292\n", - " 0.230127\n", - " 0.232815\n", - " 0.241358\n", - " 0.217724\n", - " 0.237834\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.236874\n", - " 0.239292\n", - " 0.230596\n", - " 0.241300\n", - " 0.236838\n", - " 0.444804\n", - " 0.762590\n", - " 0.239166\n", - " 0.312165\n", - " 0.240711\n", - " 0.254518\n", - " \n", - " \n", - " 9\n", - " 16288.0\n", - " 1.0\n", - " train\n", - " (41, 51]\n", - " (260560,...]\n", - " (9, 10]\n", - " [..., 1797]\n", - " [..., 1258]\n", - " (35, 40]\n", - " (3, 4]\n", - " (4, 6]\n", - " (12,...]\n", - " (16,...]\n", - " (11, 16]\n", - " (18, 25]\n", - " (21, 28]\n", - " (9, 17]\n", - " (9, 18]\n", - " (30, 40]\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " Married-civ-spouse\n", - " ...\n", - " 0.301925\n", - " 0.242674\n", - " 0.213624\n", - " 0.237765\n", - " 0.237778\n", - " 0.239292\n", - " 0.248492\n", - " 0.232815\n", - " 0.230504\n", - " 0.217724\n", - " 0.246939\n", - " 0.239292\n", - " 0.226278\n", - " 0.205332\n", - " 0.241725\n", - " 0.239292\n", - " 0.230596\n", - " 0.243842\n", - " 0.241786\n", - " 0.444804\n", - " 0.182432\n", - " 0.239166\n", - " 0.380231\n", - " 0.231227\n", - " 0.254518\n", + " B\n", + " B\n", + " C\n", + " C\n", + " A\n", + " 1\n", + " 0\n", + " 0\n", + " 0\n", + " 1\n", " \n", " \n", "\n", - "

    10 rows × 71 columns

    \n", + "

    5 rows × 36 columns

    \n", "" ], "text/plain": [ - " ID TARGET PARTITION B_age B_fnlwgt B_education-num \\\n", - "0 6640.0 1.0 train (41, 51] (195948, 260560] (13,...] \n", - "1 8393.0 1.0 train (26, 33] (195948, 260560] (13,...] \n", - "2 32199.0 1.0 train (51,...] (156845, 195948] (9, 10] \n", - "3 16168.0 1.0 train (41, 51] (260560,...] [..., 9] \n", - "4 6469.0 1.0 train (33, 41] (195948, 260560] (13,...] \n", - "5 101.0 1.0 train (51,...] (105659, 156845] (13,...] \n", - "6 22547.0 1.0 train (41, 51] [..., 105659] (10, 13] \n", - "7 1319.0 1.0 train (33, 41] (105659, 156845] (10, 13] \n", - "8 653.0 1.0 train (33, 41] (260560,...] (13,...] \n", - "9 16288.0 1.0 train (41, 51] (260560,...] (9, 10] \n", - "\n", - " B_capital-gain B_capital-loss B_hours-per-week B_scont_1 B_scont_2 \\\n", - "0 [..., 1797] (1258,...] (35, 40] [..., 2] (2, 4] \n", - "1 [..., 1797] [..., 1258] (40, 50] [..., 2] (2, 4] \n", - "2 (1797,...] [..., 1258] [..., 35] (4,...] [..., 2] \n", - "3 (1797,...] [..., 1258] (40, 50] [..., 2] (6, 8] \n", - "4 [..., 1797] [..., 1258] (40, 50] [..., 2] (8,...] \n", - "5 [..., 1797] [..., 1258] (35, 40] [..., 2] [..., 2] \n", - "6 [..., 1797] [..., 1258] (35, 40] (3, 4] (4, 6] \n", - "7 [..., 1797] [..., 1258] (35, 40] (4,...] [..., 2] \n", - "8 [..., 1797] [..., 1258] (35, 40] (4,...] (6, 8] \n", - "9 [..., 1797] [..., 1258] (35, 40] (3, 4] (4, 6] \n", - "\n", - " B_scont_3 B_scont_4 B_scont_5 B_scont_6 B_scont_7 B_scont_8 B_scont_9 \\\n", - "0 (9, 12] (16,...] [..., 5] (7, 12] (21, 28] [..., 9] [..., 9] \n", - "1 (6, 9] [..., 5] (21,...] (18, 25] [..., 7] (9, 17] (37,...] \n", - "2 [..., 3] (12, 16] (11, 16] (18, 25] [..., 7] (24, 33] (37,...] \n", - "3 (9, 12] (8, 12] (11, 16] (12, 18] (28,...] (9, 17] (37,...] \n", - "4 (12,...] (8, 12] [..., 5] [..., 7] [..., 7] (24, 33] (9, 18] \n", - "5 (6, 9] (5, 8] (5, 11] (12, 18] (7, 14] (17, 24] (28, 37] \n", - "6 [..., 3] [..., 5] (5, 11] (18, 25] (14, 21] [..., 9] [..., 9] \n", - "7 [..., 3] [..., 5] (5, 11] (12, 18] [..., 7] (17, 24] (28, 37] \n", - "8 (9, 12] (5, 8] (11, 16] (7, 12] (14, 21] [..., 9] (37,...] \n", - "9 (12,...] (16,...] (11, 16] (18, 25] (21, 28] (9, 17] (9, 18] \n", + " age workclass fnlwgt education education-num \\\n", + "0 39 State-gov 77516 Bachelors 13 \n", + "1 50 Self-emp-not-inc 83311 Bachelors 13 \n", + "2 38 Private 215646 HS-grad 9 \n", + "3 53 Private 234721 11th 7 \n", + "4 28 Private 338409 Bachelors 13 \n", "\n", - " B_scont_10 B_sflag_1 B_sflag_2 B_sflag_3 B_sflag_4 B_sflag_5 \\\n", - "0 (30, 40] 1.0 1.0 0.0 0.0 1.0 \n", - "1 (20, 30] 1.0 1.0 0.0 0.0 0.0 \n", - "2 [..., 10] 0.0 0.0 0.0 1.0 1.0 \n", - "3 (30, 40] 1.0 1.0 0.0 1.0 0.0 \n", - "4 [..., 10] 0.0 0.0 1.0 0.0 0.0 \n", - "5 (40,...] 1.0 0.0 1.0 1.0 0.0 \n", - "6 [..., 10] 0.0 1.0 1.0 1.0 0.0 \n", - "7 (20, 30] 0.0 1.0 1.0 0.0 1.0 \n", - "8 (10, 20] 0.0 1.0 0.0 0.0 0.0 \n", - "9 (30, 40] 0.0 0.0 1.0 1.0 0.0 \n", + " marital-status occupation relationship race sex \\\n", + "0 Never-married Adm-clerical Not-in-family White Male \n", + "1 Married-civ-spouse Exec-managerial Husband White Male \n", + "2 Divorced Handlers-cleaners Not-in-family White Male \n", + "3 Married-civ-spouse Handlers-cleaners Husband Black Male \n", + "4 Married-civ-spouse Prof-specialty Wife Black Female \n", "\n", - " B_marital-status ... D_sex D_native-country D_hours-per-week \\\n", - "0 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", - "1 Never-married ... 0.113317 0.242674 0.395141 \n", - "2 Married-civ-spouse ... 0.301925 0.242674 0.082494 \n", - "3 Married-civ-spouse ... 0.301925 0.242674 0.395141 \n", - "4 Married-civ-spouse ... 0.301925 0.242674 0.395141 \n", - "5 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", - "6 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", - "7 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", - "8 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", - "9 Married-civ-spouse ... 0.301925 0.242674 0.213624 \n", + " ... scat_1 scat_2 scat_3 scat_4 scat_5 sflag_1 sflag_2 sflag_3 \\\n", + "0 ... A C C A A 0 1 0 \n", + "1 ... B B A B A 1 0 0 \n", + "2 ... A C A A F 0 1 0 \n", + "3 ... B B C A B 0 0 1 \n", + "4 ... B B C C A 1 0 0 \n", "\n", - " D_scont_2 D_sflag_2 D_scat_4 D_scont_4 D_sflag_1 D_scont_1 D_workclass \\\n", - "0 0.230408 0.240833 0.239292 0.248492 0.245857 0.246575 0.557160 \n", - "1 0.230408 0.240833 0.239292 0.237532 0.245857 0.246575 0.217724 \n", - "2 0.239825 0.237778 0.239292 0.244302 0.232815 0.241358 0.557160 \n", - "3 0.235776 0.240833 0.239292 0.234490 0.245857 0.246575 0.217724 \n", - "4 0.252790 0.237778 0.239292 0.234490 0.232815 0.246575 0.217724 \n", - "5 0.239825 0.237778 0.239292 0.230127 0.245857 0.246575 0.217724 \n", - "6 0.237765 0.240833 0.239292 0.237532 0.232815 0.230504 0.217724 \n", - "7 0.239825 0.240833 0.239292 0.237532 0.232815 0.241358 0.273360 \n", - "8 0.235776 0.240833 0.239292 0.230127 0.232815 0.241358 0.217724 \n", - "9 0.237765 0.237778 0.239292 0.248492 0.232815 0.230504 0.217724 \n", + " sflag_4 sflag_5 \n", + "0 0 0 \n", + "1 0 0 \n", + "2 1 0 \n", + "3 0 0 \n", + "4 0 1 \n", "\n", - " D_scont_9 D_scat_1 D_capital-loss D_capital-gain D_sflag_3 D_scat_2 \\\n", - "0 0.220824 0.239292 0.512613 0.205332 0.236874 0.239292 \n", - "1 0.237834 0.239292 0.226278 0.205332 0.236874 0.239292 \n", - "2 0.237834 0.239292 0.226278 0.646965 0.236874 0.239292 \n", - "3 0.237834 0.239292 0.226278 0.646965 0.236874 0.239292 \n", - "4 0.246939 0.239292 0.226278 0.205332 0.241725 0.239292 \n", - "5 0.248562 0.239292 0.226278 0.205332 0.241725 0.239292 \n", - "6 0.220824 0.239292 0.226278 0.205332 0.241725 0.239292 \n", - "7 0.248562 0.239292 0.226278 0.205332 0.241725 0.239292 \n", - "8 0.237834 0.239292 0.226278 0.205332 0.236874 0.239292 \n", - "9 0.246939 0.239292 0.226278 0.205332 0.241725 0.239292 \n", - "\n", - " D_fnlwgt D_scont_7 D_sflag_4 D_relationship D_education D_scont_5 \\\n", - "0 0.224519 0.243842 0.236838 0.444804 0.756410 0.235711 \n", - "1 0.224519 0.231617 0.236838 0.101233 0.560060 0.250251 \n", - "2 0.251229 0.231617 0.241786 0.444804 0.182432 0.239166 \n", - "3 0.230596 0.243021 0.241786 0.444804 0.162138 0.239166 \n", - "4 0.224519 0.231617 0.236838 0.444804 0.560060 0.235711 \n", - "5 0.264947 0.236933 0.241786 0.444804 0.560060 0.234372 \n", - "6 0.225179 0.241300 0.241786 0.444804 0.246997 0.234372 \n", - "7 0.264947 0.231617 0.236838 0.444804 0.415358 0.234372 \n", - "8 0.230596 0.241300 0.236838 0.444804 0.762590 0.239166 \n", - "9 0.230596 0.243842 0.241786 0.444804 0.182432 0.239166 \n", - "\n", - " D_age D_scont_10 D_race \n", - "0 0.380231 0.231227 0.254518 \n", - "1 0.182973 0.236608 0.254518 \n", - "2 0.310733 0.241169 0.254518 \n", - "3 0.380231 0.231227 0.254518 \n", - "4 0.312165 0.241169 0.254518 \n", - "5 0.310733 0.246743 0.254518 \n", - "6 0.380231 0.241169 0.254518 \n", - "7 0.312165 0.236608 0.254518 \n", - "8 0.312165 0.240711 0.254518 \n", - "9 0.380231 0.231227 0.254518 \n", - "\n", - "[10 rows x 71 columns]" + "[5 rows x 36 columns]" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df_transformed.head(n=10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The library also offers a function to print basic summary (will be improved in the future)" + "path = \"../datasets/data.csv\"\n", + "\n", + "basetable = pd.read_csv(path)\n", + "\n", + "basetable.head()" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "----------------- SUMMARY -----------------\n", - "Dataset has 48842 rows and 71 columns.\n", - "Train set has 24422 rows\n", - "Selection set has 14654 rows\n", - "Validation set has 9766 rows\n", - "Overall incidence rate is 23.93%\n", - "0.00% records in the dataset are missing.\n", - "-------------------------------------------\n" - ] + "data": { + "text/plain": [ + "Index(['age', 'workclass', 'fnlwgt', 'education', 'education-num',\n", + " 'marital-status', 'occupation', 'relationship', 'race', 'sex',\n", + " 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country',\n", + " 'ID', 'scont_1', 'scont_2', 'scont_3', 'scont_4', 'scont_5', 'scont_6',\n", + " 'scont_7', 'scont_8', 'scont_9', 'scont_10', 'scat_1', 'scat_2',\n", + " 'scat_3', 'scat_4', 'scat_5', 'sflag_1', 'sflag_2', 'sflag_3',\n", + " 'sflag_4', 'sflag_5', 'TARGET', 'split'],\n", + " dtype='object')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "build.summary(df_transformed) #Dataframe with transformed dataset" + "basetable.columns" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Moreover, the class has few available attributes, which migth help you throughout your work." + "## Data preparation" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "train 29305\n", + "validation 9769\n", + "selection 9768\n", + "Name: split, dtype: int64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#Dictionary with variables used grouped by data type\n", - "#build._headers_dict\n", + "# Prepare data\n", + "path = \"../test_pipeline.json\"\n", + "preprocessor = PreProcessor.from_params(serialization_path=path)\n", "\n", - "#Size of partitionsd\n", - "#build._partitioning_settings" + "basetable = preprocessor.train_selection_validation_split(basetable, target_column_name=\"TARGET\",\n", + " train_prop=0.6, selection_prop=0.2,\n", + " validation_prop=0.2)\n", + "basetable.split.value_counts()" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 20, "metadata": {}, + "outputs": [], "source": [ - "## Univariate selection\n", - "The second step in the process is univariate selection." + "# create lists containing the continuous and discrete variables \n", + "continuous_vars = [\"age\", \"capital-gain\", \"capital-loss\"] + [f\"scont_{i}\" for i in range(1, 11)]\n", + "discrete_vars = ([\"workclass\", \"fnlwgt\", \"education\", \"marital-status\",\n", + " \"occupation\", \"relationship\", \"race\", \"sex\",\n", + " \"hours-per-week\", \"native-country\"] \n", + " + [f\"scat_{i}\" for i in range(1, 6)] \n", + " + [f\"sflag_{i}\" for i in range(1, 6)])" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Only 1 bin was found for predictor 'capital-gain' so it will be ignored in computation\n", + "Only 1 bin was found for predictor 'capital-loss' so it will be ignored in computation\n", + "The number of actual bins for predictor 'scont_1' is 4 which is smaller than the requested number of bins 10\n", + "The number of actual bins for predictor 'scont_2' is 9 which is smaller than the requested number of bins 10\n", + "/home/matthias/.local/lib/python3.8/site-packages/pandas/core/indexing.py:845: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " self.obj[key] = _infer_fill_value(value)\n", + "/home/matthias/.local/lib/python3.8/site-packages/pandas/core/indexing.py:966: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " self.obj[item] = s\n", + "DataFrame has no column 'capital-gain_bin', so it will be skipped in fitting\n", + "DataFrame has no column 'capital-loss_bin', so it will be skipped in fitting\n" + ] + } + ], "source": [ - "df_unisel, df_corr = build.fit_univariate(df_transformed, #Dataframe with transformed data\n", - " preselect_auc=0.53, #Minimal threshol for AUC selection\n", - " preselect_overtrain=5) #Threshold for difference between train and test performance (prevent overfitting)" + "preprocessor.fit(basetable[basetable[\"split\"]==\"train\"],\n", + " continuous_vars=continuous_vars, discrete_vars=discrete_vars,\n", + " target_column_name=\"TARGET\")" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 23, "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Unknown column 'capital-gain_bin' will be skipped\n", + "Unknown column 'capital-loss_bin' will be skipped\n" + ] + } + ], "source": [ - "Below is first 10 rows of the output of univariate selection. You can see AUC on train and test and column preselection, which shows which variables met the first selection criteria (AUC >= 0.53 and not overtrained)." + "basetable = (preprocessor\n", + " .transform(basetable,\n", + " continuous_vars=continuous_vars, \n", + " discrete_vars=discrete_vars))" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -933,120 +447,244 @@ " \n", " \n", " \n", - " variable\n", - " AUC train\n", - " AUC selection\n", - " preselection\n", + " age\n", + " workclass\n", + " fnlwgt\n", + " education\n", + " education-num\n", + " marital-status\n", + " occupation\n", + " relationship\n", + " race\n", + " sex\n", + " ...\n", + " scont_1_enc\n", + " scont_2_enc\n", + " scont_3_enc\n", + " scont_4_enc\n", + " scont_5_enc\n", + " scont_6_enc\n", + " scont_7_enc\n", + " scont_8_enc\n", + " scont_9_enc\n", + " scont_10_enc\n", " \n", " \n", " \n", " \n", " 0\n", - " scont_6\n", - " 0.512\n", - " 0.505\n", - " False\n", + " 56\n", + " Self-emp-not-inc\n", + " 206149\n", + " 7th-8th\n", + " 4\n", + " Never-married\n", + " Other-service\n", + " Unmarried\n", + " Black\n", + " Female\n", + " ...\n", + " 0.240395\n", + " 0.248292\n", + " 0.234307\n", + " 0.240191\n", + " 0.248383\n", + " 0.236050\n", + " 0.245621\n", + " 0.228513\n", + " 0.230132\n", + " 0.229873\n", " \n", " \n", " 1\n", - " scont_3\n", - " 0.508\n", - " 0.507\n", - " False\n", + " 38\n", + " Self-emp-not-inc\n", + " 342635\n", + " Bachelors\n", + " 13\n", + " Married-civ-spouse\n", + " Prof-specialty\n", + " Husband\n", + " White\n", + " Male\n", + " ...\n", + " 0.230626\n", + " 0.223856\n", + " 0.235635\n", + " 0.233058\n", + " 0.237876\n", + " 0.247832\n", + " 0.242909\n", + " 0.237694\n", + " 0.244211\n", + " 0.227472\n", " \n", " \n", " 2\n", - " marital-status\n", - " 0.769\n", - " 0.773\n", - " True\n", + " 60\n", + " Self-emp-not-inc\n", + " 197060\n", + " HS-grad\n", + " 9\n", + " Married-civ-spouse\n", + " Craft-repair\n", + " Husband\n", + " White\n", + " Male\n", + " ...\n", + " 0.243971\n", + " 0.267262\n", + " 0.234307\n", + " 0.246637\n", + " 0.248383\n", + " 0.225694\n", + " 0.233650\n", + " 0.232350\n", + " 0.244211\n", + " 0.229873\n", " \n", " \n", " 3\n", - " occupation\n", - " 0.729\n", - " 0.722\n", - " True\n", + " 21\n", + " Private\n", + " 113106\n", + " HS-grad\n", + " 9\n", + " Never-married\n", + " Sales\n", + " Other-relative\n", + " White\n", + " Female\n", + " ...\n", + " 0.240395\n", + " 0.244848\n", + " 0.234307\n", + " 0.233058\n", + " 0.237245\n", + " 0.244987\n", + " 0.245621\n", + " 0.245230\n", + " 0.244211\n", + " 0.229873\n", " \n", " \n", " 4\n", - " sflag_5\n", - " 0.506\n", - " 0.514\n", - " False\n", - " \n", - " \n", - " 5\n", - " scont_8\n", - " 0.507\n", - " 0.495\n", - " False\n", - " \n", - " \n", - " 6\n", - " scat_5\n", - " 0.500\n", - " 0.500\n", - " False\n", - " \n", - " \n", - " 7\n", - " education-num\n", - " 0.698\n", - " 0.695\n", - " True\n", - " \n", - " \n", - " 8\n", - " scat_3\n", - " 0.500\n", - " 0.500\n", - " False\n", - " \n", - " \n", - " 9\n", - " sex\n", - " 0.615\n", - " 0.621\n", - " True\n", + " 27\n", + " Private\n", + " 169117\n", + " HS-grad\n", + " 9\n", + " Married-civ-spouse\n", + " Adm-clerical\n", + " Wife\n", + " Black\n", + " Female\n", + " ...\n", + " 0.230626\n", + " 0.244848\n", + " 0.229594\n", + " 0.246637\n", + " 0.232236\n", + " 0.233454\n", + " 0.242909\n", + " 0.241368\n", + " 0.240012\n", + " 0.227472\n", " \n", " \n", "\n", + "

    5 rows × 99 columns

    \n", "" ], "text/plain": [ - " variable AUC train AUC selection preselection\n", - "0 scont_6 0.512 0.505 False\n", - "1 scont_3 0.508 0.507 False\n", - "2 marital-status 0.769 0.773 True\n", - "3 occupation 0.729 0.722 True\n", - "4 sflag_5 0.506 0.514 False\n", - "5 scont_8 0.507 0.495 False\n", - "6 scat_5 0.500 0.500 False\n", - "7 education-num 0.698 0.695 True\n", - "8 scat_3 0.500 0.500 False\n", - "9 sex 0.615 0.621 True" + " age workclass fnlwgt education education-num \\\n", + "0 56 Self-emp-not-inc 206149 7th-8th 4 \n", + "1 38 Self-emp-not-inc 342635 Bachelors 13 \n", + "2 60 Self-emp-not-inc 197060 HS-grad 9 \n", + "3 21 Private 113106 HS-grad 9 \n", + "4 27 Private 169117 HS-grad 9 \n", + "\n", + " marital-status occupation relationship race sex \\\n", + "0 Never-married Other-service Unmarried Black Female \n", + "1 Married-civ-spouse Prof-specialty Husband White Male \n", + "2 Married-civ-spouse Craft-repair Husband White Male \n", + "3 Never-married Sales Other-relative White Female \n", + "4 Married-civ-spouse Adm-clerical Wife Black Female \n", + "\n", + " ... scont_1_enc scont_2_enc scont_3_enc scont_4_enc scont_5_enc \\\n", + "0 ... 0.240395 0.248292 0.234307 0.240191 0.248383 \n", + "1 ... 0.230626 0.223856 0.235635 0.233058 0.237876 \n", + "2 ... 0.243971 0.267262 0.234307 0.246637 0.248383 \n", + "3 ... 0.240395 0.244848 0.234307 0.233058 0.237245 \n", + "4 ... 0.230626 0.244848 0.229594 0.246637 0.232236 \n", + "\n", + " scont_6_enc scont_7_enc scont_8_enc scont_9_enc scont_10_enc \n", + "0 0.236050 0.245621 0.228513 0.230132 0.229873 \n", + "1 0.247832 0.242909 0.237694 0.244211 0.227472 \n", + "2 0.225694 0.233650 0.232350 0.244211 0.229873 \n", + "3 0.244987 0.245621 0.245230 0.244211 0.229873 \n", + "4 0.233454 0.242909 0.241368 0.240012 0.227472 \n", + "\n", + "[5 rows x 99 columns]" ] }, - "execution_count": 7, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df_unisel.head(n=10)" + "basetable.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Second output is a correlation matrix." + "## Univariate selection\n" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['workclass_enc', 'fnlwgt_enc', 'education_enc', 'marital-status_enc', 'occupation_enc', 'relationship_enc', 'race_enc', 'sex_enc', 'hours-per-week_enc', 'native-country_enc', 'scat_1_enc', 'scat_2_enc', 'scat_3_enc', 'scat_4_enc', 'scat_5_enc', 'sflag_1_enc', 'sflag_2_enc', 'sflag_3_enc', 'sflag_4_enc', 'sflag_5_enc', 'age_enc', 'scont_1_enc', 'scont_2_enc', 'scont_3_enc', 'scont_4_enc', 'scont_5_enc', 'scont_6_enc', 'scont_7_enc', 'scont_8_enc', 'scont_9_enc', 'scont_10_enc']\n" + ] + } + ], + "source": [ + "preprocessed_predictors = [col for col in basetable.columns if col.endswith(\"_enc\")]\n", + "print(preprocessed_predictors)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "df_auc = univariate_selection.compute_univariate_preselection(\n", + " target_enc_train_data=basetable[basetable[\"split\"] == \"train\"],\n", + " target_enc_selection_data=basetable[basetable[\"split\"] == \"selection\"],\n", + " predictors=preprocessed_predictors,\n", + " target_column=\"TARGET\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below is first 10 rows of the output of univariate selection. You can see AUC on train and test and column preselection, which shows which variables met the first selection criteria (AUC >= 0.53 and not overtrained)." + ] + }, + { + "cell_type": "code", + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -1070,638 +708,165 @@ " \n", " \n", " \n", - " D_scont_6\n", - " D_scont_3\n", - " D_marital-status\n", - " D_occupation\n", - " D_sflag_5\n", - " D_scont_8\n", - " D_scat_5\n", - " D_education-num\n", - " D_scat_3\n", - " D_sex\n", - " D_native-country\n", - " D_hours-per-week\n", - " D_scont_2\n", - " D_sflag_2\n", - " D_scat_4\n", - " D_scont_4\n", - " D_sflag_1\n", - " D_scont_1\n", - " D_workclass\n", - " D_scont_9\n", - " D_scat_1\n", - " D_capital-loss\n", - " D_capital-gain\n", - " D_sflag_3\n", - " D_scat_2\n", - " D_fnlwgt\n", - " D_scont_7\n", - " D_sflag_4\n", - " D_relationship\n", - " D_education\n", - " D_scont_5\n", - " D_age\n", - " D_scont_10\n", - " D_race\n", + " predictor\n", + " AUC train\n", + " AUC selection\n", + " preselection\n", " \n", " \n", " \n", " \n", - " D_scont_6\n", - " 1.000000\n", - " 0.007128\n", - " 0.009146\n", - " 0.013050\n", - " -0.001755\n", - " -0.001888\n", - " 0.0\n", - " 0.016360\n", - " 0.0\n", - " -0.004987\n", - " 0.005869\n", - " 0.006357\n", - " 0.000534\n", - " -0.000288\n", - " 0.0\n", - " -0.010261\n", - " 0.006295\n", - " -0.002706\n", - " 0.008775\n", - " -0.002586\n", - " 0.0\n", - " -0.005740\n", - " 0.009891\n", - " -0.003451\n", - " 0.0\n", - " 0.011761\n", - " 0.006252\n", - " 0.006015\n", - " 0.011639\n", - " 0.016165\n", - " -0.005525\n", - " 0.013099\n", - " 0.000822\n", - " 0.010272\n", + " 0\n", + " relationship\n", + " 0.777379\n", + " 0.779450\n", + " True\n", " \n", " \n", - " D_scont_3\n", - " 0.007128\n", - " 1.000000\n", - " 0.008258\n", - " 0.008033\n", - " 0.008862\n", - " 0.004288\n", - " 0.0\n", - " 0.007674\n", - " 0.0\n", - " 0.007207\n", - " 0.010168\n", - " 0.005486\n", - " 0.003948\n", - " -0.000631\n", - " 0.0\n", - " 0.008515\n", - " 0.009562\n", - " 0.001590\n", - " 0.015998\n", - " -0.005320\n", - " 0.0\n", - " 0.002691\n", - " -0.003005\n", - " 0.005161\n", - " 0.0\n", - " -0.005306\n", - " -0.018140\n", - " -0.005946\n", - " 0.008531\n", - " 0.005123\n", - " 0.004172\n", - " 0.011820\n", - " -0.008748\n", - " 0.004958\n", + " 1\n", + " marital-status\n", + " 0.767728\n", + " 0.771644\n", + " True\n", " \n", " \n", - " D_marital-status\n", - " 0.009146\n", - " 0.008258\n", - " 1.000000\n", - " 0.194836\n", - " 0.003072\n", - " 0.002741\n", - " 0.0\n", - " 0.110648\n", - " 0.0\n", - " 0.417974\n", - " 0.007336\n", - " 0.241776\n", - " 0.009113\n", - " 0.012480\n", - " 0.0\n", - " 0.005861\n", - " 0.014518\n", - " 0.004128\n", - " 0.153130\n", - " 0.008042\n", - " 0.0\n", - " 0.080024\n", - " 0.136724\n", - " 0.007978\n", - " 0.0\n", - " 0.023283\n", - " -0.007105\n", - " 0.003858\n", - " 0.968994\n", - " 0.124311\n", - " -0.005558\n", - " 0.406239\n", - " 0.005080\n", - " 0.127041\n", + " 2\n", + " occupation\n", + " 0.727460\n", + " 0.734469\n", + " True\n", " \n", " \n", - " D_occupation\n", - " 0.013050\n", - " 0.008033\n", - " 0.194836\n", - " 1.000000\n", - " 0.002311\n", - " 0.001243\n", - " 0.0\n", - " 0.498731\n", - " 0.0\n", - " 0.101265\n", - " 0.089346\n", - " 0.260357\n", - " 0.008588\n", - " 0.004220\n", - " 0.0\n", - " 0.008526\n", - " 0.006996\n", - " 0.008079\n", - " 0.265053\n", - " 0.007554\n", - " 0.0\n", - " 0.077405\n", - " 0.121000\n", - " -0.003457\n", - " 0.0\n", - " 0.018786\n", - " 0.005152\n", - " 0.009259\n", - " 0.213882\n", - " 0.514657\n", - " 0.000169\n", - " 0.207039\n", - " -0.000281\n", - " 0.102792\n", + " 3\n", + " age\n", + " 0.707488\n", + " 0.716277\n", + " True\n", " \n", " \n", - " D_sflag_5\n", - " -0.001755\n", - " 0.008862\n", - " 0.003072\n", - " 0.002311\n", - " 1.000000\n", - " -0.000989\n", - " 0.0\n", - " 0.000375\n", - " 0.0\n", - " -0.005644\n", - " 0.007657\n", - " 0.001318\n", - " -0.004619\n", - " -0.008614\n", - " 0.0\n", - " -0.003978\n", - " 0.009308\n", - " 0.000902\n", - " 0.002748\n", - " 0.003078\n", - " 0.0\n", - " 0.026558\n", - " -0.003853\n", - " -0.016438\n", - " 0.0\n", - " -0.004404\n", - " -0.000654\n", - " 0.003012\n", - " 0.003403\n", - " -0.000387\n", - " -0.002178\n", - " -0.002454\n", - " -0.000998\n", - " 0.013506\n", + " 4\n", + " education\n", + " 0.717664\n", + " 0.714776\n", + " True\n", " \n", " \n", - " D_scont_8\n", - " -0.001888\n", - " 0.004288\n", - " 0.002741\n", - " 0.001243\n", - " -0.000989\n", - " 1.000000\n", - " 0.0\n", - " -0.000552\n", - " 0.0\n", - " 0.005559\n", - " 0.005384\n", - " 0.003167\n", - " -0.005675\n", - " 0.003946\n", - " 0.0\n", - " 0.003569\n", - " -0.003863\n", - " -0.014552\n", - " 0.007620\n", - " 0.010211\n", - " 0.0\n", - " -0.000134\n", - " 0.008799\n", - " 0.008798\n", - " 0.0\n", - " 0.005380\n", - " 0.008139\n", - " -0.001740\n", - " 0.002170\n", - " -0.000085\n", - " 0.005394\n", - " 0.002401\n", - " -0.002377\n", - " 0.007083\n", + " 5\n", + " hours-per-week\n", + " 0.667920\n", + " 0.664445\n", + " True\n", " \n", " \n", - " D_scat_5\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", + " 6\n", + " sex\n", + " 0.617710\n", + " 0.622441\n", + " True\n", " \n", " \n", - " D_education-num\n", - " 0.016360\n", - " 0.007674\n", - " 0.110648\n", - " 0.498731\n", - " 0.000375\n", - " -0.000552\n", - " 0.0\n", - " 1.000000\n", - " 0.0\n", - " 0.028074\n", - " 0.099447\n", - " 0.170187\n", - " -0.000245\n", - " 0.006791\n", - " 0.0\n", - " -0.003311\n", - " 0.005988\n", - " 0.001628\n", - " 0.159168\n", - " 0.010261\n", - " 0.0\n", - " 0.081967\n", - " 0.136519\n", - " -0.005692\n", - " 0.0\n", - " 0.015937\n", - " 0.011899\n", - " 0.001293\n", - " 0.133911\n", - " 0.932743\n", - " -0.001994\n", - " 0.165189\n", - " 0.000595\n", - " 0.085323\n", + " 7\n", + " workclass\n", + " 0.584366\n", + " 0.584248\n", + " True\n", " \n", " \n", - " D_scat_3\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.0\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", - " 0.000000\n", + " 8\n", + " race\n", + " 0.533289\n", + " 0.535948\n", + " True\n", " \n", " \n", - " D_sex\n", - " -0.004987\n", - " 0.007207\n", - " 0.417974\n", - " 0.101265\n", - " -0.005644\n", - " 0.005559\n", - " 0.0\n", - " 0.028074\n", - " 0.0\n", - " 1.000000\n", - " -0.022878\n", - " 0.245770\n", - " 0.005180\n", - " 0.014224\n", - " 0.0\n", - " 0.000182\n", - " 0.003786\n", - " 0.005233\n", - " 0.102203\n", - " 0.000280\n", - " 0.0\n", - " 0.049508\n", - " 0.069717\n", - " -0.001209\n", - " 0.0\n", - " -0.013472\n", - " -0.007598\n", - " 0.003615\n", - " 0.410982\n", - " 0.041657\n", - " 0.003662\n", - " 0.108070\n", - " 0.003784\n", - " 0.116125\n", + " 9\n", + " native-country\n", + " 0.514099\n", + " 0.513408\n", + " True\n", " \n", " \n", "\n", "" ], "text/plain": [ - " D_scont_6 D_scont_3 D_marital-status D_occupation \\\n", - "D_scont_6 1.000000 0.007128 0.009146 0.013050 \n", - "D_scont_3 0.007128 1.000000 0.008258 0.008033 \n", - "D_marital-status 0.009146 0.008258 1.000000 0.194836 \n", - "D_occupation 0.013050 0.008033 0.194836 1.000000 \n", - "D_sflag_5 -0.001755 0.008862 0.003072 0.002311 \n", - "D_scont_8 -0.001888 0.004288 0.002741 0.001243 \n", - "D_scat_5 0.000000 0.000000 0.000000 0.000000 \n", - "D_education-num 0.016360 0.007674 0.110648 0.498731 \n", - "D_scat_3 0.000000 0.000000 0.000000 0.000000 \n", - "D_sex -0.004987 0.007207 0.417974 0.101265 \n", - "\n", - " D_sflag_5 D_scont_8 D_scat_5 D_education-num D_scat_3 \\\n", - "D_scont_6 -0.001755 -0.001888 0.0 0.016360 0.0 \n", - "D_scont_3 0.008862 0.004288 0.0 0.007674 0.0 \n", - "D_marital-status 0.003072 0.002741 0.0 0.110648 0.0 \n", - "D_occupation 0.002311 0.001243 0.0 0.498731 0.0 \n", - "D_sflag_5 1.000000 -0.000989 0.0 0.000375 0.0 \n", - "D_scont_8 -0.000989 1.000000 0.0 -0.000552 0.0 \n", - "D_scat_5 0.000000 0.000000 0.0 0.000000 0.0 \n", - "D_education-num 0.000375 -0.000552 0.0 1.000000 0.0 \n", - "D_scat_3 0.000000 0.000000 0.0 0.000000 0.0 \n", - "D_sex -0.005644 0.005559 0.0 0.028074 0.0 \n", - "\n", - " D_sex D_native-country D_hours-per-week D_scont_2 \\\n", - "D_scont_6 -0.004987 0.005869 0.006357 0.000534 \n", - "D_scont_3 0.007207 0.010168 0.005486 0.003948 \n", - "D_marital-status 0.417974 0.007336 0.241776 0.009113 \n", - "D_occupation 0.101265 0.089346 0.260357 0.008588 \n", - "D_sflag_5 -0.005644 0.007657 0.001318 -0.004619 \n", - "D_scont_8 0.005559 0.005384 0.003167 -0.005675 \n", - "D_scat_5 0.000000 0.000000 0.000000 0.000000 \n", - "D_education-num 0.028074 0.099447 0.170187 -0.000245 \n", - "D_scat_3 0.000000 0.000000 0.000000 0.000000 \n", - "D_sex 1.000000 -0.022878 0.245770 0.005180 \n", - "\n", - " D_sflag_2 D_scat_4 D_scont_4 D_sflag_1 D_scont_1 \\\n", - "D_scont_6 -0.000288 0.0 -0.010261 0.006295 -0.002706 \n", - "D_scont_3 -0.000631 0.0 0.008515 0.009562 0.001590 \n", - "D_marital-status 0.012480 0.0 0.005861 0.014518 0.004128 \n", - "D_occupation 0.004220 0.0 0.008526 0.006996 0.008079 \n", - "D_sflag_5 -0.008614 0.0 -0.003978 0.009308 0.000902 \n", - "D_scont_8 0.003946 0.0 0.003569 -0.003863 -0.014552 \n", - "D_scat_5 0.000000 0.0 0.000000 0.000000 0.000000 \n", - "D_education-num 0.006791 0.0 -0.003311 0.005988 0.001628 \n", - "D_scat_3 0.000000 0.0 0.000000 0.000000 0.000000 \n", - "D_sex 0.014224 0.0 0.000182 0.003786 0.005233 \n", - "\n", - " D_workclass D_scont_9 D_scat_1 D_capital-loss \\\n", - "D_scont_6 0.008775 -0.002586 0.0 -0.005740 \n", - "D_scont_3 0.015998 -0.005320 0.0 0.002691 \n", - "D_marital-status 0.153130 0.008042 0.0 0.080024 \n", - "D_occupation 0.265053 0.007554 0.0 0.077405 \n", - "D_sflag_5 0.002748 0.003078 0.0 0.026558 \n", - "D_scont_8 0.007620 0.010211 0.0 -0.000134 \n", - "D_scat_5 0.000000 0.000000 0.0 0.000000 \n", - "D_education-num 0.159168 0.010261 0.0 0.081967 \n", - "D_scat_3 0.000000 0.000000 0.0 0.000000 \n", - "D_sex 0.102203 0.000280 0.0 0.049508 \n", - "\n", - " D_capital-gain D_sflag_3 D_scat_2 D_fnlwgt D_scont_7 \\\n", - "D_scont_6 0.009891 -0.003451 0.0 0.011761 0.006252 \n", - "D_scont_3 -0.003005 0.005161 0.0 -0.005306 -0.018140 \n", - "D_marital-status 0.136724 0.007978 0.0 0.023283 -0.007105 \n", - "D_occupation 0.121000 -0.003457 0.0 0.018786 0.005152 \n", - "D_sflag_5 -0.003853 -0.016438 0.0 -0.004404 -0.000654 \n", - "D_scont_8 0.008799 0.008798 0.0 0.005380 0.008139 \n", - "D_scat_5 0.000000 0.000000 0.0 0.000000 0.000000 \n", - "D_education-num 0.136519 -0.005692 0.0 0.015937 0.011899 \n", - "D_scat_3 0.000000 0.000000 0.0 0.000000 0.000000 \n", - "D_sex 0.069717 -0.001209 0.0 -0.013472 -0.007598 \n", - "\n", - " D_sflag_4 D_relationship D_education D_scont_5 D_age \\\n", - "D_scont_6 0.006015 0.011639 0.016165 -0.005525 0.013099 \n", - "D_scont_3 -0.005946 0.008531 0.005123 0.004172 0.011820 \n", - "D_marital-status 0.003858 0.968994 0.124311 -0.005558 0.406239 \n", - "D_occupation 0.009259 0.213882 0.514657 0.000169 0.207039 \n", - "D_sflag_5 0.003012 0.003403 -0.000387 -0.002178 -0.002454 \n", - "D_scont_8 -0.001740 0.002170 -0.000085 0.005394 0.002401 \n", - "D_scat_5 0.000000 0.000000 0.000000 0.000000 0.000000 \n", - "D_education-num 0.001293 0.133911 0.932743 -0.001994 0.165189 \n", - "D_scat_3 0.000000 0.000000 0.000000 0.000000 0.000000 \n", - "D_sex 0.003615 0.410982 0.041657 0.003662 0.108070 \n", - "\n", - " D_scont_10 D_race \n", - "D_scont_6 0.000822 0.010272 \n", - "D_scont_3 -0.008748 0.004958 \n", - "D_marital-status 0.005080 0.127041 \n", - "D_occupation -0.000281 0.102792 \n", - "D_sflag_5 -0.000998 0.013506 \n", - "D_scont_8 -0.002377 0.007083 \n", - "D_scat_5 0.000000 0.000000 \n", - "D_education-num 0.000595 0.085323 \n", - "D_scat_3 0.000000 0.000000 \n", - "D_sex 0.003784 0.116125 " + " predictor AUC train AUC selection preselection\n", + "0 relationship 0.777379 0.779450 True\n", + "1 marital-status 0.767728 0.771644 True\n", + "2 occupation 0.727460 0.734469 True\n", + "3 age 0.707488 0.716277 True\n", + "4 education 0.717664 0.714776 True\n", + "5 hours-per-week 0.667920 0.664445 True\n", + "6 sex 0.617710 0.622441 True\n", + "7 workclass 0.584366 0.584248 True\n", + "8 race 0.533289 0.535948 True\n", + "9 native-country 0.514099 0.513408 True" ] }, - "execution_count": 8, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df_corr.head(n=10)" + "df_auc.head(n=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Clearly, for meaningful inspection, we need to visualize the data. Therefore, below are plots for **Predictor quality**, **Correlation Matrix** and **Incidence**." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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mjVravv27UXq2blzp8aPHy8XFxdJUlBQkF555ZUi9VSoUEE9e/bUwIEDFRAQoEceeUSPPvron54rAGMhCABAGatSpYrS0tJUtWpV27LLly8Xusj9/e0oJpNJVqu10Bgmk0n9+/fXxx9/LBcXF/Xv318mk0n//ve/NWvWLA0fPlzdu3dX/fr1bRetkuTh4VGkngsXLui5557TgAED1LZtW/Xs2VNffPFFke3y8/NlNpsLfVJ+6dIlmc3mQttVrlxZfn5++u677/TXv/5VknTx4kX5+vrq22+/VZ06dVSlSpUi55Wbm2v7eejQoWrcuLG6du2qXr166YcffijSg+J4eXmpZ8+e2rhxozZt2qQ333yzyDYFBQVFLvitVqvy8vJsF9TF+eMzAn/0W48LCgpktVqVkJAgd3d3SVJKSopcXV21Zs2aQudToUKFIuM4O1//L/n3dZ44cUL333//n55LQUFBoV7+/m8+f/58HT16VF9//bViYmL08ccf20IKAPCtQQBQxvz9/RUfH2/7VPzKlStav379LX8627dvX+3YsUOfffaZ+vXrJ0natWuXHnvsMQ0ePFjNmzfX9u3blZ+f/6fjHDx4UFWrVtXo0aP1yCOP2EJAfn6+nJ2dlZ+fL6vVqnr16snNzc0WBM6fP68nn3xSBw8eLDJmSEiI5s6dq3379km6/szAkCFDNGvWLE2cOFHS9UD0448/ymq1ymKx2I6bnp6uAwcO6PXXX9cTTzyhCxcu6PTp0386i1ChQgXl5eXZfh8yZIji4uJktVrVsmXLItt37txZX331lX799VdJ0jfffKPz58/roYce+tNe3QpPT0+1atVKH3zwge28Bg0apM8//1xdunTRrl27dOHCBUnXb+260f7NmjWzfbPT+fPnNWjQIF29erXQ+Xbt2lWrV69Wbm6uCgoKtGrVKtvzEb+XkpKiRx99VN7e3ho2bJjGjRt3Ww+CA7h3MSMAAGVsypQpioyM1JNPPmn7JPiZZ55R3759b2kcHx8fPfjgg8rLy5Ovr68kaeDAgfr73/+up556Snl5eerSpYu2bt36pxfRXbp00T//+U/17NlTJpNJHTp0UNWqVfXLL7+oTp06atmypfr06aNVq1Zp6dKlmjVrlt577z3l5eXptddeu+HtRo8++qgiIyP19ttv6/z585KkatWq6f7779euXbvUrl07Pf300/rPf/6jJ554Qr6+vurQoYOsVqu8vLw0cuRI9e3bVx4eHvL19VWbNm30yy+/2G7l+SN/f39FRkZKkl5++WU1adJElStX1sCBA2+4fYMGDfTmm2/q1VdfVX5+vtzc3LRs2bIisxulNX/+fM2cOVNPPfWUcnJy9OSTT+rpp5+WJL3xxht64YUXVKlSpRuGFUlasGCBpk+frvj4eJlMJs2aNUs+Pj6Fzjc4OFhz587Vs88+q7y8PLVs2VLTpk0rMlbVqlUVHBysYcOGyc3NTRUqVFBERIRdzxfA3c1kvdm5VwAAbpHVatXOnTvVoUMH2+0yZeH06dMKCgrSli1byvQ4AHAvIQgAAO5qb7/9ttauXavp06fr8ccfd3Q5AHDXIAgAAAAABsTDwgAAAIABEQQAAAAAA+Jbgxzg2rUcWSzZji7jrufp6Uof7YA+lh49tA/6aB/0sfTooX3QR/sobR99fIr/djRmBBzA2bnoi2Rw6+ijfdDH0qOH9kEf7YM+lh49tA/6aB9l2UeCAAAAAGBABAEAAADAgHhGwAE6ha5ydAkAAAAoB1sm3tpb5MsTMwIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADIggAAAAABgQQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAMiCAAAAAAGdFcHgSVLlmj16tXFrj937px27NghSZo1a5bOnTtX6mOeOXNGAwYMKLI8JiZG+/fvL/X4AAAAQHlwdnQBZenbb7/ViRMn1K1bN02ZMqVMjzVy5MgyHR8AAACwpzs6CCQmJupf//qXCgoKFBQUpJUrV8rJyUlt27bV66+/btsuPz9fYWFhunDhglJTU+Xv768xY8YoJiZGWVlZat26tWJjYxUeHi4fHx+98cYbslgsys/P12uvvabOnTvrqaeeUocOHXTkyBGZTCYtXbpUubm5GjdunKxWq3JzczV9+nRVqlRJKSkpGj16tJKTk9W4cWNFRERo0qRJ6t27ty5duqTPP/9cFotFqampeuWVV/TXv/7VgV0EAAAAirqjg4AkeXl5ac6cORo8eLD+9a9/yd3dXW+88YZ27dpl2+b8+fNq1aqVAgMDlZ2dLX9/f40bN04jR47UiRMn1L17d8XGxkqSoqOj9fDDD+uFF17QxYsXNWjQIG3fvl0ZGRnq06ePpk2bpr///e/auXOnKlWqJLPZrAULFujYsWOyWCyqVKmSLBaL5syZI7PZrB49eujy5cuFas7MzNQHH3yglJQUBQYGqnv37nJ2/v9WrzdHlUvvAAAA4Gh9HV1Ase74IFCvXj2dPn1aKSkptttvMjIy9Ouvv9q28fb21oEDB/Ttt9/K09NTOTk5xY53/PhxPfXUU5IkX19feXp6KiUlRZL04IMPSpJq1qyp7Oxs9erVS6dOndLo0aPl7Oys4OBgSVLt2rVVuXJlSVK1atV07dq1Qsdo3769nJycdN9998nLy0spKSmqXr26nToCAAAAlN4d/7Cwk5OTatWqpZo1a2rFihWKj4/X0KFD9dBDD9m2SUxMtH1y/+KLLyorK0tWq1VOTk4qKCgoNJ6fn5/27NkjSbp48aLS09Pl7e0tSTKZTIW23b17t6pXr64VK1YoODhYCxcuvOF2f/Tjjz9Kki5duiSLxaJq1aqVrgkAAACAnd3xMwKSVLVqVQ0bNkxBQUHKz8/XAw88oF69etnWd+7cWRMmTNDevXvl7u6uOnXqKCkpSY0aNVJ0dLSaNWtm2/bll19WaGioPvvsM2VlZWnGjBmFbtv5vSZNmmj8+PG2ZxNeeeWVm6r30qVLeuGFF3T16lW9+eabqlChQukaAAAAANiZyWq1Wh1dxL0kMTFRJ06cKPQw8x+dntGiHCsCAACAo7gHf12q/b29PZSWlnnb+/v4mItdd8ffGgQAAADA/u6KW4PuJv369XN0CQAAAECJmBEAAAAADIggAAAAABgQQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAPizcIOkJubX6o3xOG60r5pD9fRx9Kjh/ZBH+2DPpYePbQP+mgfvFkYAAAAgF0RBAAAAAADIggAAAAABkQQAAAAAAyIIAAAAAAYEEEAAAAAMCBnRxdgRJ1CVzm6BAAAAJTClol9HV1CqTEjAAAAABgQQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADIggAAAAABgQQQAAAAAwIIIAAAAAYEB3bRB49dVXJUlHjhzR999/X+x2u3fv1vjx429qzLS0NG3atOlPt9m2bZsuXrx484UCAAAAd6C7Ngi88847kqStW7fq2LFjdhnzyJEj2rFjx59uExcXJ4vFYpfjAQAAAI7i7IiDJiYm6osvvlBWVpaSk5P1/PPP6/PPP9fPP/+siRMn6sKFC9q6davy8vJkNpu1ZMkSffLJJ/rXv/6lgoICjR07Vq+//roSExO1fv16ubi4qFmzZjp37pxWrVplO87bb79dbA1bt27V8uXL5ezsrAceeEDz5s3TsmXL9NNPP2nNmjVq3bq1IiMjVVBQoPT0dE2dOlXp6ek6fPiwQkJCFBUVpZCQEK1du1aSNGDAAC1cuFAXL17U3Llz5ezsLC8vL82fP1+enp5l3lMAAADgVjgkCEhSRkaGVqxYoU8//VSxsbFau3atdu/erdjYWDVv3lyxsbFycnLSiBEjdODAAUmSl5eXoqOjbWP4+vqqb9++uu+++9SyZUt9/fXXiomJkbu7u8LCwvTVV1/J19f3hsf/5JNPNGzYMPXp00cbNmyQxWLRqFGjlJCQoOeee06bN29WSEiIGjdurE2bNikxMVERERFq2rSpwsPD5eLicsNxt2/frh49emjEiBHasWOH0tPTiwSB9eYoO3URAAAAjnAtuvD1nHvw1w6q5PY5LAg0bdpUkmQ2m+Xn5yeTyaTKlSsrNzdXLi4umjBhgjw8PHThwgXl5eVJkurVq/enY1arVk0hISGqVKmSTpw4oVatWtnW7dmzxzZDMGLECE2ePFnvvvuuVq9erfr16+vxxx8vNFb16tW1dOlSubm5KSMjo8RP9a1WqyRp1KhRWrZsmV544QX5+vqqZcuWt9YYAAAAoBw47BkBk8l0w+W5ubnavn273nrrLU2bNk0FBQW2i2wnp6LlmkwmFRQU6OrVq1q8eLEWLVqkiIgIubq62vaTpHbt2ik+Pl7x8fEKCAjQmjVrNGbMGH344YeSrj8E7OTkpIKCAknSrFmzNHbsWM2dO1eNGjWyjWUymWS1WuXq6qrLly8rPz9f6enpOnPmjCRp06ZN6tu3r+Lj49WwYUPbrUMAAADAncRhMwLFcXZ2lru7u/r166eKFSvKx8dHSUlJxW7fvHlzzZs3T35+fmrTpo369u0rDw8PeXl5KSkpSbVq1brhfi1bttTw4cPl7e2tSpUqKSAgQDk5OTp69KhiY2P19NNPa/To0apWrZpq1Kih1NRUSVLr1q01ceJErVixQl26dFH//v31l7/8RXXq1JEktWjRQpMmTZKHh4dcXFw0Y8YM+zcJAAAAKCWT9fcfm6NcnJ7RwtElAAAAwI7K6hkBb28PpaVl3vb+Pj7mYtfdtV8fCgAAAOD2EQQAAAAAAyIIAAAAAAZEEAAAAAAMiCAAAAAAGBBBAAAAADAgggAAAABgQAQBAAAAwIAIAgAAAIAB8WZhB8jNzS/VG+JwXWnftIfr6GPp0UP7oI/2QR9Ljx7aB320D94sDAAAAMCuCAIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADIggAAAAABgQQQAAAAAwIGdHF2BEnUJXOboEAACAu9qWiX0dXcJdjxkBAAAAwIAIAgAAAIABEQQAAAAAAyIIAAAAAAZEEAAAAAAMiCAAAAAAGBBBAAAAADAgggAAAABgQAQBAAAAwIAIAgAAAIABEQRuwrZt23Tx4kUlJycrPDzc0eUAAAAApUYQuAlxcXGyWCzy8fEhCAAAAOCe4OzoAm5Gbm6uQkND9euvvyo/P1/Dhw/XAw88oFmzZslqtcrX11fz58/XkSNHiix76aWXFB4eLj8/P61evVqXLl1S37599dprr8nHx0cXL16Uv7+/xo8fr6NHjyoyMlIFBQVKT0/X1KlTlZ6ersOHDyskJERRUVEKCQnR2rVrtWvXLr311ltydXWVt7e3Zs+ercOHD2v58uVycXHRmTNn1Lt3bwUHBzu6fQAAAEARd0UQWLNmjapUqaKoqChZLBb169dPLi4uWrx4sfz8/LRq1SodP35c06ZN06JFiwotK87Zs2f1/vvvy2w2a/Dgwfrxxx/1yy+/KCQkRI0bN9amTZuUmJioiIgINW3aVOHh4XJxcZEkWa1WTZs2TatXr5avr69Wrlyp6OhoBQQE6Ny5c9q4caNycnLUtWvXGwaB9eaoMusVAADAvcQ9+GtHl3DPuiuCwPHjx/Xwww9Lkjw9PeXn56cdO3bIz89PkjRkyBBJ0uXLl4ss+z2r1Wr7uUmTJvL29pYktWzZUidPnlSNGjW0dOlSubm5KSMjQ56enjesJzU1VZ6envL19ZUktW/fXgsXLlRAQIAaNWokZ2dnOTs7y83NzU4dAAAAAOzrrnhGwM/PT3v27JEkWSwWHT16VLVq1dKpU6ckSTExMdq2bZuqV69eZFnFihWVnJwsSTp06JBtzOPHj+vatWvKz8/X/v371aBBA82aNUtjx47V3Llz1ahRI1twMJlMhUJElSpVZLFYlJSUJEn67rvvVLduXdu2AAAAwJ3urpgRGDBggKZNm6ZBgwYpOztbr776qvz8/BQaGionJyf5+Pho2LBh8vX1LbKsYsWKmjFjhmrWrKnq1avbxnRxcdFrr72mS5cuqWfPnmrSpImefvppjR49WtWqVVONGjWUmpoqSWrdurUmTpyomTNnSrp+sR8REaExY8bIZDKpcuXKmjNnjn7++WeH9AcAAAC4VSbr7z/qNogzZ85owoQJWrt2rUOOf3pGC4ccFwAA4G5j9GcEvL09lJaWedv7+/iYi113V9waBAAAAMC+DBkEatWq5bDZAAAAAOBOYMggAAAAABgdQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADMhktVqtji7CaHJz80v1qmhcV9pXbuM6+lh69NA+6KN90MfSo4f2QR/to7R99PExF7uOGQEAAADAgAgCAAAAgAERBAAAAAADIggAAAAABkQQAAAAAAyIIAAAAAAYkLOjCzCiTqGrHF0CAADAHWfLxL6OLsFQmBEAAAAADIggAAAAABgQQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADIggAAAAABgQQQAAAAAwoHs2CMyfP1+JiYl2GWvbtm26ePGikpOTFR4ebpcxAQAAAEe6Z4OAPcXFxcliscjHx4cgAAAAgHuCs6MLuF25ubl688039csvv6igoEDjxo1TWlqaoqOjVbVqVeXm5qp+/fravXu3EhIStGjRIklSly5dtGvXLp06dUpTp05Vbm6u3NzctGjRIl26dEmRkZEqKChQenq6pk6dqvT0dB0+fFghISGKiopSSEiI1q5dq127dumtt96Sq6urvL29NXv2bB0+fFjLly+Xi4uLzpw5o969eys4OLhI7evNUeXdLgAAgDuOe/DXji7B0O7aILBu3TpVqVJFs2fPVmpqqoYOHars7GytW7dO3t7eGjly5J/uP3fuXI0cOVL+/v7avHmzDh06pPT0dIWEhKhx48batGmTEhMTFRERoaZNmyo8PFwuLi6SJKvVqmnTpmn16tXy9fXVypUrFR0drYCAAJ07d04bN25UTk6OunbtesMgAAAAADjaXRsEjh49qr1792r//v2SpGvXrkmSqlSpIklq3br1DfezWq2SpJMnT9q26d27tyRpz549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glSlShWlp6fr/fffV4UKFSRJsbGxys/P14gRIxQWFqaKFStq6tSpWrp0qWrXrq1GjRopIiJCkmz1m83mGx7jNzt27NAHH3ygf/zjH/Ly8rqFPwsAAABQtooNAjVq1JAk1alTx3axfEsDOzvr/fff19mzZ/XSSy8pOztbq1evlq+vr1auXKno6GgFBAQUu3+nTp00bNgw/fvf/7ZdcB87dkwWi+WG27dp00YzZszQqlWr9O6776pr1646c+aMEhISlJ2drQEDBqhLly6SpKeeeko9evQotP8TTzyh0NBQjRgxQidPnlRWVpYk6auvvlJMTIxefPFFzZ49Ww0aNNC6dev03nvvqU2bNsUeY9u2bfr+++/17rvvysPDo9Cx1pujbrmfAAAAt+tadNFrD/fgrx1QCe4kxQaBrl27SpI2b96sFStW3PLADz74oEwmk3x8fHT+/Hn95S9/ka+vrySpffv2WrhwYZEgYLVabT/Xq1dPkuTv769Tp05p9OjRcnZ2VnBwsLZs2aJVq1ZJkkJCQiRdDw7S9UCwY8cO+fr66scff1RQUJAkKS8vT+fOnSs09qJFi/Tf//5X0vUZgaysLO3fv19+fn46d+6c9u/fL7PZLE9PTx0/flzTp0+XJOXm5qpevXo6evRoscf45ptvZLFY5Oxc4t1XAAAAQLkr8SrVbDbr888/V926deXkdP3Z4t8upP/M728nqlKliiwWi5KSklS9enV99913qlu3rlxdXW1vMD579qyuXLlSZP/du3erevXqWrFihf73v/9p4cKFio+PV8+ePQsd7+DBg6pRo4b++9//qkGDBqpfv746duyomTNnqqCgQEuXLlWtWrUKjT1+/PhCYzz66KOKiorSCy+8oHPnzikiIkKBgYG2c547d67uv/9+7d27V8nJyXJxcSn2GGFhYdq4caMWL16s119/vcR+AQAAAOWpxCCQkpKi2NhY2+8mk0lxcXG3dBCTyaSIiAiNGTNGJpNJlStX1pw5c+Tl5SWz2azAwED5+fnZLqJ/r0mTJho/frxWrlwpJycnvfLKKzc8xvr16xUbGyt3d3fNmzdP3t7e+u677zR48GBlZmbq8ccfl6en55/W+cQTT+idd95RdHS0kpKSFBkZqWXLlkmSwsPDFRISovz8fEnSrFmzVLdu3T89xiuvvKLAwEAFBASoXbt2t9QzAAAA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q6/k5eUls9msBQsW6NixY7JYLDp9+rQuXbqk2NhYXb58WadOnbph/evNUeXRJgAADMs9+GtHlwDc8e7KIPDEE09o2bJlqlmzpsaPH6/4+HhZrVb17t1be/fulYuLiySpXbt2+vnnnyVJ9erVs+1/5MgReXp6KjMzU5J08uRJtWzZUhUqVJC7u7stMPymWrVqCgkJUaVKlXTixAm1atVK/v7+OnXqlEaPHi1nZ2cFBwerYcOGGjJkiCZMmKC8vDwFBQWVY1cAAACAm3dXPiPQqFEjnTlzRvv379ejjz6qzMxMff7556pfv77279+vvLw8Wa1Wff/997YA4OT0/6farFkzxcTEaOPGjfrpp59Uv359HTp0SAUFBcrNzdXw4cOVk5MjSbp69aoWL16sRYsWKSIiQq6urrJardq9e7eqV6+uFStWKDg4WAsXLtSRI0eUkZGhmJgYRUZGaubMmQ7pDwAAAFCSu3JGQJLat2+vM2fOyMnJSe3bt9exY8fUuHFj9erVS4MGDVJBQYHatm2rxx9/XD/99FOR/d3c3BQeHq6QkBCtW7dOXbt2te03aNAgVaxYUZLk6empNm3aqG/fvvLw8JCXl5eSkpLUrVs3jR8/XitXrpSTk5NeeeUV1a1bV//4xz+0YcMGubi4aOzYseXdFgAAAOCmmKxWq9XRRRjN6RktHF0CAAD3tNI8I+Dt7aG0tEw7VmNM9NE+SttHHx9zsevuyluDAAAAAJQOQQAAAAAwIIIAAAAAYEAEAQAAAMCACAIAAACAAREEAAAAAAMiCAAAAAAGRBAAAAAADIggAAAAABiQs6MLMKKak/fxpj074I2F9kEfS48e2gd9tA/6COBmMSMAAAAAGBBBAAAAADAgggAAAABgQAQBAAAAwIAIAgAAAIABEQQAAAAAA+LrQx2gU+gqR5cAAP/X3r3HRVnm/x9/zcigjCKI4gEV8YRm5Gnb8ruth6JaExU1D+RiHsrTag/TTVOMNNcjeajFU5RJIWliaqJmqVva11LzgKJfytLUdj2QAirKaeD+/eGv2WVVsByZwXk//4G572uu+3N/mAE+c133fYk41JYJPZ0dgoj8ShoREBERERFxQyoERERERETckAoBERERERE3pEJARERERMQNqRAQEREREXFDKgRERERERNyQCgERERERETekQkBERERExA2pEBARERER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B/DQQ+3IysqkXbvrF1I3atSERo2acPbsmRvO5dSpH2nVqjUmkwkPDw9atmzFyZMnAGjatBkANWvWIjX10J0ljXJwsbCIiIiISEk++2wzXbuGs2DBIubPjyUu7j327t1DQEBdduz43N7u0KGDBAVdn/8fFtaNlSsTyMm5fn+jzMwMZs6cRm5ubrG+N2xYz0svTWLhwji+//47UlMPERgYxMiRL7BwYRzjx0fRqVOovb2Pjy81a9Zk9uz5LFwYx8CBQ2jb9kEaNGhIWtr/AfCvf/2TqVMnYzKZMYyiYsdr0KChfVqQzWYjJSWFevUCgetT3R3J5UcEbldCQoKzQxARERFxe7/c7tPX10pW1rUyOWZy8sdER/97lkalSpXo2PExcnNz8fKyMmhQf6xWKxaLxT6fPySkJd2792Ts2FF4eHiQl5fLiBGjaNKkabG+GzduwtChz+LrWw1/f39atAhh1KgxzJs3m/z8fPLychkz5iV7e7PZzJgxLzF+/BgMw8BqrUx09GuEhLRk1qxpjB49jMLCQsaM+SvVqlWjoMDG4sV/p2LFigA88kh7Dh7cz/DhgykoKCAsrMsdXwtwKybjP6+UlTJxetoDzg5BRETkjt2r93cvy39g72XKo2PcaR79/b1vuU9Tg0RERERE3JAKARERERERN6RCQERERETEDakQEBERERFxQyoERERERETckAoBERERERE3dM+sI1Ce1JmUottpOYBuS+YYyuOdUw4dQ3l0DOVRRG6XRgRERERERNyQCgERERERETeklYVFRERERNyQRgRERERERNyQCgERERERETekQkBERERExA2pEBARERERcUNaR+AuKioqYurUqXz33Xd4enoyffp0GjRoYN+/evVqVq1ahYeHByNHjuTRRx91YrSuqbQcAmRkZBAREUFycjIVK1Z0UqSurbQ8xsfHs2nTJgA6duzI6NGjnRWqSystj4mJiaxduxaTycSoUaP0nr6J23lPFxUVMWzYMEJDQ3nmmWecFKlrKy2P06dP58CBA1SuXBmAxYsX4+3t7axwXVZpedyxYweLFi0CoEWLFkyZMgWTyeSscF1SSTlMS0tj5syZ9rYpKSksWrSIDh06OCtcl1Xaa3HZsmVs2rQJk8nEiBEjeOKJJxxzYEPumk8//dR4+eWXDcMwjIMHDxojRoyw70tPTze6du1q5OXl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\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "build.plotPredictorQuality(df_unisel, #Dataframe with univariate selection\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" + "Second output is a correlation matrix." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 32, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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0Wg0DB48mOeee+6O9UlALIQQQgghysWDCoi/+eYbCgsLSUtLY+fOnUydOpXExEQA8vLySEpKIiMjg6tXr9KrV6/KHxBnZWURGRlJQEAAFosFo9HIgAEDCAkJscr+srOzcXd357HHHit1m169euHu7g5A/fr1mTJlilXaIoQQQgjxMPrll1/o0KEDAC1btuS3334rWle1alXq1avH1atXuXr16j0F6XYfEAMEBQUxa9YsAAoKCtDr9eh0OgIDA8t9X19++SUhISGlBsS3MtolJSWV+76FEEIIISqyB9VDbDAYihKhATg4OGA0GotySdStW5cePXpgMpl455137lpfpQiI/5urqyuhoaFs2LChxIA4IyODhQsX4ujoiI+PD9OnTyc3N5eoqCjy8/OxWCxMmzYNFxcXYmNjuX79OpcuXWLIkCF4e3vzww8/sHfvXgICAqhXr16x+vfv38/Vq1d58803MRqNjBgxgpYtWz6IQxdCCCGEeCi4ublRUFBQ9G+z2VwUDG/dupWzZ8/y7bffAhAeHk7r1q1p3rx5qfVVuoAYwNPTk71795a4bu3atQwcOJAePXqwatUqDAYDiYmJBAcH069fP3766Sd2796Nl5cXgwYN4qmnnmLHjh3MmTOHpUuX0qFDB0JCQkoMhgFcXFwIDw/n9ddf59ixY7z11lts2LDhjtnvhBBCCCEqhQc0mXLr1q3ZsmULISEh7Ny5k0aNGhWtq1GjBi4uLjg7O6PRaHB3dycvL++O9VXKKC0nJwdvb+8S10VHR7NgwQJSUlLw8/Oja9euHD16lNdeew2Adu3aAfD777+TmJjIihUr0Gg0GI33NnewTqejYcOGaDQadDodHh4enDt3jrp165bPwQkhhBBCVFAPasjEc889x7Zt2+jbty8Wi4XJkyezdOlSfH196dKlC//5z3/o06cPWq2W1q1b8/TTT9+xvkoXEBsMBtLT00lISChxfVpaGhEREXh6evLRRx+xadMm/P392bNnD4899hjZ2dl89913HD9+nNdff51OnTrx5Zdf8tVXXwE3X+g75TJZsWIFBw8eJDY2ljNnzmAwGKhVq5ZVjlUIIYQQ4mGk1WqLZSX29/cv+v9hw4bdU/bgWypFQJyZmYler0er1WIymYiIiMDPz6/EbZs3b86gQYPw8PDA1dWVzp0706lTJ2JiYli9ejUAkydPZteuXUyaNIkFCxZQt25dcnNzAWjRogXx8fHUr1//thN/y2uvvUZ0dDT9+vVDo9EwefJkGS4hhBBCiIfCg+ohLm+SutkGJHWzEEIIIcpbRUjd7P3WCqvUe3rha1ap9xbbnzkrKCwsJDw8vNhynU5XrHu9rGJjYzl8+HCx5QsXLsTFxeWOZU3msn8H0Sp+87pgKFQqP/374sd8X+VfLP+p8B6UBh0ilcqf+GF2mcsaTWrfW/Ou3lAqn39N7UtcQ69qZS6r+o1dta9CtbND4e2u3PYTF64qla9fs6pSeZVzd1nxmr1y3aRU3ucRtWM3K/Q1qXxGgPo1q/o5c/rStTKXbTug5OGO9+rkmtFK5Y+fv6JU3tez7Pc6gMtX1K77ihAQ22sPse3PnBU4OztbfR7g2NhYq9YvhBBCCCEejEoZEAshhBBCiAdPeoiFEEIIIcTDzT7jYbS2boAQQgghhBC2dF8BcVZWFu3atUOv1xMWFkbfvn1Zt26dUgP69OnDyZMnleq4JTk5GbiZsi8tLa1c6hRCCCGEEPdGo9FY5c/a7nvIRFBQELNmzQKgoKAAvV6PTqcjMND2swckJiYSFhZGx44dbd0UIYQQQghhJ5TGELu6uhIaGsqGDRtKDIhPnTrF2LFjuX79OlWqVGHChAnUrVuXWbNm8cMPP+Dt7V2U8GLOnDl4eXnRr18/Dh8+TGxsLElJSWzZsoW5c+cC0KRJE8aPH09GRgbLli0r2k9CQgJpaWlcvnyZ2NhYmjdvzpEjRxg5ciRLlizh66+/xtHRkTZt2jBq1CjmzJnDyZMnuXDhAjk5OURHR9OhQ4fb2l7aNsHBwaxfv54qVaoQHx+Pn58fPj4+fPLJJzg5OXH69Gn69u1LZmYm+/fvZ8CAAfTv31/lNAshhBBC2IWH9qE6T09P9u7dW+K6adOmodfr6dSpEz/99BPx8fG88847ZGdns2LFCq5cucLzzz9fat1Go5EJEyaQnp6Op6cnc+fO5fTp0xw7doxPPvmEqlWr8tFHH/Hjjz/y7rvvkpycTGxsLCtXrgTgwIEDrF+/ntTUVBwdHYmIiGDLli3AzanZFi1axLZt21iyZEmxgPhet7nl9OnTrFq1ir179zJ8+HA2bdrEmTNnGDp0qATEQgghhHgoPLQBcU5ODt7e3iWuO3jwIAsWLGDRokVYLBacnJw4dOgQzZo1Q6vV4ubmRqNGjUqtOzc3l+rVq+Pp6QnA0KFDgZtB+OjRo3F1deXIkSO0bNmyxPJHjhyhRYsWODk5AdCmTRt+//13gKIebW9vbwoLCzl+/DgffvghAC+99FKJ2/zVfyf5e/TRR3FycsLd3R1fX1+cnZ2pUaMG169fL/X4hBBCCCGE7SkFxAaDgfT0dBISSs4s4+fnx5tvvknr1q05fPgw2dnZ6HQ6Pv/8c8xmM9euXePQoUMAVKlShXPnzgEU9Th7enqSl5fHpUuX8PDwYOLEiTz//PN8/PHHfPfddwAMGjSoKDD9axZqPz8/li5ditFoxMHBgezsbHr16sX+/fuLfYNp2LDhbck85syZU+K3HGdnZ86ePUv9+vXZv38//v7+gP1+IxJCCCGEKDd2Gg7dd0CcmZmJXq9Hq9ViMpmIiIjAz8+vxG1Hjx5NbGws169f59q1a4wZM4bAwEC6devGa6+9Ru3atYt6f7t3705kZCTZ2dk0a9YMAK1Wy7hx43jnnXfQarU0adKEtm3b0rp1a3r37k21atWoXr06Z8+eBcDf35+RI0fSvn17ABo3bkz37t3p168fZrOZJ554gq5du7J///4ynSyAf/zjH7z99tv4+PhQvXr1MtcjhBBCCCEqBo3lr92qwurO5JU9V7lqjvkLhuJDP+7H9O8Pq5V/0fazkZRVgw6RSuVP/DC7zGWNJrW3ad7Vsl9zAPnXjErlG3pVK3NZ1RuUameF6o8/ZoUDUG37iQtXlcrXr1lVqbzKubuseM1euW5SKu/ziNqxmxU+Wk0qFw3q16zq58zpS9fKXLbtgJJ/cb5XJ9eMVip//PwVpfK+nmW/1wFcvqJ23fvXVrtuy4NvxGqr1PvHnJesUu8tygFxYWEh4eHhxZbrdDri4uJUqq60zhvUggshhBBCiL/ycrN9AuKGw9ZYpd7jH/e0Sr23KJ85Z2fn28beCiGEEEIIYU9s/1VCCCGEEEJUCvY6ycB9pW4WQgghhBCisrH7HuKsrCwiIyMJCAjAYrFgNBoZMGAAISEhVtlfdnY27u7uPPbYYyWuN5lMfPjhhxw9ehQHBwemTJmCr6+vVdoihBBCCFGRSA+xDQUFBZGUlERycjKLFy9m0aJF7Nu3zyr7+vLLL4umeSvJrUx4qampDBs2jClTplilHUIIIYQQonzYfQ/xX7m6uhIaGsqGDRuKMs39t4yMDBYuXIijoyM+Pj5Mnz6d3NxcoqKiyM/Px2KxMG3aNFxcXIrmUL506RJDhgzB29ubH374gb179xIQEEC9evWK1d+1a1c6d+4M3Mzi5+XlZe1DFkIIIYSoGOyzg7jyBcRwM8PdrWx3f7V27VoGDhxIjx49WLVqFQaDgcTERIKDg+nXrx8//fQTu3fvxsvLi0GDBvHUU0+xY8cO5syZw9KlS+nQoQMhISElBsO3ODo6Mnr0aDZt2sTHH39srcMUQgghhKhQZMhEBZKTk4O3t3eJ66Kjo8nOziYsLIwdO3ag1Wo5evQorVq1AqBdu3a89NJL1KpVi7S0NEaNGkVqaipG4/3NHTxt2jQ2btzI2LFjuXJFbaJvIYQQQghhPZUuIDYYDKSnp9OtW7cS16elpREREUFycjIAmzZtwt/fnz179gA3H5qbMWMGCQkJvPzyy8yYMYOnnnqKW/lLNBoNd8plsmrVKhYsWABA1apV0Wg0ODg4lOchCiGEEEJUSBqNxip/1lYphkxkZmai1+vRarWYTCYiIiLw8/MrcdvmzZszaNAgPDw8cHV1pXPnznTq1ImYmBhWr76ZbnDy5Mns2rWLSZMmsWDBAurWrUtubi4ALVq0ID4+nvr16+Pv71+s/ueff57o6GjeeOMNjEYjMTExVKlSxXoHL4QQQgghlCinbhb3T1I3CyGEEKK8VYTUzQEj11ul3kPx3a1S7y22P3NWUFhYSHh4eLHlOp2OuLi4ctlHbGwshw8fLrZ84cKFuLi4lMs+hBBCCCHsib0+VCc9xDZwscBU5rImxZfr//3nqFL5/s19lMp7uZd9+Ijqsau+RY0mtf07OpS9BQ06RCrt+/R/1GY7yTp2Qal8qwaPlLms6i1Ka+Obsy1vsLmGQqXyNd2cy6kl9+9qYdnvkwAms9qZd6+q1l+kctk6aNWuWdVPddV7beJPx8pc9s02aomsqjipPRp1IV/tPePprvaeOZ93Xal8YD1XpfLl4dFRG6xS7+8zSn42rLxUyh5iIYQQQgjx4NlpB3Hlm2VCCCGEEEKI+yE9xEIIIYQQolzY6xjihzYgzsrKIjIykoCAACwWC0ajkQEDBhASEmLrpgkhhBBC2CU7jYcf3oAYICgoiFmzZgFQUFCAXq9Hp9MRGBho45YJIYQQQogHRcYQ/y9XV1dCQ0PZsKHkpyMzMjJ4/fXX6devHyNHjsRsNpOfn8+wYcPQ6/Xo9XoOHDjAqVOneP755zl79iw//fQT/fv3v++0z0IIIYQQ9kir1Vjlz9oe6h7iv/L09GTv3r0lrlu7di0DBw6kR48erFq1CoPBwIIFCwgKCqJ///4cO3aM6OhoUlJSGDVqFFFRUZw/f55PPvkER0c5zUIIIYQQFZX0EP+XnJwcvL29S1wXHR1NdnY2YWFh7NixA61Wy8GDB/nyyy/R6/WMHTuWvLw8ALp27crp06d58sknS61PCCGEEKKy0Wis82dtEhD/L4PBQHp6Ot26lTzxc1paGhERESQnJwOwadMm/Pz8GDhwIElJScyePZuePXsCsGTJEp5++ml+++03du7c+cCOQQghhBBC3L+H+rf8zMxM9Ho9Wq0Wk8lEREQEfn5+JW7bvHlzBg0ahIeHB66urnTu3JnOnTszZswYli9fjsFgYOjQoezZs4e1a9eSlpbGiRMniIiIIC0tDXd39wd8dEIIIYQQD5ZMu2ZnnnrqKX766ad73j44OJjg4OBiy+fNm1ds2VdffQWAv78/69atK3sjhRBCCCHsiJ3Gw2gsFtWs55VHYWEh4eHhxZbrdDri4uLKbT/nDAqzTqi+WooX6uUrN5TK16jqVPbCim1XfY9eNKjluK/pVvYc904OaqObvNsPUyp/9PtZSuUdFJ4QdlC8u5oVb3GqbzmVh6PNZrV9F5rUKnBxUrvuVD9dTOayV6C6b5VrFtSDAsP1sn9OuFVR6+vSKjbeqHjhnssr+722To0qSvu+UmhSKu9axUGpvOq5f6Sa2v7Lw+NjN1ml3j0TnrNKvbc8tD3EJXF2diYpKcnWzRBCiIeeSjBs71SCYXunEgyLisFeh0zIQ3VCCCGEEOKhJj3EQgghhBCiXDx0PcRZWVm0a9cOvV5PWFgYffv2LbcHyK5fv056ejoAK1eu5Ntvvy2XesvTpUuXWLNmja2bIYQQQghRYdjrPMRKPcRBQUHMmnXzYZuCggL0ej06nY7AwEClRp07d4709HRef/11XnnlFaW6rOXAgQNs3ry5aO5hIYQQQghhn8ptyISrqyuhoaFs2LChxIA4KioKZ2dn/vzzT86ePcvUqVNp2rQpycnJZGRkYDQacXd3Z86cOcyfP59Dhw4xd+5cLBYLXl5eHDt2jMcee4zevXtz7tw53nnnHVauXMm//vUvsrOzsVgsDBw4kO7du9+232vXrhEdHU1OTg43btxg7NixNGvWjJiYGE6cOIHJZGLQoEGEhISg1+uJjY3F39+flJQUzp8/T+/evfnggw/w9vbmxIkTPP7444wfP5758+ezf/9+0tLS+PXXX7l06RKXLl2icePGNGrUiDfeeIPLly8zaNAgVq5cWV6nWQghhBCiwnrohkyUxNPTk9zc3FLX16tXj8WLF6PX60lLS8NsNnPp0iU+/fRTvvjiC4xGI3v27GHw4MEEBAQwdOjQorJ9+vQpmt/33//+N6+88grff/89J0+eJDU1lc8//5z58+cXpU++JTU1FR8fH9LS0pg6dSq7du0iLS2NRx55hNTUVJYuXcrs2bO5ePFiqe0+duwYkyZNIj09na1bt3Lu3DkGDx5MUFAQoaGhwM3e8tTUVP7xj3+watUqANauXSs9yEIIIYQQFVy5PlSXk5ODt7d3qetv9Rx7e3uzY8cOtFotTk5OjBgxgmrVqnH69GmMxpKnm/H398dkMvHnn3+ybt06Pv30U9LS0ti7dy96vR4Ao9FITk4OQ4YMAaB9+/acOnWKjh07AtCoUSMaNWrE+PHjad++PQBubm74+/tz4sSJ2/b339Mz+/r64ubmBkCtWrW4fv16sfbpdDoAGjRogKurK4cOHWLNmjUlJu4QQgghhKiM7LSDuPwCYoPBQHp6OgkJCaVu89du9P379/PNN9+Qnp7O1atXeeWVV7BYLGi1WswlTOz92muvMWPGDAICAqhevTp+fn489dRTTJgwAbPZzLx586hfv/5tcwl/9tln7Nmzh65du3LixAlmz55Nq1at2L59O8899xwGg4GDBw9Sv359nJ2dOXfuHP7+/vzP//wPderUKbHdQLE2/vc2ffr0ITExkTp16lCzZs17P4lCCCGEEHbMXodMKAXEmZmZ6PV6tFotJpOJiIgI/Pz87rl8w4YNqVq1Kq+88grOzs7UqlWLs2fP0qpVK27cuMGMGTNwcXEp2r5bt25MmjSJxMRE4GY65Z9//pn+/ftz5coVunbtWtSTe0vfvn2JiYkhLCwMk8lETEwMjRs3ZuzYsfTr14/r168zdOhQPD09GTBgAHFxcdStW5fatWvfse2+vr4cPHiQTz/9tNi6rl27EhcXx4wZM+75XAghhBBCCNuQ1M1WcPXqVcLCwkhPT0erLT5MW1I3l5Gkbi4zSd1cdpK6uexUTr1qpjp7Tt2smqnOnlM3q2aqk9TNtk/d3GbiFqvUu/3DZ61S7y3lOoa4sLCQ8PDwYst1Oh1xcXHluasKa8eOHYwbN47IyMgSg2EhhBBCCFGxSA+xDSj1ECvaeeKSUvkbil1WTza03zHVx89fUSrf0KtamcvuOFH67C33ormPh1J5Xaf3lcof31r2HmbVHhN7pnp3PnFB7Zr1VbhmVan2khYa1e5Vj1Qr+y86oNZDrHrJq143qu+5zGMXylzW00Wth/dRb7e7b3QHvyrea1s2ULvXXipQ+xXWv3ZVpfLloe2k76xSb/aYzlap9xbpwhRCCCGEEA+1ch0yIYQQQgghHl72+qOeBMRCCCGEEKJc2Ou0a3cdMpGVlUW7du3Q6/WEhYXRt29f1q1bV+r2UVFRbN26tVwbaW+efvppWzdBCCGEEELco3vqIQ4KCmLWrJsPxRQUFKDX69HpdEWZ54QQQgghhLDTDuL7f6jO1dWV0NBQNmzYUOo2aWlpDBgwgFdeeYXdu3cDsGTJEl599VVCQ0OLElbMmTOHlJQUAA4fPlyUgvnFF19k6NChjBgxgl9++YU+ffrQv39/Bg8ejMFgKLa/Ll26MGLECF577TWio6Mxm83k5+czbNgw9Ho9er2eAwcOAPDss88SHh7OpEmTbqvjvffeY8+ePQC88MILbNq0CYA333yTM2fOsH79ekJDQ+nXrx/x8fEApe7jlpkzZzJ+/HhkIg8hhBBCiIqrTGOIPT092bt3b6nrmzZtynvvvcfKlStZuXIlVapUYf369aSmpuLo6EhERARbtpQ+cfOVK1d47733aNKkCdOmTeO5554jPDyczZs3k5eXVywb3ZkzZxg+fDgNGzZk+PDhfPPNN+zatYugoCD69+/PsWPHiI6OJiUlhVOnTrFy5UoeeeSR2+p4/vnn2bp1Kx4eHlSpUoVt27YRFBTE9evXqVKlCnPmzOHLL7+katWqjBo1im3btvGf//ynxH0ATJs2DY1Gw7hx48pyioUQQggh7I69jiEuU0Cck5ODt7d3qeubNm0KgJeXF9euXePIkSO0aNECJ6ebWcratGnD77//fsd96HQ6AAYPHsz8+fP5+9//Tp06dWjevDmzZs1ix44dAHz66afUrVuXhg0bAtCqVSuOHj3KwYMHyczMZP369QDk5eUB8MgjjxQFw++88w5XrlyhUaNGDBs2jPfee49HHnmEt956i6VLl7J161aeffZZ/vjjDy5evMjbb78N3Bw2cuLEiVL3cf78eQ4cOICvr+99nlkhhBBCCPtlp/Hw/QfEBoOB9PR0EhISSt3mr98O/Pz8WLp0KUajEQcHB7Kzs+nVqxdHjx7l3LlzAMV6nG9leVuzZg29e/dm9OjRLFiwgOXLl/P++7cnCThz5gznzp2jVq1a7Nixg5dffpmLFy/y0ksv0bNnTy5cuEB6evpt9QIsWLDgtnpcXFxYv349c+bMYePGjXz22WfEx8fj5uZG3bp1WbJkCU5OTqxcuZLAwECOHj1a4j68vLxYvHgxer2erVu30rFjx/s5xUIIIYQQ4gG6p4A4MzMTvV6PVqvFZDIRERGBn5/fPe+kcePGdO/enX79+mE2m3niiSfo2rUrJ0+eJDIykuzsbJo1a1Zi2ccff5yoqCiqVauGk5NTiSmgnZ2dmTBhAqdOnaJFixYEBwfTunVrxowZw/LlyzEYDAwdOvSu7ezSpQsrV67Ew8ODZ555hi+++KKol3fgwIHo9XpMJhM+Pj50796dwYMHl7oPjUbD5MmTCQ8PZ/ny5cWGaAghhBBCVDb2OmSiUqRufvrpp9m2bZutm3HPJHWzfZLUzWUnqZvLRlI3l52kbi47Sd1cdpK6GZ6e8YNV6t02qoNV6r2lTGOICwsLCQ8PL7Zcp9OV2IMrhBBCCCEqP3vtw6gUPcT25rxCD7Hyi2XjV1t73xP9/R8Nau8ysx1f6rZuueptomHHsvcw52wr/XmFe2Eyq7Vd9dyrnDtHlTcM6te86lvGlh+Mtu5lVaHaQ2tU/CXPyUHtujOayn7ylF83teKKnzLq9xsHrVoLvNxsn4C4w79+tEq9P3zwjFXqvUXtqhdCCCGEEMLO2f6rhBBCCCGEqBTs9aE66SEWQgghhBAPNbvpIc7KyiIyMpKAgAAsFgtGo5EBAwYQEhLyQNuRnJxMWFhYqev37dvHhAkTcHBwwNnZmWnTpuHl5fUAWyiEEEIIYRt22kFsXz3EQUFBJCUlkZyczOLFi1m0aBH79u17oG1ITEy84/pJkyYxduxYkpKSeO6551i4cOEDapkQQgghhCgLu+kh/itXV1dCQ0PZsGEDgYGBxdZnZGSwcOFCHB0d8fHxYfr06eTm5hIVFUV+fj4Wi4Vp06ZRs2ZNRo0ahcFgwGQyMXz4cNq1a0fPnj158sknOXDgABqNhnnz5pGcnMzly5eJjY0lNja2xHbNnDmT2rVrA2AymahSRW1ORSGEEEIIeyFjiG3A09OT3NySJ9Feu3YtAwcOJCUlhWeeeQaDwUBiYiLBwcGkpqYSGRnJ7t27SUxMpH379ixbtoyEhATGjBmD2WymoKCAHj16kJycTO3atdm6dSvvvvsuNWrUKDUYBoqC4R07dpCcnMzAgQOtcORCCCGEEBWPRmOdv78ym8189NFHhIaGotfrOX78+G3rv//+e/r06UOfPn2IjY296xSYdh0Q5+Tk4O3tXeK66OhosrOzCQsLY8eOHWi1Wo4ePUqrVq0AaNeuHS+99BKHDx+mbdu2ANSpUwc3NzcuXrwIQJMmTQCoW7cu169fv+d2rVu3jnHjxvHJJ59Qs6b9ZmYTQgghhEEitNUAACAASURBVKiIvvnmGwoLC0lLS+ODDz5g6tSpResMBgMzZsxg/vz5LF++HB8fn1I7UG+x24DYYDCQnp5Ot27dSlyflpZGREQEycnJAGzatAl/f3/27NkDQHZ2NjNmzMDf35/t27cDcObMGfLy8vDwuJl6saRu/7t9w/j3v/9NcnIySUlJNGjQoMzHJ4QQQghhbzQajVX+/uqXX36hQ4eb6ZxbtmzJb7/9VrTu119/pVGjRkybNo3+/fvj5eV11w5KuxpDnJmZiV6vR6vVYjKZiIiIwM/Pr8RtmzdvzqBBg/Dw8MDV1ZXOnTvTqVMnYmJiWL16NQCTJ0/G3d2dmJgYNm7cyLVr14iLi8PRsfTT4u/vz8iRI4mPjy+2zmQyMWnSJOrWrUtERAQAbdu2ZdiwYeVw9EIIIYQQAm52jLq5uRX928HBAaPRiKOjI7m5uWRlZbFq1SqqVavGG2+8QcuWLdHpdKXWZzcB8VNPPcVPP/10z9sHBwcTHBxcbPn8+fOLLZs3b16xZZs3by76/5EjRxb9f1JSUqn7dHBw4Oeff77nNgohhBBCVCYP6pk6Nzc3CgoKiv5tNpuLOjQ9PDx4/PHHqVWrFgBt2rRh3759lSMgLklhYSHh4eHFlut0OuLi4qy235ycHEaPHl1sufQGCyGEEOJhpn1AEXHr1q3ZsmULISEh7Ny5k0aNGhWta9asGQcPHuTixYtUr16dXbt20adPnzvWp7HcbVCsKHfnDUab7dus+HKrTqfioFDepNp2pdKg+kZR2b/q6+aoVXtcQHX/DtqyH329p4cr7fvED7OVyqve21VOna1vzgovG6B27KofqhbFs2dWPPm2nHhK+bqx4YWneKtSuubKg+r9QvW6q+Vm+37O5+ZmWqXeTUODbvu32WwmNjaWgwcPYrFYmDx5Mlu3bsXX15cuXbrw9ddfs3jxYgC6devG22+/fcf6bX/mhBBCCCFEpfCghkxotdpiowH8/f2L/r9Hjx706NHj3usrt5YJIYQQQghhh+yyhzgrK4vIyEgCAgKwWCwYjUYGDBhASEjIPdeRl5fHW2+9haurK7Vr1yYkJISOHTsqty0/P78o892NGzeIiooqmvtYCCGEEKIys9dMdXYZEAMEBQUxa9YsAAoKCtDr9eh0uhLTOJfk4MGD1K5dmzn/n717j4uyzv///5jhoDmYKB5QywK01DZPfUwoTXPtE+KWn/qkoMsUxNZmiZqLqVA5YRqmLZmbk1lmgWss6fZV1yW1bdMtMcxS1w7mIcswtUR0cAU5/P7wJ5/K8dC8GYah573b3G421/U+XBfXNfOe17zn/Zo3jylTptRZv1555RWio6NJSkpiz549/OEPf+Cvf/1rndUvIiIi0lCZ/vbAV/x2QPxDNpuN+Ph4CgoK3A6I16xZw8KFCwkMDKRjx47MnDmT6dOnc+jQIZ577rna/VwuFxkZGRw/fpySkhJGjBjB6NGj2bZtG0888QQ2m42wsDCaNGnyo4woP5SUlERwcDBwel3iJk2aeOegRURERKRONJo5xGFhYedMy7dq1SqSkpJYunQp/fv35+TJk6SnpxMdHf2jZdL27dvHsGHDWLRoES+88AKLFy8GYNq0aWRlZfHaa6/RqVOn8/bj0ksvpWnTphw+fJhJkyYxceLEOjtGERERkYasvjLV1bVGMyAuLi4mPDzc7bapU6dSVFREYmIiW7ZswXqOdV1at27NunXrSEtLw+l0Ull5enm0Q4cO0aVLFwCuu+66C/bl888/JykpiYcffpjrr7/ewyMSERERkfrQKAbELpeL/Px8YmNj3W7Py8sjNTWV3NxcANauXet2v0WLFtGrVy/mzJlDbGwsZ5ZoDg8PZ9euXQBs3br1vH3ZtWsX48eP55lnnmHgwIGeHpKIiIiI37FYvPPwNr+dQ1xYWIjdbsdqtVJVVUVqaiqRkZFu9+3RowfJycmEhoZis9kYNGgQO3fuPGu/m2++GYfDwcqVKwkNDSUgIICKigqmTZtGeno6zZo1IygoiHbt2p2zX8888wwVFRXMmDEDOJ1a0Ol01s1Bi4iIiEidU6a6i7BkyRKGDh1Kq1atyM7OJigoiLFjx3pcnzLVeUaZ6jynTHWeU6Y6zylTnW8oU53vKFMd/GZBkVfqXfX7vl6p9wzfn7k6UlFRQUpKylnPR0REnJXJ5OcKCwvj3nvvpVmzZjRv3pysrCzGjh1LaWnpj/ZTNFhERER+yfx12TVFiH1AEWLPKELsOUWIPacIsecUIfYNRYh9RxFiuP1F70SIV9yvCLHUIdM3GVMmAytff+j0ZfumfzfTAa2pKoNXedMBrSlfnjpfX/O+PHZfX7O+PvcmjPvuw4P39YDWlGn//fm6O8NfM9U1ilUmREREREQ8pQixiIiIiNQJPw0Qa0AsIiIiInXD11MzPeX3UyY2bdpETEwMdrudxMREEhISWL16tdfaKyoq4rPPPrvgft9//z0DBw5k9+7dXuuLiIiIiJhrFBHi6OhosrOzASgrK8NutxMREUG3bt3qvK1ly5YRFxdH165dz7nPqVOnePzxx2natGmdty8iIiLSUPlpgLhxDIh/yGazER8fT0FBgdsB8Zo1a1i4cCGBgYF07NiRp59+mpKSEqZMmcLx48epqalh1qxZNG3aFIfDQXl5OUePHuWhhx4iPDycDRs2sGPHDjp37kyHDh3c9mHWrFkkJCTw4osvevtwRURERMRQoxsQw+lEGjt27HC7bdWqVSQlJTFs2DDefPNNXC4XTqeTwYMHM2rUKDZu3Mi2bdto3bo1ycnJ9OvXjy1btjBv3jxeeeUVBgwYQFxc3DkHw8uXL6dVq1YMGDBAA2IRERH5RfHXZdca5YC4uLiY8PBwt9umTp3KggULWLp0KZGRkQwZMoS9e/dy1113ARATEwPAF198gdPp5I033sBisVBZeXHJNJYtW4bFYmHjxo18+umnTJ48GafTSZs2berm4EREREQaKD8dDze+AbHL5SI/P5+5c+e63Z6Xl0dqaiphYWE8/vjjrF27lqioKLZv307Xrl0pKirin//8J/v27WPEiBEMHDiQZcuW8de//hU4/cnnfMn9lixZUvtvu92Ow+HQYFhERESkAWsUA+LCwkLsdjtWq5WqqipSU1OJjIx0u2+PHj1ITk4mNDQUm83GoEGDGDhwIOnp6axYsQKAmTNnsnXrVmbMmMGCBQto3749JSUlAPTs2ZM5c+Zw2WWXERUVVW/HKCIiItLQ+euya5aa84U7xSu+c13c9AsRERGRi9U6xPdxzvhXP/JKvXn39PZKvWf4/sx5QUVFBSkpKWc9HxERQWZmZp204XA43K4xvHDhwgsutxZg9fzTU2WVjxOlGzZvNVj5utrHh276odeXHz1Nm/blZWNwu5xu2/DgLx8wwah88Xvup29djFNV1UZtm/64xT/jPGJ6vwcYXjdVhjedyT1v+j5hyvT1yl+jqz/kr0fQKAfEwcHB5OTkeLUNh8Ph1fpFRER+aUwHlCKeapQDYhERERGpf1p2TURERER+0fw1ym8wo1NERERExP/5zYB406ZNxMTEYLfbSUxMJCEhgdWrV9d7P3Jzcy9qv5UrVxIfH+/l3oiIiIg0HBaLxSsPb/OrKRPR0dFkZ2cDUFZWht1uJyIigm7dutVbH5xOJ4mJiefd59NPP+WNN944bwIPEREREWkY/CZC/FM2m434+HgKCgrcbl+zZg0jRoxg1KhRpKWlUV1dzffff899991HQkIC8fHxfPnllxw7dozf//73/Pa3vyUhIYGNGzcCcNtttzF9+nQSExOx2+0cP34cp9NJaWnpeVeYKCkpYc6cOaSnp3vjsEVEREQaLIvFOw9v89sBMUBYWFhtBrmfWrVqFUlJSSxdupT+/fvjcrlwOp0MHjyY119/nQkTJrBt2zacTic33HADS5YsYe7cuWRkZFBdXU1ZWRnDhg0jNzeXtm3bsn79esaMGUOLFi3OOSCuqqoiIyOD9PR0bDabF49cREREpOHx1ykTfj0gLi4uJjw83O22qVOnUlRURGJiIlu2bMFqtbJ371569z6d6SQmJobbb7+d3bt307dvXwDatWtHSEgIR44cAaB79+4AtG/fnvLy8gv2Z8eOHezbtw+Hw8HEiRPZtWsXM2bMqItDFREREREv8as5xD/kcrnIz89n7lz3WaDy8vJITU0lLCyMxx9/nLVr1xIVFcX27dvp2rUrRUVF/POf/yQqKorNmzfTvXt3Dh48yLFjxwgNDQXcr6V3vnnBPXr04G9/+xsA+/fvZ+LEiWRkZNTB0YqIiIg0fP667JpfDYgLCwux2+1YrVaqqqpITU0lMjLS7b49evQgOTmZ0NBQbDYbgwYNYuDAgaSnp7NixQoAZs6cSfPmzUlPT+ett97i5MmTZGZmEhh47tMSFRVFWloac+bM8coxioiIiEj9stRoKYR6V3KiyuOylVWGfy7TT26GzVsNJumY5qg3PXTTKUy+vNNMm/blZWMabTA975cPmGBUvvg9999iXYxTVdVGbZvOu/PTQM8vnun9HmB43VQZ3HSm97vp+4Qp0/5bDc99y2YBZh2oA8mvb/dKva8kXOuVes/wqwjxT1VUVJCSknLW8xEREWRmZnqt3eLiYiZPnnzW83379mXcuHEXLF/pwzvW9POP6RtstcH7u8lgGswHRr4ckJteMb4eVJpcdzU+HpaZDGgBOtw43uOy+9ZnG7VtyvTvbvJyYfoB1OS1BiAwwHBQaPCCEWB4w5oGTk4ZnjyTv1216f3u40//pu8TjSFG6a8fpP16QBwcHExOTk69t9uhQweftCsiIiIidc+vB8QiIiIi0nCYTvvwFb9edk1ERERExJRfRog3bdrEhAkT6Ny5MzU1NVRWVnL33XcTFxd30XUcO3aM++67D5vNRtu2bYmLi+Omm26qsz6uXbuWgoICnnnmmTqrU0RERKQh89MAsX8OiAGio6PJzj79g5OysjLsdjsRERF069btosrv3LmTtm3bMm/ePKZMmVKnfXvyySf517/+ddF9EREREWkM6iOrnDf47YD4h2w2G/Hx8RQUFLgdhK5Zs4aFCxcSGBhIx44dmTlzJtOnT+fQoUM899xztfu5XC4yMjI4fvw4JSUljBgxgtGjR7Nt2zaeeOIJbDYbYWFhNGnShKysrHP2p0+fPgwZMoS8vDyvHK+IiIiI1J1GM4c4LCyMkpISt9tWrVpFUlISS5cupX///pw8eZL09HSio6N/tEzavn37GDZsGIsWLeKFF15g8eLFAEybNo2srCxee+01OnXqdMG+xMXF+e0nJBERERFPWSzeeXhboxkQFxcXEx4e7nbb1KlTKSoqIjExkS1btmA9x4K2rVu3Zt26daSlpeF0OqmsrATg0KFDdOnSBYDrrrvOOwcgIiIiIj7RKAbELpeL/Px8YmNj3W7Py8sjNTWV3Nxc4PQP3txZtGgRvXr1Ys6cOcTGxtYukB0eHs6uXbsA2Lp1qxeOQERERMT/WS0Wrzy8zW/nEBcWFmK327FarVRVVZGamkpkZKTbfXv06EFycjKhoaHYbDYGDRrEzp07z9rv5ptvxuFwsHLlSkJDQwkICKCiooJp06aRnp5Os2bNCAoKol27dt4+PBERERGpJ5aaxpAn0MuWLFnC0KFDadWqFdnZ2QQFBTF27FiP6zvsqvS8M6ZpIQ0rMJ4bbdC8r1M3+zIjqL+nbq42qMDXi7ybptH1ZepmX96vp9v3TVlQ6mYTJvcrmP7d/Tt1synT5luH+D7O+eDyT7xS7/w7u3ul3jN8f+bqSEVFBSkpKWc9HxERQWZmplHdYWFh3HvvvTRr1ozmzZuTlZXF2LFjKS0t/dF+ISEhOJ1Oo7ZERERE/JW/LiqgCLEPfF/meYTY11FOX0aMTKMmJhEbX/P1382Uyd+uyjRaZVTaPFpm4oqbHjYqv/9fzxqV9+W7g6+veV++p5u2bfqtimmE2eSmMz3tpufO9H3C4uMQc5vmvo9zPvTXT71S7/N3eDe3g+/PnIiIiIg0Cv66WoO/9ltEREREpE4oQiwiIiIidcJf5xD7TYR406ZNxMTEYLfbSUxMJCEhgdWrV9d7P86sZXwhM2fOZOnSpV7ujYiIiEjDYbV45+H1fnu/iboTHR1NTk4Oubm5vPzyy7z00kt8+ql3Jm+fy4VWkThy5Ai/+93v+Mc//lFPPRIRERERE347ZcJmsxEfH09BQQHdup39y8M1a9awcOFCAgMD6dixI08//TQlJSVMmTKF48ePU1NTw6xZs2jVqhWTJk3C5XJRVVXF+PHjiYmJ4bbbbuP666/n888/x2KxMH/+fHJzcyktLcXhcOBwONz2q6ysjNTUVNavX+/lMyAiIiLSsNRHNNcb/CpC/FNhYWGUlJS43bZq1SqSkpJYunQp/fv3x+Vy4XQ6GTx4MK+//joTJkxg27ZtOJ1ObrjhBpYsWcLcuXPJyMigurqasrIyhg0bRm5uLm3btmX9+vWMGTOGFi1anHMwDHD55ZfTs2dPLx2xiIiIiNQ1vx4QFxcXEx4e7nbb1KlTKSoqIjExkS1btmC1Wtm7dy+9e/cGICYmhttvv53du3fTt29fANq1a0dISAhHjhwBoHv301lR2rdvT3l5eT0ckYiIiIj/slgsXnl4m98OiF0uF/n5+cTGxrrdnpeXR2pqau2P4NauXUtUVBTbt28HoKioiNmzZxMVFcXmzZsBOHjwIMeOHSM0NBRw/0tJ5TERERERcc9ff1TnV3OICwsLsdvtWK1WqqqqSE1NJTIy0u2+PXr0IDk5mdDQUGw2G4MGDWLgwIGkp6ezYsUK4PRKEM2bNyc9PZ233nqLkydPkpmZSWDguU9LVFQUaWlpzJkzxyvHKCIiIiL1S6mbfUCpmz2j1M2+rMCMUjd7RqmbfVeBUjcbUOpmn2kIqZsf+dvnXqn36WFXe6XeM3x/5gxUVFSQkpJy1vMRERFkZmZ6rd3i4mImT5581vN9+/Zl3LhxXmtXREREROqeIsQ+cPi45xFi02jVgaMnjcrvP3bCqHz0lWEelzW9Uk0jB19//x+j8peHXeJx2SOuCqO2bU3NPvseNLxuOrVu5nFZ0xco03iNL18gTefNXdZ/glH5b/4116i8yevVyVPVRm2fqjIrH2J4z5j86Uxfq0y/DDO9Z/Yf8fy10jRC2ynM89cagC8PlxmVv7Ktzah8peF1275FsFH5ujBl9U6v1JsVd5VX6j3Db39UJyIiIiJSF/x6yoSIiIiINBz+GmnVgFhERERE6oQvf5BqokEMiDdt2sSECRPo3LkzNTU1VFZWcvfddxMXF+eV9pYvX86ePXtIS0urk/1ERERExH81iAExQHR0NNnZ2QCUlZVht9uJiIigW7duPu6ZiIiIiFwM02X/fKVBTvWw2WzEx8dTUFBw1raSkhKGDx8OwEcffcT1119PVVUV3377LSkpKZw6dYpJkyaRkJDAiBEjWL16NQB2u51x48aRlJREVVUVAEeOHCEhIYGNGzdy8uRJHn74YeLj47nzzjv56KOPftTuM888Q3JyMiNHjmTq1KkAfPjhh4wcOZLRo0fzwAMP4HK52Lt3LwkJCSQmJnLPPfdw8OBBb54qERERETHUYCLEPxUWFsaOHTvOer5ly5aEhoZy4MABNmzYQHh4ODt27GD79u0MGTKEvLw8WrZsyezZs3G5XNx5551ER0cDcNttt3HLLbewfPlyvv/+e8aMGUN6ejo9e/Zk8eLFdOzYkezsbHbu3Mn777/PpZdeCpxOE33ppZfyyiuvUF1dzbBhwzh48CDr1q3jlltuISUlhX/84x8cO3aM999/n2uuuYYpU6awefNmSktLadeuXb2eOxERERFf8NMAccOMEMPp5Bfh4eFut91yyy28++67fPTRR9x333289957vPvuuwwZMoTdu3fTt29fAEJCQoiKiuLrr78GTifsOGPDhg1UVFRQXX16zb89e/bQq1cvAK666iqSkpJq923SpAlHjhxh4sSJPP7445w4cYJTp07xwAMPcOTIEe655x4KCgoIDAzkrrvuomXLlvzud79jyZIlBAQEeOP0iIiIiDQ4Vot3Hl7vt/eb+PlcLhf5+fnExsa63T5kyBBWrVpFSEgIN910E+vWraOiooI2bdoQFRXF5s2ba+vZuXMnl112GQCWH3xs+Z//+R9mz57No48+yokTJ4iKimL79u0AfP311/zhD3+o3Xf9+vUcOHCAP/7xj0ycOJGTJ09SU1PDypUrueOOO8jJyaFLly785S9/4e233+a6667j1VdfJTY2lpdeeslbp0lERERE6kCDmTJRWFiI3W7HarVSVVVFamoqkZGRbvcNDw+nvLyc6OhoWrRoQWBgIIMGDQJg5MiRPPbYY4waNYry8nLGjh1LWJj77GidO3fm9ttv56mnnuLRRx8lPT2dxMREqqqqSE9P54svvgCgR48ezJ8/n5EjRxIcHMzll1/OoUOHuPbaa5kyZQrNmjUjKCiIzMxMampqmDRpEvPmzcNqtdbONxYRERFp7Pz1R3VK3ewDSt3sGaVu9pxSN3tOqZs9p9TNHpZV6maPKXWz71M3Z67d5ZV6H7+ls1fqPaPBRIh/qqKigpSUlLOej4iIIDMz0wc9EhEREZHzqa8AcXV1NQ6Hg88//5zg4GCefPJJrrjiirP2uf/++/n1r3/NqFGjzltfgx0QBwcHk5OT4+tueIXVYOZ2gOG0744tPY9SAnRsZVben13mw2NvFWL2qd/0Bcokwgtm0X2T+8W0baiDCLNB+6bRadMIb8f+443Kf73hWY/LNgk0+8MHB5r95Uyj8yZ/d9MIr+mFYzG850zeJ0y/tDZ9vTCN8AaYvtiaHkADUB8/gANqfz+Wl5fHxx9/TFZWFk6n80f7PPvss5SWll5Uff5/5kVERETkF+XDDz9kwIABAPTq1Yt///vfP9peUFCAxWLhpptuuqj6NCAWERERkTph8dJ/P+VyuQgJCan9/4CAACorT/9Ga+fOnaxatYrx4y/+W64GO2VCRERERMSdkJAQysr+70eQ1dXVBAaeHta++eabHDx4kHvuuYdvvvmGoKAgOnbseN5osd8MiDdt2sSECRPo3LkzNTU1VFZWcvfddxMXF1ev/cjNzSUxMfGc23fs2MG0adMIDg6mW7duZGRkYG0Ec4JERERELqS+5hD36dOHd955h7i4OD7++GOuuuqq2m2PPPJI7b/nzZtH69atLzh1wq9GatHR0eTk5JCbm8vLL7/MSy+9xKefflqvffjphO2feuyxx0hPT+fPf/4zISEhrFy5sp56JiIiIvLLcMsttxAcHExCQgJPPfUUU6dO5ZVXXuHtt9/2qD6/iRD/lM1mIz4+noKCArp163bW9jVr1rBw4UICAwPp2LEjTz/9NCUlJUyZMoXjx49TU1PDrFmzaNWqFZMmTcLlclFVVcX48eOJiYnhtttu4/rrr+fzzz/HYrEwf/58cnNzKS0txeFw4HA43Pbr4MGD9OnTBzj96eXtt99m+PDh3jwVIiIiIg1CfUWIrVbrWcvwRkVFnbVfamrqxdVXJ73ykbCwMEpKStxuW7VqFUlJSSxdupT+/fvjcrlwOp0MHjyY119/nQkTJrBt2zacTic33HADS5YsYe7cuWRkZFBdXU1ZWRnDhg0jNzeXtm3bsn79esaMGUOLFi3OORgGuPzyy/nggw8AeOedd/jPf8ySOYiIiIj4C4vF4pWHt/n1gLi4uJjw8HC326ZOnUpRURGJiYls2bIFq9XK3r176d27NwAxMTHcfvvt7N69m759+wLQrl07QkJCOHLkCADdu3cHoH379pSXl19Un2bOnMmCBQu4//77CQsLo2XLlqaHKSIiIiJe5LcDYpfLRX5+PrGxsW635+XlkZqaSm5uLgBr164lKiqK7du3A1BUVMTs2bOJiopi8+bNwOnpDseOHSM0NBTA7SeSCy0a/u677zJz5kxefPFFjh49yo033ujxMYqIiIj4E6vFOw9v86s5xIWFhdjtdqxWK1VVVaSmphIZGel23x49epCcnExoaCg2m41BgwYxcOBA0tPTWbFiBXA6mtu8eXPS09N56623OHnyJJmZmbXLdrgTFRVFWloac+bMcbv9iiuu4P777+eSSy6hX79+DBw40PzARURERMRrLDWmeRLlZ/u+rNLjsu4Wp/45qkxzghp+SjMpbpyC14epWOuifV+2bXrs/py62ZRJ+8ZZYA0r8GXqZuNrzjB/cYBhSMqXKbtNKzC950zeZkyHJKZ/N9O3SNPUzabv0W2a+z7O+cf1e7xS78Sb3AdA64rvz5yBiooKUlJSzno+IiLirF8e1qXi4mImT5581vN9+/Zl3LhxFyxv9kJpdrP4clBmytd993X7Jnw9KDQ5d77uuylfXjfVhifPZEBryvS8mQYPfHndGV8yvv7wb1LW8A/vy76D+T3nz+8zZ5h+EPcVRYh94DuX5xFiEREREXdah/g+zvnshr1eqXfCgAiv1HuG78+ciIiIiDQK9bUOcV3z21UmRERERETqgiLEIiIiIlIn/HQKsf9HiDdt2kRMTAx2u53ExEQSEhJYvXq119orKiris88+u+B+W7duxW63e60fIiIiIg2NFYtXHt7WKCLE0dHRZGdnA1BWVobdbiciIoJu3brVeVvLli0jLi6Orl27nnOfhQsXsmLFCi655JI6b19ERERE6lajGBD/kM1mIz4+noKCArcD4jVr1rBw4UICAwPp2LEjTz/9NCUlJUyZMoXjx49TU1PDrFmzaNq0KQ6Hg/Lyco4ePcpDDz1EeHg4GzZsYMeOHXTu3JkOHTq47UOnTp2YN28ejzzyiLcPV0RERKTB8NcpE41uQAwQFhbGjh073G5btWoVSUlJDBs2jDfffBOXy4XT6WTw4MGMGjWKjRs3/KHJIgAAIABJREFUsm3bNlq3bk1ycjL9+vVjy5YtzJs3j1deeYUBAwYQFxd3zsEwwK233sr+/fu9dXgiIiIiUoca5YC4uLiY8PBwt9umTp3KggULWLp0KZGRkQwZMoS9e/dy1113ARATEwPAF198gdPp5I033sBisVBZqbWDRURERM5Hy641EC6Xi/z8fGJjY91uz8vLIzU1ldzcXADWrl1LVFQU27dvB07/aG727NnMnTuX4cOHM3v2bPr161ebTtJisRinlhQRERGRhqNRRIgLCwux2+1YrVaqqqpITU0lMtJ9zusePXqQnJxMaGgoNpuNQYMGMXDgQNLT01mxYgUAM2fOZOvWrcyYMYMFCxbQvn17SkpKAOjZsydz5szhsssuIyoqqt6OUURERKShU+pmuWhK3SwiIiJ1rSGkbl64aZ9X6r2v3xVeqfcM3585L6ioqCAlJeWs5yMiIsjMzKyTNhwOB7t37z7r+YULF9K0adPzljX59FRt+Pml9D+njMpbDNcCbBYc4HHZwADffuo0PXctLgnyuOx/KqqM2g4ONJsd9Z9TZu2HNPH8pcY02mB6z5gy6X61YdfLT1UblW9ieN2YHPvlAyYYtV1S9Cej8sdPmgUuTK5aX0epTF9pS0+YvVaauLSZ56+zYP5ae4nBexxAZZXZPSuea5QD4uDgYHJycrzahsPh8Gr9IiIiIv7GX6dMNLof1YmIiIiI/ByNMkIsIiIiIvXPTwPE9Rsh3rRpEzExMdjtdhITE0lISGD16tVea2/58uW8/fbbALXLrJ2L3W4/a07w/v37GTlypNf6JyIiItKYWL308LZ6jxBHR0eTnZ0NQFlZGXa7nYiICLdplk3deeedtf92Op0kJibWeRsiIiIi4t98OmXCZrMRHx9PQUGB2wHxl19+yaOPPsqpU6do2rQp2dnZfPfdd2RlZVFdXc2xY8d49NFH6dOnD7/+9a/p2bMnX331FV26dGHGjBk8//zztG7dmqNHj1JaWorD4SAtLY2MjAyOHz9OSUkJI0aMYPTo0Rfs63vvvcezzz5LkyZNCA0NZebMmVRWVjJhwgRqamo4deoUTzzxBFdeeSXjx4/H5XJx8uRJJk2aRL9+/bxx+kREREQaFIufzpnw+RzisLAwduzY4XbbrFmzuP/++7nppptYvXo1n3zyCceOHWPy5MlcffXVrFy5kuXLl9OnTx8OHjzI+PHjueKKKxg/fjzr1q2rrWfMmDHk5ubicDjYsWMHw4YN47//+785ePAgdrv9ggPimpoaHnvsMZYuXUq7du149dVXcTqd9OvXj+bNm/PMM8+wa9cuXC4XX331Fd999x2LFy/m+++/58svv6zL0yUiIiIidcznA+Li4mLCw8Pdbtu7dy+9e/cGIC4uDoDNmzczf/58mjZtSllZGSEhIQC0b9+eK644vWhz79692bt3r9s6W7duzauvvsqaNWsICQmhsvLHa01mZGTw1Vdf0bJlSx555BEASkpKCAkJoV27dgD07duXP/7xj0yaNIkvv/ySBx98kMDAQMaMGUOXLl347W9/y8SJE6msrMRutxueIRERERH/4J/xYR8PiF0uF/n5+cydO9ft9qioKLZv384NN9zAihUrKC0tZfny5cyZM4eoqCiee+45vvnmGwAOHjzI4cOHadOmDVu2bGH48OF88skntXWdSci3aNEievXqxejRoyksLOTdd9/9UZszZsyo/ff+/fsBaNmyJS6Xi0OHDtG2bVs++OADrrzySjZt2kTbtm1ZtGgRH330EX/84x959NFHKSsr48UX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pveryYDWlC9fa+qC0Qdg09c6s+I+fY8T39KAWERERETqhL8GYBrFsmsiIiIiIp5ShFhERERE6kR9JNHwBr8fEG/atIkJEybQuXNnampqqKys5O677yYuLs4r7RUVFdG8eXO6du3qdvupU6dIT0/nm2++oaKigjFjxvDrX//aK30RERERaUhMf5joK34/IAaIjo4mOzsbgLKyMux2OxEREXTr1q3O21q2bBlxcXHnHBCvWLGC0NBQZs+eTUlJCXfccYcGxCIiIiINWKMYEP+QzWYjPj6egoICtwPiNWvWsHDhQgIDA+nYsSNPP/00JSUlTJkyhePHj1NTU8OsWbNo2rQpDoeD8vJyjh49ykMPPUR4eDgbNmxgx44ddO7cmQ4dOpxVf2xsLLfeemvt/yt1s4iIiPxSaMpEAxIWFsaOHTvcblu1ahVJSUkMGzaMN998E5fLhdPpZPDgwYwaNYqNGzeybds2WrduTXJyMv369WPLli3MmzePV155hQEDBhAXF+d2MAynB+QALpeLcePGMWHCBK8dp4iIiIiYa5QD4uLiYsLDw91umzp1KgsWLGDp0qVERkYyZMgQ9u7dy1133QVATEwMAF988QVOp5M33ngDi8VCZeXFJ9M4cOAADz30EKNHj+a2224zPyARERERP6Bl1xoIl8tFfn4+sbGxbrfn5eWRmppKbm4uAGvXriUqKort27cDp380N3v2bObOncvw4cOZPXs2/fr140xCP4vFwvmS+3333Xfce++9TJo0qXaQLSIiIiINV6OIEBcWFmK327FarVRVVZGamkpkZKTbfXv06EFycjKhoaHYbDYGDRrEwIEDSU9PZ8WKFQDMnDmTrVu3MmPGDBYsWED79u0pKSkBoGfPnsyZM4fLLruMqKios+p/4YUXOHbsGPPnz2f+/PkALFy4kKZNm3rp6EVEREQaBn+dQ2ypOV+4U7ziO9fFT78QERERuRitQ3wf51y/84hX6r3pqlZeqfcM3585L6ioqCAlJeWs5yMiIsjMzKyTNhwOB7t37z7r+YuJBpvkqQ+0ms1yqaiqNipv+rkvMMDzGqp9nOO+yrADAQaLM5qUBSj9zymj8s2bmr1UmEQMTO6XhsDkb+frY/flPWd65NWGnQ8KMHutNfnbXT7A7MfYX2141qi86cn35XVr+lp58lSVUflmwWavleWVZu2L5xrl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YREREZFfND8NETeoAfEP2Ww24uPjKSgocDsgPnDgAI899hjl5eU0adKE6dOn0759e7Kzs9mwYQPh4eG1GevmzZtH69atGTVqFLt378bhcJCTk8M777zDn/70JwC6d+/OE088wZo1a1iyZEltO3PnziUvL4/S0lIcDgc9evRgz549pKWlsWjRIv72t78RGBjIf/3XfzFp0iTmzZvH/v37+f777ykuLmbq1KkMGDCgfk6aiIiIiPxsDWbKhDthYWG1g9qfmjVrFna7nZycHFJSUpgzZw47d+6kqKiIN954g6effpqysrJz1l1ZWcn06dN58cUXWbZsGe3atePbb7/lyy+/5MUXXyQnJ4eIiAj+9a9/MWbMGFq0aIHD4agt//nnn/P3v/+d119/nddff519+/bxzjvvABAcHMxLL71ERkYGixcvrstTIiIiItJgWbz0n7c12AgxQHFxMeHh4W637dy5kwULFvDSSy9RU1NDUFAQu3bt4le/+hVWq5WQkBCuuuqqc9ZdUlLCpZdeSlhYGABjx44FTg/CJ0+ejM1mY8+ePfTq1ctt+T179tCzZ0+CgoIA+K//+i+++OILgNqIdnh4OBUVZotsi4iIiPgLf11losFGiF0uF/n5+cTGxrrdHhkZSVpaGjk5OTzxxBPceuutREREsG3bNqqrqzlx4gS7dp3OqtakSRMOHz4MwI4dO4DTA99jx45x9OhRAJ588kk++OADnnvuObKzs3nyySdp0qQJZxL5/TShX2RkJNu2baOyspKamhqKioqIiIgAwOKvV4OIiIjIL1CDihAXFhZit9uxWq1UVVWRmppKZGSk230nT56Mw+GgvLyckydPkpGRQbdu3YiNjeWuu+6ibdu2tdHfoUOHMmHCBIqKivjVr34FgNVqZdq0afz+97/HarXSvXt3+vbtS58+fbjjjjto1qwZl156KYcOHQIgKiqKtLQ0brjhBgCuvvpqhg4dyqhRo6iurua6665jyJAhfPbZZ/VwpkREREQaHn8NCVpqfhr6FK87eMzzlKSmOeoPlZYblc/65y6j8tnDrzEq70uX3z7LqPzXKyZ7XNY0dbNpGlzXyf+PvTOPqzl9//+rtDAylUghlTCWGbuxrxODsWZSVEhmKFOyRIokaaEYhooZg6kUjWVsIxpGlkGWZItUjLEVRZ20nbp/f/Q751NU5/1+36fe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\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "build.plotCorrMatrix(df_corr, #Dataframe with correlation\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" + "df_corr = (univariate_selection\n", + " .compute_correlations(basetable[basetable[\"split\"] == \"train\"],\n", + " predictors=preprocessed_predictors))" ] }, { - "cell_type": "code", - "execution_count": 11, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAv8AAAIrCAYAAAB4VPYbAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzs3Xd4FFXbx/HvtvSEJIQiCApogop0AyJFEUQEKYKQhGJ78AUEBBvSQ5FieVRAKZZHpRlQRCyA0kVCEVCaItKkSDEkkIRks2XePyKrMVRNsim/z3Xlgj1z5sy9M2S5Z/bMPSbDMAxERERERKTYM3s7ABERERERKRhK/kVERERESggl/yIiIiIiJYSSfxERERGREkLJv4iIiIhICaHkX0RERESkhFDyLyIF4siRI0RFRdGjR49831ZUVBTt27e/bL+zZ88WWEwFaeHChURFReX6ufnmm6lfvz5dunRh1qxZuFwuzzobN24kKiqKF1544R9v98CBAyxZsiQv3oKIiOQTq7cDEBHJa/369SMiIsLbYXhddHQ00dHRntcul4szZ87w9ddfM27cOH744QdefvnlPNnWTz/9ROfOnYmNjaV169Z5MqaIiOQ9Jf8iUuz079/f2yEUCtHR0RfcF/3796d9+/Z89tlnxMTEUL9+/X+9rTNnzuBwOP71OCIikr807UdEpIQJDw/ngQceAGDt2rVejkZERAqSkn8R8Zrz9wFMmTKFFStW0LlzZ2rWrMntt9/O8OHDOX36dK51Nm/ezP/93//RoEED6tWrR0xMDMuXL8/R50Jz/o8cOcIzzzxDo0aNqFOnDv369ePYsWMXjMswDObNm0fHjh2pWbMmt912G71792b37t05+p2fJ79w4UI++ugj7r//fm699VaaNm3KpEmTyMjIyDX28uXL6dGjB/Xr16dBgwY8/PDDbN68OVe/xMREHnnkEerVq0ft2rXp2rUrS5cuvew+vVLlypUDICUl5ZL9Tp48yciRI2nWrBk1atSgWbNmjBw5kpMnT3r6TJkyhZ49ewLwwQcfEBUVxcaNG/MsVoDTp08zadIkWrduTa1atahVqxZt2rRh+vTpOJ3OHH2dTiczZsygVatW1KxZk/vuu4+PPvqIN998k6ioKI4cOZKjf37vaxGRwkTJv4h43apVq+jXrx9lypShR48elCtXjgULFvDUU0/l6Pfpp5/y0EMPsXnzZpo2bUqnTp347bffeOKJJ/joo48uOv7x48eJiYnh888/p3bt2nTq1Im9e/fyn//854L9Bw8eTHx8PA6Hg5iYGO69916+++47YmJiSExMzNV/9uzZxMfHc+ONN9KjRw98fX159913GTduXI5+M2bM4IknnmDfvn20atWKNm3asHv3bh5++GHWrVvn6bdgwQIeeeQR9uzZw3333UfXrl1JSkriySefZPr06Vezay/q119/BaBs2bKX7NOxY0cSEhKoWrUq3bt3p2rVqiQkJPDAAw9w+PBhIHt6UceOHQGoVasW/fr1o2LFinkSJ0BqaipdunThgw8+4IYbbqBnz560bduWU6dO8eqrr/LKK6/k6D9w4ED++9//4uvrS1xcHNdffz3Dhg1j4cKFucYuiH0tIlKoGCIiBeDw4cNGZGSk0b1791xtkZGRxpdffulpz8rKMtq0aWNERkYahw4dMgzDMFJSUox69eoZDRs2NPbv3+/pm5SUZDRu3NiIjo42srKyDMMwjMjISKNdu3aePs8995wRGRlpLFy40NOWnp5udO/ePVdMX375pREZGWk89dRThsPh8LT/+uuvRnR0tNGkSRPDbrcbhmEYGzZsMCIjI42bbrrJ2Lp1q6fv2bNnjYYNGxq33nqrkZ6ebhiGYezfv9+4+eabjXvvvdc4efKkp+/BgweN2rVrG23btjUMwzB+++03o0aNGkbr1q2N06dPe/plZGQYXbt2NapXr27s2bPnkvv6448/NiIjI43JkydfcPmRI0eMevXqGVFRUcZPP/2U472MGzfO069nz55GZGSkMX/+/Bzrz5kzx4iMjDR69uzpabvQ+nllxowZF4zj2LFjRo0aNYw77rjD07Z06VIjMjLS6Nu3r+ffg2EYxuzZsz3/1g4fPmwYRt7saxGRokZX/kXE6ypVqpSjQozNZuP2228H4ODBgwCsWbOG1NRUHnroIapUqeLpGx4ezpAhQ/jPf/7DuXPnco2dlZXFV199xY033ui5Og0QEBDAM888k6v/+W8Qhg0bhtX6Z02ESpUqERMTw4kTJ1i/fn2OdW677Tbq1KnjeR0cHEydOnWw2+389ttvACxduhSn00nfvn0pU6aMp+91113H4MGD6dSpEw6Hg8WLF5OVlcWAAQMICwvz9PPz82PAgAG43W4++eSTS+zNP23atIkpU6Z4fl577TWeffZZ2rVrR2pqKo899hhRUVEXXPf48eNs2LCB+vXr8+CDD+ZYFhcXx6233sqGDRtyTaHJD40bN2b06NF06NAhR/s111xDpUqVckwPO79vBg8ejM1m87THxsbm+HcD5Om+FhEpKlTtR0S87vrrr8/VFhwcDGQn75BdShKgdu3aufred999Fx378OHDnDt3jho1auRaVqNGjRwJIsCuXbvw9fVlzpw5ufofOHAAgB9//JE777zziuI/XwHnUvHHxMR4/r5z504gex763r17c/Q7f3JzfqzL2bRpE5s2bfK8tlqthISEeKY+XWq/nb+/4WKVgOrWrcuOHTv46aefuPbaa68onn/q5ptv5uabbyY9PZ0ffviBQ4cOcfDgQXbs2MGhQ4dyPK9g586dhIaGUrly5RxjmM1m6tSp4zmG5/tC3uxrEZGiQsm/iHidj49PrjaTyZTj9dmzZwEICgq6qrHPnDkDQGBgYK5lFosl13ipqak4nU6mTp162THPu1T8hmEAVx5/amoqAB9++OEVb/9i+vXr94/LnqalpQF/nsT83fl7BTIzM69q3CNHjlzwavpDDz1ESEjIBdex2+3897//JSEhwXMTdbly5bjtttsICwvj1KlTnr7Jycm5rvD/Pebz8nJfi4gUFUr+RaRICAgIACA9PT3XsqysLMxmc45pOueVKlUK+DPR+yvDMHJV5AkICCAwMJDVq1fnQdQ5x4Xs+P86xQSyE2gfHx/MZrOn3/Lly6lUqVKexnA1zp8s/bWqz1+dP5kJDQ29qnGPHj16wROrjh07XjT5nzhxInPnzqVVq1Z069aNqKgoz3Zbt26dI/kPCgrynLj83d/bC8u+FhEpSJrzLyJFQmRkJADbt2/Pteydd96hVq1aOaa4nFe5cmWCg4PZtm1brmW//PJLrivXUVFRHD9+PEdCed6qVat49dVX/9FUkEvFP27cOGrVqsXhw4c9c/B37NiRq9/BgweZNGkSK1euvOrtX62bbroJgO++++6Cyzdv3ozJZOKGG24Acn9TczENGjRgz549uX4uNXXo888/p3Tp0rz++us0aNDAk/hnZmZ6yrWe/4bllltu4fjx4xc8afnhhx9yvC4s+1pEpCAp+ReRIqFFixYEBAQwa9Ysjh496mlPSUkhISGBwMDAC86nt9lstG3bll9//ZX//e9/nvasrKxcJSIh+wq0YRiMHTvWc78BZF8Bj4+PZ+bMmZ4rxlejbdu2mM1mpk+fTnJysqf9119/ZcmSJVSqVIlKlSrRrl07LBYLr732Wo4TEKfTydixY3n33XcvW5s/L1SoUIEGDRqwa9cu5s2bl2PZggUL2Lp1Kw0aNKB8+fIAnm9d8uMpv76+vtjtds+3DQAul4sXXnjBc/J2frsPPPAAhmHw4osv5rgX4NNPP82V5BeWfS0iUpA07UdEioTQ0FBGjhzJkCFD6NixI3fffTeBgYEsXbqUU6dOMXXq1AvOvQcYNGgQiYmJTJw4kXXr1lGtWjUSExNJSUnB19c3R98HHniAlStXsmzZMvbs2UOTJk1wOp0sWbKElJQUnn766Vw3k16JatWq0a9fPyZPnkz79u256667MAyDL7/8ErvdzoQJE4Dsm4efffZZJk6cSNu2bWnevDmlSpVi7dq17Nu3j7vuuot27dpd/Q78B8aMGUO3bt2Ij4/nq6++Iioqip9//plvv/2WsmXLMnbsWE/f8w8NW7JkCQEBAXTs2JEbb7wxT+K4//77effdd+nUqRMtWrTA6XSybt06Dhw4QHh4OKdPnyYlJYWyZcvSpk0bPv30Uz777DN++eUXGjRowKFDh1i9ejVhYWEkJydjsViAwrWvRUQKiq78i0iR0bFjR959911uuukmli1bxvz587n22muZOXMmLVu2vOh6pUqVYt68ecTExLBnzx4SEhKIiIjgvffey3XCYDKZmDx5MsOGDcPf358FCxawZMkSbrjhBt544w0ef/zxfxz/E088wauvvso111zjSVBr1qzJ7NmzqVWrlqffI488wsyZM6levTpfffUVCQkJWK1Wnn/+eSZPnnzBexvyw/XXX8/HH39Mly5d+OWXX5g9ezYHDx6kR48eLFq0KMdJUMWKFRk4cCAmk4k5c+ZccHrTPzVo0CD69++P2Wxm7ty5LF++nIoVK/LOO+/Qu3dvILsULGQfvylTptC7d2/OnDnDnDlzOHz4MC+++CINGzYEskt5nldY9rWISEExGecnSoqIiBRxv/32G8HBwResqtS9e3d27tzJtm3brvgeBRGR4kZX/kVEpNh46623qFevXq6bv7///nu2bNlCdHS0En8RKdF05V9ERIqNXbt20bVrV2w2G/fccw/lypXjyJEjLF++HB8fHxISEqhWrZq3wxQR8Rol/yIiUqzs3r2bGTNm8P3335OUlER4eDiNGjWib9++/+hmbRGR4kTJv4iIiIhICaE5/yIiIiIiJYSSfxERERGREkLJv4iIiIhICaHkX0RERESkhFDyLyIiIiJSQij5FxEREREpIZT8i4iIiIiUEEr+RURERERKCCX/IiIiIiIlhNXbARSIlH3w3Svw42zISgOfILipO9R/GkKreTs6EREREZECYTIMw/B2EPnqwBJY3Bncjuyf88y27J92H0GV1t6LT0RERESkgBTvaT8p+7ITf+e5nIk/ZL92nstenrLvqodeu3YtCQkJV7XOwoULWbFiRa72O+6446q3n59mz55doNu70Pu/2L4SERERkX+ueF/5X94XdrydO/H/K7MNbn0cWkwtuLj+5o477uDbb7/12vb/rqDjKWzvX0RERKS4Kh5z/r97BdbHgyPt6td1O+CHN7J//soWBI3is+8LuICFCxeyf/9+YmJiePrppylfvjyHDx/m1ltvZfTo0SQlJfH888+TmpqKYRhMmjSJzz77jIiICLp06cKIESP45ZdfqFSpEllZWQD89ttvjBgxArvdjq+vL2PHjsXlcl3x+KVLl2bYsGEkJycDMHz4cKKiojwxb9y4kZdffhmbzUaXLl3w8/Njzpw5nuWvv/46CQkJnDlzhvj4eIYNG8aoUaM4dOgQbrebgQMH0qBBA0//I0eO8OSTT1KmTBlOnDhB06ZNGTRo0EXfR58+fQgNDaVp06b06tXLM05WVpZnvaioKOLj45k6dSoRERFUrVqVt956C5vNxpEjR7jvvvvo06fP1R9nERERESlGyf8/SfwvxZGWPe5Fkv+/OnjwIO+88w7+/v60aNGCU6dOMWPGDJo3b05sbCyJiYls377d03/t2rXY7Xbmz5/PsWPHWLZsGQCTJk2iR48eNGvWjMTERF5++WUGDRp0xePv2bOHhg0bEhcXx8GDBxkyZAjz5s3LEavdbmfBggUATJ8+nZkzZ+Lv78/IkSNZt24dffr0Yfbs2cTHxzN37lzCwsIYP348ycnJdO/enS+++CLHeEePHuWdd94hODiYuLg4du3axVtvvXXB93Hq1Ck+/vhjfHx8coyRmZnJM888Q8WKFXnyySdZuXJljuXHjh1j8eLFZGVl0aRJEyX/IiIiIv9Q8Uj+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JtOmTaNUqVIX3Q8iIiIiUgySf2+56667iI+P57PPPiM0NBSLxUJWVhY+Pj60atWK9evXc9111wEQHx/P4MGDcblcALzwwgs57lMICgqibt26dOzYkYCAAEJCQjh58iQPPPAAa9euJSYmhoiICPz8/LDZbAwePJj4+HjsdjuZmZkMGzbsqmLv3bs3Q4cOZfHixQCMHz+eRYsWAdCiRQu2bNnCgw8+SIUKFTxX0q9mmxcaPygoiGHDhjF//nzS0tLo16/fRde/2P6A7BOLZ555hvHjxzN8+HC6d+9OWloacXFxnpMHEREREbmwIl/tpzjbt28fP/30E23atCE5OZm2bduyatUqfHx8vB2aiIiIiBRBSv4LsXPnzvH000+TlJSEy+Wie/fudOzY0dthiYiIiEgRpeRfRERERKSE0CRpEREREZESQsm/iIiIiEgJoeRfRERERKSEUPIvIiIiIlJCFOk6/27D4LPNGWz4OYvfz7rzdOyIEDMNI324/zZ/zBd4kNXChQvZv38/zzzzTI72QYMGMWnSpH9UjvPUqVO88cYbxMfH/9OwRUREREQuqkhX+/l00zk+/y4zX7fRtr4f7aMDcrVfLPkXERERESmsivSV/w0/ZxXINi6U/AN8//33PPTQQ6SlpdG/f3/uvPNOmjdvzpIlSxg1ahQ+Pj4cPXqUkydPMnHiRG655RbPuqdPn2bgwIEYhoHD4WD06NEEBgby1FNP8cEHH9CrVy8AnE4nP/zwA0uXLuX48eO8+uqrWCwWKlWqxJgxY7DZbPm+D0RERESkeNCc/3/B39+f9957j5kzZzJmzBjc7pxTjypUqMA777xDjx49SEhIyLFs+/btBAcH89ZbbzF8+HDS0tI8y/z8/Jg1axYffPABFStWZNSoUVSqVIkRI0YwdepUZs+eTbly5fjkk08K5H2KiIiISPFQpK/8N4z0yfdpPw0jLz53v169ephMJkqXLk1wcDApKSk5lt90000AlC9fnq1bt+ZY1rRpUw4ePEjfvn2xWq306dMn1/hjx46lSpUqdO3alaSkJE6ePMnAgQMByMzM5I477vi3b09ERERESpAinfzff5s/QL7f8HsxO3bsALJv1D137hxhYWE5lpsucKPweRs3bqRs2bK8++67bNu2jf/+979MmDDBs/y1117DMAyeeOIJAMLCwihfvjxvvvkmwcHB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\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ - "build.plotIncidence(df_transformed, #Dataframe with transformed dataset\n", - " variable='age', #Which variable should be visualized\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" + "Clearly, for meaningful inspection, we need to visualize the data. Therefore, below are plots for **Predictor quality** and **Correlation Matrix**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Model building\n", - "The next step is to find the best model. The library allows to build a model and specify which variables will be forced or excluded." + "### Create pig tables " ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ - "df_model1 = build.fit_model(df_transformed, #Dataframe with transformed data \n", - " df_unisel, #Dataframe with univariate selection\n", - " modeling_nsteps=30, #How many variables will be used for modelling\n", - " forced_vars=['scont_1','scont_2'], #List of variables forced to be in the models, list (or None))\n", - " excluded_vars=None, #List of variables to be excluded, list (or None)\n", - " name='Experiment', #Name of the model\n", - " verbose=False, #Verbose=True will print extra output about skipped models\n", - " positive_only=True) #positive_only=True [recommended, set automatically]:\n", - " #(whether only positive coeficients should be considered)" + "columns = [col for col in basetable.columns if col.endswith(\"_bin\") or col.endswith(\"_processed\")]" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 36, "metadata": {}, + "outputs": [], "source": [ - "We can inspect what the output is. It contais all the necessary information about the model building:\n", - " * model coefficients\n", - " * predictors from forward selection\n", - " * AUC performance on train/selection/validation set\n", - " * cumulative response/gains\n", - " * variable importance\n", - " * etc.\n", - " \n", - "Ever row contains one step in the forward selection process, thus one model with selected variables." + "pigs = generate_pig_tables(basetable[basetable[\"split\"] == \"train\"],\n", + " id_column_name=\"ID\", target_column_name=\"TARGET\",\n", + " preprocessed_predictors= columns)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -1725,362 +890,278 @@ " \n", " \n", " \n", - " step\n", - " coef\n", - " intercept\n", - " auc_train\n", - " auc_selection\n", - " auc_validation\n", - " predictors_subset\n", - " last_var_added\n", - " pred_training\n", - " pred_selection\n", - " pred_validation\n", - " cum_response\n", - " cum_gains\n", - " importance\n", + " variable\n", + " label\n", + " pop_size\n", + " avg_incidence\n", + " incidence\n", " \n", " \n", " \n", " \n", + " 1\n", + " sflag_4_processed\n", + " 1\n", + " 0.499198\n", + " 0.239277\n", + " 0.243216\n", + " \n", + " \n", " 0\n", + " sflag_5_processed\n", + " 0\n", + " 0.503395\n", + " 0.239277\n", + " 0.235426\n", + " \n", + " \n", + " 1\n", + " sflag_5_processed\n", " 1\n", - " [[4.8883387625778765]]\n", - " [-2.326640746611817]\n", - " 0.510697\n", - " 0.505875\n", - " 0.502901\n", - " [D_scont_2]\n", - " D_scont_2\n", - " [[0.768583054973849, 0.231416945026151], [0.76...\n", - " [[0.768583054973849, 0.231416945026151], [0.76...\n", - " [[0.7638833845524492, 0.23611661544755083], [0...\n", - " [21.77, 24.49, 26.36, 26.58, 24.83, 25.11, 24....\n", - " [0, 0.91, 2.05, 3.31, 4.45, 5.19, 6.3, 7.04, 8...\n", - " {'scont_2': 0.9999602164427089}\n", + " 0.496605\n", + " 0.239277\n", + " 0.243180\n", + " \n", + " \n", + " 0\n", + " workclass_processed\n", + " ?\n", + " 0.057430\n", + " 0.239277\n", + " 0.093880\n", " \n", " \n", " 1\n", - " 2\n", - " [[5.6064185594945055, 5.4040124941185645]]\n", - " [-3.792097034819709]\n", - " 0.515900\n", - " 0.500047\n", - " 0.495731\n", - " [D_scont_2, D_scont_1]\n", - " D_scont_1\n", - " [[0.762753676141239, 0.23724632385876096], [0....\n", - " [[0.762753676141239, 0.23724632385876096], [0....\n", - " [[0.7572651247462866, 0.24273487525371337], [0...\n", - " [38.78, 40.48, 40.91, 35.26, 28.24, 23.86, 20....\n", - " [0, 1.63, 3.39, 5.13, 5.9, 5.9, 5.99, 6.07, 7....\n", - " {'scont_2': 0.7487108741471443, 'scont_1': 0.6...\n", + " workclass_processed\n", + " Federal-gov\n", + " 0.029790\n", + " 0.239277\n", + " 0.388316\n", " \n", " \n", " 2\n", - " 3\n", - " [[5.236067628970329, 5.8321544245240755, 6.589...\n", - " [-5.805207410483017]\n", - " 0.784975\n", - " 0.779989\n", - " 0.774255\n", - " [D_scont_2, D_scont_1, D_relationship]\n", - " D_relationship\n", - " [[0.5571763524627912, 0.4428236475372088], [0....\n", - " [[0.5571763524627912, 0.4428236475372088], [0....\n", - " [[0.5502313500711142, 0.4497686499288857], [0....\n", - " [43.54, 46.94, 45.68, 45.66, 44.75, 45.57, 45....\n", - " [0, 1.82, 3.93, 5.73, 7.64, 9.35, 11.43, 13.34...\n", - " {'scont_2': 0.03837649674025111, 'scont_1': 0....\n", + " workclass_processed\n", + " Local-gov\n", + " 0.065791\n", + " 0.239277\n", + " 0.303423\n", " \n", " \n", " 3\n", - " 4\n", - " [[5.829258106552866, 7.1572932587913565, 6.910...\n", - " [-7.817413679010036]\n", - " 0.858115\n", - " 0.856198\n", - " 0.853614\n", - " [D_scont_2, D_scont_1, D_relationship, D_educa...\n", - " D_education\n", - " [[0.07688945912663137, 0.9231105408733686], [0...\n", - " [[0.3481266903051372, 0.6518733096948628], [0....\n", - " [[0.3410603659913337, 0.6589396340086663], [0....\n", - " [82.99, 83.33, 81.59, 80.75, 78.17, 75.57, 74....\n", - " [0, 3.48, 6.99, 10.24, 13.52, 16.34, 18.96, 21...\n", - " {'scont_2': 0.02268402069355277, 'scont_1': 0....\n", + " workclass_processed\n", + " Other\n", + " 0.040915\n", + " 0.239277\n", + " 0.262719\n", " \n", " \n", " 4\n", - " 5\n", - " [[6.308402146245059, 6.6555736472448945, 6.864...\n", - " [-8.726303718168493]\n", - " 0.871315\n", - " 0.871706\n", - " 0.868557\n", - " [D_scont_2, D_scont_1, D_relationship, D_educa...\n", - " D_capital-gain\n", - " [[0.09781071808143116, 0.9021892819185688], [0...\n", - " [[0.39754912429362177, 0.6024508757063782], [0...\n", - " [[0.3894682127983645, 0.6105317872016355], [0....\n", - " [94.56, 88.44, 86.36, 87.39, 84.04, 82.5, 79.7...\n", - " [0, 3.96, 7.41, 10.84, 14.63, 17.56, 20.7, 23....\n", - " {'scont_2': 0.024497630031203383, 'scont_1': 0...\n", + " workclass_processed\n", + " Private\n", + " 0.690599\n", + " 0.239277\n", + " 0.218006\n", + " \n", + " \n", + " 5\n", + " workclass_processed\n", + " Self-emp-inc\n", + " 0.034977\n", + " 0.239277\n", + " 0.541463\n", + " \n", + " \n", + " 6\n", + " workclass_processed\n", + " Self-emp-not-inc\n", + " 0.080498\n", + " 0.239277\n", + " 0.274693\n", " \n", " \n", "\n", "" ], "text/plain": [ - " step coef \\\n", - "0 1 [[4.8883387625778765]] \n", - "1 2 [[5.6064185594945055, 5.4040124941185645]] \n", - "2 3 [[5.236067628970329, 5.8321544245240755, 6.589... \n", - "3 4 [[5.829258106552866, 7.1572932587913565, 6.910... \n", - "4 5 [[6.308402146245059, 6.6555736472448945, 6.864... \n", - "\n", - " intercept auc_train auc_selection auc_validation \\\n", - "0 [-2.326640746611817] 0.510697 0.505875 0.502901 \n", - "1 [-3.792097034819709] 0.515900 0.500047 0.495731 \n", - "2 [-5.805207410483017] 0.784975 0.779989 0.774255 \n", - "3 [-7.817413679010036] 0.858115 0.856198 0.853614 \n", - "4 [-8.726303718168493] 0.871315 0.871706 0.868557 \n", - "\n", - " predictors_subset last_var_added \\\n", - "0 [D_scont_2] D_scont_2 \n", - "1 [D_scont_2, D_scont_1] D_scont_1 \n", - "2 [D_scont_2, D_scont_1, D_relationship] D_relationship \n", - "3 [D_scont_2, D_scont_1, D_relationship, D_educa... D_education \n", - "4 [D_scont_2, D_scont_1, D_relationship, D_educa... D_capital-gain \n", - "\n", - " pred_training \\\n", - "0 [[0.768583054973849, 0.231416945026151], [0.76... \n", - "1 [[0.762753676141239, 0.23724632385876096], [0.... \n", - "2 [[0.5571763524627912, 0.4428236475372088], [0.... \n", - "3 [[0.07688945912663137, 0.9231105408733686], [0... \n", - "4 [[0.09781071808143116, 0.9021892819185688], [0... \n", - "\n", - " pred_selection \\\n", - "0 [[0.768583054973849, 0.231416945026151], [0.76... \n", - "1 [[0.762753676141239, 0.23724632385876096], [0.... \n", - "2 [[0.5571763524627912, 0.4428236475372088], [0.... \n", - "3 [[0.3481266903051372, 0.6518733096948628], [0.... \n", - "4 [[0.39754912429362177, 0.6024508757063782], [0... \n", - "\n", - " pred_validation \\\n", - "0 [[0.7638833845524492, 0.23611661544755083], [0... \n", - "1 [[0.7572651247462866, 0.24273487525371337], [0... \n", - "2 [[0.5502313500711142, 0.4497686499288857], [0.... \n", - "3 [[0.3410603659913337, 0.6589396340086663], [0.... \n", - "4 [[0.3894682127983645, 0.6105317872016355], [0.... \n", - "\n", - " cum_response \\\n", - "0 [21.77, 24.49, 26.36, 26.58, 24.83, 25.11, 24.... \n", - "1 [38.78, 40.48, 40.91, 35.26, 28.24, 23.86, 20.... \n", - "2 [43.54, 46.94, 45.68, 45.66, 44.75, 45.57, 45.... \n", - "3 [82.99, 83.33, 81.59, 80.75, 78.17, 75.57, 74.... \n", - "4 [94.56, 88.44, 86.36, 87.39, 84.04, 82.5, 79.7... \n", - "\n", - " cum_gains \\\n", - "0 [0, 0.91, 2.05, 3.31, 4.45, 5.19, 6.3, 7.04, 8... \n", - "1 [0, 1.63, 3.39, 5.13, 5.9, 5.9, 5.99, 6.07, 7.... \n", - "2 [0, 1.82, 3.93, 5.73, 7.64, 9.35, 11.43, 13.34... \n", - "3 [0, 3.48, 6.99, 10.24, 13.52, 16.34, 18.96, 21... \n", - "4 [0, 3.96, 7.41, 10.84, 14.63, 17.56, 20.7, 23.... \n", - "\n", - " importance \n", - "0 {'scont_2': 0.9999602164427089} \n", - "1 {'scont_2': 0.7487108741471443, 'scont_1': 0.6... \n", - "2 {'scont_2': 0.03837649674025111, 'scont_1': 0.... \n", - "3 {'scont_2': 0.02268402069355277, 'scont_1': 0.... \n", - "4 {'scont_2': 0.024497630031203383, 'scont_1': 0... " + " variable label pop_size avg_incidence incidence\n", + "1 sflag_4_processed 1 0.499198 0.239277 0.243216\n", + "0 sflag_5_processed 0 0.503395 0.239277 0.235426\n", + "1 sflag_5_processed 1 0.496605 0.239277 0.243180\n", + "0 workclass_processed ? 0.057430 0.239277 0.093880\n", + "1 workclass_processed Federal-gov 0.029790 0.239277 0.388316\n", + "2 workclass_processed Local-gov 0.065791 0.239277 0.303423\n", + "3 workclass_processed Other 0.040915 0.239277 0.262719\n", + "4 workclass_processed Private 0.690599 0.239277 0.218006\n", + "5 workclass_processed Self-emp-inc 0.034977 0.239277 0.541463\n", + "6 workclass_processed Self-emp-not-inc 0.080498 0.239277 0.274693" ] }, - "execution_count": 13, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df_model1.head(n=5)" + "# As you can see below, the pigs DataFrame is idealy suited to export to csv and load into Jan's Excel template!\n", + "pigs.tail(n=10)" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 39, "metadata": {}, + "outputs": [], "source": [ - "Clearly, we again need visual inspection. Therefore, below are plots for **AUC**, **Variable importance** and **Cumulative gain/response**.\n", - "\n", - "AUC plots is supposed to help choosing optimal number of variables in the model. Further down, it needs to be specified which model should be used." + "preselected_predictors = univariate_selection.get_preselected_predictors(df_auc)" ] }, { - "cell_type": "code", - "execution_count": 14, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAs8AAAI6CAYAAADYLaUdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzs3Xd8VFX+//HXnZreIJTQBSYCoQiIsBQFCaIigriiq+IuiO1rR9B1dxVcii4r/tZ1XcuqKyooAjaQqgKKLgrSS5Dea0yvM3N/fyQZEpKQoYQJw/vpIyZz75k755NJyHvOnHuuYZqmiYiIiIiIVMkS6A6IiIiIiFwoFJ5FRERERPyk8CwiIiIi4ieFZxERERERPyk8i4iIiIj4SeFZRERERMRPCs8ip+Gf//wniYmJJCYm8uqrr56y7fjx431t9+3bd0aPl5iYyI033lhm25EjR5g1a1aZbX369KFz585n9BjnwlNPPUViYiKbN28+o/t7PB7ef/99cnJyzmm/7rzzTt9zsHLlylO2veGGG0hMTKRPnz7ntA8ADzzwwDn/OahKXl4enTp1IjExkf/+97+Vtvv+++9JTEzkT3/60ymP16tXL1q3bl3hvoKCAj7//HOGDx9Or169SEpKokePHjz66KOsWbPmtPp9Op544gnf83uqj9///vfV1odzqaSerVu3BrorfsnLy+Odd94JdDdEzjtboDsgcqFatGgRDzzwQIX7TNNk4cKF5/wxjx8/Tv/+/enatStDhgzxbR82bBgFBQXn/PH81bdvXxo0aEDt2rXP6P6jRo1i3rx5DBw48Bz37IRFixZV+gJj165dF0xg8dfixYvJysoiNDSUjz/+uNoC5MGDB3n44YdZt24dDRs2pHv37sTFxbF7924WL17M/PnzefbZZ7ntttuq5fEBbrrpJhISEird36hRo2p77HOpX79+NGnShFq1agW6K3753e9+x/79+/nDH/4Q6K6InFcKzyJnID4+nk2bNrFv3z4aNmxYbv/q1as5fPgwYWFh53Q0NTc3l+zs7HLbAz2y1rdvX/r27XvG9z9+/Pg57E158fHxLFq0iD/+8Y8V7p8/fz52ux3DMKq1H+fTp59+SlRUFIMGDWLq1KmsXr2ayy677Jw+Rl5eHiNGjGD79u08+uijjBw5EpvtxJ+V7du3c9dddzFu3DgaNmxIz549z+njlxgyZEhA33k5V/r160e/fv0C3Q2/HTt2LNBdEAkITdsQOQNXX301UDS6V5EFCxYQGRkZFH/Qg8HVV1/N/v372bRpU4X7FyxYQLdu3XA6nee5Z9Xj6NGjfP/993Tt2pX+/fsD8PHHH5/zx/n3v//N9u3bue2227j//vvLBGeA5s2bM2HCBEzTrHKak4jIhULhWeQMdO3alejo6EqnZixatIg+ffpgt9vLbN+3bx+JiYkVTvcomU9dWSCfPXu2L7R/9dVXJCYmMnv2bKDsnOcFCxaQmJjIlClTyh2joKCAzp07c9111/m2paam8sILL3DttdfSvn172rdvz/XXX89rr72G2+0u8/iJiYnMmzePESNG0LZtW3r37s3evXsrnPNcWFjIu+++yy233EKnTp1ISkqid+/ePPPMM6SmpvraJSYm8uOPPwJw+eWXc+edd5bp7+uvv851111H27Zt6datG6NGjWLv3r0Vfo8qc8011wBU+Hzt3buXTZs2+dqczOv1Mm3aNAYNGkS7du3o1KkTf/jDH1i+fHm5th6Ph//85z9cc801tGvXjhtuuOGU03c2btzIAw88wBVXXEG7du248cYbmT59OqZpnlZ9J/viiy/weDz06NGDjh07Uq9ePebNm0dWVtZZHbc0r9fL7NmzMQyD++67r9J2vXr1YtSoUTzwwANnXdfZ2r59u+85PHLkiG+7aZrcddddJCYm8uWXXwLw0ksvkZiYyLZt23juuefo2rWr77lfvXp1uWN7vV4++OAD389Jly5deOCBB9iyZUuZdh9//DGJiYksXLiQP/zhDyQlJdGnTx/2799fbs7z7t27SUxM5I033mD+/Pm+Y/fp04d3330XgJUrV3LbbbfRvn17+vTpw7/+9S88Hs8Z9a1k/vtnn33Gxx9/zA033EDbtm258sor+dvf/kZeXl6Zfh0+fJi0tDS/5syLBBOFZ5EzYLfb6d27N6tXry731uW6devYv3+/b8TvXGnVqhXDhg0DoFmzZjz44IO0atWqXLvevXsTFRXlCwGlLVu2jMzMTN/c4szMTG655RamTp1KixYtGDZsGAMGDODo0aO89NJLvPjii+WOMX78eFJTU7nzzjtp27ZtpfNJR40axcSJE7HZbNxyyy0MHToUh8PBRx99xMiRI33tHnzwQRo0aADA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unTpQkZGBvPnzyc3N5fJkyef0R/9fv36MXPmTDZu3HjKKRtQNPXl66+/ZsGCBQwcOJBevXqRk5PDV199RWZmJn/5y198J3O1b9+e4cOH89ZbbzFo0CCuuuoqDh06xOLFi2ncuHGZMBwVFcX48eN54oknGDx4MH379qVOnTr8+OOPrF+/nrZt2zJ8+PDTqqtkysbJ7y6UFhYWRv/+/Zk9ezYzZ87kscceA+D//u//WLVqFTNmzGDFihX06NGDyMhIDh06xJIlS0hLS+P++++nS5cuZY4XGhrK22+/zYgRI3jppZeYMWMGPXr0IDw8nK1bt7J8+XIMw+DJJ588q8u3V2XWrFnlVow42b333ovdbufpp58mJyeHSZMm+X5+xo0bx8CBAxk7diyXX355mTnq6enpDBo0iOTkZDIyMliwYAGhoaH89a9/9bW5+eab+frrr5k3bx5btmyhR48eFBYWMn/+fNLS0hgzZsxpnSB4LlVn3+rWrcu+ffsYM2YM3bt3P613SkQuZArPImcpMjKSbt26sWzZskqvUlfa448/TmhoKJ9++invvfceTZs25S9/+QsxMTFVhmeHw8EzzzzDyy+/zLRp06q8BHidOnXo1q0by5cv54Ybbii3/7HHHiM8PJzPP/+cadOmERcXR/Pmzfnzn//M9u3bmThxIkuXLuW3v/1t1d+IUq666ipeeukl3nzzTb744gtCQkJo1KgRzzzzDJdddhmDBw9m6dKlvqkD9913H9u3b2f58uXs2rWLYcOGERISwtSpU/nPf/7Dl19+6au3Y8eO3HvvveWCnL+6detGVFQU2dnZJCcnn7KtYRj8v//3//jggw+YOXMmM2fOJDQ0lA4dOjBixAi6du1apv2YMWNo1qwZU6dOZcaMGdSrV4/x48eTkpLC1KlTy7S99tprqVevHq+//jrffvstubm5NGjQgAceeIARI0ac1hJwBw4c4KeffqJevXpVfl+GDBnC7NmzmT17Ng8//DBWq5WIiAjef/99pk+fzrx585g3bx6ZmZnUrl2bTp06cfvtt1e6GkWzZs347LPPmDVrFvPmzePrr78mLS2N2NhYrr/+eu66664yy55Vh5IrbZ7K8OHD+fjjj/nxxx+56qqrypw4e8kll3DvvffyyiuvMH78eCZPnuzbN3bsWH744Qe++OIL3zsXjzzySJkpKBaLhVdeeYX333+fTz75hBkzZhAaGkrLli0ZPnx4QC8gUp19GzNmDH/605/48ssvSU1NVXiWi4Zh6tRZERGRMl566SVee+01XnvttTJrLIuIaM6ziIiIiIifFJ5FRERERPyk8CwiIiIi4ifNeRYRERER8ZNGnkVERERE/KTwLCIiIiLiJ4VnERERERE/KTyLiIiIiPhJ4VlERERExE8KzyIiIiIiflJ4FhERERHxk8KziIiIiIifFJ5FRERERPyk8CwiIiIi4ieFZxERERERPyk8i4iIiIj4SeFZRERERMRPCs8iIiIiIn5SeBYRERER8ZPCs4iIiIiInxSeRURERET8pPAsIiIiIuInhWcRERERET8pPIuIiIiI+EnhWURERETETwrPIiIiIiJ+UngWEREREfGTwrOIiIiIiJ8UnkVERERE/KTwLCIiIiLiJ4VnERERERE/KTyLiIiIiPhJ4VlERERExE8KzyIiIiIiflJ4FhERERHxk8KziIiIiIifFJ5FRERERPyk8CwiIiIi4ieFZxERERERPyk8i4iIiIj4SeFZRERERMRPCs8iIiIiIn6yBboDp+Po0cyAPG5EhJOsrPyAPPa5ohpqhmCoAYKjDtVQM6iGmiMY6lANNUMw1AAQHx9Z4XaNPPvBZrMGugtnTTXUDMFQAwRHHaqhZlANNUcw1KEaaoZgqOFUFJ5FRERERPyk8CwiIiIi4ieFZxERERERPyk8i4iIiIj4SeFZRERERMRPCs8iIiIiIn5SeBYRERER8ZPCs4iIiIiInxSeRURERET8pPAsIiIiIuInhWcRERERET8pPIuIiIiI+MkW6A6IiIiIXMz2Z+/j453TWXxgAbnuXEJtofRNuIbfNruNBuENA909vwRDDf6qlvDs9XoZO3YsKSkpOBwOxo8fT5MmTXz733jjDebOnUtERAR33303vXv3JjU1lSeeeIK8vDzq1KnDpEmTCA0NrY7uiYiInJWDh1Ywe91k5hfsI8cwCDNN+jsaclO70dSvd0Wgu+e3YKjjQq9hxZEfGPfz07i9+biLt+W4c/hyzycs3Pclz3acyBV1ugW0j1UJhhpOR7VM21i8eDEFBQV89NFHjBo1iueff963LyUlhTlz5jBjxgzefvttXn75ZXJzc3n11VcZMGAA06ZNo3Xr1nz00UfV0TUREZGzsmrDa4xY+SifFe4n22LB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RYueV1JSaBOR119/Ve+886ZsNotOPvlU3XLLbTp48ID+8Id5crurZbPFBbsW1tmwYb0WLVoos9ms9PT+uvPOe+T1ejRv3gM6cOCAPB6Pbr/9Dr311grt3LlDixc/I5/Pp169emnKlCv0+ON/0pdf5kmSJky4SFdddY3mzp0tq9WqAwf269ChEt1992wNGxba9fBEEJ4beb5nanC2+VhqDEN/7ZGk+w+VHvf5TO6jMh06Ksuhr5u93W+Y5HOkhQRrb1J/+Wpnsb2J/eWP7ym1cYOfbdf/KvlfN0neGv3Xapav9vGZHo/SX52koxf9Re6B32vTcwIAgI5hz1vUruDcElONS/a8RccNz2+++U9NnjxFmZlZslqt2rx5U7P3q4seJSXFSk/vH3JbcnJyk/u//fZK/c//3KFzz/1/Wrz4BXk8Hj355KO64oqrddZZ52jduv/o6aef0MyZP5cU6Mb84INz9dRTz6pnz1Q988xTevvtlaqsrNBJJ6XrgQfm65tvtmnduv9o+vSfaPv2bbrhhhl67rm/SJLWrv239u/fp0WL/iav16uf/eynOv30b0uSTjqpn+688x6tWPEPrVjxuu644+4m9bZXWMKzz+fT7NmztXXrVtlsNuXm5mrgwIHB23Nzc/XFF18oISFw+sPChQtVU1OjX//616qqqlJaWprmz58vu73zzyF+KzFBHnlbvI/XMLQ8OUlr0garv623BliSNMBvVabHq8yqSmVWHJKjfL/Mzn0yvNUtPpfh98nsOiCz64CsWt/sffyW+MAsdWK6vEnpgWBdF7KT+sub2E+y1P9emY7sDK6XkqR8W0Lwthy3O7he6vDU99mxCwBABFSOntnumedj8VkTVDl6Zov3OXr0qD79dK1KSw9r+fJlcrmcev31ZbLbHaqpcYfc1+sN5KG+ffupqOighg7NCd725Zd5Sk3tpYyMAcFrd999n5YufUnPPvukhg0bKUn65pttevHFxXr55cAsucVSHz3Lykp16FCJ7r33N5IC65q/850zVVZWqjPPPFuSNHjwEA0ePET79+9rMpZdu3botNNGyzAMWSwWjRx5inbu/EaSNHToMElSWlpfffXVxlb87rVeWMLzqlWr5Ha7tWzZMuXl5WnBggV66qmngrdv3rxZzz77rFJT6/uy5+bmatKkSbr88su1aNEiLVu2TNdff304ymtR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\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ - "build.plotAUC(df=df_model1, #Dataframe with models\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" + "## Model building\n", + "The next step is to find the best model. COBRA allows you to build a model and specify which variables will be forced or excluded." ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 40, "metadata": {}, + "outputs": [], "source": [ - "For example below, we plot variable importance for 5th model with 5 variables.\n", + "forward_selection = ForwardFeatureSelection(pos_only=True)\n", "\n", - "Keep in mind that the `fit_model()` method will return multiple models, each with different set of variables." + "forward_selection.fit(basetable[basetable[\"split\"] == \"train\"], \n", + " \"TARGET\", \n", + " preselected_predictors,\n", + " forced_predictors=[\"scont_1_enc\", \"scont_1_enc\"])" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 41, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvAAAAHoCAYAAAAvy9sRAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3Xd4VGXe//HPpIBAIiESAZFlIZhIExBEehGRCPKgLhAIyYINRUCKlAgIeZAmwqLgiuAuHQwtuKKsvaC4FCkSlFWXonQSQ0IKIWXu3x/8mGcjSQZkkuHG9+u6vC6Ycs733EzwzcmZicMYYwQAAADACj7eHgAAAADA5SPgAQAAAIsQ8AAAAIBFCHgAAADAIgQ8AAAAYBECHgAAALAIAQ+gRMTExGjhwoWX3L5o0SINGjToirb1yiuv6K233ir2MQkJCXryySeLnOW99967on326NFDZ8+evaLneMKECRO0b9++Ut/vRR9//LGmTJnitf1LUmJiou655x63jyvqz3XevHlq0aKFevToUeC/WbNmlcS4BZTW+qWnp+vPf/7zVW3D06+1Tz75RM2bNy+w5hkZGR7bPoD/4+ftAQBcn6KiovTyyy9r4MCBBW5fs2aNJkyYcEXbGjZsmCdHuyz/+Mc/Sn2fkvTVV18pMjLSK/uWpE6dOqlTp05e27+ndO3aVRMnTiz1/ZbW+qWlpSkxMfGqtuHp19ru3bv16KOP6qmnnvLYNgEUjoAHUCI6d+6sadOm6euvv1azZs0kSdu3b5cxRq1bt5bT6dS0adP0zTffKDMzU8YYTZkyRU2bNlVsbKxSU1N15MgRdejQQb/88otuu+02PfbYY1q3bp1Wr16t3NxcpaWl6YknnlBUVJQkKSkpSY899phOnz6t6tWr64UXXlBISEiBuXbt2qVZs2bp3Llz8vHx0ZAhQ9SxY8dL5g8PD9e//vUvffbZZ/rggw/kdDp1/PhxValSRb1799aKFSt0+PBhPfLII3r00UeVkJCg9957r8DjZsyYoSpVqujkyZOKi4vTsWPHZIzRgw8+qMcff1xHjx5Vv379FBoaqmPHjqlp06Y6ffq0Ro0apZkzZ8oYo5deekk5OTlKSkpSq1atNG3aNB09elQDBgxQ+/bt9c033+js2bMaPXq0OnfurLy8PL300kv67LPP5OvrqyZNmmjSpEkqU6aM5s+f7zqW6tWra9KkSapSpUqB405ISND777+vBQsWKCYmRo0bN9auXbt04sQJtWzZUi+88IJ8fAp+8zYmJkb169fXnj17lJKSot69eys5OVnbt2/XuXPn9PLLLys8PLzIdZCkVatWaenSpQoICFBYWFiB7V/O3JcrOztbf/rTnxQVFaV+/fpp7dq1WrZsmdasWaOBAweqXr162rlzp86cOaMePXromWeeKfZ1k5CQoHXr1uncuXMKCAjQQw89VGD9Lmdd0tPTNXXqVP3www/Kzc1Vy5YtNWbMGPn5+alhw4YaOHCgtmzZotOnT+vxxx9XVFSUnnvuOWVnZ6tHjx5KSEiQr69vocebl5enF154Qbt27ZK/v79uvfVWTZ8+XQsXLizwWqtdu3aRM9SrV09PPPGEvvjiC2VlZWnkyJG67777LtnX7t275efnp02bNikgIEAjRozQXXfd9Zv+nAC4YQCghMydO9eMHTvW9fuRI0eaJUuWGGOM2bVrlxk6dKjJz883xhizYMEC8+STTxpjjBk7dqzp37+/63ljx441f/vb30xGRobp3bu3SUlJMcYYs3v3btO4cWNjjDHr1683jRs3NocPHzbGGDN79mwzbNgwY4wx0dHR5p///KdJTU019913nzly5IgxxpiTJ0+adu3amWPHjl0ye1hYmPnll1/M+vXrTdOmTc3x48dNfn6+6dq1q2vu/fv3m4YNG5r8/HzX/g8ePGiMMeall14yQ4cONcYY069fP7No0SJjjDFnz5413bt3N++88445cuSICQsLMzt27HDtt2PHjmbv3r3GGGNGjBhhtm7daowxJiMjw9x9990mMTHR9bxPPvnEGGPMe++9Zzp06GCMMWbp0qWmX79+5ty5cyY/P98MGzbMbNiwwWzYsMEMHz7c5ObmGmOMiY+PN48//vglx71+/XozcOBA17o988wzJj8/36Snp5s2bdqYf/3rX5c8Jzo62gwZMsQYY8yePXtMWFiY+fjjj40xxkydOtVMmDCh2HX47rvvTMuWLc3p06eNMcY8//zzpmPHjsYYU+zcF/9cf23u3Lnm7rvvNv/zP/9T4L/NmzcbY4z597//bZo3b24+++wz06pVK3PgwAHX9p544gmTk5Nj0tLSTJcuXcwnn3xS7Otm/fr15q677jLp6emFrt/lrEtsbKxZtmyZMcaYvLw8M2rUKLNw4UJjzIXX4fLly40xxiQmJpoGDRqY7Oxsc+TIEddrvzg7duwwERERxul0GmOMmTlzptm5c6cxpuBrzd0M8+fPN8YYs3//ftO0aVPzyy+/XLKvwYMHm3/+85/G6XSaHTt2mObNm5sTJ064nRHAleMMPIAS07t3b3Xr1k0ZGRnKy8vTl19+qbi4OElSkyZNVLFiRcXHx+vIkSPatm2bKlSo4Hpu06ZNL9lehQoV9Prrr+vzzz/X4cOH9e9//1tZWVmu+1u1aqWaNWtKknr27KmePXsWeP6ePXuUlJSkwYMHu25zOBz6/vvvdcsttxR5HA0bNlS1atUkSbfeeqvatGkjHx8f1ahRQ+fPn9e5c+ckSa1bt1atWrVcx96jRw9lZWVp165dWrRokSQpMDBQDz/8sDZv3qxGjRrJz89PjRs3LnS/M2bM0ObNm/X666/r4MGDOn/+vLKyshQUFCR/f3+1b99eklSvXj2lpqZKunBZRI8ePXTDDTdIkl5++WVJFy5DSkxM1J/+9CdJktPpdM1dnI4dO8rHx0cBAQGqWbOm0tLSCn1c586dJUk1atSQJLVt21aS9Ic//EHbt28vdh1OnTql1q1bu75bEhkZqS+//FKS9Omnn/6muYu7hCY8PFxDhgzRk08+qRkzZqh27dqu+yIjI+Xv7y9/f39FREToyy+/lI+PT5Gvm4vbCwgI+E3rIkmfffaZEhMTtW7dOkkXvkvw3y5eklO/fn3l5OQUeM27ExYWJl9fX/Xq1Utt2rRRly5ddMcdd1zyOHczREdHS5Juv/12hYWFaceOHerSpUuBx7z66quuXzdr1kxNmjTRli1bXH92ADyHgAdQYqpUqaJWrVpp06ZNysrKUpcuXRQYGCjpQjBMnTpVjzzyiDp16qTatWvr7bffdj23fPnyl2zv5MmTioyMVO/evdW0aVNFRETo008/dd3/35cROJ1O+fkV/CsuPz9foaGhWrt2reu2U6dOKTg4uNjjKFOmTIHf/3q7Re3f19dXTqdTxpgCj3M6ncrLy3Ntu6jtRUdHKzw8XG3bttX999+vb775xrUtf39/16UsDoejyNmSk5PldDrldDpdl19IUk5OTpEx/t8u/kPg4n5+fSwX/XqN/P39Lznm4tbhv+/79Tr+lrnd+fHHH1W5cmV98803evDBB123//f6GWPk4+NT7Otm48aNhb5WL3K3LtKFY3zllVcUGhoqSTp79myBP9OyZctK+r8/56L+DApz44036h//+Id27dqlrVu3avjw4XrsscfUr1+/K5qhsNf2fzt79qxWrVqlJ598ssCcRb22AVwdPoUGQInq16+fNm7cqLfeeqtANGzZskUdO3ZUVFSUGjRooI8++kj5+fnFbmvfvn0KDg7W008/rTZt2rji/eLztm3bpuPHj0uS4uPj1a5duwLPb9y4sX766Sft2LFDkrR//3516dJFp06d8sixbt261bWt+Ph4dezYUQEBAWrUqJFWrlwp6cKnh7z11ltq1apVodvw9fVVXl6ezp49q8TERI0aNUr33XefTp48qZ9//llOp7PYGVq2bKl33nlHOTk5cjqdiouL07vvvqs2bdpo3bp1rk8FeeWVVzRmzBiPHPflKG4dWrdurS1btujkyZOSpA0bNrieVxJzf/DBB9q2bZvefvttbdmyRR999JHrvrfffltOp1NpaWn65z//qXvuuafEXzdt2rTRkiVLZIxRTk6OBg0apBUrVhT7HD8/P+Xn57uN+U8//VQDBgxQkyZNNHToUD344IOuT565+Fq7nBkufgrUt99+q0OHDl1ybXuFChW0cuVKffDBB5Kk7777Tnv37nV9xwGAZ/FPYwAl6u6779aUKVNUsWJFhYeHu27v06ePnn32WXXv3l15eXlq3bq1642KRWndurXWrVuniIgIORwONW/eXMHBwfrpp58kXbhcYNy4cUpOTlbt2rU1efLkAs8PDg7W3LlzNXPmTJ0/f17GGM2cOVO33nqrR461SpUqGj16tJKSklSnTh3X/mfNmqXJkycrISFBOTk56t69ux5++GEdO3bskm107txZo0ePVlxcnAYOHKiHHnpI5cuXV5UqVXTnnXfqp59+cl2OUZg+ffro2LFjevjhh2WMUfPmzRUTEyMfHx+dOnVKvXv3lsPhULVq1TRjxgyPHPflKmodHA6HRo8erf79+6tChQoFLvHo1avXb5p706ZN2rlzZ4HbqlWrpkmTJmnSpEl6/fXXFRwcrBkzZmjw4MFq0KCBpAuXjvTs2VOZmZmKiopSy5YtJanI183Fy2Cuxvjx4zV16lR1795dubm5atWqlevNvUUJCQnRHXfcoW7dumnlypVatmyZpEs/saldu3bavHmzHnjgAZUv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UVFRqly5soKDg701qhXcfc1LUlZWlubNm6fx48eX9njXLQK+EBkZGQoICHD93tfXV3l5ea77/vtbkxUqVFBGRkapz2iT4tZTklq3bq1KlSp5YzQrFbee/v7+Cg4OljFGL774ourVq6datWp5a1QruHt9+vn5acWKFYqMjFSXLl28MaJVilvPH374Qe+8846GDRvmrfGsVNyaZmVlKTo6Wi+99JL+9re/adWqVZxUcqO49Txz5oy2bdumUaNG6Y033tDSpUt16NAhb41qBXd/h0rSunXrFBERwT+GPIiAL0RAQIAyMzNdv3c6nfLz8yv0vszMTK41dKO49cSVc7ee58+f16hRo5SZmalJkyZ5Y0SrXM7rMzo6Wl988YV27NihrVu3lvaIViluPd966y2dOnVK/fv314YNG7RkyRJt3rzZW6Nao7g1LVeunP785z+rXLlyCggIUIsWLQh4N4pbz6CgIDVs2FAhISGqUKGCmjVrpv3793trVCtczt+hGzduVK9evUp7tOsaAV+IO++80/U/lT179igsLMx13x133KGdO3fq/PnzSk9P14EDBwrcj0sVt564csWtpzFGTz/9tMLDwzV58mT5+vp6a0xrFLeeBw8e1JAhQ2SMkb+/v8qUKSMfH/7aLE5x6zlmzBitXbtWy5cv10MPPaQBAwZwKc1lKG5NDx8+rKioKOXn5ys3N1e7du1S/fr1vTWqFYpbzwYNGuiHH35QSkqK8vLy9M0336hOnTreGtUK7v4fn56erpycHFWrVs0b4123OA1aiM6dO2vLli3q06ePjDGaNm2aFi9erD/84Q/q1KmTYmJiFBUVJWOMRowYwTXGbrhbT1yZ4tbT6XRq+/btysnJ0RdffCFJGjlypJo0aeLlqa9d7l6ft99+uyIjI+VwONS2bVs1b97c2yNf0/h69zx3a9q9e3f17t1b/v7+6tGjh2677TZvj3xNc7eezz77rB5//HFJUkREBCed3HC3nocOHVL16tW9PeZ1x2GMMd4eAgAAAMDl4XvBAAAAgEUIeAAAAMAiBDwAAABgEQIeAAAAsAgBDwAAAFiEgAeA37nNmzdr9erVJbqPFStWlOj2AeD3hI+RBACUuNatW2vLli3eHgMArgv8ICcA+J1LSEjQF198oaNHj6patWo6evSounXrph9//FHfffedOnTooJEjRyomJka1atXSoUOHZIzRnDlzFBISohkzZmjnzp2SpAceeED9+/dXbGysUlNTlZqaqvbt2ystLU1xcXEaNWqUxo8fr/T0dJ05c0a9evVSVFSUYmJidPvtt+vHH39URkaGXnnlFVWvXl2vvfaaPvroI+Xn56tv377q06ePli9frnfeeUcOh0Ndu3bVn//8Zy+vIACULgIeACBJOnLkiBYtWqTs7Gx16tRJmzdvVrly5dSxY0eNHDlS0oUfmz558mStXLlSCxYsUOvWrXX06FGtWbNGeXl5ioqKUosWLSRJLVq00IABAyRduIQmLi5O3377rbp166b77rtPp06dcv1ka0m64447NH78eM2ZM0fvvvuu2rRpo82bN2vt2rXKycnR7Nmz9eOPP2rTpk1atWqVHA6HBgwYoDZt2qh27dpeWTMA8AYCHgAgSapRo4YCAwNVpkwZVa5cWUFBQZIkh8PheszFOL/zzjv1ySefqGrVqmrWrJkcDof8/f3VqFEjHThwQJJUq1atS/ZRuXJlLV26VB988IECAgKUl5fnuq9evXqSpKpVqyo5OVmHDh3SHXfcIV9fX5UrV04TJkzQpk2bdPz4cdc/DNLS0vTzzz8T8AB+V3gTKwBAUsFQL8q+ffskSbt27VKdOnUUGhrqunwmNzdXu3fvVs2aNS/Z3sW3Wy1atEiNGzfWrFmzFBERoeLehlW7dm199913cjqdys3N1SOPPKLatWurTp06WrZsmZYvX66HH35YYWFhv/mYAcBGnIEHAFy2DRs2aMmSJSpXrpxmzpypSpUqafv27YqMjFRubq4iIiJUv379S54XGhqqUaNGqWfPnoqLi9PGjRsVFBQkX19f5eTkFLqvunXrqm3bturbt6+cTqf69u2r22+/XS1btlTfvn2Vk5OjO+64Q1WqVCnpwwaAawqfQgMAuCwxMTGKi4tTaGiot0cBgN81LqEBAAAALMIZeAAAAMAinIEHAAAALELAAwAAABYh4AEAAACLEPAAAACARQh4AAAAwCL/DxLMWLmrLDjkAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "build.plotVariableImportance(df_model1, #Dataframe with models\n", - " step=5, #What model should be plotted\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" + "performances = forward_selection.compute_model_performances(basetable, \"TARGET\")" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 42, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1gAAAI4CAYAAAB3HEhGAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzs3Xd4FNXXwPHv7KZ3SuiEnkDovQf9gVJeQYMgHQEFAUEQVBAURBFRFBCUIgJKQKUpKBBExUJHOtJ76IZASN9Ndu/7x5INawIkENjN5nyeJ092JlPO3M3uzJl7515NKaUQQgghhBBCCPHAdPYOQAghhBBCCCGchSRYQgghhBBCCJFLJMESQgghhBBCiFwiCZYQQgghhBBC5BJJsIQQQgghhBAil0iCJYQQQgghhBC5xMXeAQjHcOjQIZYuXcrOnTu5fPkyer2e4OBg2rdvT5cuXXBxyRv/Kjt27KB379707t2bsWPH3tc2zpw5w9GjR2nbtq11XkhICJUrV2b16tW5FWq2jR49mh9++CHLv3l5eVG4cGEaNWrEkCFDKFq06COOTgghHJ+c4zI42jnudkeOHGHFihXs2LGDK1eukJaWRtGiRWncuDG9e/emfPny973tCxcu0LJlS1q2bMmsWbNyMWohMssb3yjioTGbzcycOZPZs2fj6upKWFgYjz/+OPHx8WzevJl3332X9evXM2/ePDw8POwd7kN39OhROnXqRLdu3WxOPkOGDKFw4cJ2jAzCw8MpWbKkzbzo6Gi2bt3KsmXL2LRpE99//z0FCxa0U4RCCOFY5Bxny1HPcWazmU8//ZS5c+ei1+tp2LAhTZs2Ra/Xc+zYMZYtW8ayZcuYMGECnTt3vq99+Pn5MWTIkAdK0oTILkmw8rk5c+Ywa9YsatWqxYwZM2xqQIxGI2PGjOGnn35i9OjRTJ8+3Y6RPho3b94kNTU10/yhQ4faIRpb4eHhNGzYMNN8o9HIwIED2bJlC1999RUjRoywQ3RCCOF45Bxny1HPcTNmzGDOnDlUrVqVadOmUaZMGZu/Hz9+nAEDBjB+/HiqVatGlSpVcrwPPz8/ux+nyD/kGax87MyZM8yaNYuCBQsyb968TM3L3Nzc+OCDDyhZsiTr16/n1KlTdopU3I2bmxsDBgwAYNu2bXaORgghHIOc4/KGEydOMG/ePPz8/Jg3b16m5AogODiYSZMmYTKZmD9/vh2iFCJnJMHKx1atWkVqaio9evTAz88vy2VcXV15++23mTRpEgUKFAAsbcBDQkJ4//33My0/evRoQkJCOHLkiHVeSEgIY8eOZefOnXTv3p2aNWvSrFkzpk6dislk4uTJk7zwwgvUrl2b5s2b895775GcnGxd//vvvyckJISvvvoq0/569epFSEgIcXFxdz3W48eP8/rrr9OiRQuqVatGnTp16Nq1Kz///LN1mZkzZ9K7d28AFi1aREhICDt27LAew9NPPw3AggULCAkJYenSpZn2c/XqVapUqWJNeMByl3Tu3Lm0a9eO6tWr07hxY0aOHMn58+fvGnNOFCpUyLqv2yml+PbbbwkPD6dGjRrUr1+fgQMHcvjw4Uzb2Lx5M88//zyNGzemZs2atG/fnrlz59psM/29X758OUuWLKFVq1bUrFmTDh068P3332cZ27p16+jatSu1atWidu3adO3albVr12ZaLiQkhNGjR7Nnzx569epF7dq1qV+/PsOHD+fChQs2y167do0xY8bwxBNPUL16dZo1a8brr7/OuXPnMm333LlzvPbaazRp0oRq1arRtm1b5s6dm+VdXCGE85BzXN44xy1fvpy0tDQGDhxoPZdlpUmTJoSHh9OoUSOb+YmJiXz++ec8/fTT1K5dm+rVq/Pkk0/y0UcfkZSUZF3uwoULhISEMHjwYOu89Pfz5s2bjB8/nqZNm1K9enU6duxoU3bpIiIi6NixI7Vr16ZOnTp0796dyMjIbB2nyF8kwcrHNm3aBEDz5s3vutzjjz9Ox44dH+jZnv3799OvXz8KFixIt27dcHNzY+7cuYwbN45u3bphNpvp1q0b/v7+LF68mGnTpt33vv7rwIEDdO7cmT/++INmzZrRt29fmjVrxsGDB3nllVf4/fffAWjQoAHh4eEA1KxZkyFDhmR65gngqaeeQqfTsW7dukx/i4yMxGw20759ewBSU1Pp378/U6dOxdvbm549e9K8eXM2bNhAp06dOH78eK4cY/p7WblyZZv5o0aN4p133iE1NZWuXbvSpk0bdu3aRdeuXW1qu3bt2sXAgQM5ffo07dq1o0ePHuj1eqZOnco777yTaX/ffvst77//PjVr1uTZZ58lNjaWN998k5kzZ9os9+GHH/Lqq69y4cIFnnrqKf7v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+CL//vuvzd/j4+N57bXXOHv2LI8//jj169cHLDUner2e7du329xd++OPP6zPveSm8uXLA5amXre3pf7mm29suuTOSnq1/38f8o2NjeWjjz4CsElK0u/MZWeMJBcXF9q1a8fu3btZs2YNZcqUsSYX6cLDw4mNjeXjjz+2OTGcPHmSd999l4ULF1prfO7XiBEjKFGiBMeOHWPBggU2+1ZK8d5779m0I//333955513+OKLL6x3IDdv3sycOXOsSVK6ixcvApZno273yy+/2CRO0dHRzJ49Gy8vL9q2bQtAx44dAZg6darNSfP69evWsk8fdyUnoqOjiYiIsDlWsLT3NxgM1m6H69evT6lSpVixYgV79+61WfaLL75g4cKFD+X/VQjhGOQclzfOcaGhoQwePJjo6Gh69+7NsWPHMi0TFxfHxx9/zJ9//gmATme5fHV3d8dkMmVKzGbNmmU9f91+/PfL29ubRYsWMW3atEzvyaVLl4DM50mRv0kTwXzu1VdfJSYmhu+//56WLVvy2GOPERQUxNWrV9myZQvXr1+nTp061i9qsDTPatWqFT///DOdO3emRYsWnD9/no0bN1K3bt1MtR0PKjQ0lKpVq7J37166d+9O/fr1OX78ONu2baNmzZrs37//juuWLVuWGjVqsGvXLrp3706dOnW4ceMGv/76K0ajEU9PT27cuGFdPr39d2RkJF5eXoSHh9+1A4oOHTqwePFiTp48yZAhQzL9fcCAAWzevJmIiAh2795NgwYNiIuLY/369SQnJzNlypQHfv7Hy8uLcePGMXDgQD7//HPatm1L6dKl6dixIxs3buTnn3/m2LFjNG/e3NrLX2xsLCNHjiQoKAiAoUOHsmPHDnr37k2bNm0oWrQoJ0+e5Pfff6dChQrWh5rTeXt706dPH9q0aYOPjw+//vor165d47333rPeQaxfvz59+/Zl4cKFdOjQgccffxyw1J5FR0fTv39/6wVNTrRq1YratWvz7bffcvz4cWrVqkVCQoK1t6uhQ4cCoNfr+fDDD+nfvz89e/akZcuWlC5dmn/++Yft27dTqlQpRowYcd/lLoRwfHKOyxvnuJdffhkPDw+mT59Ohw4dqFOnDqGhobi7u3P27Fm2bNlCSkoKRYsWZezYsdZkpkOHDuzbt49u3brRtm1bXF1d2bFjB4cOHaJQoULExMTcM0nNDjc3N1555RUmTpzIU089xRNPPIGHhwd///03Bw8e5Omnn7YmykKA1GDle3q9ng8++ID58+fTokULjh49SkREBBs3bqRs2bJMmDCBxYsXZ+pCddKkSfTq1YvY2FgiIiK4ePEiM2bMyNZAgfdj7ty5hIeHc/bsWRYvXkxSUhJff/11prtp/6XT6Zg1axYdO3bkwoULREREsGvXLsLCwli5ciVNmzbl7NmzREVFAVCyZElrG/wlS5Zw4MCBu26/Zs2alC1bFrBtOpHOw8ODRYsWMXToUAwGA9988w1//vknderUYdGiRVmucz8ef/xxWrduTUpKirVZn6ZpzJgxg7Fjx+Lp6cny5cuJjIykYsWKfP755wwYMMC6fo0aNVi8eDFNmzZl+/btLFy4kGPHjtG7d2+WLFli030tWE5qo0aNYteuXaxatYrSpUszd+5cOnfubLPc6NGjmTJlCiVLluSnn34iMjKScuXKMXPmTF577bX7Otb0ATz79+/P9evXWbJkCevXr6dmzZpERETQrFkz67L16tVj+fLl1gGWFy1axKVLl+jVqxdLly59oIfahRCOT85xeeMcp2kaL774ImvXruWll14iJSWFNWvWEBERweHDh2nevDkffvghv/76K61bt7au1717d95++20CAgJYvnw5P/30E97e3kydOpV3330XwFrr9aB69erFtGnTKFWqFOvWrWPJkiUYjUbefPNNJk2alCv7EM5DU7nROFUIkS+k13L17t2bsWPH2jscIYQQQgiHIzVYQgghhBBCCJFLJMESQgghhBBCiFwiCZYQQog8Y//+/daBr8+dO0e3bt3o3r0748ePt/Zi9tlnn9GpUye6du1qfcbkr7/+olOnTrzyyivW5d59990HGiZBCCGEyIr0IiiEyLaGDRtm2YWuEI/CvHnz+PHHH/H09ATggw8+YPjw4TRs2JBx48bx22+/UaJECXbu3Mny5cu5fPkyQ4cOZeXKlXzzzTcsWLCAGTNmcPToUfR6PT4+PpQqVcrORyWEEMLZOESCFR2d9QCAOeXj405CgiFXtuVMpFyyJuWSNSmXrEm5ZO1+yiUw0Pe+9hUUFMTMmTN54403ADh06BANGjQAICwsjC1btlCuXDmaNWuGpmmUKFHCOkaOt7c3ycnJJCcn4+npyWeffcY777xz1/2lpZmQbqCyT6/XMJmkwLJL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TXoUy7IwTZM777yXli1b8csvP3H77beQnZ1F374X4HZH+u8zYsTNPPHEZD799COysjK5+eZbqVGjBsOG3cjYsbdiGAbnndeH+Pg6tG/fkSlTJnD//Q8DvvB4//0P8/DDfyUkxIXbHclDD01k27Ytp9+ZUmqG4zjOqW9WtlJS0gOynbg4N6kl+HWjulG/FE/9Ujz1S/HUL8U7nX6Jj48uo2oC67F/pbL7sO+X4T+fb9GklpfQPV+R1/BPQa6sYtJ7pHTUX6Wj/io99VnpBPKzScu0i4iIFCM5tWDaTb04g8hVU4n9ZgjRS0dDfkYQKxMRkYpMAUtERKQY3mP5qoYbog8uImLtswCEb3uX8K3vBrEyERGpyBSwRERETqJp1EGil/4ZA9+M+rx6F5DTSssbi4hI8RSwRERETsBwbK48cgdW9n4A7PB40nrPAkMfnyIiUjytIigiInICF3peoX7OV/522nkzcSISgliRiIhUdPoJTkREpBiN7F+4xjPJ385qO478BhcGsSIREakMFLBERESKcWven3GRD0B+ra5kdhkf5IpERKQyUMASEZEqZ9WqVQwePJihQ4cyffp0ADIzMxkxYgRDhgxhw4YNAKxcuZJZs2YVu406znYA7JBo0vrOBiu0fIoXEZFKTQFLRESqnAkTJvDMM8/w7rvvsnr1atatW8d3331H//79mTBhAh9++CGO4/DWW29x4403nnRbGWc/hx3dtJwqFxGRys5wHMcJdhEiIiKBkpGRweDBg5k/fz4Ab775Jvn5+bRu3Zpff/2VHj16sHjxYpo3b45hGFxxxRVBrlhERKoSjWCJiEiVkpGRQVRUlL8dGRlJeno6vXr14tChQ7z77rv83//9H1999RWtW7dm/PjxvPrqq0GsWEREqhIFLBERqVKioqLIzMz0tzMzM4mJicE0TR555BGeeeYZ5s2bx4gRI5gxYwZ33303ycnJbN++PYhVi4hIVaGAJSIiVUpUVBQhISHs3LkTx3FYunQp3bt39//90KFD7Nixg+7du5OdnY1lWRiGQXZ2dhCrFhGRqkInGhYRkSrnscce47777sPr9dK7d286derk/9uMGTO47bbbALj++usZNWoU9evXp3Xr1sEqV0REqpBKPYIViGV4K7u9e/cycuRIbrjhBoYPH862bdsAWLhwIYMGDWLIkCG8//77AOzbt4+h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\n", + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    predictorslast_added_predictortrain_performanceselection_performancevalidation_performance
    0[scont_1_enc]scont_1_enc0.5060350.4891550.497746
    1[scont_1_enc, relationship_enc]relationship_enc0.7796810.7752670.787203
    2[scont_1_enc, relationship_enc, education_enc]education_enc0.8580480.8563760.859707
    3[scont_1_enc, education_enc, relationship_enc,...age_enc0.8705360.8721490.871496
    4[scont_1_enc, education_enc, relationship_enc,...occupation_enc0.8783800.8824680.880178
    \n", + "
    " + ], "text/plain": [ - "
    " + " predictors last_added_predictor \\\n", + "0 [scont_1_enc] scont_1_enc \n", + "1 [scont_1_enc, relationship_enc] relationship_enc \n", + "2 [scont_1_enc, relationship_enc, education_enc] education_enc \n", + "3 [scont_1_enc, education_enc, relationship_enc,... age_enc \n", + "4 [scont_1_enc, education_enc, relationship_enc,... occupation_enc \n", + "\n", + " train_performance selection_performance validation_performance \n", + "0 0.506035 0.489155 0.497746 \n", + "1 0.779681 0.775267 0.787203 \n", + "2 0.858048 0.856376 0.859707 \n", + "3 0.870536 0.872149 0.871496 \n", + "4 0.878380 0.882468 0.880178 " ] }, + "execution_count": 42, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "build.plotCumulatives([(df_model1,5)], #List of tuples (dataframe with models, model number)\n", - " df_transformed, #Dataframe with transformed dataset\n", - " dim=(12,8)) #Size of the figure, if not specified, automatically set to (12,8)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After fitting the model, following class attributes are available" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "#Dictionary containing paritioned data in forms of dataframes\n", - "#partition_dict = build._partition_dict \n", - "#print(partition_dict.values)" + "performances.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Model comparison\n", - "In the next step, the analyst needs to try different models. Thus, we can build multiple models and compare them. Below we build another two models and give them a name." + "## Model selection and evaluation" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "df_model2 = build.fit_model(df_transformed, \n", - " df_unisel,\n", - " modeling_nsteps=30,\n", - " forced_vars=None,\n", - " excluded_vars=None,\n", - " name='All variables')\n", - "\n", - "df_model3 = build.fit_model(df_transformed, \n", - " df_unisel,\n", - " modeling_nsteps=5,\n", - " forced_vars=['capital-gain','hours-per-week'],\n", - " excluded_vars=['age','relationship', 'sex'],\n", - " name='Limited model')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And now we can compare all the three models as we are used to from the web version of COBRA - with **AUC comparison** and **Cumulative gains/response** plots." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwUAAAHoCAYAAAAYIEMMAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3XtYlHX+//HXcFJABAw8BiqSWp5P20GzVcvMdFUUPBRZWlnZamoaYmqyHlBzU3NttUgMzYTykIcsrTY33TTENP2appmpmXjCVGAA5/794df5fVkVQYGRPs/Hde11ycx93/Oe+Vy7O0/uewabZVmWAAAAABjLzdUDAAAAAHAtogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAGCg3NxctWnTRk8//XS+2+vVq6fTp0/nu23dunWKjo52/vz7779r4sSJ6tq1q7p166bu3bsrJSWlVOYubrNmzdKKFStcPQYAuJyHqwcAAJS+9evXq379+tq1a5cOHDigOnXqFGo/u92uxx9/XF27dtXy5cvl4eGho0eP6sknn5QkRUZGluDUxW/o0KGuHgEAbglEAQAYaMmSJercubNCQ0O1cOFCxcXFFWq/tWvXysfHR88884zztho1amjmzJnKzc29Yvu8vDxNnz5d//rXv+Tu7q5mzZpp/Pjxstlsio+P13/+8x+5u7urcePGGj16tCpUqKD27durS5cu+uabb3T27Fk9/fTTSktL0+7du+Xh4aG33npLVapUUfv27fXoo49q06ZNOnfunJ566in169dPDodDkydP1o4dO3ThwgVZlqWJEyeqRYsWiomJUUZGhg4fPqw///nPOnXqlO644w4NHDhQs2fP1vr16+Xp6anAwEBNmTJFlStXVmpqqqZNm6asrCx5enrqpZdeUtu2bbVs2TKtX79ebm5uOnTokMqXL6+pU6cWOrAA4FbC5UMAYJj9+/dr+/bt6tSpk7p3766VK1fqzJkzhdp3165dat68+RW3N2jQQE2bNr3i9vfff1+7d+/WypUrtXr1al24cEFr167VW2+9pfT0dK1cuVIrV66Uw+HQtGnTnPvZ7XYlJydr6NChGjdunPr376+PP/5Y1apV0/Lly53bnT17Vh999JGSkpI0e/Zs7d27Vzt27FB6erqWLl2qtWvX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dejQz0QBAAAAUNzy8vL04ovP6qmnntEdd9TVkCHPa8aM2frb38apZs1aOnToZ0nShAmTddttQfrnP+dox440ORyWevd+TO3bP6gXX3xWAQGBOnfunB56qKMOHz6s7t17aty40apSpYqOHTumDh066uDBA9q3b6/uu6+NBg0arAMH9mvmzOmyLEv+/v4aPXq89u37QYsXvydPTw8dO/ar2rd/SI8//qQWLUpUdna2GjVqrDZtHnDti/a/iAIAAACUSdu2perFF591/nzffW00fvxEjRr1km67LUiDBw9VlSpVJUkNGzbWyJGxWrYsRUlJC3T33ffp2LGjeuutd2W32zVo0FNq1epuSdJDD3XSAw+009q1q5zHPnbsqN544x+y27MVGdlNK1asVbly5dWrV1cNGjRYU6dO1OjR41S7dphWr16hxYsXqlWru3X8+DElJi5Rbm6uunfvpP79B+rxx5/83zMFt0YQSEQBAAAAyqhrXT7UuHFT7dr1fb7Lf1q0aCVJatSosb7++isFB1fW3r0/OKMiLy9Pv/12TJIUGlrzimNWq1ZDFSpUkKenpypVqqSKFf0lSTabTZJ06NBBzZgRL0m6eDFPISGXjhEWFi4PDw95eHioXLnyxfXUix1RAAAAgD+MXbu+108/HVDTps20ZMki9esXLUnau3ePKleuop07d6h27TDVrFlLzZq11CuvjJHD4VBi4juqUaOGJMnN7cpv7b/85v9aQkNr6tVX41S1alXt3PmdTp06+b/7XbmtzWaTZTlu8pkWL6IAAAAAN8VVXyH635cPXbhwXhcuXNDrr89WlSpV9eyzT6p58xaSpLVrV2vp0vdVvnx5jR0bp4oV/bV9+za98MLTysrKVNu27eTj43vDs4wYMVoTJ46Tw3HpzX5MzFidPHniqtvWqROu9957V3Xr1teDDz58w49ZnGyWZVmuHsIELUa+5+oRAAAoNtd7ExgQ4KOMjMxSmgbF7Y+2fi+++KxGjoxVzZq1XD1KqQgO9ivyPvxFYwAAAMBwXD4EAACAP7Q5c+a7eoRbHmcKAAAAAMMRBQAAAIDhiAIAAADAcHymAAAAADcl6637rr9REXg/v7lYj4fr40wBAAAAyqxFixLVrdvDstvtki59/eihQz8rIWGeVqz4sFgeY+3aVfr666+uef+kSa/pm2/yh8yxY7/q2WefLJbHLw1EAQAAAMqs9evXqUOHjvr8889K7DE6d+6qNm0eKLHj3wq4fAgAAABlUlpaqqpXv13du/dUXNw4de7ctcDtBw6M1sSJU1WtWnV98cUG7dz5nfr1i9brr8crJ8eu338/qyeffEZt2/5Z0dFRCgmpKU9PT4WG1tRtt92mrl17aPr0yUpPP66zZ8/qnnvu0zPPPC9JWr48RUuWJOnixYuKiRkrd3d35+Nu375N8+fPlbu7u6pXr6FRo8bo11+PavLkCfLw8JC7u7tefXWCgoMrl+jrVRDOFAAAAKBMWr16pbp27a7Q0Fry9PTU7t27Cty+S5duWrdujSTpk09W6S9/6a5Dh35Wnz6PaebMuRo2bJSWLUuWJGVlZenJJwdqwoTJzv3T04+rQYNG+vvf52ju3HfyXZ7UsGETzZr1lh577AnNnTvbebtlWZo6dZImT56uOXPmKzi4stauXaVvv92ievXqa+bMuXriiQE6d+734nxpiowzBQAAAChzfv/9d/3nP5t05sxpffjhUl24cF7Lli0tcJ+OHTvphReeVteu3XXhwgWFhYVLsmnhwgStWbNSkk15eXnO7UNDa+Xbv2LFitqzZ7fS0lLl6+urnJxc531NmzaTdCkO/vGP/x8FGRlndOrUSY0dGyNJstvt+tOf7tETTwzQ4sULNWLEX+XrW0GDBg2+uRfkJhEFAAAAKHM++2ytunTppsGDh0qSsrOzFRn5F/n7B1xzH1/fCqpX707Nnv1356VG77zzT3Xt2l333ttaa9Z8rE8+We3c3maz5dt/7drVqlDBT6NGjdGRI4f18cfLZVmWJGnPnt1q1KiJduzYrrCwOs59/P0DVLlyZcXH/10VKlTQ119/JW9vH3399Vdq0qSZBgx4VuvXr9PixQsVGzu+2F6foiIKAAAAcFNc8RWiq1at1Nixcc6fy5cvrwceaK/Vq1cUuF/Xrt01YsQQjR49TpLUrl0HzZr1upKSFqhy5SrKyMi45r4tWrTSa6/FaufO71S+fHndfnuITp48IUnavft7DRnynGw2m0aPHueMBTc3Nw0d+rJGjhwqy7Lk4+OrsWMnKDMzU3Fxlz574Obmpr/+dfjNviQ3xWZdnhglqsXI91w9AgAAxWbdqB4F3h8Q4KOMjMxSmgbFjfUr24KD/Yq8Dx80BgAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwfCUpAAAAbkr3DzoV6/FW9FlXrMfD9XGmAAAAAGVKWlqqxo8ffcXt48ePVm5u7lX2yO/HH/dqwYK3JUlfffWl828NXM8332zWpEmvFWnWwvrLXx6+5n3Hjv2qZ599skQe9zKiAAAAAH8IEyZMkaen53W3u+OOenrqqWckSSkpS3ThwoWSHu2Wx+VDpWS533RXjwAAQLHJeqvg/1/LKqU5SpIr/kovbk6vXl21ePGHev31KfLw8NBvvx1Tbm6uOnToqE2bNur48d8UH/93HT/+m1au/EgPP/yo9u/fp4kTx2nu3AStXPmR1q//VDabTR06dFRkZB/9/PNBTZkSp/LlveXtXV5+fhXzPWZaWqoWLUqUp6en0tOPq1u3nkpLS9X+/fsUGdlXPXr00rfffqP5899SuXLlVLGiv0aPHicfHx9NmzZJBw/+pBo1bldOTo4k6fjx3zRt2mTl5Njl5VVOo0bFlsprRxQAAADgD6dq1Wp65ZVXNX36ZB07dlSvvz5bCQnztGnTRoWH15Uk3XdfG4WH19XIkbE6cuSwPv98vebOfUc2m00vvfSC7r77Hr3zzlt6+ulBatXqHi1alKhDh36+4rHS09OVmPi+fvhhj8aNi9HSpSt04kS6YmNHqnv3npo2bbLmzn1HwcGVlZy8RAsXJqhZsxbKycnR/PmJ+u233/Svf30uSfrHP2apV6/euvfe1kpN3ap//nOOnn32hRJ/vYgCAAAA/OHUrVtfklShgp9q1qwlSfLz85PdnnPV7X/66YCOH/9NQ4c+L0k6d+6cjhw5ooMHf9KddzaUJDVq1PSqURAWVkceHh7y8/NT9eo15OnpKT+/isrJsSsjI0M+Pr4KDq4sSWratJnmzZurgIAA3XlnA0lS1apVVblylf+dY7+SkhZo8eKFkiQPj9J5u04UAAAA4A/HZrMVajs3Nzc5HA6FhtZUrVphmjFjtmw2m5YuXaywsHCFhtbSrl07dc899+mHH3Zf47GuffyAgABlZl7QyZMnFRQUpO++S1NISKhq1qylDRs+ldRXJ0+e0IkTlz7sHBpaS337Pq5GjZro0KGftX37tqI+9RtCFAAAAOCmuOIrRLdu3aKBA6OdP48fP/GGjtOwYWNNnDheb7wxRy1bttILLwxUTk6u7ryzgYKDgzViRIzGjx+tJUuSFBAQIC+vckU6vs1m06hRYzRmzEi5udnk51dRsbGvKSAgQDt37tAzz/RX1arVFBAQIEkaPHioZsyIV05Ojuz2bA0d+vINPa+islmWZZXKIxnul7hGrh4BAAAUgckfNA4I8FFGRqarx8ANCg72K/I+fCUpAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwXJmNgvnz56tNmzay2+2SpJiYGG3cuDHfNq1bt3b+e8OGDYqOjlZ0dLQiIyO1bt26K465d+9effvtt4WeYdiwYcrJybnBZwAAAADcGjxcPcCNWrVqlTp37qw1a9YoIiKiwG3T0tKUmJioefPmydfXV2fOnFHv3r0VHh6u8PBw53afffaZgoKC1KpVq0LN8MYbb9zUcwAAAABuBWUyCrZs2aLQ0FD16dNHI0eOvG4UpKSkqH///vL19ZUkBQYGKiUlRRUrVnRuc/z4cS1fvlyenp5q0KCBYmNjVatWLXl5eWnUqFF67bXXZLfblZGRocGDB+vBBx9U+/bt9cknn2j8+PHy8vLS0aNHlZ6ervj4eDVo0KBEXwMAAACguJTJKEhJSVFkZKTCwsLk5eWlHTt2XHU7m80mSUpPT1dISEi++/z9/fP9XKVKFfXo0UNBQUFq3LixMjMz9cILL+iuu+7S5s2b9dRTT+nuu+9WWlqa3nzzTT344IP59q9evbri4uKUnJyspUuXKi4urhifMQAAAFByylwUnD17Vhs3btTp06eVlJSk8+fPa9GiRfLx8bni+v68vDxJl96wHzt2TPXr13fet23bNgUFBalmzZrXfKzatWtLkoKDg/XWW2/pww8/lM1mcx73/7rzzjslSVWrVlVaWtpNP08AAACgtJS5Dxp//PHH6tmzp959910lJCQoOTlZmzZtUkhIiNavX+/cLjU11fl5gYiICCUkJCgzM1OSdOrUKcXGxiorKyvfsW02mxwOh/NnN7dLL8+sWbPUrVs3TZ8+XXfffbcsy7pirstnJQAAAICypsydKUhJSdG0adOcP3t7e6tjx47Kzs6Wj4+PunXrJl9fX3l6ejov4WnWrJmioqI0YMAAeXh4KDs7W8OHD8935kCSGjZsqGnTpqlOnTr5bu/UqZMmTZqkefPmqVq1ajpz5kzJP1EAAACglNisq/3aG8Xul7hGrh4BAAAUgffzm109gssEBPgoIyPT1WPgBgUH+xV5nzJ3+RAAAACA4kUUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOA9XD2CKvoEVXT0CAAC4hhV91rl6BMClOFMAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcDbLsixXD2GC3NyLysjIdPUYuEEBAT6sXxnF2pVtrF/ZxdqVbaxf2RYc7FfkfThTAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMN5uHoAU9wTu9jVIwAAcFPWjerh6hEAlBDOFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADCczbIsy9VDmOCXuEauHgEAANwA7+c3u3qEUhcQ4KOMjExXj4EbFBzsV+R9OFMAAAAAGI4oAAAAAAxHFAAAAACG8yjMRpZl6fvvv5fdbnfe1qpVqxIbCgAAAEDpKVQU/PWvf9WpU6dUrVo1SZLNZiMKAAAAgD+IQkXByZMn9cEHH5T0LAAAAABcoFCfKahdu7aOHz9e0rMAAAAAcIFCnSlIS0tTu3btFBgYKJvNJkn6+uuvS3QwAAAAAKWjUFHw6aeflvQcAAAAAFykUJcP7d27Vz179lSbNm3UvXt3/c///E9JzwUAAACglBTqTMHEiRM1adIk1a9fX3v27NGECRP44DEAAADwB1GoMwWWZal+/fqSpDvvvFMeHoVqCQAAAABlQKGiwMPDQ19++aXOnTunL774Ql5eXiU9FwAAAIBSUqgomDRpkpYvX66+fftq5cqV+tvf/lbScwEAAAAoJQVeB5SXlycPDw8FBwfr9ddfL62ZAAAAAJSiAs8UvPLKK5KkTp066ZFHHtEjjzzi/HdRzZ8/X23atJHdbpckxcTEaOPGjfm2ad26tfPfGzZsUHR0tKKjoxUZGal169YV+TEvO3LkiKKiooq0j91uV0pKiiRp2bJl+vzzz2/48QEAAIBbWYFnCmbMmCFJmjlzpho3buy8fcuWLUV+oFWrVqlz585as2aNIiIiCtw2LS1NiYmJmjdvnnx9fXXmzBn17t1b4eHhCg8PL/Jj34gTJ04oJSVFkZGR150XAAAAKMsKjILU1FTt379fiYmJeuqppyRJDodDixcv1urVqwv9IFu2bFFoaKj69OmjkSNHXvdNdkpKivr37y9fX19JUmBgoFJSUlSxYsV82y1evFgrVqyQm5ubmjdvrldeeUXHjh3T2LFjZbfbVa5cuSs+/7B161a98cYbcnd3V0hIiOLi4nTx4kWNHj1av/76q3JzczV27Fh99NFH2r9/v+bMmSPLshQUFKS+ffsqPj5e27ZtkyR16dJF/fv3V0xMjLy8vHT06FGlp6crPj5eDRo0KPTrAwAAALhSgZcPVaxYUSdPnlROTo5OnDihEydO6PTp0xo5cmSRHuTyb9zDwsLk5eWlHTt2XHU7m80mSUpPT1dISEi++/z9/Z33X7Zs2TKNGTNGS5cuVUhIiPLy8jR16lRFR0crKSlJAwcOzPdZCMuyNHbsWM2ZM0eLFi1SlSpVtHz5cn3wwQeqUaOGli5dqvj4eO3YsUPPPfecwsPD9eKLLzr3//LLL3XkyBElJyfr/fff1+rVq7V3715JUvXq1ZWQkKDo6GgtXbq0SK8PAAAA4EoFnimoW7eu6tatq6ioKFWuXPmGHuDs2bPauHGjTp8+raSkJJ0/f16LFi2Sj4+PcnJy8m2bl5cn6dIb7GPHjjn/NoIkbdu2TUFBQapZs6bztilTpujdd9/V66+/rqZNm8qyLO3bt0/z5s3TO++NfbwRAAAVT0lEQVS8I8uy5Onp6dz+9OnTSk9P10svvSRJys7OVuvWrXX69Gm1bds233M+cuTIFc/lwIEDatmypWw2mzw9PdWkSRMdOHBA0qW/3yBJVatWVVpa2g29VgAAAIArFHimYMiQIZKkiIgItWnTJt9/Cuvjjz9Wz5499e677yohIUHJycnatGmTQkJCtH79eud2qampzs8LREREKCEhQZmZmZKkU6dOKTY2VllZWfmOnZycrAkTJmjRokXas2ePtm/frrCwML388stKSkrShAkT9PDDDzu3DwwMVNWqVTV37lwlJSXpueee09133606dero+++/lyQdPnxYI0aMkJubmxwOR77Hq1OnjvPSodzcXG3fvt0ZKf99FgMAAAAoKwo8UzB79mxJly7/qVatmvP2y78dL4yUlBRNmzbN+bO3t7c6duyo7Oxs+fj4qFu3bvL19ZWnp6fi4uIkSc2aNVNUVJQGDBggDw8PZWdna/jw4fnOHEhSvXr11KtXLwUGBqpKlSpq0qSJXnnlFb322muy2+3Kzs7WmDFjnNu7ublpzJgxevbZZ2VZlnx9fTVt2jQ1b95csbGxevzxx3Xx4kXFxsbqtttuU25urqZPn67y5ctLktq1a6etW7eqd+/eys3NVadOnfjsAAAAAMo8m2VZ1rXu3Ldvn44fP67XX39do0aNkmVZcjgcmjFjhlauXFmac5Z5v8Q1cvUIAADgBng/v9nVI5S6gAAfZWRkunoM3KDgYL8i71PgmYLff/9da9eu1alTp5zfNmSz2dSvX78bmxAAAADALafAKGjZsqVatmypOXPm5PsWHgAAAAB/HAV+0Piyb775pqTnAAAAAOAiBZ4puCwnJ0fdu3dX7dq15eZ2qSMu/7VjAAAAAGVboaLg5ZdfLuk5AAAAALhIoS4fqlu3rtLT0/Xrr7/q6NGj2r59e0nPBQAAAKCUFOpMwZAhQ1SrVi3t27dP5cqVk7e3d0nPBQAAAKCUFOpMgSTFxcWpdu3aWrBggc6ePVuSMwEAAAAoRYWOArvdrqysLNlsNmVm8scsAAAAgD+KQkXBY489poSEBNWtW1dt27ZVWFhYSc8FAAAAoJQU6jMFNptNH374oSpWrCgvLy9FRESU9FwAAAAASkmhomDu3LlKSUnRbbfdppMnT+q5557T/fffX9KzAQAAACgFhbp8KCAgQLfddpskKSgoSBUqVCjRoQAAAACUnkKdKahQoYIGDhyoVq1aaffu3crOztbf//53SdLw4cNLdEAAAAAAJatQUdChQwfnv6tUqVJiw/yR9Q2s6OoRAABAAVb0WefqEQCXKVQU9OjRo6TnAAAAAOAihf47BQAAAAD+mIgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABjOZlmW5eohTJCbe1EZGZmuHgM3KCDAh/Uro1i7so31K7tYu7KN9SvbgoP9irwPZwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOA9XD2CKe2IXu3oEAACKbN2oHq4eAUAp4EwBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGs1mWZbl6CBP8EtfI1SMAAIAb4P38ZlePUOoCAnyUkZHp6jFwg4KD/Yq8D2cKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGK9EomD9/vtq0aSO73S5JiomJ0caNG/Nt07p1a+e/N2zYoOjoaEVHRysyMlLr1q274cc+cuSIoqKiJEnDhg1TTk5Ovvs3btyomJiYa+5vt9uVkpIiSVq2bJk+//zzG54FAAAAuJV5lOTBV61apc6dO2vNmjWKiIgocNu0tDQlJiZq3rx58vX11ZkzZ9S7d2+Fh4crPDz8puZ44403irzPiRMnlJKSosjIyOvODgAAAJRlJRYFW7ZsUWhoqPr06aORI0de9411SkqK+vfvL19fX0lSYGCgUlJSVLFiRec2p0+f1mOPPaa1a9fKZrNpwoQJuu++++Tv7685c+ZIkrKzszV16lR5eno692vfvr0++eQTHTlyRLGxsfL29pa3t7f8/f0lSYsWLdJnn32mvLw8+fn56c0339Q///lP7d+/X3PmzJFlWQoKClLfvn0VHx+vbdu2SZK6dOmi/v37KyYmRl5eXjp69KjS09MVHx+vBg0aFOvrCQAAAJSUErt86PJv2cPCwuTl5aUdO3ZcdTubzSZJSk9PV0hISL77/P39nfdLUqVKlVSvXj2lpqYqJydHW7duVbt27fTjjz9q+vTpeu+999S+fftrXnY0a9YsDRkyRImJiWrWrJkkyeFwKCMjQ4mJiXr//feVl5en77//Xs8995zCw8P14osvOvf/8ssvdeTIESUnJ+v999/X6tWrtXfvXklS9erVlZCQoOjoaC1duvTGXzgAAACglJXImYKzZ89q48aNOn36tJKSknT+/HktWrRIPj4+V1zbn5eXJ+nSm+pjx46pfv36zvu2bdumoKAg1axZ03lbVFSUli9frhMnTqh9+/by8PBQlSpVNGnSJPn4+Oj48eNq3rz5Vef68ccf1bhxY0lS8+bN9dNPP8nNzU2enp4aPny4fHx89Ntvvzln+m8HDhxQy5YtZbPZ5OnpqSZNmujAgQOSpDvvvFOSVLVqVaWlpd3gKwcAAACUvhI5U/Dxxx+rZ8+eevfdd5WQkKDk5GRt2rRJISEhWr9+vXO71NRU5+cFIiIilJCQoMzMTEnSqVOnFBsbq6ysrHzHvvfee7Vnzx599NFH6tWrlyTp1Vdf1eTJkxUfH6/KlSvLsqyrzhUWFqbt27dLknbt2iVJ+uGHH7RhwwbNnDlTY8eOlcPhkGVZcnNzk8PhyLd/nTp1nJcO5ebmavv27c5g+b9nNAAAAICypETOFKSkpGjatGnOn729vdWxY0dlZ2fLx8dH3bp1k6+vrzw9PRUXFydJatasmaKiojRgwAB5eHgoOztbw4cPz3fmQLr05vvhhx/W5s2bnW/Iu3XrpqioKFWsWFFBQUFKT0+/6lzjx4/XsGHDlJCQoEqVKqlcuXKqWbOmvL29FRERIS8vLwUHBys9PV3NmjVTbm6upk+frvLly0uS2rVrp61bt6p3797Kzc1Vp06d+OwAAAAAyjybda1fq6NY/RLXyNUjAACAG+D9/GZXj1DqAgJ8lJGR6eoxcIOCg/2KvA9/vAwAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhPFw9gCn6BlZ09QgAAOAaVvRZ5+oRAJfiTAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABiOKAAAAAAMRxQAAAAAhiMKAAAAAMMRBQAAAIDhiAIAAADAcEQBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADD2SzLslw9hAlycy8qIyPT1WPgBgUE+LB+ZRRrV7axfmUXa1e2sX5lW3CwX5H34UwBAAAAYDiiAAAAADAcUQAAAAAYjigAAAAADEcUAAAAAIYjCgAAAADDEQUAAACA4YgCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGs1mWZbl6CAAAAACuw5kCAAAAwHBEAQAAAGA4ogAAAAAwHFEAAAAAGI4oKGYOh0Pjxo1T7969FR0drUOHDuW7Pzk5WREREYqKitKXX37poilxNddbu8TEREVGRioyMlJz5sxx0ZS4luut3+Vtnn76aS1ZssQFE+Jarrd2X331laKiohQVFaXXXntNfD/GreV665eQkKCIiAj17NlT69evd9GUKMiOHTsUHR19xe1ffPGFevbsqd69eys5OdkFk6EwrrV+q1evVmRkpPr06aNx48bJ4XAUfCALxerTTz+1XnnlFcuyLGv79u3Wc88957wvPT3d6tKli2W3263ff//d+W/cGgpau19++cXq0aOHlZeXZ128eNHq3bu3tWfPHleNiqsoaP0umzFjhtWrVy/r/fffL+3xUICC1u7cuXPWo48+ap06dcqyLMuaP3++89+4NRS0fmfPnrUeeOABy263WxkZGdaf//xnV42Ja5g/f77VpUsXKzIyMt/tOTk51oMPPmhlZGRYdrvdioiIsNLT0100Ja7lWuuXlZVldejQwcrMzLQsy7KGDRtmbdiwocBjcaagmG3btk3333+/JKlp06batWuX876dO3eqWbNm8vLykp+fn0JDQ/XDDz+4alT8l4LWrmrVqnrnnXfk7u4uNzc35eXlqVy5cq4aFVdR0PpJ0rp162Sz2dS2bVtXjIcCFLR227dvV926dTV16lT169dPQUFBqlSpkqtGxVUUtH7e3t6qXr26srKylJWVJZvN5qoxcQ2hoaF68803r7j9wIEDCg0Nlb+/v7y8vNSiRQulpqa6YEIU5Frr5+XlpQ8++EDe3t6SVKj3LURBMTt//rwqVKjg/Nnd3V15eXnO+/z8/Jz3+fr66vz586U+I66uoLXz9PRUpUqVZFmWpk6dqrvuuku1a9d21ai4ioLWb9++fVq9erWGDh3qqvFQgILW7syZM9qyZYtefvllvf3221q4cKEOHjzoqlFxFQWtnyRVq1ZNjz76qHr06KEnnnjCFSOiAA8//LA8PDyuuJ33LGXDtdbPzc1NQUFBkqSkpCRlZmaqdevWBR7ryqPgplSoUEEXLlxw/uxwOJyL9d/3XbhwId9/4eBaBa2dJNntdsXGxsrX11fjx493xYgoQEHrt2LFCh0/flz9+/fX0aNH5enpqRo1anDW4BZR0NoFBASoUaNGCg4OliS1bNlSe/bsIcpvIQWt38aNG5Wenq7PP/9ckjRw4EA1b95cjRs3dsmsKDzes5R9DodD06dP18GDB/Xmm29e90wdZwqKWfPmzbVx40ZJ0nfffae6des672vcuLG2bdsmu92uc+fO6cCBA/nuh2sVtHaWZemFF15QvXr1FBcXJ3d3d1eNiWsoaP1GjRqllJQUJSUlqUePHnryyScJgltIQWvXsGFD7du3T6dPn1ZeXp527Nih8PBwV42Kqyho/fz9/VW+fHl5eXmpXLly8vPz0++//+6qUVEEderU0aFDh5SRkaGcnBylpqaqWbNmrh4LRTBu3DjZ7XbNnTvXeRlRQThTUMweeughbdq0SX369JFlWZo8ebIWLFig0NBQdejQQdHR0erXr58sy9KwYcO4Lv0WUtDaORwObd26VTk5Ofr3v/8tSRo+fDj/A3kLud5/93Drut7ajRgxQk8//bQkqVOnTvwy5RZzvfXbvHmzoqKi5ObmpubNm1/3Ega41qpVq5SZmanevXsrJiZGAwcOlGVZ6tmzp6pUqeLq8XAdl9evYcOG+vDDD9WyZUv1799fkvTEE0/ooYceuua+Nsviu90AAAAAk3H5EAAAAGA4ogAAAAAwHFEAAAAAGI4oAAAAAAxHFAAAAACGIwoAAC4zf/58tWnTRna7XZIUExPj/M77y/7vV1hu2LBB0dHRio6OVmRkpNatW1eq8wLAHxV/pwAA4DKrVq1S586dtWbNGkVERBS4bVpamhITEzVv3jz5+vrqzJkz6t27t8LDw/mDZgBwkzhTAABwiS1btig0NFR9+vTR4sWLr7t9SkqK+vfvL19fX0lSYGCgUlJSVKdOnZIeFQD+8DhTAABwiZSUFEVGRiosLExeXl7asWPHVbez2WySpPT0dIWEhOS7z9/fv8TnBAATEAUAgFJ39uxZbdy4UadPn1ZSUpLOnz+vRYsWycfHRzk5Ofm2zcvLkyRVr15dx44dU/369Z33bdu2TUFBQapZs2apzg8AfzRcPgQAKHUff/yxevbsqXfffVcJCQlKTk7Wpk2bFBISovXr1zu3S01NdX5eICIiQgkJCcrMzJQknTp1SrGxscrKynLJcwCAPxLOFAAASl1KSoqmTZvm/Nnb21sdO3ZUdna2fHx81K1bN/n6+srT01NxcXGSpGbNmikqKkoDBgyQh4eHsrOzNXz48HxnDgAAN8ZmWZbl6iEAAAAAuA6XDwEAAACGIwoAAAAAwxEFAAAAgOGIAgAAAMBwRAEAAABgOKIAAAAAMBxRAAAAABju/wHZ4VrVzPQsCwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "build.plotAUCComparison([(df_model1,3), (df_model2,5), (df_model3, 4)])" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
    " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "build.plotCumulatives([(df_model1,3), (df_model2,5), (df_model3, 4)], df_transformed)" - ] + "source": [] } ], "metadata": { @@ -2099,9 +1180,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.8.0" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } From ba01627a4081d42a0daa0cdab86708fca1bedbae Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 11:22:31 +0100 Subject: [PATCH 72/98] Modify evaluation's __init__.py --- cobra/evaluation/__init__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py index 3172704..4575e23 100644 --- a/cobra/evaluation/__init__.py +++ b/cobra/evaluation/__init__.py @@ -1,5 +1,17 @@ -from .pigs_tables import generate_pig_tables, compute_pig_table, plot_pig_graph +from .pigs_tables import generate_pig_tables +from .pigs_tables import compute_pig_table +from .pigs_tables import plot_pig_graph + +from .performance_curves import plot_performance_curves + +from .predictor_quality import plot_variable_importance +from .predictor_quality import plot_predictor_quality +from .predictor_quality import plot_correlation_matrix __all__ = ['generate_pig_tables', 'compute_pig_table', - 'plot_pig_graph'] + 'plot_pig_graph', + 'plot_performance_curves', + 'plot_variable_importance', + 'plot_predictor_quality', + 'plot_correlation_matrix'] From 2c47dd215168738a1b96c63a90300f215b899e8c Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 11:27:05 +0100 Subject: [PATCH 73/98] Add evaluation to setup.py --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 20ac799..a13d0d8 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,10 @@ name="cobra", version="1.0.0", description="Python Prediction's methodology for predictive analytics", - packages=["cobra", "cobra.preprocessing", "cobra.model_building"], + packages=["cobra", + "cobra.preprocessing", + "cobra.model_building", + "cobra.evaluation"], url="https://github.com/PythonPredictions", #long_description=long_description, # TO DO #long_description_content_type="text/markdown", @@ -16,5 +19,5 @@ "scikit_learn>=0.22.1", "matplotlib>=3.0.2", "seaborn>=0.9.0"], - python_requires=">=3.5", + python_requires=">=3.6", ) From 746c99b7fac3528b1d1adf4932f020248395d7b6 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 14:14:09 +0100 Subject: [PATCH 74/98] Change line endings to linux style in evaluation module --- cobra/evaluation/__init__.py | 34 +- cobra/evaluation/model_evaluator.py | 708 ++++++++++++------------- cobra/evaluation/performance_curves.py | 70 +-- cobra/evaluation/pigs_tables.py | 218 ++++---- cobra/evaluation/predictor_quality.py | 170 +++--- 5 files changed, 600 insertions(+), 600 deletions(-) diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py index 4575e23..5a3f437 100644 --- a/cobra/evaluation/__init__.py +++ b/cobra/evaluation/__init__.py @@ -1,17 +1,17 @@ -from .pigs_tables import generate_pig_tables -from .pigs_tables import compute_pig_table -from .pigs_tables import plot_pig_graph - -from .performance_curves import plot_performance_curves - -from .predictor_quality import plot_variable_importance -from .predictor_quality import plot_predictor_quality -from .predictor_quality import plot_correlation_matrix - -__all__ = ['generate_pig_tables', - 'compute_pig_table', - 'plot_pig_graph', - 'plot_performance_curves', - 'plot_variable_importance', - 'plot_predictor_quality', - 'plot_correlation_matrix'] +from .pigs_tables import generate_pig_tables +from .pigs_tables import compute_pig_table +from .pigs_tables import plot_pig_graph + +from .performance_curves import plot_performance_curves + +from .predictor_quality import plot_variable_importance +from .predictor_quality import plot_predictor_quality +from .predictor_quality import plot_correlation_matrix + +__all__ = ['generate_pig_tables', + 'compute_pig_table', + 'plot_pig_graph', + 'plot_performance_curves', + 'plot_variable_importance', + 'plot_predictor_quality', + 'plot_correlation_matrix'] diff --git a/cobra/evaluation/model_evaluator.py b/cobra/evaluation/model_evaluator.py index a0addd9..f2d1093 100644 --- a/cobra/evaluation/model_evaluator.py +++ b/cobra/evaluation/model_evaluator.py @@ -1,354 +1,354 @@ -""" -Created on Fri Apr 12 09:36:37 2019 -@author: AP_JBENEK -""" -import numpy as np -import pandas as pd -import seaborn as sns -import matplotlib.pyplot as plt - -from sklearn.metrics import precision_score -from sklearn.metrics import recall_score -from sklearn.metrics import f1_score -from sklearn.metrics import accuracy_score -from sklearn.metrics import roc_curve -from sklearn.metrics import confusion_matrix -from sklearn.metrics import roc_auc_score - - -class Evaluator(): - - def __init__(self, y_true, y_pred_p, threshold=0.5, lift_at=0.1): - self.y_true = y_true - self.y_pred_p = y_pred_p # As probability - self.lift_at = lift_at - self.threshold = threshold - - #Convert to bool - self.y_pred_b = np.array([0 if pred <= self.threshold else 1 - for pred in self.y_pred_p]) - - def plotROCCurve(self, save_pth=None, desc=None): - ''' - Plot ROC curve and print best cutoff value - - Parameters - ---------- - y_true: True values of target y - proba: Predicted values of target y, probabilities - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - fpr, tpr, thresholds = roc_curve(self.y_true, self.y_pred_p) - - #--------------------------- - #Calculate AUC - #-------------------------- - out_perfo = self.evaluation() - score = out_perfo['AUC'] - - fig, ax = plt.subplots(figsize=(8, 5)) - ax.plot(fpr, tpr, color='darkorange', lw=2, - label='ROC curve (area = {s:.3})'.format(s=score)) - ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') - ax.set_xlabel('False Positive Rate', fontsize=15) - ax.set_ylabel('True Positive Rate', fontsize=15) - ax.legend(loc="lower right") - ax.set_title('ROC Curve {}' .format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - #Best cutoff value - #i want value where FPR is highest and FPR is lowest - #https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python - i = np.arange(len(tpr)) - roc = pd.DataFrame({'tf': pd.Series(tpr-(1-fpr), index=i), - 'threshold': pd.Series(thresholds, index=i)}) - roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] - - best_cutoff = list(roc_t['threshold']) - print(f'Best cutoff value for probability is: {best_cutoff[0]}') - - def plotConfusionMatrix(self, labels=None, color='Reds', save_pth=None, desc=None): - ''' - Plot Confusion matrix with performance measures - - Parameters - ---------- - y_test: True values of target y - pred: Predicted values of target y, boolean - labels: labels for the matrix, if empty, values from y_test_ are used - color: Color of the matrix, its a cmap, so many values possible - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if labels is None: - labels = [str(lab) for lab in np.unique(self.y_true)] - - if desc is None: - desc = '' - - cm = confusion_matrix(self.y_true, self.y_pred_b) - - fig, ax = plt.subplots(figsize=(8,5)) - ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, - xticklabels=labels, yticklabels=labels) - ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - out_perfo = self.evaluation() - - # If we mark customer as a churner, how often we are correct - print('Precision: {s:.3}'.format(s=out_perfo['precision'])) - # Overall performance - print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) - # How many churners can the model detect - print('Recall: {s:.3}'.format(s=out_perfo['recall'])) - # 2 * (precision * recall) / (precision + recall) - print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) - # 2 * (precision * recall) / (precision + recall) - print('Lift at top {l}%: {s:.3}' - .format(l=self.lift_at*100, s=out_perfo['lift'])) - # 2 * (precision * recall) / (precision + recall) - print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) - - def plotCumulativeGains(self, save_pth=None, desc=None): - ''' - Functions plot cumulative gains - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - #--------------------------- - #Calculate cumulative gains - #-------------------------- - nrows = len(self.y_true) - npositives = self.y_true.sum() - df_y_pred = (pd.DataFrame({"y": self.y_true, "y_pred": self.y_pred_p}) - .sort_values(by='y_pred', ascending=False) - .reset_index(drop=True)) - cgains = [0] - for stop in (np.linspace(0.01, 1, 100) * nrows).astype(int): - cgains.append(round(df_y_pred.loc[:stop, 'y'].sum()/npositives*max(100, 1), 2)) - - #--------------------------- - #Plot it - #--------------------------- - plt.style.use('seaborn-darkgrid') - fig, ax_cgains = plt.subplots(figsize=(8, 5)) - ax_cgains.plot(cgains, color='blue', linewidth=3, - label='cumulative gains') - ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, - ls="--", color="darkorange", label='random selection') - ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) - - ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) - #Format axes - ax_cgains.set_xlim([0, 100]) - ax_cgains.set_ylim([0, 100]) - #Format ticks - ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) - for x in ax_cgains.get_yticks()]) - ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) - for x in ax_cgains.get_xticks()]) - #Legend - ax_cgains.legend(loc='lower right') - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotLift(self, desc=None, save_pth=None): - ''' - Method plots lift per decile - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- -# inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=perc_lift) - for perc_lift in np.arange(0.1, 1.1, 0.1)] - - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - plt.style.use('seaborn-darkgrid') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0, nrows, 1)] - - plt.bar(x_labels[::-1], lifts, align='center', color="cornflowerblue") - plt.ylabel('lift', fontsize=15) - plt.xlabel('decile', fontsize=15) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=1, color='darkorange', linestyle='--', - xmin=0.1, xmax=0.9, linewidth=3, label='Baseline') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeResponse(self, desc=None, save_pth=None): - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- - inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=perc_lift) - for perc_lift in np.arange(0.1, 1.1, 0.1)] - lifts = np.array(lifts)*inc_rate*100 - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8, 5)) - #plt.style.use('seaborn-darkgrid') - plt.style.use('default') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0, nrows, 1)] - - plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") - plt.ylabel('response (%)', fontsize=16) - plt.xlabel('decile', fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', - xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative response {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def evaluation(self): - ''' - Convenient function, returns various performance measures in a dict - - Parameters - ---------- - y_true: true values - y_pred: predictions as booleans - - Output - ------ - Returns dictionary with the measures - ''' - - dict_perfo = {'precision': precision_score(self.y_true, self.y_pred_b), - 'accuracy': accuracy_score(self.y_true, self.y_pred_b), - 'recall': recall_score(self.y_true, self.y_pred_b), - 'F1': f1_score(self.y_true, self.y_pred_b, - average=None)[1], - 'lift': np.round(Evaluator - .liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=self.lift_at), - 2), - 'AUC': roc_auc_score(self.y_true, self.y_pred_p) - } - return dict_perfo - - @staticmethod - def liftCalculator(y_true, y_pred, lift_at=0.05, **kwargs): - ''' - Calculates lift given two arrays on specified level - - Parameters - ---------- - y_true: numpy array with true values - y_pred: numpy array with predictions (probabilities) - lift_at: lift at what top percentage - - Output - ------ - Scalar value, lift. - - 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, - 10000 loops each) - ''' - #Make sure it is numpy array - y_true_ = np.array(y_true) - y_pred_ = np.array(y_pred) - - #Make sure it has correct shape - y_true_ = y_true_.reshape(len(y_true_), 1) - y_pred_ = y_pred_.reshape(len(y_pred_), 1) - - #Merge data together - y_data = np.hstack([y_true_, y_pred_]) - - #Calculate necessary variables - nrows = len(y_data) - stop = int(np.floor(nrows*lift_at)) - avg_incidence = np.einsum('ij->j', y_true_)/float(len(y_true_)) - - #Sort and filter data - data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] - .reshape(stop, 1)) - - #Calculate lift (einsum is very fast way of summing, - # needs specific shape) - inc_in_top_n = np.einsum('ij->j', data_sorted)/float(len(data_sorted)) - - lift = np.round(inc_in_top_n/avg_incidence, 2)[0] - - return lift +""" +Created on Fri Apr 12 09:36:37 2019 +@author: AP_JBENEK +""" +import numpy as np +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt + +from sklearn.metrics import precision_score +from sklearn.metrics import recall_score +from sklearn.metrics import f1_score +from sklearn.metrics import accuracy_score +from sklearn.metrics import roc_curve +from sklearn.metrics import confusion_matrix +from sklearn.metrics import roc_auc_score + + +class Evaluator(): + + def __init__(self, y_true, y_pred_p, threshold=0.5, lift_at=0.1): + self.y_true = y_true + self.y_pred_p = y_pred_p # As probability + self.lift_at = lift_at + self.threshold = threshold + + #Convert to bool + self.y_pred_b = np.array([0 if pred <= self.threshold else 1 + for pred in self.y_pred_p]) + + def plotROCCurve(self, save_pth=None, desc=None): + ''' + Plot ROC curve and print best cutoff value + + Parameters + ---------- + y_true: True values of target y + proba: Predicted values of target y, probabilities + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + fpr, tpr, thresholds = roc_curve(self.y_true, self.y_pred_p) + + #--------------------------- + #Calculate AUC + #-------------------------- + out_perfo = self.evaluation() + score = out_perfo['AUC'] + + fig, ax = plt.subplots(figsize=(8, 5)) + ax.plot(fpr, tpr, color='darkorange', lw=2, + label='ROC curve (area = {s:.3})'.format(s=score)) + ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') + ax.set_xlabel('False Positive Rate', fontsize=15) + ax.set_ylabel('True Positive Rate', fontsize=15) + ax.legend(loc="lower right") + ax.set_title('ROC Curve {}' .format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + #Best cutoff value + #i want value where FPR is highest and FPR is lowest + #https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python + i = np.arange(len(tpr)) + roc = pd.DataFrame({'tf': pd.Series(tpr-(1-fpr), index=i), + 'threshold': pd.Series(thresholds, index=i)}) + roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] + + best_cutoff = list(roc_t['threshold']) + print(f'Best cutoff value for probability is: {best_cutoff[0]}') + + def plotConfusionMatrix(self, labels=None, color='Reds', save_pth=None, desc=None): + ''' + Plot Confusion matrix with performance measures + + Parameters + ---------- + y_test: True values of target y + pred: Predicted values of target y, boolean + labels: labels for the matrix, if empty, values from y_test_ are used + color: Color of the matrix, its a cmap, so many values possible + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if labels is None: + labels = [str(lab) for lab in np.unique(self.y_true)] + + if desc is None: + desc = '' + + cm = confusion_matrix(self.y_true, self.y_pred_b) + + fig, ax = plt.subplots(figsize=(8,5)) + ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, + xticklabels=labels, yticklabels=labels) + ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + out_perfo = self.evaluation() + + # If we mark customer as a churner, how often we are correct + print('Precision: {s:.3}'.format(s=out_perfo['precision'])) + # Overall performance + print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) + # How many churners can the model detect + print('Recall: {s:.3}'.format(s=out_perfo['recall'])) + # 2 * (precision * recall) / (precision + recall) + print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) + # 2 * (precision * recall) / (precision + recall) + print('Lift at top {l}%: {s:.3}' + .format(l=self.lift_at*100, s=out_perfo['lift'])) + # 2 * (precision * recall) / (precision + recall) + print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) + + def plotCumulativeGains(self, save_pth=None, desc=None): + ''' + Functions plot cumulative gains + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + if desc is None: + desc = '' + + #--------------------------- + #Calculate cumulative gains + #-------------------------- + nrows = len(self.y_true) + npositives = self.y_true.sum() + df_y_pred = (pd.DataFrame({"y": self.y_true, "y_pred": self.y_pred_p}) + .sort_values(by='y_pred', ascending=False) + .reset_index(drop=True)) + cgains = [0] + for stop in (np.linspace(0.01, 1, 100) * nrows).astype(int): + cgains.append(round(df_y_pred.loc[:stop, 'y'].sum()/npositives*max(100, 1), 2)) + + #--------------------------- + #Plot it + #--------------------------- + plt.style.use('seaborn-darkgrid') + fig, ax_cgains = plt.subplots(figsize=(8, 5)) + ax_cgains.plot(cgains, color='blue', linewidth=3, + label='cumulative gains') + ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, + ls="--", color="darkorange", label='random selection') + ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) + + ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) + #Format axes + ax_cgains.set_xlim([0, 100]) + ax_cgains.set_ylim([0, 100]) + #Format ticks + ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) + for x in ax_cgains.get_yticks()]) + ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) + for x in ax_cgains.get_xticks()]) + #Legend + ax_cgains.legend(loc='lower right') + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotLift(self, desc=None, save_pth=None): + ''' + Method plots lift per decile + + Parameters + ---------- + save: whether plot should be saved (if yes, then now shown) + desc: description of the plot, used also as a name of saved plot + ''' + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- +# inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8,5)) + plt.style.use('seaborn-darkgrid') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0, nrows, 1)] + + plt.bar(x_labels[::-1], lifts, align='center', color="cornflowerblue") + plt.ylabel('lift', fontsize=15) + plt.xlabel('decile', fontsize=15) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color='darkorange', linestyle='--', + xmin=0.1, xmax=0.9, linewidth=3, label='Baseline') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def plotCumulativeResponse(self, desc=None, save_pth=None): + #--------------------- + #-- CALCULATE LIFT --- + #--------------------- + inc_rate = self.y_true.mean() + lifts = [Evaluator.liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + lifts = np.array(lifts)*inc_rate*100 + #--------------------- + #------- PLOT -------- + #--------------------- + if desc is None: + desc = '' + + fig, ax = plt.subplots(figsize=(8, 5)) + #plt.style.use('seaborn-darkgrid') + plt.style.use('default') + + nrows = len(lifts) + x_labels = [nrows-x for x in np.arange(0, nrows, 1)] + + plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") + plt.ylabel('response (%)', fontsize=16) + plt.xlabel('decile', fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', + xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') + + #Legend + ax.legend(loc='upper right') + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title('Cumulative response {}'.format(desc), fontsize=20) + + if save_pth is not None: + plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') + + plt.show() + + def evaluation(self): + ''' + Convenient function, returns various performance measures in a dict + + Parameters + ---------- + y_true: true values + y_pred: predictions as booleans + + Output + ------ + Returns dictionary with the measures + ''' + + dict_perfo = {'precision': precision_score(self.y_true, self.y_pred_b), + 'accuracy': accuracy_score(self.y_true, self.y_pred_b), + 'recall': recall_score(self.y_true, self.y_pred_b), + 'F1': f1_score(self.y_true, self.y_pred_b, + average=None)[1], + 'lift': np.round(Evaluator + .liftCalculator(y_true=self.y_true, + y_pred=self.y_pred_p, + lift_at=self.lift_at), + 2), + 'AUC': roc_auc_score(self.y_true, self.y_pred_p) + } + return dict_perfo + + @staticmethod + def liftCalculator(y_true, y_pred, lift_at=0.05, **kwargs): + ''' + Calculates lift given two arrays on specified level + + Parameters + ---------- + y_true: numpy array with true values + y_pred: numpy array with predictions (probabilities) + lift_at: lift at what top percentage + + Output + ------ + Scalar value, lift. + + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, + 10000 loops each) + ''' + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_), 1) + y_pred_ = y_pred_.reshape(len(y_pred_), 1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum('ij->j', y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] + .reshape(stop, 1)) + + #Calculate lift (einsum is very fast way of summing, + # needs specific shape) + inc_in_top_n = np.einsum('ij->j', data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence, 2)[0] + + return lift diff --git a/cobra/evaluation/performance_curves.py b/cobra/evaluation/performance_curves.py index 9ce9be4..1e0032d 100644 --- a/cobra/evaluation/performance_curves.py +++ b/cobra/evaluation/performance_curves.py @@ -1,35 +1,35 @@ -# third party imports -import numpy as np -import pandas as pd - -import matplotlib.pyplot as plt - - -def plot_performance_curves(model_performances: list, - dim: tuple=(12, 8)): - - df_plt = pd.DataFrame(model_performances) - - highest_auc = np.round(max(max(df_plt['train_performance']), - max(df_plt['selection_performance']), - max(df_plt['validation_performance'])), 1) - - fig, ax = plt.subplots(figsize=dim) - - plt.plot(df_plt['train_performance'], marker=".", markersize=20, - linewidth=3, label='AUC train') - plt.plot(df_plt['selection_performance'], marker=".", markersize=20, - linewidth=3, label='AUC selection') - plt.plot(df_plt['validation_performance'], marker=".", markersize=20, - linewidth=3, label='AUC validation') - # Set x/yticks - ax.set_xticks(np.arange(len(df_plt['last_added_predictor']) + 1)) - ax.set_xticklabels(df_plt['last_added_predictor'].tolist(), - rotation=40, ha='right') - ax.set_yticks(np.arange(0.5, highest_auc + 0.02, 0.05)) - #Make Pretty - ax.legend(loc='lower right') - fig.suptitle('Performance curves - forward feature selection', - fontsize=20) - plt.ylabel('Model performance') - plt.show() +# third party imports +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt + + +def plot_performance_curves(model_performance: pd.DataFrame, + dim: tuple=(12, 8)): + + highest_auc = np.round(max(max(model_performance['train_performance']), + max(model_performance['selection_performance']), + max(model_performance['validation_performance']) + ), 1) + + fig, ax = plt.subplots(figsize=dim) + + plt.plot(model_performance['train_performance'], marker=".", markersize=20, + linewidth=3, label='AUC train') + plt.plot(model_performance['selection_performance'], marker=".", + markersize=20, linewidth=3, label='AUC selection') + plt.plot(model_performance['validation_performance'], marker=".", + markersize=20, linewidth=3, label='AUC validation') + # Set x/yticks + ax.set_xticks(np.arange(len(model_performance['last_added_predictor']) + + 1)) + ax.set_xticklabels(model_performance['last_added_predictor'].tolist(), + rotation=40, ha='right') + ax.set_yticks(np.arange(0.5, highest_auc + 0.02, 0.05)) + #Make Pretty + ax.legend(loc='lower right') + fig.suptitle('Performance curves - forward feature selection', + fontsize=20) + plt.ylabel('Model performance') + plt.show() diff --git a/cobra/evaluation/pigs_tables.py b/cobra/evaluation/pigs_tables.py index ccd64e9..8afb4f7 100644 --- a/cobra/evaluation/pigs_tables.py +++ b/cobra/evaluation/pigs_tables.py @@ -1,109 +1,109 @@ -# third party imports -import pandas as pd -#import matplotlib.pyplot as plt -#import seaborn as sns - -import cobra.utils as utils - - -def generate_pig_tables(data: pd.DataFrame, - id_column_name: str, - target_column_name: str, - preprocessed_predictors: list) -> pd.DataFrame: - """Summary - - Parameters - ---------- - data : pd.DataFrame - basetable to compute PIG tables of - id_column_name : str - column name of the id (e.g. customernumber) - target_column_name : str - column name of the target - predictors: list - list of preprocessed predictor names - - Returns - ------- - pd.DataFrame - DataFrame containing a PIG table for all predictors - """ - - # Based on the data, get column names by datatype - # threshold to decide whether a numeric column should be considered - # a categorical variable (if the number of distinct values is smaller - # or equal to the number of requested bins) - - pigs = [compute_pig_table(data, column_name, target_column_name, - id_column_name) - for column_name in sorted(preprocessed_predictors) - if column_name not in [id_column_name, target_column_name]] - - output = pd.concat(pigs) - - return output - - -def compute_pig_table(data: pd.DataFrame, - column_name: str, - target_column_name: str, - id_column_name: str) -> pd.DataFrame: - """Compute the pig table of a given predictor for a given target - - Parameters - ---------- - data : pd.DataFrame - input data from which to compute the pig table - column_name : str - predictor name of which to compute the pig table - target_column_name : str - name of the target variable - id_column_name : str - name of the id column (used to count population size) - - Returns - ------- - pd.DataFrame - pig table as a DataFrame - """ - avg_incidence = data[target_column_name].mean() - - # group by the binned variable, compute the incidence - # (=mean of the target for the given bin) and compute the bin size - # (e.g. COUNT(id_column_name)). After that, rename the columns - res = (data.groupby(column_name) - .agg({target_column_name: "mean", id_column_name: "size"}) - .reset_index() - .rename(columns={column_name: "label", - target_column_name: "incidence", - id_column_name: "pop_size"})) - - # add the column name to a variable column - # add the average incidence - # replace population size by a percentage of total population - res["variable"] = utils.clean_predictor_name(column_name) - res["avg_incidence"] = avg_incidence - res["pop_size"] = res["pop_size"]/len(data.index) - - # make sure to always return the data with the proper column order - column_order = ["variable", "label", "pop_size", - "avg_incidence", "incidence"] - - return res[column_order] - - -def plot_pig_graph(pig_table: pd.DataFrame, - dim: tuple=(12, 8), - save_path: str=None): - """Create the Predictor Insights Graphs from a PIG table - - Parameters - ---------- - pig_table : pd.DataFrame - Description - dim : tuple, optional - Tuple with width and lentgh of the plot - save_path : str, optional - path to store the plot on disk - """ - pass +# third party imports +import pandas as pd +#import matplotlib.pyplot as plt +#import seaborn as sns + +import cobra.utils as utils + + +def generate_pig_tables(data: pd.DataFrame, + id_column_name: str, + target_column_name: str, + preprocessed_predictors: list) -> pd.DataFrame: + """Summary + + Parameters + ---------- + data : pd.DataFrame + basetable to compute PIG tables of + id_column_name : str + column name of the id (e.g. customernumber) + target_column_name : str + column name of the target + predictors: list + list of preprocessed predictor names + + Returns + ------- + pd.DataFrame + DataFrame containing a PIG table for all predictors + """ + + # Based on the data, get column names by datatype + # threshold to decide whether a numeric column should be considered + # a categorical variable (if the number of distinct values is smaller + # or equal to the number of requested bins) + + pigs = [compute_pig_table(data, column_name, target_column_name, + id_column_name) + for column_name in sorted(preprocessed_predictors) + if column_name not in [id_column_name, target_column_name]] + + output = pd.concat(pigs) + + return output + + +def compute_pig_table(data: pd.DataFrame, + column_name: str, + target_column_name: str, + id_column_name: str) -> pd.DataFrame: + """Compute the pig table of a given predictor for a given target + + Parameters + ---------- + data : pd.DataFrame + input data from which to compute the pig table + column_name : str + predictor name of which to compute the pig table + target_column_name : str + name of the target variable + id_column_name : str + name of the id column (used to count population size) + + Returns + ------- + pd.DataFrame + pig table as a DataFrame + """ + avg_incidence = data[target_column_name].mean() + + # group by the binned variable, compute the incidence + # (=mean of the target for the given bin) and compute the bin size + # (e.g. COUNT(id_column_name)). After that, rename the columns + res = (data.groupby(column_name) + .agg({target_column_name: "mean", id_column_name: "size"}) + .reset_index() + .rename(columns={column_name: "label", + target_column_name: "incidence", + id_column_name: "pop_size"})) + + # add the column name to a variable column + # add the average incidence + # replace population size by a percentage of total population + res["variable"] = utils.clean_predictor_name(column_name) + res["avg_incidence"] = avg_incidence + res["pop_size"] = res["pop_size"]/len(data.index) + + # make sure to always return the data with the proper column order + column_order = ["variable", "label", "pop_size", + "avg_incidence", "incidence"] + + return res[column_order] + + +def plot_pig_graph(pig_table: pd.DataFrame, + dim: tuple=(12, 8), + save_path: str=None): + """Create the Predictor Insights Graphs from a PIG table + + Parameters + ---------- + pig_table : pd.DataFrame + Description + dim : tuple, optional + Tuple with width and lentgh of the plot + save_path : str, optional + path to store the plot on disk + """ + pass diff --git a/cobra/evaluation/predictor_quality.py b/cobra/evaluation/predictor_quality.py index f2a2ea3..327d592 100644 --- a/cobra/evaluation/predictor_quality.py +++ b/cobra/evaluation/predictor_quality.py @@ -1,85 +1,85 @@ -# third party imports -import pandas as pd - -import matplotlib.pyplot as plt -import seaborn as sns - - -def plot_variable_importance(importance_by_variable: dict, - title: str=None, - dim: tuple=(12, 8)): - """Plot variable importance of a given model - - Parameters - ---------- - importance_by_variable : dict - Map of predictor -> importance - title : str, optional - Title of the plot - dim : tuple, optional - tuple with width and lentgh of the plot - """ - df = pd.DataFrame.from_dict(importance_by_variable, - orient='index').reset_index() - - df.columns = ["predictor", "importance"] - - df = df.sort_values(by="importance", ascending=False) - - # plot data - fig, ax = plt.subplots(figsize=dim) - ax = sns.barplot(x="importance", y="predictor", data=df) - if title: - ax.set_title(title) - else: - ax.set_title("Variable importance") - plt.show() - - -def plot_predictor_quality(df_auc: pd.DataFrame, - dim: tuple=(12, 8)): - """Plot univariate quality of the predictors - - Parameters - ---------- - df_auc : pd.DatFrame - Contains for each variable the train auc and selection auc allong with - a boolean indicating whether or not it is selected based on the - criteria - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - plt.style.use('seaborn-darkgrid') - - df = (df_auc[df_auc["preselection"]] - .sort_values(by='AUC train', ascending=False)) - - df = pd.melt(df, id_vars=["predictor"], - value_vars=["AUC train", "AUC selection"], - var_name="partition", - value_name="AUC") - - # plots - fig, ax = plt.subplots(figsize=dim) - - ax = sns.barplot(x="AUC", y="predictor", hue="partition", data=df) - ax.set_title('Univariate Quality of Predictors') - plt.show() - - -def plot_correlation_matrix(df_corr: pd.DataFrame, - dim: tuple=(12, 8)): - """Plot correlation matrix amongst the predictors - - Parameters - ---------- - df_corr : pd.DataFrame - Correlation matrix - dim : tuple, optional - tuple with width and lentgh of the plot - """ - fig, ax = plt.subplots(figsize=dim) - ax = sns.heatmap(df_corr, cmap='Blues') - ax.set_title('Correlation Matrix') - plt.show() +# third party imports +import pandas as pd + +import matplotlib.pyplot as plt +import seaborn as sns + + +def plot_variable_importance(importance_by_variable: dict, + title: str=None, + dim: tuple=(12, 8)): + """Plot variable importance of a given model + + Parameters + ---------- + importance_by_variable : dict + Map of predictor -> importance + title : str, optional + Title of the plot + dim : tuple, optional + tuple with width and lentgh of the plot + """ + df = pd.DataFrame.from_dict(importance_by_variable, + orient='index').reset_index() + + df.columns = ["predictor", "importance"] + + df = df.sort_values(by="importance", ascending=False) + + # plot data + fig, ax = plt.subplots(figsize=dim) + ax = sns.barplot(x="importance", y="predictor", data=df) + if title: + ax.set_title(title) + else: + ax.set_title("Variable importance") + plt.show() + + +def plot_predictor_quality(df_auc: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot univariate quality of the predictors + + Parameters + ---------- + df_auc : pd.DatFrame + Contains for each variable the train auc and selection auc allong with + a boolean indicating whether or not it is selected based on the + criteria + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + plt.style.use('seaborn-darkgrid') + + df = (df_auc[df_auc["preselection"]] + .sort_values(by='AUC train', ascending=False)) + + df = pd.melt(df, id_vars=["predictor"], + value_vars=["AUC train", "AUC selection"], + var_name="partition", + value_name="AUC") + + # plots + fig, ax = plt.subplots(figsize=dim) + + ax = sns.barplot(x="AUC", y="predictor", hue="partition", data=df) + ax.set_title('Univariate Quality of Predictors') + plt.show() + + +def plot_correlation_matrix(df_corr: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot correlation matrix amongst the predictors + + Parameters + ---------- + df_corr : pd.DataFrame + Correlation matrix + dim : tuple, optional + tuple with width and lentgh of the plot + """ + fig, ax = plt.subplots(figsize=dim) + ax = sns.heatmap(df_corr, cmap='Blues') + ax.set_title('Correlation Matrix') + plt.show() From 45c1ee6b56ca3ba5dc0c6d3141ce920dc34c82ae Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Wed, 25 Mar 2020 14:15:57 +0100 Subject: [PATCH 75/98] Type fix in README, add examples.ipynb --- README.md | 13 +- examples/examples.ipynb | 356 ++++++++++++++++++++++++++++++++++------ 2 files changed, 318 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 9dd035c..1c7bcc6 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ preprocessor.fit(basetable[basetable["split"]=="train"], basetable = preprocessor.transform(basetable, continuous_vars=continuous_vars, discrete_vars=discrete_vars) + ``` Once the preprocessing pipeline is fitted and applied to your data, it is time for the actual modelling. In this part of the process, @@ -103,14 +104,18 @@ we first start with the _univariate preselection_: ```python from cobra.model_building import univariate_selection +# Get list of predictor names to use for univariate_selection +preprocessed_predictors = [col for col in basetable.columns if col.endswith("_enc")] + # perform univariate selection on preprocessed predictors: df_auc = univariate_selection.compute_univariate_preselection( target_enc_train_data=basetable[basetable["split"] == "train"], target_enc_selection_data=basetable[basetable["split"] == "selection"], predictors=preprocessed_predictors, target_column=target_column_name, - preselect_auc_threshold=0.5, - preselect_overtrain_threshold=5) + preselect_auc_threshold=0.53, # if auc_selection <= 0.53 exclude predictor + preselect_overtrain_threshold=0.05 # if (auc_train - auc_selection) >= 0.05 --> overfitting! + ) # compute correlations between preprocessed predictors: df_corr = (univariate_selection @@ -142,13 +147,13 @@ performances = (forward_selection # After plotting the performances and selecting the model, # we can extract this model from the forward_selection class: -model = forward_selection.get_model_from_step(5) +model = forward_selection.get_model_from_step(5) # Python indexing starts from 0, so this model has 6 predictors # Note that model has 6 variables (python lists start with index 0), # which can be obtained as follows: final_predictors = model.predictors # We can also compute the importance of each predictor in the model (dict): -variable_importance = model.compute_variable_importance(transformed_data) +variable_importance = model.compute_variable_importance(basetable) ``` ## Development diff --git a/examples/examples.ipynb b/examples/examples.ipynb index 4611637..146ec0c 100644 --- a/examples/examples.ipynb +++ b/examples/examples.ipynb @@ -41,20 +41,18 @@ "outputs": [], "source": [ "# third party imports \n", - "import numpy as np\n", "import pandas as pd \n", "\n", - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "\n", "# Custom imports\n", - "import sys\n", - "sys.path.append(\"/mnt/c/Users/matroe/Documents/workspace/cobra\")\n", "from cobra.preprocessing import PreProcessor\n", "from cobra.model_building import univariate_selection\n", "from cobra.model_building import ForwardFeatureSelection\n", - "from cobra.evaluation import generate_pig_tables" + "\n", + "from cobra.evaluation import generate_pig_tables\n", + "from cobra.evaluation import plot_performance_curves\n", + "from cobra.evaluation import plot_variable_importance\n", + "from cobra.evaluation import plot_predictor_quality\n", + "from cobra.evaluation import plot_correlation_matrix" ] }, { @@ -67,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -272,13 +270,13 @@ "[5 rows x 36 columns]" ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "path = \"../datasets/data.csv\"\n", + "path = \"\"\n", "\n", "basetable = pd.read_csv(path)\n", "\n", @@ -287,7 +285,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -296,14 +294,14 @@ "Index(['age', 'workclass', 'fnlwgt', 'education', 'education-num',\n", " 'marital-status', 'occupation', 'relationship', 'race', 'sex',\n", " 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country',\n", - " 'ID', 'scont_1', 'scont_2', 'scont_3', 'scont_4', 'scont_5', 'scont_6',\n", - " 'scont_7', 'scont_8', 'scont_9', 'scont_10', 'scat_1', 'scat_2',\n", - " 'scat_3', 'scat_4', 'scat_5', 'sflag_1', 'sflag_2', 'sflag_3',\n", - " 'sflag_4', 'sflag_5', 'TARGET', 'split'],\n", + " 'TARGET', 'ID', 'scont_1', 'scont_2', 'scont_3', 'scont_4', 'scont_5',\n", + " 'scont_6', 'scont_7', 'scont_8', 'scont_9', 'scont_10', 'scat_1',\n", + " 'scat_2', 'scat_3', 'scat_4', 'scat_5', 'sflag_1', 'sflag_2', 'sflag_3',\n", + " 'sflag_4', 'sflag_5'],\n", " dtype='object')" ] }, - "execution_count": 12, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -321,7 +319,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -333,14 +331,14 @@ "Name: split, dtype: int64" ] }, - "execution_count": 8, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Prepare data\n", - "path = \"../test_pipeline.json\"\n", + "path = \"\"\n", "preprocessor = PreProcessor.from_params(serialization_path=path)\n", "\n", "basetable = preprocessor.train_selection_validation_split(basetable, target_column_name=\"TARGET\",\n", @@ -351,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -366,7 +364,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -402,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -423,7 +421,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -628,7 +626,7 @@ "[5 rows x 99 columns]" ] }, - "execution_count": 24, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -646,7 +644,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -664,7 +662,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -684,7 +682,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -803,7 +801,7 @@ "9 native-country 0.514099 0.513408 True" ] }, - "execution_count": 30, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -821,7 +819,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -837,6 +835,46 @@ "Clearly, for meaningful inspection, we need to visualize the data. Therefore, below are plots for **Predictor quality** and **Correlation Matrix**." ] }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwoAAAHsCAYAAAByulqXAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdeVzU1f7H8Rf7ohToRdO0rkualqgFiopZ7hubW2UXM7FcUvSnRqhl5HVJsDDN9eZy85pFiiKmWKbXLMzULKNrqUSiuCco+zLM748eTdHgysAovp9/zZzv+Z7v5/shbD6cc+ZrYzQajYiIiIiIiPyJrbUDEBERERGRW48KBRERERERMaNCQUREREREzKhQEBERERERMyoURERERETEjAoFERERERExo0JBROQWNW3aNBYuXHjbjV3Z9u7dy2OPPWZ636dPH/bu3Vvh1zUajUyePBkfHx8GDBhQ4df7s5MnT9K0aVOKi4sBGD58OBs2bKjUGESk6rO3dgAiIlVV06ZN+eSTT7j//vtNbQsWLOD48ePMnTv3mudPnz69wmK7kbFDQkIICAhg4MCBN329nTt3snDhQo4dO4aTkxMdO3bkpZdeonbt2jc95pV8/PHHptc3ku8bdeDAAb788kt27dqFq6ur2fG4uDimTp2Ks7MzNjY21K9fn/Hjx/PEE09YPJZ33333uvp17tyZGTNm0L59e4vHICJVj2YURETuMAaDoVKvl5iYyMSJE3n22Wf56quv2Lx5Mw4ODgwePJjLly9XaiyWlJ6ezr333ltmkfC7Vq1acfDgQfbv38+AAQMYP348ly5dMuv3+8zArex2iFFELEuFgoiIlfy+ZGbFihW0a9cOPz8/1q9fbzoeERFBTEwMAL169WLnzp2mY8XFxfj6+vLDDz8AEBYWRocOHXj00Ud55plnOHr0aKlxXnvtNZ5//nlatWrF3r17S4196dIlRowYga+vLz4+PowYMYIzZ84AEBMTw/79+5k+fTqtW7c2zUSkpKTw3HPP0aZNG3r06MGWLVvKvEej0cicOXMYNWoU/v7+ODs74+npycyZM3FxceG9994DfvvL/6RJk0zn/XVpzfr16+nVqxetW7emS5cufPDBB1fMa+fOnUlKSuLzzz9n6dKlbN26ldatWxMQEMDWrVvp169fqf4rV65k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dIni4mI++eQTU38/Pz9Wr15ten/48GFrhC0iIiIickUqFCzgscceo7i4mF69evHmm2/SqlUrateuzYgRIxg4cCBPP/009957L25ubgBMnTqV5ORk/P396d27N2vXrrXyHYiIiIiIlGZjNBqN1g6iqvp930FxcTFjxoyhf//+dOvW7brOTZveooKjExEREbk9aY+COXd3VzIzc2/qXE9PtzLbtUehAr3zzjskJSVRUFCAn58fXbt2ve5z60z+9qZ/2PKH8vzSyB+UR8tQHstPObQM5dEylEfLUB5vXSoUKtDLL79s7RBERERERG6K9viFmMYAACAASURBVCiIiIiIiIgZzSjconynrLF2CCIiInIHSwwPtnYIYmWaURARERERETMVViicPHmSvn37VtTwt7UFCxawfPlya4chIiIiInJFt9WMQnFxcaVcx2AwVMp1RERERERuVRVaKBgMBl555RX69OnDsGHDyM/P5/DhwwwaNAh/f39efPFFLl26BEBISAjff/89ABcvXqRz584AxMXFMXLkSIYMGcLQoUM5d+4czzzzDIGBgfTt25f9+/ebXTcuLo5Ro0YREhJC9+7deeedd0zH4uPjGTBgAIGBgUybNs1UFLRu3Zo33niDgIAADh48aOp/6NAhxowZA8D27dvx8vKisLCQgoICunTpAkBaWhqhoaH069ePwYMHk5KSYrqPsWPH0r9/f/r378+BAwfMYo2NjWX48OHk5+eXO98iIiIiIpZSoYXC8ePHeeaZZ/j4449xc3Nj27ZthIeHM2nSJBISEmjSpEmpD/FX8r///Y/58+fzn//8h82bN+Pn50d8fDzx8fE8+OCDZZ7z/fffM3/+fDZt2kRiYiLff/89KSkpbN26lbVr1xIfH4+trS0JCQkA5Obm4uXlxaZNm/D29jaN07x5cw4fPgzAgQMHeOCBB/j+++/57rvvaNmyJQCvvvoqr776KnFxcbz88su8/vrrAMycOZNnn32W9evXs2DBAl555ZVSMf7nP/9h586dLFq0CGdn5xtPsIiIiIhIBanQbz2qV68ezZo1A+Chhx7ixIkTZGVl0aZNGwCCg4MZN27cNcfp0KED7u7uALRo0YIpU6ZQXFxM165dTeP/Vfv27fHw8ACgW7duHDhwAHt7e5KTkxkwYAAA+fn51KxZEwA7Ozt69OhhNo69vT333Xcf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G2+DW7RZm4iIiFQUfeuRyF9VmaVHDg4O1KtXj7i4OFq3bo23tzd79+4lLS2Ne++994rnOTk5YWdnV6rtkUceYffu3RiNxuu+fkREBNOmTSMhIYExY8ZQWFgIwPTp0xk/fjynT5+mf//+ZGRk4O/vz+LFi3F2duaFF15gz549N3fTIiIiIiIVpMoUCvDH8iMfHx+8vb354IMPaNasGV5eXuzbt4+LFy9iMBj4+OOP8fHxueI4YWFh3H333bz++usA+Pr6kpiYSEZGBkCZS49ycnLw9PSkqKiIhIQEU3taWhotW7Zk3LhxeHh4cObMGU6cOEH9+vUZMmQIXbp04aeffrJwJkREREREyqfKLD2C3wqFJUuW0KpVK1xdXXFycsLb25tatWoxceJEnn32WYxGI506daJr165XHWvq1KlMmTKFqKgowsPDGTlyJCEhIdja2tK8eXPeeOONUv3HjRvHwIEDqVGjBi1btiQnJweAqKgojh8/jtFoxNfXlwcffJB//etfxMfHY29vz9/+9jdGjBhRYTkREREREbkZNsYbWV8jlSZtegtrhyAiInLHcBmVdMPnuLu7kpmZWwHR3FmUR8soTx49Pd3KbK9SMwpVSZ3J3+qXxgL0j49lKI+WoTyWn3JoGcqjiFyPKrVHQURERERELEOFgoiIiIiImNHSo1uU75Q11g5BRESk3BLD9XwCkduVZhRERERERMSMCgURERERETGjQsECjEYjJSUl1g5DRERERMRitEfhJp08eZLQ0FBatmzJDz/8gJeXFz/99BMFBQX06NGDsLAwAA4dOsSsWbPIzc3F0dGRVatW4eLiwty5c/n6668pLCzkmWee4amnnrLyHYmIiIiI/EGFQjkcP36cOXPm0KpVKzIzM3F3d8dgMDB06FB+/PFHGjZsyP/93/8RExODl5cX2dnZODs7s27dOtzc3Fi/fj2FhYU89dRTdOjQgfr161v7lkREREREABUK5VK3bl1atWoFwNatW4mNjaW4uJjz58+TkpKCjY0Nnp6eeHl5AVC9enUAvvzyS3766Se2bdsGQFZWFsePHy9VKGxwi67kuxEREbG8vMV//P/sZp5+LCLWo0KhHFxdXQE4ceIEK1asYN26ddx9991ERERQUFBwxfOMRiOvvPIKHTt2rKxQRURERERuiDYzW0BOTg4uLi64ublx4cIFPv/8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vv7665afCwsLuf76688rEBERERERufB5NPXo//2//8cvfvELBg8eDMChQ4cICwsjPj4eoGUev/yvqqoqpk+f3uq6w+GgX79+Z70/+pncLohKRETEMxtTJvR0CCLSwzwqFFavXt3Vcfzk9OvXr8PzFERERERELmQeFQqXXnppV8chIiIiIiIXEI/WKIiIiIiISO+iQsFAZWVlZ12v8emnnzJx4kTi4+OZOHEin332WTdFJyIiIiLiOY+mHolnysvLWb9+fcsi77b069ePV199lYEDB1JcXExSUhIFBQXdGKWIiIiIyNmpUDiNy+UiOTmZiooKmpubmTlzJqGhoSxevBiXy4XFYsHhcHD8+HFSUlKoq6sDfjhALTIykuXLl1NSUoLdbmfChAlt7no0bNiwls9XX301J0+e5NSpU60OclsblNGlYxURETmd3+M7ezoEEbnAqFA4TUFBASEhIWRlZQFQXV1NYmIiK1aswGazUVNTg6+vL8HBwWRnZ+Pj40NpaSlz5szB6XQyd+5c1qxZw6pVqzzqb9OmTQwbNuyspz2LiIiIiHQ3FQqniYiIYOnSpWRkZBAbG0tQUBBWqxWbzQZAYGAgAHV1daSlpbFv3z5MJhOlpaXn3Nd///d/s2zZMtasWWPkEEREREREDKFC4TRhYWE4nU62bdtGZmYm0dHRbbZzOBwMGDCAvLw8mpubWwoJT1VUVPDrX/+apUuXcvnllxsRuoiIiIiIobTr0WkqKyvx8/PDbreTlJREYWEhR44coaioCICamhoaGxuprq7GarViMpnIy8ujqakJgICAAGprazvs48SJEzz22GPMnTuXn/3sZ10+JhERERGR86E3CqcpLi4mPT0dk8mE2WwmNTUVt9vNokWLqK+vx9fXl+zsbKZOncrs2bNZt24dMTEx+Pv7AzB06FBMJhMJCQlMnDixzcXMb731Ft9++y2vvPIKr7zyCgBr1qwhODi4O4cqIiIiItIhL7fb7e7pIKS1hoYmjh939XQYP3p9+/orjwZQHo2hPHaecmgM5dEYyqMxlEdjdCaPVmtQm9c19UhERERERFrR1KMuUlBQwLJly864Fhoa2jLd6Gyin8ntirBERETOamPKhJ4OQUQuACoUukhMTAwxMTE9HYaIiIiIyHnR1CMREREREWlFhYKIiIiIiLSiqUcGKisr46uvviI+Pr7D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c8ImCgpBEIQJ2TKNy1OJo18UTURENF48ozA+vCjcNNijaYynRycnu2Ff53MUhnHu3Dmo1WpotVrU19cjLCzMpNtPSUlBeHg4NBoN4uLixnz2AhERERGRuXGiMIxjx45h7dq1yM/Pn5CLTlJSUnDs2DEUFBTAxcUFOTm8HoGIiIiI5OWhv0ahq6sLCQkJaGlpgcFgQHh4OIqLi/HVV1+hoqICb7zxxuDYpqYmJCUl4e7dgYeNp6WlYf78+TAYDHjnnXdw+vRpuLi4QKlUIjIyEqGhocPu8956OEEQ8Ntvvw075nO7DBO/UyIietjdWzPOpR5EJMZDP1GorKzErFmzkJ2dDQDo6OjAjz/+iIULFyI0NBRNTU2DYx0dHbF//8CtrRoaGvDmm28iLy8PJSUlaG5uRlFREX799VeoVCpERkaOut8tW7agvLwcHh4eSE5OntD3SERERER0vx76pUdeXl44deoUMjIycO7cOdjZDX8xBwD09fUhNTUVGo0G8fHxqK+vBwBUV1cjNDQUVlZWcHJywrPPPjvmfnfs2IHKykp4eHigqKjIZO+HiIiIiMgUHvqJwpw5c5CXlwcvLy/s3bsXH3300YhjDxw4gJkzZyI/Px+5ubno7e0d176tra2hVqtRUlIyru0QEREREZnaQz9RaG1tha2tLbRaLV599VVcunRpxLEdHR1wcnKClZUV8vPzB594N3/+fJSUlMBgMKCtrQ1nz87T+b8AAAkZSURBVJ4dcRuCIKCxsXHwa71ej8cee8y0b4qIiIiIaJwe+msUamtrsXv3blhZWUGpVOLtt98e8S5EK1euxMaNG/HFF18gICAAU6dOBQCEhISgqqoKKpUKLi4umDt37ohLmARBwObNm9HZ2QlBEODt7Y309PQJe39ERERERA+CD1wzkc7OTjzyyCO4efMmoqKicPjwYTg5OT3w9np7+3lHChPgnT1Mgz2aBnscP3ZoGuzRNNijabBH05iIB65xomAiq1evRnt7O3p7exETE4MXXnhB6khERERERA+ME4UJtH79+iG3VwWATZs2ISAgQKJERERERETicKJARERERERGHvq7HhERERERkTFOFIiIiIiIyAgnChKrqKhASEgIgoODkZ2dbfT3PT09SEhIQHBwMKKiooyueaABY/X4zTffICIiAnPnzkVxcbEECeVvrA73798PlUoF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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_predictor_quality(df_auc)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsEAAAIiCAYAAAApYRVYAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdeVyVdfr/8dcBBERxwQVzqdFKW9wwzEzIQk0z2dymdDRTR819tAgtHTO1RE3T1LJcmqlszFDEtTEbc8pyyZ9KaZm5m7ihsi+H8/vD75wZBFk8H+CA7+fjwePBcp/rfO773BwuPvd1fy6LzWazISIiIiJyG3Ep7QGIiIiIiJQ0JcEiIiIicttREiwiIiIitx0lwSIiIiJy21ESLCIiIiK3HSXBIiIiInLbURIsInILoqOjefbZZ2/58YMHD2bNmjUGR1Tyzp49i5+fH1artbSHIiJSZEqCRaTMio2NpXv37vj5+REQEMDgwYPZs2dPaQ8rlwULFvDiiy/m+N4HH3xAeHi48eeKjIykSZMmbN26Ncf3Z8yYQZMmTYiOji5UnKCgIL799tt8t6lbty779u3D1dX1lscrIlJalASLSJm0fPlyZsyYwbBhw/jmm2/46quv6NOnD19++WWRY2VlZRXqe2XFH/7wB2JiYuxfZ2VlsWnTJu68805jz1GWj4+ICCgJFpEyKDExkfnz5zN58mSefPJJvLy8qFChAkFBQbz88ssAZGRkMH36dAICAggICGD69OlkZGQA8P333/PYY4+xZMkS2rVrx4QJE1iwYAGjR4/mxRdfpFWrVqxZs4bExEQmTpxIQEAAgYGBzJ0796aX/qdNm0b79u1p1aoV3bt3t89If/3117z33nts2rQJPz8/QkJCAOjXrx+fffYZANnZ2SxatIgnnniCtm3bEhERQWJiIgCnT5+mSZMmrFmzhscff5w2bdqwePHifI9PUFAQe/fu5erVqwDs2LGDJk2aULNmTfs2J0+epH///rRp04Y2bdowfvx4rl27BsBLL73E2bNnGTZsGH5+frz//vv2cXz22Wc8/vjjPPfcc/bvZWVlceXKFR577DG2bdsGQHJyMp06dWLt2rVFf4FFREqAkmARKXP27dtHeno6nTp1uuk2ixcvZv/+/cTExLBu3ToOHjzIokWL7D+/ePEiV69e5auvvuL1118H4Msvv6RLly7s2bOH4OBgIiMjcXNz44svvmDt2rV888039sT1Rs2aNWPt2rXs2rWLbt26MWbMGNLT03nssccYOnQoTz31FPv27WPdunW5HhsdHc2aNWv429/+xtatW0lJSWHq1Kk5ttm7dy+bN2/mww8/ZOHChRw9evSm++7u7k6HDh3YsGEDAGvXriUsLCzHNjabjaFDh7Jjxw42bdrEuXPnWLBgAQCzZs2ibt26vPvuu+zbt48///nP9sft3r2bjRs3snTp0hzxqlWrxowZM5g0aRKXLl3ijTfe4P7778/1vCIizkJJsIiUOVeuXKF69eq4ubnddJvY2FhGjBhBjRo18PHxYcSIETkSUBcXF0aPHo27uzuenp4AtGzZko4dO+Li4kJSUhLbt29n4sSJeHl5UaNGDQYMGGBPLG8UGhpqH9PAgQPJyMjg2LFjhdqf2NhYBgwYQIMGDahUqRLjxo1j48aNOUoORo4ciaenJ/fddx/33Xcfhw8fzjdmaGgoMTExXLt2jd27d9OxY8ccP7/rrrto164d7u7u+Pj48Pzzz7N79+4Cxzpq1Ci8vLzsx+x/BQQE0KVLFwYMGMD27dt57bXXCrX/IiKl4eZ/QUREnFS1atVISEggKyvrponw+fPnqVu3rv3runXrcv78efvX1atXx8PDI8dj6tSpY//87NmzZGVlERAQYP9ednY2d9xxR57Pt3TpUlavXs358+exWCwkJSWRkJBQqP05f/489erVs39dr149srKyuHTpkv17/1vKULFiRVJSUvKN6e/vz+XLl1m8eDGPP/54rqT14sWLTJ8+nT179pCcnIzNZqNKlSoFjvV/j1FeevfuzUcffcSwYcOoXr16gfFEREqLZoJFpMzx8/PD3d091woI/6t27dqcPXvW/vXvv/9O7dq17V9bLJZcj/nf79WpUwd3d3e+++479uzZw549e/jhhx/ynAnes2cPH3zwAfPmzWP37t3s2bMHb29vbDbbTZ/rxrGeOXPG/vXZs2dxc3OjRo0a+T6uICEhISxfvjzPkoS33noLi8VCbGwsP/zwA7NmzbKPNz/57YvVamXy5MmEhYXxySefcOLECYfGLyJSnJQEi0iZ4+3tzejRo5k6dSpbt24lNTWVzMxMtm/fTlRUFABPP/00ixcv5vLly1y+fJmFCxcSHBxc6OeoXbs27dq148033yQpKYns7GxOnjzJrl27cm2bnJyMq6srPj4+ZGVl8c4775CUlGT/eY0aNThz5gzZ2dl5Ple3bt348MMPOXXqFMnJycydO5ennnoq33KPwujXrx/Lly+ndevWeY7Zy8sLb29v4uPj+eCDD3L8vGbNmpw6dapIz/fuu+9isViYMWMGgwYN4uWXX9YawiLitJQEi0iZNHDgQCIjI1m0aBFt27bl8ccf5+OPP7bXvg4fPpymTZsSEhJCSEgIDz74IMOHDy/Sc0RFRZGZmUnXrl1p3bo1o0eP5sKFC7m2+8/qEZ07dyYoKAgPD48cZRNdunQBoE2bNnmuDdyjRw9CQkL405/+RIcOHXB3d2fSpElFGmteqlWrRtu2bfOcvR05ciQ//fQT/v7+DBkyhCeffDLHz4cMGcLixYvx9/fPdRNcXuLi4lixYgUzZ87E1dXVfjPdkiVLHN4PEZHiYLEV5vqXiIiIiEg5oplgEREREbntKAkWEREREac2YcIE2rZtS7du3fL8uc1mY9q0aXTq1Ing4GB+/PHHAmMqCRYRERERp9a9e/dcN/D+r6+//prjx4/zxRdf8PrrrzNlypQCYyoJFhERERGn1rp1a6pWrXrTn3/55ZeEhYVhsVho2bIl165dy7E2fF6UBIuIiIhImRYfH5+jmU+dOnWIj4/P9zHqGFfMKvqNdDjGqR3zHI5xJiHV4Rjurmb+Zzp5Jf9OV4VRv6qXwzHqVs/d9vVWXE3JdDjGtdSsgjcqwB3VHN+fCm75N3UoLJcCmkMUhomRFNSkorCybrK+b1GYOCYm1vIxdEiMMLE/v8YnFbxRARrfUdnxgQAGThOyDRwUVxczL7LTjMXAeZJtaB0sFwN/Bq2GBlOtoquROI4wkePkJXXfO8US90aaCRYRyYeJBFhERIqXr68v586ds3997tw5fH19832MkmARERERKTqLS/F83IKgoCDWrl2LzWbj//2//4e3tze1a9fO9zHlthwiKCiI1atX4+PjU+C2CxYswMvLi0GDBpXAyERERESkKMaNG8euXbtISEjgscceY9SoUWRlXS8lfPbZZ2nfvj3bt2+nU6dOVKxYkRkzZhQYs1wmwepVLyIiIlLMSvAmg7feeivfn1ssFv76178WKabTlUN88MEH/O1vfwNgxowZ9O/fH4CdO3cyfvx41q9fT3BwMN26dWPWrFn2x/n5+fHmm28SEhLCvn377N9PS0tj8ODBrFq1CoC1a9cSHBxMSEgIL730Uq7nX7VqFT169CAkJIRRo0aRmnr9hrJNmzbRrVs3QkJC6Nu3LwBHjhyhZ8+ehIaGEhwczPHjx4vlmIiIiIiIWU43E+zv78+yZcvo378/cXFxZGRkkJmZyd69e2nYsCGzZ88mOjqaKlWqMHDgQLZu3UrHjh1JSUmhefPmREZG2mOlpKQwbtw4wsLCCAsL48iRIyxevJiVK1fi4+PDlStXcj1/p06d6N27NwBz585l9erV9OvXj0WLFrF06VJ8fX25du0aAJ9++in9+/cnJCSEjIwMsnUDjYiIiNwubrF+11k43egffPBBfvzxR5KSknB3d6dly5bExcWxZ88evL29efjhh/Hx8cHNzY3g4GB2794NgKurK507d84Ra/jw4XTv3p2wsDAAvvvuO7p06WKvE65WrVqu5z9y5Ah9+vQhODiY2NhYjhw5AlyfaY6MjGTVqlX2couWLVvy3nvvsWTJEs6ePYunp5klt0REREScnsVSPB8lxOmS4AoVKlC/fn2io6Px8/PD39+f77//npMnT1KvXr2bPs7DwwNX15xr5rVq1YodO3ZgK8Jah5GRkUyePJnY2FhGjhxJRkYGAFOnTmXs2LH8/vvv9OjRg4SEBIKDg1m8eDGenp4MGTKEnTt33tpOi4iIiEiJcrokGP5bEtG6dWv8/f359NNPuf/++2nevDm7d+/m8uXLWK1WNmzYQOvWrW8aZ/To0VStWpXXXnsNgEceeYTNmzeTkJAAkGc5RHJyMrVq1SIzM5PY2Fj790+ePEmLFi0YM2YM1atX59y5c5w6dYoGDRrQv39/OnTowM8//2z4SIiIiIg4KSdaIu1WOF1NMFxPgt99911atmyJl5cXHh4e+Pv7U7t2bcaPH89zzz2HzWajffv2dOzYMd9Yr7zyChMnTiQqKoqIiAiGDRtGv379cHFx4YEHHuDNN9/Msf2YMWPo1asXPj4+tGjRguTkZACioqI4ceIENpuNRx55hPvuu4/333+fmJgY3NzcqFmzJkOHDi22YyIiIiIi5lhsRakVkCJT2+Tc1DY5N7VNzs1Z2iab6hintsm5qW1yHjGcpVUxTjQWtU3Ok1O0TW6Te5UtE1K/n1XwRgY45UywiIiIiDg5rQ4hIiIiIlK2aCa4mJkoZWgQONbhGMe3z3U4hqnLqD6V3R2OYWIspgqBKro7fknKRAwTpQwWI0UIzsNm4DqqqUvLRi7pGghi6jV2ltKMetUrOhzD1HuBif1xMxDE1KV/VwNjMXHp38Q5a+rvl5Hzvjy9zzpTfdUt0EywiIiIiNx2NBMsIiIiIkWnmmDn9be//Y2nnnqK8ePH33QbPz8/h58nOjqa+Ph4h+OIiIiISMko1zPBn3zyCStWrKBOnTrF+jxr1qzh3nvvxdfXt1ifR0RERMRplPGa4HKbBE+ePJnTp0/z5z//mbNnz/Lkk09y+vRpzp49y3PPPUf//v1zbP/aa68REBBAhw4dGDFiBFWqVOGNN95g9erVnDp1ir/85S8sXLiQdevW4ePjwx133MGDDz5IvXr1iIuL48UXX8TT05N//OMfeHqaWX9WRERExGmpHMI5TZ06ldq1a/Phhx8yYMAAjh07xtKlS/nss89YuHAhmZk5Gxz4+/uzZ88eAOLj4zl69CgAe/fuxd/fnwMHDvDFF1+wbt063n//feLi4gDo0qULTZs2Zfbs2cTExCgBFhERESkDym0SfKP27dvj7u6Oj48PPj4+XLp0KcfP/f392bt3L7/++iv33HMPNWrU4Pz58+zbtw8/Pz9++OEHOnTogIeHB5UrV+aJJ54opT0RERERcQIWS/F8lJByWw5xI3f3/65N6+rqSlZWzja1vr6+XLt2jR07duDv78/Vq1fZtGkTXl5eVK5spqWmiIiIiDiH22YmuDBatmzJhx9+SOvWrfH392fZsmX4+/sD0KpVK7766ivS09NJTk7mX//6l/1xlSpVIjk5uZRGLSIiIlIKLC7F81FCbpuZ4MJ46KGH+Pe//81dd91F3bp1uXr1qj0Jbt68OUFBQYSEhFCjRg0aN26Mt7c3AOHh4fz1r3/VjXEiIiJy+yjjN8ZZbDZTDSPLv+TkZCpVqkRqaip9+/bl9ddf58EHH8z3MReTsvL9eWGUt7bJztJu1c3FzC9vpjXbSBxHeVRwfH9MtfM08foYGYkzrd5j4Ly3GvjlcTH0i+wsv8cp6VaHY3h5ON623BQTr46ptskmOoabOGedqW2yCaayrqoVSz8Brdh+arHETd0+uVji3kgzwUUwefJkfv31V9LT0wkPDy8wARYREREpt0z8p1SKlAQXwZw5c0p7CCIiIiJigJLgYnYmIdXhGCZKGf7Q/i8Oxzi4ZZbDMQCmb/vV4RgR7Rs5HMPL3czpX9Hd8UtSv8U7fmNlVa8KDse4o7qhenabc9RDWK1mrjs6y6VUE6UMzlTWZEL81TSHY/yhViUDIzFzbLMM1DKYKnkxUVZhqsTKUVZDNSImjq3NRG2UsyjjNcFKgkVE8uEsCbCIiNMp42+QZTuFFxERERG5BWUuCY6OjmbqVLN3I27dupVff/3vJfq3336bb7/91uhziIiIiJQrZXyd4DKXBBeHG5PgMWPG8Oijj5biiERERESkODldEhwTE0PPnj0JDQ1l8uTJWK1WPv/8czp37kzPnj354Ycf7NtGRkayefNm+9d+fn72z5csWUJwcDAhISHMnj0bgFWrVtGjRw9CQkIYNWoUqamp/PDDD2zbto2oqChCQ0M5efJkjrg7d+4kLCyM4OBgJkyYQEZGBgBBQUHMnz+f8PBwgoODOXr0aEkcHhERERHnYLEUz0cJcaok+OjRo2zatImVK1cSExODi4sL69atY8GCBaxcuZJPPvkkx4ztzWzfvp1t27axatUq1q1bx+DBgwHo1KkTn3/+OevWraNRo0asXr2aVq1aERQUREREBDExMdx55532OOnp6URGRjJ37lxiY2OxWq188skn9p9Xr16dNWvW8Mwzz7Bs2TLzB0REREREioVTrQ6xc+dO4uLi6NmzJwBpaWns27ePhx9+GB8fHwC6du3K8ePHC4zTvXt3KlasCEC1atUAOHLkCPPmzSMxMZHk5GQCAgLyjXPs2DHq169Pw4YNgevtkT/++GMGDBgAwJNPPglA06ZN+ec//3lL+ywiIiJSJmmJNHNsNhvh4eGMHz/e/r2tW7fyxRdf5Lm9q6sr2dnXW9ZmZ2eTmZmZb/zIyEgWLVrEfffdR3R0NLt27XJovBUqXF+X1cXFBavV8dadIiIiImWGlkgzp23btmzZsoVLly4BcOXKFe6//352795NQkICmZmZOWqA69Wrx48//gjAtm3b7Enwo48+SnR0NKmpqfY4AMnJydSqVYvMzExiY2PtcSpVqkRycu5mBQ0bNuTMmTOcOHECuF6v3Lp162LYcxEREREpSU41E3zPPfcwduxYBg4cSHZ2NhUqVGDy5MmMHDmSZ555Bm9vb+6//3779r1792b48OGEhIQQGBiIl5cXAI899hiHDx+mR48eVKhQgfbt2zNu3DjGjBlDr1698PHxoUWLFvbEt2vXrkyaNIm///3vzJ8/3x7fw8ODN954gzFjxmC1WmnatCnPPvtsyR4UEREREWdUxsshLDabszS/LJ/2n0p0OEadqo63slXb5NzUNjk3U22TTbRKNXGVzUSrVGe62udMbZP/rxLNISbGcuy84787ztQ22cQ5a6ptcnmSbSjVcaa2ydUquhqJ44iKXd4qlripm8cVS9wbOdVMsIiIiIiUEWX8Hy4lwSIiIiJSdGW8HEJJcDFzd3X8BDHxj5aJUoZmnV9yfCDAN2tnOByjVbjjrbP3x/zV4RgAK/efdjhGQAMfh2PU86nocAxTxVEmLvc5S6GWzQZuLo7/EloN7JCJY2LquDrLBNCmX887HGOEb0MDI4Esa3krv3E8RlY5K+8wUVbhRLtz21MSLCKSDxMJsIhIuVTGM/qyPY8tIiIiInILnCYJjo+PZ/To0QAcOnSI7du3F/iY77//nqFDhxb6ObZu3VqotsuF3U5ERETktmVxKZ6PEuIUSXBWVha+vr72NXoLmwQXlZJgEREREUPKeBLsUE3w6dOnGTx4MC1btmTfvn00bdqUHj16MH/+fC5fvszs2bMBmD59Ounp6Xh6ejJjxgwaNWpEdHQ0X3zxBSkpKWRnZ/Pmm28ybNgwoqOjmT9/Pmlpaezdu5ehQ4dSv379PGPkZ/bs2Wzbtg1XV1cCAgLo1KkT27ZtY9euXSxevJgFCxbw3Xff8Y9//IPMzEzuuusuoqKiOHToUK7tXnnlFSIiImjWrBmXL1+mZ8+ebNu2jSNHjjBhwgQyMzPJzs5mwYIF/OEPf3DkkIqIiIhICXD4xriTJ0/y9ttvM2PGDHr27ElsbCwrV67kyy+/5N133yUqKoqPP/4YNzc3vv32W+bOncuCBQsA+Omnn1i3bh3VqlXj9Onrd9i7u7szevRo4uLimDx5MgBJSUk3jZGXhIQE/vnPf7J582YsFgvXrl2jSpUqBAUF8fjjj9OlSxcAvL296d27NwBz585l9erV9OvXL9d2N/Ppp5/Sv39/QkJCyMjIINvE6vEiIiIiZUEZvzHO4SS4fv36NGnSBLje9rht27ZYLBaaNGnCmTNnSExM5OWXX+bEiRNYLBYyMzPtj23Xrh3VqlUr8Dnyi5EXb29vPDw8mDhxIk888QSPP/54ntsdOXKEefPmkZiYSHJyMgEBAYXfcaBly5a8++67nDt3jieffFKzwCIiIiJlhMOFF+7u7v8N5uJi/9pisWC1Wnn77bdp06YN69evZ/HixWRkZNi3r1ixcOua5hfjPwYNGkRoaCivvPIKbm5urF69mi5duvDVV18xePDgPONGRkYyefJkYmNjGTlyZJ5xAVxdXflPd+n/3SY4OJjFixfj6enJkCFD2LlzZ6H2R0RERKTMu51rggsjMTERX19fANasWVOox1SqVInk5P/2gy9MjKVLl9o/T05OJi0tjfbt29OqVSs6duyYZ9zk5GRq1apFZmYmsbGx9ue4cbt69eoRFxdH8+bN2bx5s/37p06dokGDBvTv35/ff/+dn3/+mbZt2xZqH0VERESk9BR7uj148GDeeustwsLCyMrKKtRj2rRpw6+//kpoaCgbN24scozk5GSGDh1KcHAwffr0ITIyEoCuXbuydOlSwsLCOHnyJGPGjKFXr148++yzOW60u3G7gQMHsnLlSsLCwkhISLBvt2nTJrp160ZoaCi//PILYWFhRTw6IiIiImWUxVI8HyU1fJvNWZqTlk+HziYXvFEBani7F7xRAa6lFu4fkPw4U9vkdn+c7nAMU22To3/83eEYJtomN29Q1eEYztQ22QQT+2OqY5yJtskWnOcmFGe5H2bRt8ccjjGiXflqm2zqPClPbZNNtDs2xdTvTrWKrmYCOaBi+AfFEjd1Td5lrKY5xTrBIiIiIiIlqdhrgkVERESkHHKWS0K3SElwMTt5JcXhGD6VHS+HmL7N8Q54JsoYANqFTXQ4xtefO14O4eZi5kJI72b1HI7x68VEh2NcSEx3OIZPJcfPNVNMlCFYDZRlWG02I2UVJi7pmmCougMDV7mN/P0M+kNNh2OYulJuYn9MjMXUqWaihMdZzntTwzBRamLqd1AcpyRYRCQfTlRKKCLiVCxO8k/OrVISLCIiIiJFVtaTYN0YBxw6dIjt27fbv/7yyy9ZsmRJKY5IRERERIqTkmByJ8EdOnRgyJAhpTgiERERESdnKaaPElKq5RDLly/n888/B6Bnz54MGDCAtWvXsnTpUiwWC02aNGHWrFlcvHiRv/71r5w6dQqAKVOmULt2bYYNG8b69euB6x3jUlJSGDVqFP369aNJkybs3r0bq9XKjBkzaN68OQcOHGD69Omkp6fj6enJjBkzqF+/PvPnzyctLY29e/cydOhQ0tLSiIuLY/LkyZw+fZqJEyeSkJCAj48Pb7zxBnXr1iUyMpLKlSsTFxfHhQsXeOmll+jSpUupHUsRERERKbxSS4Lj4uKIjo5m1apV2Gw2evfuTbNmzVi8eDErV67Ex8eHK1euADBt2jRat27NwoULsVqtpKSkcPXq1Xzjp6WlERMTw+7du5k4cSLr16+nUaNGfPzxx7i5ufHtt98yd+5cFixYwOjRo+1JL0B0dLQ9zrRp0wgPDyc8PJzVq1czbdo0Fi1aBMD58+f55JNP+O2333jhhReUBIuIiMhto6zXBJdaErx37146duyIl5cXAJ06dSIuLo4uXbrg43O9e1a1atUA+O6774iKigLA1dUVb2/vApPgp59+GoDWrVuTlJTEtWvXSE5O5uWXX+bEiRNYLBYyMzMLHOe+fftYsGABAKGhocyaNcv+s44dO+Li4sI999zDxYsXi3gERERERMqusp4El9maYDc3N7Kzs+1fp6fnXCP1xhfGYrHw9ttv06ZNG9avX8/ixYvJyMhwaAzu7s6zpqqIiIiIFF6pJcH+/v5s3bqV1NRUUlJS2Lp1K02bNmXz5s0kJCQA2Msh2rZtyyeffAKA1WolMTGRGjVqcOnSJRISEsjIyOBf//pXjvgbN24EYM+ePXh7e+Pt7U1iYiK+vr4ArFmzxr5tpUqVSE5OznOcfn5+bNiwAYDY2Fj8/f3NHQQRERGRMspisRTLR0kptXKIBx98kO7du9OrVy/g+o1xDz30EMOGDaNfv364uLjwwAMP8Oabb/LKK68wadIkPv/8c1xcXJgyZQp+fn6MGDGCXr164evrS6NGjXLE9/DwICwsjKysLGbMuN7pbPDgwURGRrJ48WLat29v37ZNmzYsWbKE0NBQhg4dmiPOpEmTmDBhAkuXLrXfGCciIiIiZZvFZit//ZD69etHREQEzZo1K+2hsOWnCw7HaFm/msMxIjcedjjGqLZ3ORwDnKdtcg1vD4djmGKibfLdNSs7HKPctU028PZm6h3SWdrHlre2yYfPOv67c19db8cHAmQ7yflm6lyzGWg7bqLNsAkm9gWcq22yt2fpV7RWffbvxRL36sp+xRL3RuoYJyIiIiJF5xz/49yycpkE//3vxfOfiYiIiIiUD+UyCXYm9at6ORzDxJWtiPaNCt6oAK3Cpzo+EMyUMjzW4xWHY+zfHOVwDIDVcb87HKOtgZKXmpWdp7zDxKVHE5fbTZUymCjNyDKwQyYuc5s4rmDmfcnEJNLWY44vT/lAvSoGRgJWUwfXSbgaeJGd5rzPLnibwrBYDJS8GBiHs9ASaSIi5ZiJBFhERJyPZoJFREREpMg0E1wM/Pz88v35tWvX+Pjjj+1fx8fHM3r0aKNj6NevHwcPHsz1/YMHDzJt2jSjzyUiIiIiJavUZoJtNhs2mw0Xl6Ln4deuXWPlypX07dsXAF9fX+bPn296iHlq1qyZUyy9JiIiIlKayvpMcIkmwadPnx0IlxkAACAASURBVGbQoEG0aNGCH3/8kaeeeoqvvvqKjIwMOnXqlGs2Nzk5meHDh3Pt2jWysrIYM2YMHTt2ZM6cOZw8eZLQ0FAeffRR+vbty7Bhw1i/fj3p6elMmTKFuLg4XF1diYyM5JFHHiE6Oppt27aRmprKqVOn6NixIxEREVitVl555RXi4uKwWCz06NGDAQMGALB582Zee+01EhMTmT59Ov7+/nz//fcsW7aM9957jwULFnDy5ElOnjxJQkICgwcPpnfv3iV5SEVERERKhZLgIjpx4gQzZ84kKSmJLVu2sHr1amw2Gy+88AK7d++mdevW9m09PDxYuHAhlStX5vLly/zxj3+kQ4cOjB8/niNHjhATEwNcT67/4z9lErGxsRw9epRBgwaxZcsWAA4dOsTatWtxd3enS5cu9OvXj0uXLhEfH8/69euB67PM/2G1Wlm9ejXbt2/nnXfeYcWKFbn25+eff2bVqlWkpKQQHh5O+/bt7a2ZRURERMQ5lXgSXLduXVq2bMnMmTP55ptvCAsLAyAlJYXjx4/nSIJtNhtvvfUWu3fvxsXFhfj4eC5ezH85nL179/KnP/0JgLvvvpu6dety7NgxANq2bYu3t7f9Z2fOnOHee+/l1KlTvP7667Rv356AgAB7rE6dOgHXWzyfOXMmz+fr0KEDnp6eeHp60qZNGw4ePKgkWERERMq/sj0RXPJJsJfX9XVzbTYbQ4YM4ZlnnrnptrGxsVy+fJno6GgqVKhAUFAQ6enpt/zc7u7/bQnr6uqK1WqlatWqxMTE8O9//5tPP/2UTZs28cYbb+TY3sXFBavVmmfMsn4pQEREROR2VGqrQwQEBPD555+TnJwMXF/h4dKlSzm2SUxMpEaNGlSoUIHvvvvOPhtbqVIl++Nu5O/vT2xsLADHjh3j999/p1GjmzeKuHz5Mjabjc6dOzN27Fh++umnIu3Hl19+SXp6OgkJCezatUs3zYmIiMhtwWKxFMtHSSm11SECAgI4evSofSbYy8uLWbNmUaNGDfs2wcHBvPDCCwQHB9O0aVN7Mlu9enVatWpFt27dCAwMtK8SAdCnTx+mTJlCcHAwrq6uvPHGGzlmgG90/vx5JkyYQPb/tZMZN25ckfajSZMm9O/fn4SEBIYPH65SCBEREbktlGTC+vXXXzN9+nSys7Pp1asXQ4YMyfHzs2fP8vLLL5OYmIjVauXFF1+kffv2+ca02GymGovefhYsWICXlxeDBg266TY/nsl7xrooalW5eRJfWJeSMhyOYaxt8icTHY6htsm5tbqzusMxTDHRNtlioNgs28Dbm6mOcc7SPtYUZ2mbvOCbYw7HGB3geFt5gEyrod68DjJ1npg49Z3lvDfV0trIeW/o17haRVczgRxQ6/l/FEvcC8v/mONrq9VK586dWb58Ob6+vvTs2ZO33nqLe+65x77NpEmTuP/+++nTpw+//vorQ4YMYdu2bfk+j1M2yxARERER51ZS5RAHDhzgrrvuokGDBri7u/P000/z5Zdf5hpLUlIScL2ctnbt2gWOX22THTBq1KjSHoKIiIhIuRYfH0+dOnXsX/v6+nLgwIEc24wcOZJBgwbx0UcfkZqayvLlywuMqyS4mNWt7ulwDBMFK17ujr/U+2P+6vhAALdb6BJ4IxOlDC26RDgcAyBuyyyHY3hUcPyYmIiRkWXmcq6rxfGxZGU7PhYTl1ENXUU1MhYDvzoYqFQBDB0XA5eFR7Vr6HAMU1WBrgbqB0xcKjdV5GjiNXZzNfA7aOBtqYKBcYCZY2Lk99hZOE+FFhs2bCA8PJyBAweyb98+IiIiWL9+fb6dicvTSyEiIiIiJaSkyiF8fX05d+6c/ev4+PhcCxGsXr2ap556CgA/Pz/7yl35URIsIiIiIk6rWbNmHD9+nFOnTpGRkcGGDRsICgrKsc0dd9zBzp07ATh69Cjp6en4+PjkG1flECIiIiJSZCW1RJqbmxuTJ09m8ODBWK1WevTowb333svbb79N06ZN6dChA5GRkbz66qusWLECi8XCm2++WeD4lAT/D5vNhs1my7d+RERERERKVvv27XOt+ztmzBj75/fccw+ffvppkWLe9knw6dOnGTRoEC1atODHH3+kefPm/Pzzz6Snp9O5c2dGjx4NXF+eY8aMGaSkpODu7s6KFSuoWLEis2fPZteuXWRkZNC3b99820CLiIiIlBcl2SyjONz2STDAiRMnmDlzJi1btuTKlStUq1YNq9XKgAEDOHz4MI0aNeIvf/kLc+fOpXnz5iQlJeHp6cnq1avx9vbm888/JyMjg2eeeYZ27drRoEGD0t4lEREREcmHkmCgbt26tGzZEoBNmzaxatUqsrKyuHDhAkePHsVisVCrVi2aN28OQOXKlQH45ptv+Pnnn9myZQtwfXHmEydOKAkWERGRck8zweWAl5cXAKdOnWLZsmWsXr2aqlWrEhkZSXp6+k0fZ7PZePXVVwkMDCypoYqIiIg4h7KdA2uJtP+VnJxMxYoV8fb25uLFi3z99dcANGzYkAsXLti7kyQlJZGVlUVAQAArV64kMzMTgGPHjpGSklJq4xcRERGRwtFM8P+47777eOCBB3jqqaeoU6cOrVq1AsDd3Z25c+cybdo00tLS8PT0ZPny5fTq1YszZ87QvXt3bDYb1atXZ9GiRaW8FyIiIiLFr6yXQ1hspvpFSp4SUqwOxzDxCiWmZTkcIz3L8X0BM22TTbTULW9tk2t6ezgcw1TbZBMtgk28xpayfq3uBuWtbbKJv5/O9Aob6STtRG2TTcQxcc6aaJtsoKM14Fxtk709Sv9ifr0X1hRL3DOLw4sl7o00EywiIiIiRVbWZ4KVBIuIiIhIkSkJlnxdTcl0OEZFd1cDMRy/bLJy/2mHYwD0blbP4Rir4353OIaJMgaApp1fcjjG1lWvOxzDp5K7wzFMvZ1ZDVwzNFHKYOoSqAkmrlCbuDxt6pCYuKRrYn/ir918BZ/C8q3ieCkRQLaBHXJxogIPE/mNiVIGE+PIMlHHgJlSLxPvj2KGkmARERERKTrn+Z/tlpR+VbWIiIiISAnTTLCIiIiIFJlqgkVERETktlPWk2CVQ9wgJSWFIUOGEBISQrdu3di4cSNxcXH86U9/onv37gwaNIjz58+TmJhI586d+e233wAYN24cq1atKuXRi4iIiEhhaCb4Bjt27KB27dosWbIEgMTERP785z+zaNEifHx82LhxI3PnzuWNN95g8uTJTJgwgf79+3P16lV69+5dyqMXERERKRllfSZYSfANGjduzMyZM5k1axZPPPEEVapU4ZdffuH5558HIDs7m1q1agHQrl07Nm/ezNSpU4mJiSnNYYuIiIhIESgJvkHDhg2Jjo5m+/btzJs3j0ceeYR7772Xf/zjH7m2zc7O5ujRo3h6enL16lXq1KlTCiMWERERKXllfSZYNcE3iI+Pp2LFioSGhjJo0CD279/P5cuX2bdvHwCZmZkcOXIEgBUrVnD33XczZ84cJkyYQGam440xRERERKT4aSb4Br/88gtRUVG4uLjg5ubGlClTcHNzY9q0aSQmJmK1WnnuuedwdXXls88+47PPPqNy5cq0bt2axYsXM3r06NLeBREREZHiV7YngpUE3ygwMJDAwMBc3//4449zfW/Tpk32zydMmFCs4xIRERFxJiqHEBEREREpYzQTLCIiIiJFVtZngpUEF7NrqVkOx6jo7upwjN/ikx2OEdDAx+EYAL9eTHQ4Rtv61RyO4VHBzIWQratedzhGx96THI5xcsc8h2O4u5o5Jtk2m8MxTLy3ZtvMvEGb2B83F8fH4vgoINtEEMxcRjRxXE8lpDgco6a3u8MxACwGCiRNnLGGXmKnYeI8cTXw+wemXp+ynTiWJ0qCRUTyYeIPsIhIeVTGJ4KVBIuIiIhI0ZX1cogCr2idPn2abt26lcRYypwFCxawdOnS0h6GiIiIiBRRqcwEZ2Vl4eZW/E9ttVpxdXW8nlZEREREcirjE8GFu7fBarXy6quv8vTTTzNw4EDS0tI4dOgQvXv3Jjg4mBEjRnD16lUA+vXrx8GDBwG4fPkyQUFBAERHRzNs2DD69+/PgAEDOH/+PH379iU0NJRu3bqxZ8+eXM8bHR3NCy+8QL9+/XjyySd555137D+LiYmhZ8+ehIaGMnnyZKxWKwB+fn68+eabhISE2Lu8ARw4cICRI0cCsHXrVpo3b05GRgbp6el06NABgJMnTzJo0CC6d+9Onz59OHr0qH0/Ro0aRY8ePejRowd79+7NNdZVq1YxePBg0tLSCnNIRURERKQUFSoJPnHiBH379mXDhg14e3uzZcsWIiIiePHFF4mNjaVx48Y5EtSb+emnn5g/fz4fffQR69evJyAggJiYGGJiYrjvvvvyfMzBgweZP38+69atY/PmzRw8eJCjR4+yadMmVq5cSUxMDC4uLsTGxgKQkpJC8+bNWbduHf7+/vY4DzzwAIcOHQJg79693HvvvRw8eJD9+/fTokULACZNmsSkSZOIjo7m5Zdf5rXXXgNg+vTpPPfcc3z++ecsWLCAV199NccYP/roI7766isWLVqEp6dnYQ6piIiISJlmsViK5aOkFKomoX79+tx///0APPjgg5w6dYrExEQefvhhAMLDwxkzZkyBcdq1a0e1ateXtmrWrBkTJ04kKyuLjh072uPf6NFHH6V69eoAdOrUib179+Lm5kZcXBw9e/YEIC0tjRo1agDg6upK586dc++omxt33nknR48e5cCBAzz//PPs2bMHq9XKQw89RHJyMvv27cuxHxkZGQB8++23/Prrr/bvJyUlkZx8fcmxtWvXcscdd7Bw4UIqVKhQ4DEQERERKQ/KejlEoZJgd/f/rqHo6urKtWvXbrqtq6srtv9bUug/SeR/VKxY0f5569at+eijj9i+fTuRkZE8//zzVKpUyT6jPG3aNCD3nYcWiwWbzUZ4eDjjx4/P9fweHh72OuBBgwZx8eJFmjZtyvTp0/H39+frr7/Gzc2NRx99lMjISKxWKxEREdhsNqpUqUJMTEyumNnZ2axatQoPD49cP2vcuDGHDx/m3LlzNGjQ4KbHRUREREScxy2td+7t7U2VKlXsdbwxMTG0bt0agHr16hEXFwfA5s2bbxrjzJkz1KxZk969e9OrVy9+/PFHOnXqZC+PaNasGQDffPMNV65cIS0tja1bt9KqVSvatm3Lli1buHTpEgBXrlzhzJkzuZ5j6dKlxMTEMH36dAD8/f358MMPadmyJT4+Ply5coVjx47RuHFjKleuTP369dm0aRMANpuNw4cPAxAQEMDf//53e9z/lFXA9TKL1157jeHDhxMfH38rh1NERESkzHFxsRTLR4mN/1YfOHPmTKKioggODubQoUOMGDECgIEDB7Jy5UrCwsJISEi46eN37dpFaGgoYWFhbNy4kf79++e5XfPmzRk1ahQhISF07tyZZs2acc899zB27FgGDhxIcHAwAwcO5MKFCwWOuUWLFly8eNGesDdp0oTGjRvbZ5tnzZrF6tWrCQkJ4emnn2br1q0AvPLKK8TFxREcHEzXrl1ZuXJljrj+/v5EREQwdOhQLl++XPDBExEREZFSZbHZnLcdUnR0NHFxcUyePLm0h3LLDpxKcjiGb9XcZRhFdey8422TswydKilZjreSruDieNPWe2tXdjgGwImLjrdtVdvk3EzUmploY2uqY5yztE029Y5vYrLGamAw+05ecTiG352Ot2EHM+ebiePqTH/UTZxvNgN75GKoeNWZ2lpX8TTzfu2IB1/5olji/jj9yWKJe6PSP4IiIiIiIiXMqdsmd+/ene7du5f2MERERETkBmW9bbJTJ8HlwR3VHF83uIKb4ydZVS/Hl2+r51Ox4I0K4UJiusMxalZ2vETEo4KZCyE+ldwL3qgAJkoZ7gwc63CMs9+87XAMAFcD13Sd5a3VxWIh25muLzvIxKXl6xx/hVwN/AFtVq+qU4wDMHKeOFPJi4nDYqa8w4neT0zsT7bjMZxFGc+BVQ4hIpKf8pQAi4jIf2kmWERERESKrKyXQxT7TPDWrVtzdFt7++23+fbbb4v7aY149913S3sIIiIiIlIMSjwJHjNmDI8++mhxP60R7733Xp7ft9lsZGeXo6IeERERkSKyWCzF8lFSipwEnz59mqeeeopXX32Vp59+moEDB5KWlsaqVavo0aMHISEhjBo1itTUVH744Qe2bdtGVFQUoaGhnDx5ksjISDZv3szXX3/N6NGj7XG///57hg4dCsC///1v/vjHPxIeHs7o0aNJTs57jdslS5YQHBxMSEgIs2fPBq53c+vduzfBwcGMGDGCq1evAtCvXz8OHjwIwOXLlwkKCgKur0U8cuRIBg0axJNPPklUVBQAs2fPJi0tjdDQUMaPH8/p06fp3LkzERERdOvWjUWLFtk70QGsWrWKGTNmFPVwioiIiJRJFkvxfJSUW5oJPnHiBH379mXDhg14e3uzZcsWOnXqxOeff866deto1KgRq1evplWrVgQFBREREUFMTAx33nmnPcajjz7KgQMHSEm53mhg48aNdO3alcuXL7N48WKWL1/OmjVraNq0KcuXL881hu3bt7Nt2zZWrVrFunXrGDx4MAARERG8+OKLxMbG0rhxY955550C9+fQoUPMmzeP2NhYNm3axO+//86LL76Ip6cnMTExzJkzx77fffr0YcOGDTz//PN89dVXZGZmAteT6R49etzK4RQRERGREnZLN8bVr1+f+++/H4AHH3yQM2fOcOTIEebNm0diYiLJyckEBATk/8RubgQGBvLVV1/RuXNntm/fzksvvcTu3bv59ddfefbZZwHIzMykZcuWuR6/c+dOunfvTsWK15ft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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_correlation_matrix(df_corr)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -846,7 +884,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -855,7 +893,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -866,7 +904,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -996,7 +1034,7 @@ "6 workclass_processed Self-emp-not-inc 0.080498 0.239277 0.274693" ] }, - "execution_count": 38, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1008,7 +1046,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -1025,7 +1063,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -1039,7 +1077,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -1048,7 +1086,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -1106,15 +1144,15 @@ " \n", " \n", " 3\n", - " [scont_1_enc, education_enc, relationship_enc,...\n", + " [scont_1_enc, relationship_enc, education_enc,...\n", " age_enc\n", - " 0.870536\n", + " 0.870537\n", " 0.872149\n", " 0.871496\n", " \n", " \n", " 4\n", - " [scont_1_enc, education_enc, relationship_enc,...\n", + " [scont_1_enc, relationship_enc, age_enc, educa...\n", " occupation_enc\n", " 0.878380\n", " 0.882468\n", @@ -1129,18 +1167,18 @@ "0 [scont_1_enc] scont_1_enc \n", "1 [scont_1_enc, relationship_enc] relationship_enc \n", "2 [scont_1_enc, relationship_enc, education_enc] education_enc \n", - "3 [scont_1_enc, education_enc, relationship_enc,... age_enc \n", - "4 [scont_1_enc, education_enc, relationship_enc,... occupation_enc \n", + "3 [scont_1_enc, relationship_enc, education_enc,... age_enc \n", + "4 [scont_1_enc, relationship_enc, age_enc, educa... occupation_enc \n", "\n", " train_performance selection_performance validation_performance \n", "0 0.506035 0.489155 0.497746 \n", "1 0.779681 0.775267 0.787203 \n", "2 0.858048 0.856376 0.859707 \n", - "3 0.870536 0.872149 0.871496 \n", + "3 0.870537 0.872149 0.871496 \n", "4 0.878380 0.882468 0.880178 " ] }, - "execution_count": 42, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1149,6 +1187,26 @@ "performances.head()" ] }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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k66t33swqJkzb1ZVVZVs29YNN/xaknTTTbfqwQfv0/LlY2Waps4662zdeutv6p1700236oEH7tWECfWPmTDhl3rggXt11VVXyOVya+LEycrMzNKIEZdr5MjLdcopPTV79u+dOhdffIn+9a/P9POf/1SGYei6625U587fqncbwGgybNu2wx92bNq3ryT8QS20s2yHJq+/WhVmxVGPSXInaWH/Z1t0N4+2OPeLmtSkJjWpSU1qRqam9+u1Sn/9Gsmsrnd7PNvwSO4EFf/oMVV1z4p5n+255tHmTLOceBgnpnbV7LPnKsmVeNgwvkdSkitRs8+ey23xAABAs1V1z1L+2LdUcfo4Wd4OsmXI8nZQxenjlD/2LSdI49gT01vjtRUXlZdr6Y5dejYtUdlpKSozDKXatoaVBnV16QH5eperKtZNAgCANs3ynaTSzLnNupc0YocwHcbBVYl8oXLdUVmmOw7kH3aM/fo1yh/7lizfSa3fIAAAAGKGaR5hNGVVIgAAALQvhOkwEr94ud6FAEdi2CEl/vflVuoIAAAAxwqmeYRhNPLG6pFe0QgAALQvO8t26KXtL2j1rjdUHipXsidZA789RGNO/ik3OjiGMTIdhp2Q2rjjvEdfHQgAAKAhH+59X5PXX63s3JUKhoKyZSsYCio7d6Umr79aH+59v0X11617R/37n1vv3subNm3UjBk31Ttu7ty79PbbNQuzhEIhPfLIgxo79nJNnDhO11zzC73//oYW9SFJ118/RVu2fC5JuuWWG1VScvitjp944jE9//yiBuusW/eOtm//ytl//PFH9Y9/fNji/pqKMB1G5akjG7UqUWXPka3UEQAAiCc7y3bo7k9+qwqz4rBF4kw7pAqzQnd/8lvtLNvR7NdYvfoNnXlmX61e/Uajz1m48BEdOLBfzz77op588jndc899CgYje4/n++77izp0OPL9m8N59913lJNTG6YnTbpW5513QaRaazTCdBjBvlMkd0LDBzVzVSIAAICXtr+gaqvh67OqrZD+tv3FBo85mmAwqM2bP9Xtt9/Z6DBdUVGhlStf0a9/fau8Xq8kqVOnzhowYFC94z744D3dccdtzn7d0e45c+7WL395lcaPv0JPPPHYEV9n9Ogfq7CwUJL0zDNPaOzYkfrVr36pb7752jlmxYplmjTpak2Y8FPddNM0VVRU6LPP/qn1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qqrFNQ3D0KZNm/Szn/1MW7dujUiYjvR33rbtet/RNWvWtDhQH/zc161bp4kTJ2rLli0t7lOSlixZonXr1mnBggXq0qWL87vaEm63W998841uvfVWffLJJxHoMjp/LkXrc7dtW7fccoveeeediHzuUs3nNH36dH322Wct7jMaNaXofO6SdNttt+npp5+OWL1ofZci+d7rfpemT5+uN998U+Xl5cdczYN/Xrz44ov67W9/qy+++KJF9aLVpxT5zz0av0fR+DOk1dioZ8eOHfbll19uv/jiixGt+9xzz9lXX321XVxcHJF6CxcutG+66SbbsqyI1LNt237//fft8ePH24WFhRGruXXrVvuKK66wH3jggYjV/PTTT+0RI0bYH330UYtrWZZlh0Ihe+7cufajjz4age7q154zZ449e/bsiNXct2+fPX78eHvt2rURqxmN73xlZaV988032zfffLNdWVkZkZobNmywL730UvuDDz6ISD3btu1nnnnGfuWVV2zbtu3S0lLbtu0W/05t27bN/slPfmIvXry4xf0dFI3PKBo1S0pK7Guuucb+3e9+F7GaGzdutEeNGmV/8803x3TNaHzuTz/9tH3jjTdGrJ5tR+dzj8Z7Lysrs2+44Qb797///TFd8+9//3vEv0vR6DMan3s0fo+i8WdIa2j3I9OHjhDv3LlTXbp00RVXXCGp+dMU7EPm/FRWVmr06NHq0KGDysrKjnhMY2tZlqUdO3Zo4MCBMgzjsOkDja17qNzcXJ144ony+Xyqrq6OyLylN998UwMGDNCvf/3rZtc40mfUu3dvnXfeeUd8Ppy6n6lhGM4oqt/vlyRnBLmp/0d8aB+GYcjr9WrIkCGS5HzuTRmhPvT753K51KVLF51//vkyTdOp1ZQpJNH4zh/6XcnPz1d1dbXuueceeb1ehUKhIx7X1D5HjRqlCy644LDvZ1Pq5ufnO7VDoZCef/55SVJqaqqkmn9Sb8q/9hz62jk5OerZs6euvPLKI76PxojGZ9Qan3txcbFSU1P129/+VpKcz70pP4NDj921a5d69uypjIwMVVdXR+TnGYma0fjcD+XxeHTaaadJkjM6GQqFWvx7FOnPPRLv/Uh/Z9q2rRtuuEGSVF1dfcTjWrvmoe8tNzdX3/ve95zvUnP+3myNPqPxZ0g0fo8i8WdILLTrMF13XtLBMHLcccfJ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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_performance_curves(performances)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1158,10 +1216,214 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['age_enc', 'occupation_enc', 'scont_1_enc', 'relationship_enc', 'education_enc', 'hours-per-week_enc']\n" + ] + } + ], + "source": [ + "model = forward_selection.get_model_from_step(5) # Python starts to count from 0!\n", + "print(model.predictors)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'age': 0.5172855940047185,\n", + " 'occupation': 0.5634536788555236,\n", + " 'scont_1': 0.012280802872168558,\n", + " 'relationship': 0.7363851709976446,\n", + " 'education': 0.5989073013361791,\n", + " 'hours-per-week': 0.44453822509112334}" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "importance_by_variable = model.compute_variable_importance(basetable)\n", + "importance_by_variable" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwoAAAHsCAYAAAByulqXAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nO3de1iUdf7/8RcHQVESNDNN21VbNVMSw/KA2iLmgQBRMcswEzOtzDZLMdO0PFRWllSam+nmgb6usiKeF3WtTdfTmkjZZiye0CwTEjkzzO+Pfs33Sx/U0RxuwOfjuva6mHvu+cxr3hdr8+K+7xk3u91uFwAAAAD8H+5WBwAAAABQ+VAUAAAAABgoCgAAAAAMFAUAAAAABooCAAAAAANFAQAAAICBogAAcFpgYKBOnDhx2f1OnjypVq1aqaSkpNz74+Pj9dxzz11VhrCwMO3evfuqHgsAcB5FAQCqqdjYWL3zzjvG9pSUFHXt2vWib+Iv5cCBA2ratOm1iHfV1q9fr3vuucfSDL8ICQnRzp07rY4BAC5BUQCAaioqKkpr167Vr79Xc+3atQoPD5enp6fTa11NqajOmAeA6wFFAQCqqdDQUGVnZ2vfvn2ObT/99JO2b9+u/v37KzU1VQ888ICCgoIUHBysl19+WUVFRY59W7VqpeXLl+u+++7Tfffd59h27NgxSdI//vEP9e/fXx06dFCPHj0UHx9vZFi9erWCg4MVHBysRYsWXTTrF198oSFDhigoKEgRERGXPLXo//4VPz4+Xk8//bSee+45BQYGKjw8XBkZGfrggw/UuXNn9ejRQ//85z8dj42JidGbb76pQYMGqUOHDhozZoyys7Md92/dulVhYWEKCgpSTEyM0tPTyzzvwoULFR4ervbt2+vZZ5/VqVOnNHr0aAUGBurPf/6zJOnpp59W165dddddd2no0KE6cuSIY424uDhNnz5do0aNUmBgoKKjo3X8+HHH/UeOHNGjjz6qu+++W126dNGCBQskSaWlpVq4cKFCQ0N1zz33aNy4cWVyA4ArUBQAoJqqWbOm+vbtqzVr1ji2bdy4Uc2bN1fr1q3l7u6uSZMm6V//+pc++eQT7dq1SytWrCizRkpKilauXKkNGzYY69eqVUuvvfaa9u3bpw8++EAJCQlKSUkps8/u3bu1ZcsWLVq0SH/+85/LPU3nzJkzevzxxzVmzBjt2bNHEydO1NNPP61z58459Tq3b9+uyMhI7d27V7fffrtiY2NVWlqqTz/9VE8++aSmTp1aZv81a9Zo1qxZ+uc//ylPT0/NmDFDkpSRkaHx48frhRde0K5du9S9e3eNHj26THlav369Fi5cqH379umtt95S48aNtWDBAh04cECPPfaYJKl79+7avHmzdu3apTZt2hjXYmzYsEFPPfWU9u7dq1tvvVVz586VJF24cEGPPvqounXrps8++0xbtmxR586dJUlLly5VSkqKli1bps8++0x169bVyy+/7NR8AOBqURQAoBrr37+/Nm/erMLCQkk/v0mOioqSJLVt21bt27eXp6enmjRpogceeEB79+4t8/hRo0bJz89PNWvWNNa+55571KpVK7m7u6t169YKCwvTnj17yuzz5JNPysfHR61atdKAAQO0bt06Y52kpCR1795dPXr0kLu7u7p27aq2bdtqx44dTr3GoKAgdevWTZ6enurTp4+ysrI0atQo1ahRQ/369VNmZqbOnz/v2D8yMlItW7aUj4+Pxo0bp02bNslms2nDhg3q0aOHunbtqho1aig2NlYFBQU6cOCA47ExMTFq1KhRufP4xaBBg1SnTh15eXlp7Nix+vrrr5WTk+O4PzQ0VAEBAfL09FRERIQOHz4s6ecjNDfeeKNGjBghb29v1alTR3feeack6ZNPPtGf/vQn3XzzzfLy8tJTTz2lzZs3cwoUAJdy/gRVAECVExQUJH9/f6WkpKhdu3Y6dOiQ3n33XUk//wX91VdfVVpamvLz82Wz2XTHHXeUeXyjRo0uuvbBgwf1xhtv6MiRIyouLlZRUZH69Olz0cffcsst+uabb4x1Tp06pU2bNmn79u2ObSUlJU5fsFy/fn3HzzVr1pS/v788PDwctyUpLy9PN9xwg5GpcePGKi4uVlZWlr7//ns1btzYcZ+7u7saNWqkM2fOlPt6ymOz2TR37lxt2rRJ586dk7v7z3+Py8rKkq+vryTpxhtvLJM3Ly9PknT69Gndeuut5a576tQpPfnkk471fsn3448/qmHDhpfMBABXi6IAANVcZGSk1qxZo4yMDAUHBzveqE6bNk1t2rTRm2++qTp16mjJkiXavHlzmce6ublddN3x48fr4Ycf1ocffihvb2/NnDlTWVlZZfY5ffq0WrRoIennN7s33XSTsU6jRo0UGRnpOAXI1U6fPl3m5xo1asjf31833XRTmSJjt9t1+vTpMm/ELzUPSUpOTtbWrVu1ePFiNWnSRDk5OerYsaNxQXl5GjVqVO4pXpJ08803a9asWbrrrrsuuw4AXCucegQA1Vz//v21a9curVy5Uv3793dsz83NVe3atVW7dm2lp6crISHhitbNzc1V3bp15e3trdTU1HJPK3r//feVn5+vI0eOKDExUf369TP2iYiI0Pbt2/XZZ5/JZrOpsLBQu3fv1nfffXflL9YJa9eu1bfffqv8/Hy988476t27tzw8PNS3b1/t2LFDu3btUnFxsT766CN5eXkpMDDwomvdeOONZb5XIjc3V15eXvL391d+fr7eeustp3Pde++9+uGHH7RkyRIVFRXpwoULOnjwoCTpwQcf1Ntvv63MzExJ0rlz54zrQQDgWqMoAEA116RJEwUGBio/P189e/Z0bJ84caLWrVunDh06aMqUKeW+ib+Ul156SfPmzVNgYKDee+899e3b19jn7rvvVq9evTR8+HCNGDFCwcHBxj6NGjXS+++/X+aTihYtWqTS0tIrf7FOiIyMVFxcnLp27aqioiJNnjxZktS8eXPNmTNHr7zyijp16qTt27drwYIF8vLyuuhao0aN0vz58xUUFKRFixapf//+aty4sbp166awsDC1b9/e6Vx16tTRRx99pO3bt6tr167q3bu349Ofhg0bppCQEI0YMUKBgYEaPHiwUlNTf9sgAOAy3OzOHA8FAKAaiImJUUREhKKjo62OAgCVHkcUAAAAABgoCgAAAAAMnHoEAAAAwMARBQAAAAAGvkehksrPL9KFC4VWx6iW6tTxZrYuwFxdh9m6BnN1HWbrOszWNa73uTZo4Fvudo4oVFKenh5WR6i2mK1rMFfXYbauwVxdh9m6DrN1DeZaPooCAAAAAANFAQAAAICBogAAAADAQFEAAAAAYKAoAAAAADBQFAAAAAAY+B6FSqrTC8utjgAAAIAKsGlClNURysURBQAAAAAGigIAAAAAA0UBAAAAgIGiAAAAAMBAUQAAAABgoCgAAAAAMFAUAAAAABgoCgAAAAAMFAUAAAAABooCAAAAAANFAQAAAICBogAAAADAQFEAAAAAYKAoAAAAADBQFAAAAAAYKAoAAAAADBQFAAAAAAaKAgAAAAAD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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_variable_importance(importance_by_variable)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "from cobra.evaluation.model_evaluator import Evaluator" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "y_true = basetable[basetable[\"split\"] == \"selection\"][\"TARGET\"].values\n", + "y_pred = model.score_model(basetable[basetable[\"split\"] == \"selection\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 32, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "evaluator = Evaluator(y_true, y_pred)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best cutoff value for probability is: 0.28123688299785216\n" + ] + } + ], + "source": [ + "evaluator.plotROCCurve()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcEAAAFACAYAAADEV8eVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nO3deVxVdf7H8dcFRElZFOVeF6SycE/LlSQX/CFujBtM2arpmFsOWjYu5ZijNjWWmDYq6VTWTL/JBZqfNG5oAqWSmtn6c7QoNbkkKi6lwOX8/vDnnYilPHMR4byfj8d9POLc8z3n+0Xizef7Pedcm2EYBiIiIhbkVdUdEBERqSoKQRERsSyFoIiIWJZCUERELEshKCIilqUQFBERy1IISqVas2YNAwcO5LbbbqNly5a8+uqrlX7OqKgooqKiKv08VvLAAw/QsmXLqu6GiMf5VHUHxDOOHDnC3/72N/bs2cOJEye4dOkSQUFBtGnThujoaIYMGYKvr+817VNqaioLFiygTZs2PPTQQ/j6+tKxY8dr2ge5rGXLlnTt2pXXX3+9qrsicl1RCNYAy5Yt46WXXqK4uJjbb7+dYcOGccMNN3Dy5EmysrJ48sknefPNN9mwYcM17deOHTsAWLFiBXa7/Zqd91pUm1bz7LPP8sMPP1R1N0Q8TiFYza1YsYKlS5fSuHFjlixZQocOHUrts2PHDv7yl79c877l5uYCXNMABGjevPk1PZ8VNGnSpKq7IFIptCZYjR07doxly5ZRq1YtkpKSygxAgD59+rB69epS29955x3uu+8+OnXqxG233UZsbCwrV66koKCg1L5X1tm+//57nn32WXr37k27du2Ijo4mKSmJHz99b+nSpbRs2ZI9e/YAl6firryu9Ltly5bMmDGjzP6Wtf5kGAbJycncc889dO/enfbt29OrVy/GjBnDO++8U2Zff6qgoICkpCRiY2Pp0KEDd9xxB/fee2+p9j/t47Fjx5g6dSrdunWjffv2DB8+3F3l/lItW7bkgQce4OTJk8ycOZM777yTjh07cs8997B3714A9/e2T58+tGvXjkGDBvHPf/6z1LHOnTvHqlWrePDBB+nZsyft2rWje/fujB8/ng8//LDEvhs2bHB/L7Oyskr8WyxdurTUWL/66isSEhKIiIigVatW7n/Dn/6bFBQUMGLECFq2bElaWlqpPj7xxBO0bNmSl1566aq+TyLXmirBamzDhg0UFhYyaNAgwsPDK9z3p+uBL7zwAitXrqR+/foMHjyYG264gYyMDF544QUyMzNZvXp1qTaFhYWMGTOG3Nxcevbsibe3N9u2beP555+noKCAyZMnA9C1a1cmT55McnIyx48fd2//TyxevJiVK1fSrFkzBgwYgL+/P9999x0ff/wxmzZtYuDAgRW2LygoYMyYMWRlZXHzzTdz7733cvHiRTZv3szUqVP54osvmDZtWql2x48fJz4+ntDQUIYMGUJ+fj7vvPMOEydO5JVXXqF79+6/eAxnz55l5MiR1K1bl0GDBrmPNWbMGP7+978zZ84c8vPz6d27N0VFRWzcuJGpU6fSuHHjEmupR44cITExkc6dO9O7d28CAgI4ceIE27dvJyMjg+XLl9OzZ08AWrduzeTJk1m2bBlNmzZl2LBh7uN07dq1RP+++eYbfv3rX3PjjTcSGxvLxYsXqVevXplj8fX1JTExkaFDhzJr1ixSUlJo3LgxAOvXr+ftt98mIiKCCRMm/OLvj0iVMKTaevDBB43w8HDjrbfeuqp2+/fvN8LDw41evXoZubm57u2FhYXGI488YoSHhxvLly8v0aZPnz5GeHi4MXbsWOOHH35wbz958qTRqVMno1OnTkZBQUGJNvfff78RHh5e6vxHjx41wsPDjd/97ndl9q+sdl27djXuuusu4/vvvy+1f15eXqm+9unTp8S2FStWuPtfWFhYov9XxrZv375SfQwPDzeWLl1a4ljp6enuY/1SV4711FNPGS6Xy709OTnZCA8PN7p06WI88sgjxsWLF93vffDBB0Z4eLgxceLEEsc6e/ZsqTEbhmGcOHHC6NGjh9G/f/8yz3///feX2bcfj/X5558vc5/y/i1TU1ON8PBwY+TIkUZRUZFx+PBho0OHDkZERESJny2R65WmQ6ux7777Drj6Nbf169cDMGHCBBo1auTe7uPjw+9+9zu8vLxYu3ZtmW2ffPJJ6tSp4/46ODiYvn37cu7cOb766qurHcJV8fHxwdvbu9T2Bg0a/Gzb9evXY7PZmDFjBj4+/54ACQ4OdlcrZY25adOmpaqZu+66iyZNmnDw4MGr6r+fnx9PPPEEXl7//t8uNjYWHx8f8vPzmT17NrVr13a/17lzZ5o2bcrnn39e4jj+/v5ljtnhcNC/f3++/PJLvv3226vqG0DDhg2vumofOHAgd999N/v27WPRokUkJCRw8eJFnnvuuRI/WyLXK02HWtBnn30GUOZU3k033YTD4eDYsWOcO3cOf39/93v+/v6EhYWVauNwOIDL032VJTY2ltdff52BAwcyYMAAunTpwu23316if+U5f/48X3/9NXa7nRYtWpR6/8r34adhA9CqVasyg9fhcHDgwIGrGsONN95YanrR29ub4OBgfvjhB0JDQ0u1sdvtZYbtvn37WLNmDQcOHCAvL4/CwsIS7zudzqu+mKVVq1ambqOZPXs2H374ofviq0ceeYTIyMirPo5IVVAIVmONGjXiyJEjOJ3Oq2p37tw5d/vyjvvtt99y9uzZEiETEBBQ5v5XKiuXy3VV/bgaM2fOpFmzZmzYsIGkpCSSkpLw8fGhZ8+ezJgxo8xwvuL8+fNA+eMNCQkByg7xisZcXFx8VWMoL7B9fHwqfK+oqKjEtq1btzJlyhRq167NnXfeSfPmzfHz88PLy4usrCyysrLKvLjp5zRs2PCq2wDUrl2b3r17c+jQIXx8fLjvvvtMHUekKigEq7FOnTqxe/dudu/eTXx8/C9ud+UX7smTJ8u8neDKNOsvqbLMuDId+NNf7leUFUbe3t6MGjWKUaNGkZeXx759+0hNTWXTpk0cPnyY1NTUcquYK9XXyZMny3z/yq0clTVeT1uyZAm1atVi/fr1pSrbOXPmkJWVZeq4NpvNVLu9e/eyevVq6tevz+nTp5k1axarVq0yfTyRa0lrgtXY8OHDqVWrFps3b+bw4cMV7vvjyqB169YA7svff+zrr78mJyeHZs2alVsF/aeuHDcnJ6fUe+fPnyc7O7vC9sHBwfTr148lS5bQvXt3vvnmGw4dOlTu/vXq1aN58+Y4nc4yj33l+9CmTZtfPogq9PXXX3PLLbeUCsDi4mL27dtXZhsvL69KqdRPnz7NY489ho+PD6+99hqxsbFkZmby8ssve/xcIpVBIViNNWvWjMmTJ1NYWMi4ceP4+OOPy9wvPT2dsWPHur8eMWIEAMuXL+fUqVPu7S6Xi2effZbi4mLi4uIqrd/16tXj5ptvZv/+/SXC2+Vy8cwzz3Dx4sUS+xcUFJT5y72wsJD8/Hzg8kUnFRkxYgSGYfDcc8+VCINTp07x5z//2b1PddC0aVOys7NLTIMbhsHSpUvL/WMoKCiozD86/lMzZ84kJyeHmTNn0rJlS+bOnUtYWBhLlixh//79Hj+fiKdpOrSaGz9+PEVFRbz00kvExcVx++23065dO+rWrcvJkyfZu3cv2dnZtGvXzt3mjjvuYOzYsaxatYrBgwcTExODn58fGRkZHDp0iE6dOjFmzJhK7feYMWOYPXs2I0eOpH///tSuXZs9e/ZQWFhIq1at+OKLL9z7Xrx4kXvvvZewsDDatm1LkyZNuHTpEu+//z5HjhwhKiqqzAtefuzhhx8mPT2dtLQ0hgwZQs+ePbl48SKbNm0iLy+PsWPH0rlz50ods6eMGjWK3//+9wwbNox+/frh4+PD/v37OXLkCH369CnzRv6IiAhSU1MZP348bdq0wcfHhy5dutClSxfT/Xj11VfZsWMHMTExjBw5Erj8B87ixYu5++67eeyxx0hJSSEwMND0OUQqm0KwBpg8eTIDBgxwP0B7w4YNFBQUEBQURKtWrRg7dixDhgwp0Wb69Om0adOGN954g5SUFIqKimjevDkJCQk8/PDDlf6w7bi4OAzD4NVXXyU5OZnAwED69u3L1KlTmTJlSol9/fz8ePzxx9mzZw8ffvgh27Zto27dujRv3py5c+f+ogrO19eXV155hVdeeYWNGzfyxhtv4O3tTatWrZg1axaDBw+urKF63D333IOvry+vvfYaKSkp1K5dm86dO/PMM8+wZcuWMkNw9uzZ2Gw2du3axc6dOykuLmby5MmmQ/CTTz5h0aJFNG3alPnz55d4r23btjzxxBMsWLCAmTNnuittkeuRzTB+9LwrERERC9GaoIiIWJZCUERELEshKCIilqUQFBERy1IIioiIZVXKLRLjbZXzpBGRa2nFhaNV3QURz7ihcu7VNPu7foVReQ/bv1q6T1BEREypCVOJCkERETHFqwY8JF0hKCIipqgSFBERy/Kq/oWgQlBERMxRJSgiIpalNUEREbEsVYIiImJZNWFNsCYEuYiIiCmqBEVExJSaUEUpBEVExBSbLowRERGrUiUoIiKWVRMujFEIioiIKaoERUTEsnSzvIiIWJYqQRERsSytCYqIiGWpEhQREcvyovqXggpBERExRdOhIiJiWZoOFRERy1IlKCIilqU1QRERsSxVgiIiYlk1YU2wJoxBRETEFFWCIiJiiqZDRUTEsnRhjIiIWJYqQRERsawakIEKQRERMUeVoIiIWJbWBEVExLJUCYqIiGXVhBvNFYIiImJKDSgEa0SQi4hIFfCy2Uy9ynL27FmmTJlC//79GTBgAB9++CFnzpxh9OjR9OvXj9GjR5Ofnw+AYRjMnz+f6OhoYmNj+fTTT93HSU5Opl+/fvTr14/k5OSfH4NnvhUiImI1NpOvsixYsIC77rqLTZs28fbbb9OiRQuSkpKIiIhgy5YtREREkJSUBEB6ejrZ2dls2bKFP/zhD8ydOxeAM2fOsGzZMt566y3Wrl3LsmXL3MFZHoWgiIiY4qkQPHfuHB988AFxcXEA+Pr6EhAQQFpaGkOHDgVg6NChbNu2DcC93Waz0bFjR86ePUtubi6ZmZn06NGDoKAgAgMD6dGjBxkZGRWOQWuCIiJiiqfWBI8dO0aDBg2YOXMmX3zxBW3btmX27Nnk5eUREhICQKNGjcjLywPA6XTicDjc7R0OB06ns9R2u92O0+ms8NyqBEVExBSbzWbq9VNFRUV89tlnjBw5kpSUFPz8/NxTnz89l6cpBEVExBRPTYc6HA4cDgcdOnQAoH///nz22WcEBweTm5sLQG5uLg0aNAAuV3g5OTnu9jk5Odjt9lLbnU4ndru9wjEoBEVExBQvk6+fatSoEQ6Hgy+//BKAXbt20aJFC6KiokhJSQEgJSWFvn37Ari3G4bBgQMH8Pf3JyQkhMjISDIzM8nPzyc/P5/MzEwiIyMrHIPWBEVEpMo99dRTPP744xQWFhIaGsozzzxDcXExCQkJrFu3jiZNmpCYmAhAr1692LlzJ9HR0fj5+bFw4UIAgoKCmDhxovsCm0mTJhEUFFTheW2GYRieHsx4W4CnDylyza24cLSquyDiGTcEVsph3w52/PxOZRiSl/PzO10jqgRFRMQUWw14ZoxCUERETKn+EagQFBERkxSCIiJiWfooJRERsSytCYqIiGVV/whUCIqIiEmV8BSza04hKCIiptSADFQIioiIOV41IAYVgiIiYkr1j0CFoIiImKQ1QRERsawakIEKQRERMUf3CYqIiGXpiTEiImJZNSAD9cnyIiJiXaoERUTElJpQCSoERUTEFF0YIx7lFxjIA6uW0qRdGwzDYM3Dkyj4/nvuW5FI7Xp1ycv+hr/cN5aL585xY5dO3Je0BACbzcbGuc9wIGUjPrVr83j6Jnxq++Ll48P+dW+zce7CKh6ZWJ3L5WLEfQ9hD2nEyhcXsyvrA55b/CKFhYW0bd2KBb9/Eh8fH86dO8/0J+fw7YkcXC4XDz94PyOGxFZ196UcNeE+QZthGIanDzreFuDpQ1rCQ6+u4HDG+7y3eg3etWrhe8MN/Hbr26x/fDb/Sn+PO0ffT/BNN/I/c+ZTy88PV0EBxS4XAQ47T370PjOahFPsclG7bl0uXbiAl48P0zO38NZvf8dXez6o6uFVOysuHK3qLtQYr7z+Vz757HPOX7jA8sTn6TPwV7y68iVuCgtjyZ9X0qSxg/hhQ1ix+hXOnT/P9N8+yqlTp+k/LJ7Mbf/Et1atqh5C9XZDYKUcdrcj1FS77jnXz/9bP3thzJEjR0hKSmL+/PnMnz+fpKQkjhw5ci36Zil1AgK4teedvLd6DQCuwkJ+yM/HHt6Cf6W/B8DnW3dwx4hfAVD4ww8Uu1wA1KpTB370t8ylCxcA8K5VC+9aPlTC3zkiv1iO08m7me8RN2wIAGfO5FOrVi1uCgsDoEf3rmxJ2wFcnl67cOF7DMPgwg/fExgYgI+3d5X1XSpmM/m6nlQYgklJSUybNg2A9u3b0759ewCmTZtGUlJS5ffOQhreFMb57/J46JXlzNqfwf0vL8X3hhv49tMv6DBkEAB3xA+lfmhTd5sbu3Zmzid7eOrjXfxtfII7FG1eXsz+MJM/5R7h8607yM7aWyVjEgFY+KfFTP/to3h5Xf51U79+EK4iFx9/+hkAm7ZtJ8fpBOC+e+I58lU2d/UbyK/i72X29GnudnL9sdlspl7XkwrXBNevX8/GjRup9ZOpiFGjRjF48GDGjRtXqZ2zEi8fH0Lv6MB/Pzqd7Ky9/DrxWWJmTGPNwxO5+8U/MfCpJzj4j39SVFDobpOdtZd57brhaBXOqNdW8sk/t1J06RJGcTELbo/ELzCQ8cl/pUnb1nz76edVODqxqh3pGTRoUJ92bVqzZ+8+4PIvzhf+OJ9nnl9MQUEhPSK6uYMu8/3dtG55K2uS/sw3R48xesJkOt/ekXr16lXlMKQc11ecmVPhn1g2m43c3NxS27/77rvrLs2ruzPHjnPm2HF31bZ/XQrN7+iA83//xYsxQ3mmcy8+eHMdJ498VaptzheHuHj+PE3atSmx/Yf8fP53RwZt+//XNRmDyE/tP3CQ7TsziBo4hGkzZrP7g708PnsOt3e4jb/95WXWvfEqXe64nRvDmgOw4R8b6RfVB5vNRljzUJo1bcKX2V9X8SikPDVhOrTCSnDWrFmMGjWKsLAwGjduDMC3337LN998w1NPPXVNOmgVZ525nDp6HHv4LTgPHaZV396c+OwL/Bs15Nx3J7HZbAx8cjrpK1YDEHxjGKePHqPY5aJB81AcrcLJy/6aeg2DcRUW8UN+PrXq1KF1dB+2PJtYxaMTq3psyiQemzIJgD179/GXNW+waME88k6dIrhBAwoKCnj51TWMHzMagMYOO7uyPqDzHbdzMi+Pr7K/oVnTphWdQqpQTSiGKgzBnj17snnzZg4ePIjz/+fs7XY77du3x1uL1R7390en8/BfV+Ht68vJL7NZM3oi3R8cSa9JvwHgww3/4P1X3gDglsgIYmZMxVVYiFFczJsTp3Eh7xRN27floddW4OXtjc3Li31vJfNx6qaqHJZIKatee4N3MzIpLi5mZPwIIrp2AWDib8Yw8/fziI0fiWEYPP7byTSoH1TFvZXy1IRnh+oWCZFy6BYJqTEq6RaJA6E3mmrX8Wi2R/vxn9DN8iIiYkoNmA1VCIqIiDkKQRERsawaf2GMiIhIeWpABioERUTEnJpQCep5RCIiYlmqBEVExBRPFoJRUVHUrVsXLy8vvL292bBhA2fOnGHq1KkcP36cpk2bkpiYSGBgIIZhsGDBAnbu3EmdOnX44x//SNu2bQFITk5m+fLlAEyYMIFhw4ZVeF6FoIiImOLl4enQ1157jQYNGri/TkpKIiIignHjxpGUlERSUhLTp08nPT2d7OxstmzZwkcffcTcuXNZu3YtZ86cYdmyZaxfvx6bzcbw4cOJiooiMLD8+yQ1HSoiIqbYbOZev1RaWhpDhw4FYOjQoWzbtq3EdpvNRseOHTl79iy5ublkZmbSo0cPgoKCCAwMpEePHmRkZFR4DlWCIiJiiqcvjBkzZgw2m427776bu+++m7y8PEJCQgBo1KgReXl5ADidThwOh7udw+HA6XSW2m63292P/CyPQlBEREyxeXAu8c0338Rut5OXl8fo0aO5+eabS56rkj6LUNOhIiJiiic/VNdutwMQHBxMdHQ0Bw8eJDg42P1xfrm5ue71QrvdTk5OjrttTk4Odru91Han0+k+bnkUgiIiYoqn1gS///57zp8/7/7v9957j1tvvZWoqChSUlIASElJoW/fvgDu7YZhcODAAfz9/QkJCSEyMpLMzEzy8/PJz88nMzOTyMjICseg6VARETHFU9OTeXl5TJp0+XMnXS4XgwcPpmfPnrRv356EhATWrVtHkyZNSEy8/NmovXr1YufOnURHR+Pn58fChQsBCAoKYuLEicTFxQEwadIkgoIq/igufZSSSDn0UUpSY1TSRyl91S7cVLubPjnk4Z6Yp0pQRERM8fR9glVBISgiIqbUgAxUCIqIiDk14QHaCkERETGlBmSgQlBERMxRCIqIiGXZvKp/CioERUTEFFWCIiJiWTXhFgk9Nk1ERCxLlaCIiJhSAwpBhaCIiJij+wRFRMSyakAGKgRFRMQcVYIiImJZNSADFYIiImKOKkEREbEsWw24yU4hKCIipqgSFBER69KzQ0VExLJUCYqIiFVpOlRERKxL06EiImJZqgRFRMSq9KG6IiJiXaoERUTEqmpCJVgD7vcXERExR5WgiIiYo+lQERGxrBowHaoQFBERU3SzvIiIWJcqQRERsSxVgiIiYlX6PEEREbGuGlAJ1oAcFxGRqmDzspl6lcflcjF06FAeeeQRAI4ePUp8fDzR0dEkJCRQUFAAQEFBAQkJCURHRxMfH8+xY8fcx1i5ciXR0dHExMSQkZHxs2NQCIqIiDk2m7lXOdasWUOLFi3cXy9atIhRo0axdetWAgICWLduHQBr164lICCArVu3MmrUKBYtWgTA4cOHSU1NJTU1lVWrVvH000/jcrkqHIJCUEREzPGymXuVIScnh3fffZe4uDgADMNg9+7dxMTEADBs2DDS0tIA2L59O8OGDQMgJiaGXbt2YRgGaWlpDBo0CF9fX0JDQwkLC+PgwYMVD8FT3wsREbEWm81m6lWWhQsXMn36dLy8LsfS6dOnCQgIwMfn8qUrDocDp9MJgNPppHHjxgD4+Pjg7+/P6dOncTqdOBwO9zHtdru7TXkUgiIiYo6HKsEdO3bQoEED2rVrd82HoKtDRUTEHA9dHbp//362b99Oeno6ly5d4vz58yxYsICzZ89SVFSEj48POTk52O124HKFd+LECRwOB0VFRZw7d4769etjt9vJyclxH9fpdLrblEeVoIiImOKp6dDHHnuM9PR0tm/fzgsvvED37t15/vnn6datG5s3bwYgOTmZqKgoAKKiokhOTgZg8+bNdO/eHZvNRlRUFKmpqRQUFHD06FGys7O57bbbKhyDKkERETGnkh+bNn36dKZOnUpiYiKtW7cmPj4egLi4OKZPn050dDSBgYEsXrwYgFtvvZUBAwYwcOBAvL29mTNnDt7e3hWew2YYhuHpjo+3BXj6kCLX3IoLR6u6CyKecUNgpRy2YHS0qXa+r2z1cE/M03SoiIhYlqZDRUTEHH2KhIiIWFYNeHaoQlBEREyp6Dmg1YVCUEREzFElKCIilqVKsGzLj++tjMOKXFNG3vGq7oKIR9gq6RaJ8p4DWp2oEhQREXNUCYqIiGWpEhQREctSCIqIiGUpBEVExLK8qv+TNxWCIiJijipBERGxLIWgiIhYlkJQREQsS2uCIiJiWTWgEqz+MS4iImKSKkERETGnBlSCCkERETFHISgiIpalC2NERMSyVAmKiIhlKQRFRMSyFIIiImJVNq0JioiIZakSFBERy1IIioiIZSkERUTEsrQmKCIilqVKUERELEshKCIilqUQFBERy6oBa4LVfwQiIlI1bDZzr5+4dOkScXFx/OpXv2LQoEG8+OKLABw9epT4+Hiio6NJSEigoKAAgIKCAhISEoiOjiY+Pp5jx465j7Vy5Uqio6OJiYkhIyPjZ4egEBQRkSrl6+vLa6+9xj/+8Q9SUlLIyMjgwIEDLFq0iFGjRrF161YCAgJYt24dAGvXriUgIICtW7cyatQoFi1aBMDhw4dJTU0lNTWVVatW8fTTT+NyuSo8t0JQRETM8VAlaLPZqFu3LgBFRUUUFRVhs9nYvXs3MTExAAwbNoy0tDQAtm/fzrBhwwCIiYlh165dGIZBWloagwYNwtfXl9DQUMLCwjh48GCFQ1AIioiIOV5e5l5lcLlcDBkyhDvvvJM777yT0NBQAgIC8PG5fOmKw+HA6XQC4HQ6ady4MQA+Pj74+/tz+vRpnE4nDofDfUy73e5uU+4QPPF9EBERC/JQJQjg7e3N22+/zc6dOzl48CBffvnlNRmCQlBERMzxYAheERAQQLdu3Thw4ABnz56lqKgIgJycHOx2O3C5wjtx4gRwefr03Llz1K9fH7vdTk5OjvtYTqfT3aY8CkERETHHQyF46tQpzp49C8DFixd5//33adGiBd26dWPz5s0AJCcnExUVBUBUVBTJyckAbN68me7du2Oz2YiKiiI1NZWCggKOHj1KdnY2t912W4VD0H2CIiJijofuE8zNzWXGjBm4XC4Mw6B///706dOHW265halTp5KYmEjr1q2Jj48HIC4ujunTpxMdHU1gYCCLFy8G4NZbb2XAgAEMHDgQb29v5syZg7e3d4XnthmGYXhkFD9ifHvI04cUufZcRVXdAxGPsIW2qZTjupY9bqqd9+RFHu6JeaoERUTEHD02TURELMtW/S8rUQiKiIg5XqoERUTEqlQJioiIZWlNUERELKsGfJSSQlBERMxRJSgiIpalNUEREbEsVYIiImJZNWBNsPqPQERExCRVgiIiYo6mQ0VExLJ0YYyIiFiWHpsmIjDQTU0AAAuASURBVCKWpUpQREQsS2uCIiJiWaoERUTEsrQmKCIilqXpUBERsSxNh4qIiGVpOlRERCxLlaCIiFiW1gRFRMSyVAmKiIhlaU1QREQsS5WgiIhYVg1YE6z+MS4iImKSKsHrxKxnl/Du7g8IDgrkf155CYBN72ay7NW/ceSbY7y1/Hnat7zVvf/Kv65l/Ttb8fL2YvbkcdzV9Q6+/OYY0+Y9597n6Ikcpoy+j4fihlzz8Yh1zfrTUt7ds/fyz/KqFwF4buWr7Ni9l1o+PjRv4mDh9EcJqFeXg18cYs7i5QAYBkx+8G6iI7sDkJG1nwV/Xk1xcTFxA/6LcSNHVNmYpBxe1b+Oqv4jqCGG9e/Ly8/OLbHt1pvCeHHeLDrf1rbE9sPZ3/DO9nQ2vvISq56dy7wly3G5XNzcvBkpq14kZdWLrF+5GL/atfmvyIhrOAoRGBYTxcvPzCmx7c5OHfmfVUv4x8uJ3NisCUlvrgfg1hvDWPfnRaSsXMzLzzzF7xOXU+Ry4XK5mLc0iZcXPsXG1S+SuiOTw18frYrhSEVsNnOv64hC8DrRpUM7AgP8S2xrERbKzc2bldo37b09DIzqia9vLZo1dtC8SWMOfvGvEvvs2v8RoU0a09QRUqn9FvmpLre1JdC/5M9yZOeO+Hh7A9ChdTg53+UB4Fentnt7QUEhNi7/gjz4v/+ieZPGhDZx4FurFgN7R5L2XtY1HIX8IjYvc6/riOnerF+/3pP9kKvgPJlH45CG7q8djRriPJlXYp93tmcwqG/Pa901kZ+1flMaPbve7v76o88PMXjMFH71mwTmJozHx9sb58lTP/kZD8aZl1fW4aQqWbkSXLp0qSf7IR5UUFjI9vf30L9Xj6ruikgJK/66Fh9vb2L79nJv69A6nI2rX2TtS8+R9OZ6LhUUVGEP5ap4eZl7XUcqvDAmNja23PdOnjzp8c7IL2NvGMyJ3H9//3O+O4m9YbD764w9+2gT3oKGDepXRfdEyrRh83Z27N7Lq3+ah62MaqBFWCg3+NXh0FffYG/Y4Cc/43nYg4NLtZEqdp1VdWZUGIJ5eXmsXr2agICAEtsNw+Cee+6p1I5J+aLu7Mrj8xcxOn4ouXl5fH38W25r9e8rR1O3pzMoqlcFRxC5tjKy9rP678m8/sJ8/OrUdm8/dsKJI6QhPt7eHHfm8uXR4zRzhOBfry5fHz/BsRNOQho24J13M1k0a2oVjkDK5KH1vRMnTvDEE0+Ql5eHzWbj17/+NQ899BBnzpxh6tSpHD9+nKZNm5KYmEhgYCCGYbBgwQJ27txJnTp1+OMf/0jbtpcvIExOTmb58stXHE+YMIFhw4ZVeO4KQ7B3795cuHCB1q1bl3qvW7duZscrZZj2hz/xwYGPOZ1/ll7xo3h01L0EBvgz/8WVnMrPZ/zMebRqcROr/zSPW28KY0CfSAaNnoi3tzdzfjse7/+/uOD7Hy7y3r4DPD1tUhWPSKxq2oLn+eCjTy//LN8zlkcfuoekN9dTUFjIw7+bC1yeAn06YQL7Pvmcl/97Az4+3njZvPj9lEeoH3j5j+6nHv0NY2Y8TXFxMSP69+XWG5tX4aikTB6qBL29vZkxYwZt27bl/PnzjBgxgh49erBhwwYiIiIYN24cSUlJJCUlMX36dNLT08nOzmbLli189NFHzJ07l7Vr13LmzBmWLVvG+vXrsdlsDB8+nKioKAIDA8sfgmEYhkdG8SPGt4c8fUiRa89VVNU9EPEIW2ibSjmu693/NtXOu3fFM4kTJkzg/vvvZ968ebz++uuEhISQm5vLAw88wObNm5kzZw5du3Zl8ODBAMTExPD666+TlZVFVlYW8+bNAyi1X1l0s7yIiJhTCQ/QPnbsGJ9//jkdOnQgLy+PkJDLt3k1atSIvP+/QtjpdOJwONxtHA4HTqez1Ha73Y7T6azwfApBERExx8P3/F24cIEpU6Ywa9Ys6tWrV/JUNluZF1T9p66va1VFRKT68OB9goWFhUyZMoXY2Fj69esHQHBwMLm5uQDk5ubSoEED4HKFl5OT426bk5OD3W4vtd3pdGK32yscgkJQRETM8dATYwzDYPbs2dx8882MHj3avT0qKoqUlBQAUlJS6Nu3b4nthmFw4MAB/P39CQkJITIykszMTPLz88nPzyczM5PIyMgKh6DpUBERMcVT05P79u3j7bffJjw8nCFDLj/wf9q0aYwbN46EhATWrVtHkyZNSExMBKBXr17s3LmT6Oho/Pz8WLhwIQBBQUFMnDiRuLg4ACZNmkRQUFDFY9DVoSLl0NWhUkNU1tWhxbveNtXOK+L6+WQbVYIiImLOdfYwbDOq/whERERMUiUoIiLmVMJ9gteaQlBERMypAdOhCkERETGnpn+KhIiISLlUCYqIiGWpEhQREctSJSgiIpalq0NFRMSyVAmKiIhlaU1QREQsS5WgiIhYlipBERGxLFWCIiJiWV4KQRERsShPfahuVVIIioiIOZoOFRERy6oBlWD1j3ERERGTVAmKiIg5mg4VERHLqgHToQpBERExR7dIiIiIZakSFBERy9KaoIiIWJYqQRERsS6FoIiIWJUqQRERsSyFoIiIWJdCUERErEqVoIiIWFb1z0CFoIiImFX9U1AhKCIi5tSA6dDqf7u/iIhUDZvN3OsnZs6cSUREBIMHD3ZvO3PmDKNHj6Zfv36MHj2a/Px8AAzDYP78+URHRxMbG8unn37qbpOcnEy/fv3o168fycnJv2gICkERETHJZvJV0vDhw1m1alWJbUlJSURERLBlyxYiIiJISkoCID09nezsbLZs2cIf/vAH5s6dC1wOzWXLlvHWW2+xdu1ali1b5g7OiigERUSkSnXp0oXAwMAS29LS0hg6dCgAQ4cOZdu2bSW222w2OnbsyNmzZ8nNzSUzM5MePXoQFBREYGAgPXr0ICMj42fPrTVBERExpxLXBPPy8ggJCQGgUaNG5OXlAeB0OnE4HO79HA4HTqez1Ha73Y7T6fzZ8ygERUTEpGtzYYzNZsNWSYGr6VARETHHQxfGlCU4OJjc3FwAcnNzadCgAXC5wsvJyXHvl5OTg91uL7Xd6XRit9t/9jwKQRERMacSQzAqKoqUlBQAUlJS6Nu3b4nthmFw4MAB/P39CQkJITIykszMTPLz88nPzyczM5PIyMifPY+mQ0VExCTPTFFOmzaNrKwsTp8+Tc+ePXn00UcZN24cCQkJrFu3jiZNmpCYmAhAr1692LlzJ9HR0fj5+bFw4UIAgoKCmDhxInFxcQBMmjSJoKCgnx+BYRiGR0bxI8a3hzx9SJFrz1VU1T0Q8QhbaJvKOfDJo+baNQz1bD/+A6oERUTEnBrwxBiFoIiImKQQFBERq1IlKCIilqUQFBER61IIioiIVakSFBERy6r+GagQFBERs6p/CioERUTEHE2HioiIZSkERUTEuqp/COpTJERExLJUCYqIiDmaDhUREctSCIqIiHVV/xCslM8TFBERqQ50YYyIiFiWQlBERCxLISgiIpalEKxm0tPTiYmJITo6mqSkpKrujogpM2fOJCIigsGDB1d1V8TiFILViMvlYt68eaxatYrU1FQ2btzI4cOHq7pbIldt+PDhrFq1qqq7IaIQrE4OHjxIWFgYoaGh+Pr6MmjQINLS0qq6WyJXrUuXLgQGBlZ1N0QUgtWJ0+nE4XC4v7bb7TidzirskYhI9aYQFBERy1IIViN2u52cnBz3106nE7vdXoU9EhGp3hSC1Uj79u3Jzs7m6NGjFBQUkJqaSlRUVFV3S0Sk2tJj06qZnTt3snDhQlwuFyNGjGDChAlV3SWRqzZt2jSysrI4ffo0wcHBPProo8THx1d1t8SCFIIiImJZmg4VERHLUgiKiIhlKQRFRMSyFIIiImJZCkEREbEshaCIiFiWQlBERCxLISgiIpb1f1fL8+vGr9MUAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Precision: 0.712\n", + "Accuracy: 0.836\n", + "Recall: 0.526\n", + "F1 Score: 0.605\n", + "Lift at top 10.0%: 3.32\n", + "AUC: 0.885\n" + ] + } + ], + "source": [ + "evaluator.plotConfusionMatrix()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfoAAAFACAYAAABURL5aAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdeZxN9f/A8de5+52FMWOsEdkjZR1ShCghS6gUpfyMokyWSioKKYS+lkqWRFHEVFRkq0gzRkKWFmUZMZjFLHe/9/z+uLnXNdYx487yfj4eHjWfc88973vmzn3fz66oqqoihBBCiGJJE+wAhBBCCFFwJNELIYQQxZgkeiGEEKIYk0QvhBBCFGOS6IUQQohiTBK9EEIIUYxJoheikKpTpw79+vUr0GskJCRQp04dZs6cWaDXKaxK+usXJYMu2AEIkRcHDx7kk08+ISEhgePHj2O324mIiODmm2+mQ4cOdOvWDYPBEOwwgy45OZn27dvTo0cP3nzzzWCHc1X++ecfli1bRmJiIseOHSMnJ4fQ0FBuvPFGmjZtSufOnWnQoEGwwxSi0JNEL4qcWbNmMXv2bDweD40aNaJHjx6EhIRw+vRpEhMTefnll1m6dCkrV64MdqiFXsOGDfn6668pU6ZMsEPxUVWV2bNn+37H9evXp1OnTkRERJCTk8Pvv//O4sWLWbBgAa+++iqPPPJInq9VGF+/EPlNEr0oUt577z1mzpxJxYoVeeedd7j11ltzPWbTpk0sWLAgCNEVPWazmRo1agQ7jACzZ8/2/Y7ffvttmjRpkusxqampLFq0iKysrGu6VmF8/ULkN+mjF0VGcnIys2bNQq/XM3fu3AsmeYC2bdsyf/5838+X64dt164d7dq1CyhbuXIlderUYeXKlWzdupW+ffvSqFEjWrRowejRo8nMzARg3759xMbG0qxZMxo1asTgwYNJTk6+omucNXPmTOrUqUNCQsJl70FKSgqzZs3ioYceolWrVjRo0IA77riDESNG8Ndff+V63vbt2wOwatUq6tSp4/t3trXjQvfm3nvvpUGDBqSlpV0whrlz51KnTh2WLFkSUH7ixAlef/112rdvT4MGDYiJiWHw4MHs3r37sq/rrKNHj/Luu++i1+v54IMPLpjkAaKiohg+fDgDBw4MKP/nn3+YOnUqPXv2pEWLFjRo0IC2bdvyyiuvcOLEiVzPc7H3Rr9+/ahTpw4ul4v33nuPjh070qBBA9q0acOUKVNwOBy5nispKYnBgwfTunVrGjRoQKtWrejTpw+zZs264tcvREGQRC+KjJUrV+J0OunYsSO1a9e+5GPzq39+48aNxMbGEhkZyUMPPUS1atVYuXIlQ4YM4ddff6Vv3764XC569epF48aN2bRpE4MHD8bj8eTL9c+XlJTEBx98QKlSpejYsSOPPfYYt912G2vXrqV3794cOHDA99jmzZvTv39/AOrWrcvQoUN9/+rVq3fRa/To0QOn08maNWsueDw+Ph69Xk+XLl18ZXv37qVbt2588sknVK9enX79+tG2bVuSkpLo27cv33///RW9vs8//xyXy8U999xDrVq1Lvt4nS6wUfK7775j2bJlVKxYkS5dutCvXz9q1KjB8uXL6dWrFykpKVcUx1kjRoxgyZIlNGnShIcffhiTycS8efMYO3ZswON++OEH+vXrx44dO2jZsiVPPPEE7du3x2Aw8Mknn1zVNYXIb9J0L4qMHTt2ANCyZcvrds2NGzfy4Ycf0rx5cwA8Hg9PPvkkP/30E4MGDeL111/n/vvv9z3+pZde4vPPP2fjxo3cfffd+R5PixYt2Lp1K2FhYQHlBw4c4OGHH2bq1KnMmzcPgJiYGCpXrsxHH31EvXr1eOaZZ67oGt26dWPGjBmsWrUq16j/3bt3c/DgQTp27EhERAQALpeLuLg4LBYLH330ke9egbcFolevXowZM4aNGzde9gvYL7/84nudedGtWzcef/zxXNfZsmUL//d//8ecOXN47bXXrvj5jh49yurVq32v9bnnnqNbt27Ex8czfPhwoqOjAVi+fDkej4fFixdTt27dgOe4WMuIENeL1OhFkXHq1CkAypcvf92u2blz54DEpdFo6NatGwC1atUKSPIA3bt3BwioWeenqKioXEkevDX2mJgYEhIScDqd13SNChUq0LJlS/bu3cuff/4ZcCw+Ph7wv06AzZs3c+TIER599NGAewXe39XAgQM5deoU27Ztu+y1T58+7TvvfMnJycycOTPg34cffpjrehf6MnHHHXdQs2ZNtmzZctkYzjVy5EhfkgcICQmha9eueDwefvvtt1yPNxqNucoiIyOv6ppC5Dep0QtxCReavlWuXDkA6tevn+vY2QR1of7g/LJ5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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "evaluator.plotCumulativeGains()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "evaluator.plotCumulativeResponse()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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c+cK/CUUt6qcn7nkup87gKIh5AIolJCQEM2cOVPSHyGsU6dOTr9+9nr/+c9/1KtXL02dOtVhfeXKle2P9YqPj3f65/ft27drxowZLqr+5kVERNjnxE6bNk2GYeRqM3XqVO3evbvI58h5hNn27dt15MiRXNvPnDmjRx555IbHCQ4OliT7kw9ulpeXl/7nf/5HkrR27VotXrzY/gSKvKYqSNLQoUNVq1YtSdKTTz6Z68a1P9u+ffsNf6fcVc7PXHLdzx1A0TAnF0ChRUVF6cSJE3r++ed1+vRpde7cWT169FD//v3VsGFDVapUSWlpaTp8+LDWr1+vTz/9VFlZWbm+6lWSYmJi9H//939KTU3VXXfdpeeee05hYWFKS0vT+vXrNXv2bLVp00Y7duwwoae5BQQEaMCAAVqyZIk2btyo++67TzExMQoMDNTx48e1cOFCrVixQhEREUWueciQIVq7dq39Q8Szzz5r/4a5HTt26PXXX1dKSorCw8PzfZ5sRESEtmzZoj179mjKlCnq3bu3/fnGvr6+RfqK2cGDB2v+/Pm6fPmynnjiCUlSYGCg7rnnnjz38fb21rJly9S5c2ddvHhRXbt21cCBAxUZGam6desqOztbp06d0r59+7Ry5UolJyfrzTfftN/cVhQXL14s0BdK1K5dW127di3yef4sIiLC/t9jx47VhAkTFBwcbJ/GUKdOHfuHJADFzACAIlqxYoVRp04dQ9INl8aNGxsbN27MdYysrCwjMjIyz/2aNm1qnDp1yv564sSJuY4xceJE+/b8DB061JBkhIaG5tuuU6dOhiSjU6dOTrenpKQY9evXz7PmgQMHGps2bbK/3rJlS6FriYqKyvP4Hh4exvTp02/Y7xMnThhVqlRxeow/9y2/n+/1srOzjVq1ajkca/To0fnuk2Pnzp259s1ree+99wp0zD8LDQ0t0PFzlv79+9v3vdHPMz4+3r796NGjedbw17/+Nc/z5bcfANdiugKAInvggQd06NAhLVq0SA8//LDCwsJUuXJllS1bVlWqVFGrVq30+OOP6/PPP1dycrJ69OiR6xhlypTR8uXLNWvWLN15550qX768ypcvr2bNmumll17Srl27FBQUZELv8hYYGKhdu3bpmWeeUf369eXt7a0qVaro7rvv1gcffKAlS5bIw8Pjps7x7rvvauHChbrrrrvk5+cn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SkhI0MyZMzV37txirxkAAADuwy1Hcv/s3LlzkqQqVark2Wbnzp3q1q2bw7qePXtq586dTttnZGTo/PnzDktGRobrigYAAIBp3D7kZmdna8yYMerQoYOaNGmSZ7uUlBQFBgY6rAsMDFRKSorT9nFxcfL393dY4uLiXFo7AAAAzOGW0xWuFxMTowMHDmj79u0uPe748eM1btw4h3Xe3t4uPQcAAADM4dYhd9SoUVq3bp22bdummjVr5ts2KChIqampDutSU1MVFBTktL23tzehFgAAwKLccrqCYRgaNWqUVq5cqc8//1x169a94T7h4eHavHmzw7qEhASFh4cXV5kAAABwU245khsTE6PFixdr9erV8vPzs8+r9ff3l6+vryRpyJAhCgkJsc+jHT16tDp16qTXXntNffr00dKlS7V3717NmzfPtH4AAADAHG45kjtnzhydO3dOnTt3VnBwsH358MMP7W2OHz+uU6dO2V9HRERo8eLFmjdvnpo3b67ly5dr1apV+d6sBgAAAGtyy5FcwzBu2CYxMTHXugEDBmjAgAHFUBEAAABuJW45kgsAAADcDEIuAAAALIeQCwAAAMsh5AIAAMByCLkAAACwHEIuAAAALIeQCwAAAMsh5AIAAMBy3PLLIADATNGz08wuoUjmP17F7BIAwG0wkgsAAADLIeQCAADAcm4q5L7//vvasWPHDdt99dVXev/992/mVAAAAECB3VTIHTZsmN5+++0btnvnnXcUFRV1M6cCAAAACqxEpitkZ2fLZrOVxKkAAACAkgm5P/74oypWrFgSpwIAAAAK/wixSZMmObxOSkrKtS7HtWvXdOjQIW3btk3du3cvWoUAAABAIRU65MbGxspms8kwDNlsNiUlJSkpKSnffQICAvSvf/2ryEUCAAAAhVHokBsfHy9JMgxDw4cPV8eOHTVixAinbb28vFSjRg21b99e3t7eN1cpAAAAUECFCrmTJk1SixYtdN9990mS3nvvPfXu3VtDhw4tluIAAACAoijUjWexsbFatWqV/fXWrVv13XffubwoAAAA4GYUKuR6eHjo6tWrxVULAAAA4BKFCrnBwcHas2ePLl++XFz1AAAAADetUHNyIyMjNXPmTFWvXl0BAQGSpOXLlysxMfGG+9psNv3www9FKhIAAAAojEKF3ClTpkiSVq9erWPHjslms+nixYu6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\n", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "evaluator.plotLift()" + ] } ], "metadata": { From d1a67bbadc5060d41af9032d0ffdd19cf9cbedd7 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 26 Mar 2020 09:25:22 +0100 Subject: [PATCH 76/98] Update README --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c7bcc6..53ce70a 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,13 @@ As this package is an internal package that is not open-sourced, it is not avail ### Usage -This section contains detailed examples for each step. We assume the data for model building is available in a pandas DataFrame called `basetable`. +This section contains detailed examples for each step on how to use COBRA for building a predictive model. All classes and functions contain detailed documentation, so in case you want more information on a class or function, simply run the following python snippet: + +```python +help(function_or_class_you_want_info_from) +``` + +In the examples below, we assume the data for model building is available in a pandas DataFrame called `basetable`. ```python from cobra.preprocessing import PreProcessor @@ -158,4 +164,4 @@ variable_importance = model.compute_variable_importance(basetable) ## Development -We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to write or modify unit test for your changes if they are related to preprocessing! +We'd love you to contribute to the development of Cobra! To do so, clone the repo and create a _feature branch_ to do your development. Once your are finished, you can create a _pull request_ to merge it back into the main branch. Make sure to follow the _PEP 8_ styleguide if you make any changes to COBRA. You should also write or modify unit test for your changes if they are related to preprocessing! From 21cc7a735517bd549bb22fae276edab18bc73db7 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 14:57:08 +0200 Subject: [PATCH 77/98] Change return type of Models.compute_variable_importance --- cobra/evaluation/predictor_quality.py | 15 +++++---------- cobra/model_building/models.py | 16 +++++++++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/cobra/evaluation/predictor_quality.py b/cobra/evaluation/predictor_quality.py index 327d592..3fac91f 100644 --- a/cobra/evaluation/predictor_quality.py +++ b/cobra/evaluation/predictor_quality.py @@ -5,30 +5,25 @@ import seaborn as sns -def plot_variable_importance(importance_by_variable: dict, +def plot_variable_importance(df_variable_importance: pd.DataFrame, title: str=None, dim: tuple=(12, 8)): """Plot variable importance of a given model Parameters ---------- - importance_by_variable : dict - Map of predictor -> importance + df_variable_importance : pd.DataFrame + DataFrame containing columns predictor and importance title : str, optional Title of the plot dim : tuple, optional tuple with width and lentgh of the plot """ - df = pd.DataFrame.from_dict(importance_by_variable, - orient='index').reset_index() - - df.columns = ["predictor", "importance"] - - df = df.sort_values(by="importance", ascending=False) # plot data fig, ax = plt.subplots(figsize=dim) - ax = sns.barplot(x="importance", y="predictor", data=df) + ax = sns.barplot(x="importance", y="predictor", + data=df_variable_importance) if title: ax.set_title(title) else: diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index 53f8c3c..597117a 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -124,9 +124,9 @@ def evaluate(self, X: pd.DataFrame, y: pd.Series, return self._eval_metrics_by_split[split] - def compute_variable_importance(self, data: pd.DataFrame) -> dict: + def compute_variable_importance(self, data: pd.DataFrame) -> pd.DataFrame: """Compute the importance of each predictor in the model and return - it as a dictionary + it as a DataFrame Parameters ---------- @@ -135,16 +135,22 @@ def compute_variable_importance(self, data: pd.DataFrame) -> dict: Returns ------- - dict - Map of predictor -> importance + pd.DataFrame + DataFrame containing columns predictor and importance """ y_pred = self.score_model(data) - return { + importance_by_variable = { utils.clean_predictor_name(predictor): stats.pearsonr( data[predictor], y_pred )[0] for predictor in self.predictors } + + df = pd.DataFrame.from_dict(importance_by_variable, + orient='index').reset_index() + df.columns = ["predictor", "importance"] + + return df.sort_values(by="importance", ascending=False) From 0d4b1571e4837b061542795043cf2861786490ca Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 15:07:57 +0200 Subject: [PATCH 78/98] Add Evaluator class --- cobra/evaluation/__init__.py | 17 +- cobra/evaluation/evaluator.py | 456 ++++++++++++++++++++++++++++++++++ 2 files changed, 466 insertions(+), 7 deletions(-) create mode 100644 cobra/evaluation/evaluator.py diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py index 5a3f437..b7e5f8e 100644 --- a/cobra/evaluation/__init__.py +++ b/cobra/evaluation/__init__.py @@ -7,11 +7,14 @@ from .predictor_quality import plot_variable_importance from .predictor_quality import plot_predictor_quality from .predictor_quality import plot_correlation_matrix +from .evaluator import Evaluator -__all__ = ['generate_pig_tables', - 'compute_pig_table', - 'plot_pig_graph', - 'plot_performance_curves', - 'plot_variable_importance', - 'plot_predictor_quality', - 'plot_correlation_matrix'] + +__all__ = ["generate_pig_tables", + "compute_pig_table", + "plot_pig_graph", + "plot_performance_curves", + "plot_variable_importance", + "plot_predictor_quality", + "plot_correlation_matrix", + "Evaluator"] diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py new file mode 100644 index 0000000..6583670 --- /dev/null +++ b/cobra/evaluation/evaluator.py @@ -0,0 +1,456 @@ +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt +import seaborn as sns + +from sklearn.metrics import precision_score +from sklearn.metrics import recall_score +from sklearn.metrics import f1_score +from sklearn.metrics import accuracy_score +from sklearn.metrics import roc_curve +from sklearn.metrics import confusion_matrix +from sklearn.metrics import roc_auc_score +from sklearn.exceptions import NotFittedError + + +class Evaluator(): + + """Summary + + Attributes + ---------- + confusion_matrix : np.ndarray + Confusion matrix computed for a particular cut-off + cumulative_gains : tuple + data for plotting cumulative gains curve + evaluation_metrics : dict + map containing various scalar evaluation metics such as AUC, ... + lift_at : float + parameter to determine at which top level percentage the lift of the + model should be computed + lift_curve : tuple + data for plotting lift curve(s) + probability_cutoff : float + probability cut off to convert probability scores to a binary score + roc_curve : dict + map containing true-positive-rate, false-positve-rate at various + thresholds (also incl.) + """ + + def __init__(self, probability_cutoff: float=None, + lift_at: float=0.05): + + self.lift_at = lift_at + self.probability_cutoff = probability_cutoff + + # Placeholder to store fitted output + self.evaluation_metrics = None + self.roc_curve = None + self.confusion_matrix = None + self.lift_curve = None + self.cumulative_gains = None + + def fit(self, y_true: np.ndarray, y_pred: np.ndarray): + + fpr, tpr, thresholds = roc_curve(y_true=y_true, y_score=y_pred) + + # if probability_cutoff is not set, take the optimal cut off + if not self.probability_cutoff: + self.probability_cutoff = (Evaluator. + _compute_optimal_cutoff(fpr, tpr, + thresholds)) + + # Transform probabilities to binary array using cut off: + y_pred_b = np.array([0 if pred <= self.probability_cutoff else 1 + for pred in y_pred]) + + # Compute the various evaluation metrics + self.evaluation_metrics = Evaluator.compute_evaluation_metrics( + y_true, + y_pred, + y_pred_b, + self.lift_at + ) + + self.roc_curve = {"fpr": fpr, "tpr": tpr, "thresholds": thresholds} + self.confusion_matrix = confusion_matrix(y_true, y_pred_b) + self.lift_curve = Evaluator._compute_lift_per_decile(y_true, y_pred) + self.cumulative_gains = Evaluator._compute_cumulative_gains(y_true, + y_pred) + + def get_metrics(self) -> pd.Series: + """Get the evaluation_metrics attribute as a pandas Series + + Returns + ------- + pd.Series + Score of various scalar evaluation metrics for the model + """ + return pd.Series(self.evaluation_metrics) + + @staticmethod + def compute_evaluation_metrics(y_true: np.ndarray, + y_pred: np.ndarray, + y_pred_b: np.ndarray, + lift_at: float) -> dict: + """Convenient function to compute various performance measures and + return them in a dict + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + y_pred_b : np.ndarray + Predicted target data labels (binary) + lift_at : float + At what top level percentage the lift should be computed + + Returns + ------- + dict + contains various performance measures of the model + """ + return { + "accuracy": accuracy_score(y_true, y_pred_b), + "AUC": roc_auc_score(y_true, y_pred), + "precision": precision_score(y_true, y_pred_b), + "recall": recall_score(y_true, y_pred_b), + "F1": f1_score(y_true, y_pred_b, average=None)[1], + f"lift at {lift_at}": np.round(Evaluator + ._compute_lift(y_true=y_true, + y_pred=y_pred, + lift_at=lift_at), 2) + } + + def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): + """Plot ROC curves of the model + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.roc_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + auc = self.evaluation_metrics["AUC"] + + fig, ax = plt.subplots(figsize=dim) + + ax.plot(self.roc_curve["fpr"], + self.roc_curve["tpr"], + color="darkorange", lw=2, + label="ROC curve (area = {s:.3})".format(s=auc)) + + ax.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") + ax.set_xlabel("False Positive Rate", fontsize=15) + ax.set_ylabel("True Positive Rate", fontsize=15) + ax.legend(loc="lower right") + ax.set_title("ROC Curve", fontsize=20) + + if path: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_confusion_matrix(self, path: str=None, dim: tuple=(12, 8), + labels: list=["0", "1"]): + """Plot the confusion matrix + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + labels : list, optional + Optional list of labels, default "0" and "1" + """ + + if self.confusion_matrix is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + fig, ax = plt.subplots(figsize=dim) + ax = sns.heatmap(self.confusion_matrix, + annot=self.confusion_matrix.astype(str), + fmt="s", cmap="Reds", + xticklabels=labels, yticklabels=labels) + ax.set_title("Confusion matrix", fontsize=20) + + if path: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_cumulative_response_curve(self, path: str=None, + dim: tuple=(12, 8)): + """Plot cumulative response curve + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.lift_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + x_labels, lifts, inc_rate = self.lift_curve + + lifts = np.array(lifts)*inc_rate*100 + + fig, ax = plt.subplots(figsize=dim) + plt.style.use("default") + + plt.bar(x_labels[::-1], lifts, align="center", color="cornflowerblue") + plt.ylabel("response (%)", fontsize=16) + plt.xlabel("decile", fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color="darkorange", linestyle="--", + xmin=0.05, xmax=0.95, linewidth=3, label="Incidence") + + #Legend + ax.legend(loc="upper right") + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title("Cumulative response", fontsize=20) + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_lift_curve(self, path: str=None, dim: tuple=(12, 8)): + """Plot lift per decile + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.lift_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + x_labels, lifts, _ = self.lift_curve + + fig, ax = plt.subplots(figsize=dim) + plt.style.use("default") + + plt.bar(x_labels[::-1], lifts, align="center", color="cornflowerblue") + plt.ylabel("lift", fontsize=16) + plt.xlabel("decile", fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color="darkorange", linestyle="--", + xmin=0.05, xmax=0.95, linewidth=3, label="Baseline") + + #Legend + ax.legend(loc="upper right") + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title("Cumulative Lift", fontsize=20) + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): + """Plot lift per decile + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + pass + + @staticmethod + def find_optimal_cutoff(y_true: np.ndarray, + y_pred: np.ndarray) -> float: + """Find the optimal probability cut off point for a + classification model. Wrapper around _compute_optimal_cutoff + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + float + Optimal cut off probability for the model + """ + return Evaluator._compute_optimal_cutoff(roc_curve(y_true=y_true, + y_score=y_pred)) + + @staticmethod + def _compute_optimal_cutoff(fpr: np.ndarray, tpr: np.ndarray, + thresholds: np.ndarray) -> float: + """Find the optimal probability cut off point for a + classification model + + Parameters + ---------- + fpr : np.ndarray + false positive rate for various thresholds + tpr : np.ndarray + true positive rate for various thresholds + thresholds : np.ndarray + list of thresholds for which fpr and tpr were computed + + Returns + ------- + float + Description + """ + + # The optimal cut off would be where tpr is high and fpr is low, hence + # tpr - (1-fpr) should be zero or close to zero for the optimal cut off + temp = np.absolute(tpr - (1-fpr)) + + # index for optimal value is the one for which temp is minimal + optimal_index = np.where(temp == min(temp))[0] + + return thresholds[optimal_index][0] + + @staticmethod + def _compute_cumulative_gains(y_true: np.ndarray, + y_pred: np.ndarray) -> tuple: + """Compute lift of the model per decile, returns x-labels, lifts and + the target incidence to create cummulative response curves + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + tuple + x-labels, lifts per decile and target incidence + """ + pass + + @staticmethod + def _compute_lift_per_decile(y_true: np.ndarray, + y_pred: np.ndarray) -> tuple: + """Compute lift of the model per decile, returns x-labels, lifts and + the target incidence to create cummulative response curves + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + tuple + x-labels, lifts per decile and target incidence + """ + + lifts = [Evaluator._compute_lift(y_true=y_true, + y_pred=y_pred, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + + x_labels = [len(lifts)-x for x in np.arange(0, len(lifts), 1)] + + return x_labels, lifts, y_true.mean() + + @staticmethod + def _compute_lift(y_true: np.ndarray, y_pred: np.ndarray, + lift_at: float=0.05) -> float: + """Calculates lift given two arrays on specified level + %timeit + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, + 10000 loops each) + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + lift_at : float, optional + At what top level percentage the lift should be computed + + Returns + ------- + float + lift of the model + """ + + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_), 1) + y_pred_ = y_pred_.reshape(len(y_pred_), 1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum("ij->j", y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] + .reshape(stop, 1)) + + #Calculate lift (einsum is very fast way of summing, + # needs specific shape) + inc_in_top_n = np.einsum("ij->j", data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence, 2)[0] + + return lift From ec1903a60d15ea2e68d0e3ac4415dfcdb7282eb8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 15:38:22 +0200 Subject: [PATCH 79/98] Add cumulative gains metric to Evaluator --- cobra/evaluation/evaluator.py | 50 +++- cobra/evaluation/model_evaluator.py | 354 ---------------------------- 2 files changed, 48 insertions(+), 356 deletions(-) delete mode 100644 cobra/evaluation/model_evaluator.py diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 6583670..69fa997 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -303,7 +303,33 @@ def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): dim : tuple, optional tuple with width and lentgh of the plot """ - pass + plt.style.use('seaborn-darkgrid') + + fig, ax = plt.subplots(figsize=dim) + + ax.plot(self.cumulative_gains[0]*100, self.cumulative_gains[1]*100, + color='blue', linewidth=3, + label='cumulative gains') + ax.plot(ax.get_xlim(), ax.get_ylim(), linewidth=3, + ls="--", color="darkorange", label='random selection') + + ax.set_title('Cumulative Gains', fontsize=20) + + #Format axes + ax.set_xlim([0, 100]) + ax.set_ylim([0, 100]) + #Format ticks + ax.set_yticklabels(['{:3.0f}%'.format(x) + for x in ax.get_yticks()]) + ax.set_xticklabels(['{:3.0f}%'.format(x) + for x in ax.get_xticks()]) + #Legend + ax.legend(loc='lower right') + + if path is not None: + plt.savefig(path, format='png', dpi=300, bbox_inches='tight') + + plt.show() @staticmethod def find_optimal_cutoff(y_true: np.ndarray, @@ -362,6 +388,10 @@ def _compute_cumulative_gains(y_true: np.ndarray, """Compute lift of the model per decile, returns x-labels, lifts and the target incidence to create cummulative response curves + Code from (https://github.com/reiinakano/scikit-plot/blob/ + 2dd3e6a76df77edcbd724c4db25575f70abb57cb/ + scikitplot/helpers.py#L157) + Parameters ---------- y_true : np.ndarray @@ -374,7 +404,23 @@ def _compute_cumulative_gains(y_true: np.ndarray, tuple x-labels, lifts per decile and target incidence """ - pass + + # make y_true a boolean vector + y_true = (y_true == 1) + + sorted_indices = np.argsort(y_pred)[::-1] + y_true = y_true[sorted_indices] + gains = np.cumsum(y_true) + + percentages = np.arange(start=1, stop=len(y_true) + 1) + + gains = gains / float(np.sum(y_true)) + percentages = percentages / float(len(y_true)) + + gains = np.insert(gains, 0, [0]) + percentages = np.insert(percentages, 0, [0]) + + return percentages, gains @staticmethod def _compute_lift_per_decile(y_true: np.ndarray, diff --git a/cobra/evaluation/model_evaluator.py b/cobra/evaluation/model_evaluator.py deleted file mode 100644 index f2d1093..0000000 --- a/cobra/evaluation/model_evaluator.py +++ /dev/null @@ -1,354 +0,0 @@ -""" -Created on Fri Apr 12 09:36:37 2019 -@author: AP_JBENEK -""" -import numpy as np -import pandas as pd -import seaborn as sns -import matplotlib.pyplot as plt - -from sklearn.metrics import precision_score -from sklearn.metrics import recall_score -from sklearn.metrics import f1_score -from sklearn.metrics import accuracy_score -from sklearn.metrics import roc_curve -from sklearn.metrics import confusion_matrix -from sklearn.metrics import roc_auc_score - - -class Evaluator(): - - def __init__(self, y_true, y_pred_p, threshold=0.5, lift_at=0.1): - self.y_true = y_true - self.y_pred_p = y_pred_p # As probability - self.lift_at = lift_at - self.threshold = threshold - - #Convert to bool - self.y_pred_b = np.array([0 if pred <= self.threshold else 1 - for pred in self.y_pred_p]) - - def plotROCCurve(self, save_pth=None, desc=None): - ''' - Plot ROC curve and print best cutoff value - - Parameters - ---------- - y_true: True values of target y - proba: Predicted values of target y, probabilities - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - fpr, tpr, thresholds = roc_curve(self.y_true, self.y_pred_p) - - #--------------------------- - #Calculate AUC - #-------------------------- - out_perfo = self.evaluation() - score = out_perfo['AUC'] - - fig, ax = plt.subplots(figsize=(8, 5)) - ax.plot(fpr, tpr, color='darkorange', lw=2, - label='ROC curve (area = {s:.3})'.format(s=score)) - ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') - ax.set_xlabel('False Positive Rate', fontsize=15) - ax.set_ylabel('True Positive Rate', fontsize=15) - ax.legend(loc="lower right") - ax.set_title('ROC Curve {}' .format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - #Best cutoff value - #i want value where FPR is highest and FPR is lowest - #https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python - i = np.arange(len(tpr)) - roc = pd.DataFrame({'tf': pd.Series(tpr-(1-fpr), index=i), - 'threshold': pd.Series(thresholds, index=i)}) - roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] - - best_cutoff = list(roc_t['threshold']) - print(f'Best cutoff value for probability is: {best_cutoff[0]}') - - def plotConfusionMatrix(self, labels=None, color='Reds', save_pth=None, desc=None): - ''' - Plot Confusion matrix with performance measures - - Parameters - ---------- - y_test: True values of target y - pred: Predicted values of target y, boolean - labels: labels for the matrix, if empty, values from y_test_ are used - color: Color of the matrix, its a cmap, so many values possible - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if labels is None: - labels = [str(lab) for lab in np.unique(self.y_true)] - - if desc is None: - desc = '' - - cm = confusion_matrix(self.y_true, self.y_pred_b) - - fig, ax = plt.subplots(figsize=(8,5)) - ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, - xticklabels=labels, yticklabels=labels) - ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - out_perfo = self.evaluation() - - # If we mark customer as a churner, how often we are correct - print('Precision: {s:.3}'.format(s=out_perfo['precision'])) - # Overall performance - print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) - # How many churners can the model detect - print('Recall: {s:.3}'.format(s=out_perfo['recall'])) - # 2 * (precision * recall) / (precision + recall) - print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) - # 2 * (precision * recall) / (precision + recall) - print('Lift at top {l}%: {s:.3}' - .format(l=self.lift_at*100, s=out_perfo['lift'])) - # 2 * (precision * recall) / (precision + recall) - print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) - - def plotCumulativeGains(self, save_pth=None, desc=None): - ''' - Functions plot cumulative gains - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - #--------------------------- - #Calculate cumulative gains - #-------------------------- - nrows = len(self.y_true) - npositives = self.y_true.sum() - df_y_pred = (pd.DataFrame({"y": self.y_true, "y_pred": self.y_pred_p}) - .sort_values(by='y_pred', ascending=False) - .reset_index(drop=True)) - cgains = [0] - for stop in (np.linspace(0.01, 1, 100) * nrows).astype(int): - cgains.append(round(df_y_pred.loc[:stop, 'y'].sum()/npositives*max(100, 1), 2)) - - #--------------------------- - #Plot it - #--------------------------- - plt.style.use('seaborn-darkgrid') - fig, ax_cgains = plt.subplots(figsize=(8, 5)) - ax_cgains.plot(cgains, color='blue', linewidth=3, - label='cumulative gains') - ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, - ls="--", color="darkorange", label='random selection') - ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) - - ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) - #Format axes - ax_cgains.set_xlim([0, 100]) - ax_cgains.set_ylim([0, 100]) - #Format ticks - ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) - for x in ax_cgains.get_yticks()]) - ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) - for x in ax_cgains.get_xticks()]) - #Legend - ax_cgains.legend(loc='lower right') - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotLift(self, desc=None, save_pth=None): - ''' - Method plots lift per decile - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- -# inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=perc_lift) - for perc_lift in np.arange(0.1, 1.1, 0.1)] - - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - plt.style.use('seaborn-darkgrid') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0, nrows, 1)] - - plt.bar(x_labels[::-1], lifts, align='center', color="cornflowerblue") - plt.ylabel('lift', fontsize=15) - plt.xlabel('decile', fontsize=15) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=1, color='darkorange', linestyle='--', - xmin=0.1, xmax=0.9, linewidth=3, label='Baseline') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeResponse(self, desc=None, save_pth=None): - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- - inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=perc_lift) - for perc_lift in np.arange(0.1, 1.1, 0.1)] - lifts = np.array(lifts)*inc_rate*100 - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8, 5)) - #plt.style.use('seaborn-darkgrid') - plt.style.use('default') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0, nrows, 1)] - - plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") - plt.ylabel('response (%)', fontsize=16) - plt.xlabel('decile', fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', - xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative response {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def evaluation(self): - ''' - Convenient function, returns various performance measures in a dict - - Parameters - ---------- - y_true: true values - y_pred: predictions as booleans - - Output - ------ - Returns dictionary with the measures - ''' - - dict_perfo = {'precision': precision_score(self.y_true, self.y_pred_b), - 'accuracy': accuracy_score(self.y_true, self.y_pred_b), - 'recall': recall_score(self.y_true, self.y_pred_b), - 'F1': f1_score(self.y_true, self.y_pred_b, - average=None)[1], - 'lift': np.round(Evaluator - .liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=self.lift_at), - 2), - 'AUC': roc_auc_score(self.y_true, self.y_pred_p) - } - return dict_perfo - - @staticmethod - def liftCalculator(y_true, y_pred, lift_at=0.05, **kwargs): - ''' - Calculates lift given two arrays on specified level - - Parameters - ---------- - y_true: numpy array with true values - y_pred: numpy array with predictions (probabilities) - lift_at: lift at what top percentage - - Output - ------ - Scalar value, lift. - - 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, - 10000 loops each) - ''' - #Make sure it is numpy array - y_true_ = np.array(y_true) - y_pred_ = np.array(y_pred) - - #Make sure it has correct shape - y_true_ = y_true_.reshape(len(y_true_), 1) - y_pred_ = y_pred_.reshape(len(y_pred_), 1) - - #Merge data together - y_data = np.hstack([y_true_, y_pred_]) - - #Calculate necessary variables - nrows = len(y_data) - stop = int(np.floor(nrows*lift_at)) - avg_incidence = np.einsum('ij->j', y_true_)/float(len(y_true_)) - - #Sort and filter data - data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] - .reshape(stop, 1)) - - #Calculate lift (einsum is very fast way of summing, - # needs specific shape) - inc_in_top_n = np.einsum('ij->j', data_sorted)/float(len(data_sorted)) - - lift = np.round(inc_in_top_n/avg_incidence, 2)[0] - - return lift From 237ed45ee7c1a918fe020af2e67d7c0f81e1e071 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 15:42:35 +0200 Subject: [PATCH 80/98] Cleanup of Evaluator --- cobra/evaluation/evaluator.py | 1006 +++++++++++++++++---------------- 1 file changed, 504 insertions(+), 502 deletions(-) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 69fa997..de09f77 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -1,502 +1,504 @@ -import numpy as np -import pandas as pd - -import matplotlib.pyplot as plt -import seaborn as sns - -from sklearn.metrics import precision_score -from sklearn.metrics import recall_score -from sklearn.metrics import f1_score -from sklearn.metrics import accuracy_score -from sklearn.metrics import roc_curve -from sklearn.metrics import confusion_matrix -from sklearn.metrics import roc_auc_score -from sklearn.exceptions import NotFittedError - - -class Evaluator(): - - """Summary - - Attributes - ---------- - confusion_matrix : np.ndarray - Confusion matrix computed for a particular cut-off - cumulative_gains : tuple - data for plotting cumulative gains curve - evaluation_metrics : dict - map containing various scalar evaluation metics such as AUC, ... - lift_at : float - parameter to determine at which top level percentage the lift of the - model should be computed - lift_curve : tuple - data for plotting lift curve(s) - probability_cutoff : float - probability cut off to convert probability scores to a binary score - roc_curve : dict - map containing true-positive-rate, false-positve-rate at various - thresholds (also incl.) - """ - - def __init__(self, probability_cutoff: float=None, - lift_at: float=0.05): - - self.lift_at = lift_at - self.probability_cutoff = probability_cutoff - - # Placeholder to store fitted output - self.evaluation_metrics = None - self.roc_curve = None - self.confusion_matrix = None - self.lift_curve = None - self.cumulative_gains = None - - def fit(self, y_true: np.ndarray, y_pred: np.ndarray): - - fpr, tpr, thresholds = roc_curve(y_true=y_true, y_score=y_pred) - - # if probability_cutoff is not set, take the optimal cut off - if not self.probability_cutoff: - self.probability_cutoff = (Evaluator. - _compute_optimal_cutoff(fpr, tpr, - thresholds)) - - # Transform probabilities to binary array using cut off: - y_pred_b = np.array([0 if pred <= self.probability_cutoff else 1 - for pred in y_pred]) - - # Compute the various evaluation metrics - self.evaluation_metrics = Evaluator.compute_evaluation_metrics( - y_true, - y_pred, - y_pred_b, - self.lift_at - ) - - self.roc_curve = {"fpr": fpr, "tpr": tpr, "thresholds": thresholds} - self.confusion_matrix = confusion_matrix(y_true, y_pred_b) - self.lift_curve = Evaluator._compute_lift_per_decile(y_true, y_pred) - self.cumulative_gains = Evaluator._compute_cumulative_gains(y_true, - y_pred) - - def get_metrics(self) -> pd.Series: - """Get the evaluation_metrics attribute as a pandas Series - - Returns - ------- - pd.Series - Score of various scalar evaluation metrics for the model - """ - return pd.Series(self.evaluation_metrics) - - @staticmethod - def compute_evaluation_metrics(y_true: np.ndarray, - y_pred: np.ndarray, - y_pred_b: np.ndarray, - lift_at: float) -> dict: - """Convenient function to compute various performance measures and - return them in a dict - - Parameters - ---------- - y_true : np.ndarray - True binary target data labels - y_pred : np.ndarray - Target scores of the model - y_pred_b : np.ndarray - Predicted target data labels (binary) - lift_at : float - At what top level percentage the lift should be computed - - Returns - ------- - dict - contains various performance measures of the model - """ - return { - "accuracy": accuracy_score(y_true, y_pred_b), - "AUC": roc_auc_score(y_true, y_pred), - "precision": precision_score(y_true, y_pred_b), - "recall": recall_score(y_true, y_pred_b), - "F1": f1_score(y_true, y_pred_b, average=None)[1], - f"lift at {lift_at}": np.round(Evaluator - ._compute_lift(y_true=y_true, - y_pred=y_pred, - lift_at=lift_at), 2) - } - - def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): - """Plot ROC curves of the model - - Parameters - ---------- - path : str, optional - path to store the figure - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - if self.roc_curve is None: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - auc = self.evaluation_metrics["AUC"] - - fig, ax = plt.subplots(figsize=dim) - - ax.plot(self.roc_curve["fpr"], - self.roc_curve["tpr"], - color="darkorange", lw=2, - label="ROC curve (area = {s:.3})".format(s=auc)) - - ax.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") - ax.set_xlabel("False Positive Rate", fontsize=15) - ax.set_ylabel("True Positive Rate", fontsize=15) - ax.legend(loc="lower right") - ax.set_title("ROC Curve", fontsize=20) - - if path: - plt.savefig(path, format="png", dpi=300, bbox_inches="tight") - - plt.show() - - def plot_confusion_matrix(self, path: str=None, dim: tuple=(12, 8), - labels: list=["0", "1"]): - """Plot the confusion matrix - - Parameters - ---------- - path : str, optional - path to store the figure - dim : tuple, optional - tuple with width and lentgh of the plot - labels : list, optional - Optional list of labels, default "0" and "1" - """ - - if self.confusion_matrix is None: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - fig, ax = plt.subplots(figsize=dim) - ax = sns.heatmap(self.confusion_matrix, - annot=self.confusion_matrix.astype(str), - fmt="s", cmap="Reds", - xticklabels=labels, yticklabels=labels) - ax.set_title("Confusion matrix", fontsize=20) - - if path: - plt.savefig(path, format="png", dpi=300, bbox_inches="tight") - - plt.show() - - def plot_cumulative_response_curve(self, path: str=None, - dim: tuple=(12, 8)): - """Plot cumulative response curve - - Parameters - ---------- - path : str, optional - path to store the figure - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - if self.lift_curve is None: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - x_labels, lifts, inc_rate = self.lift_curve - - lifts = np.array(lifts)*inc_rate*100 - - fig, ax = plt.subplots(figsize=dim) - plt.style.use("default") - - plt.bar(x_labels[::-1], lifts, align="center", color="cornflowerblue") - plt.ylabel("response (%)", fontsize=16) - plt.xlabel("decile", fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=inc_rate*100, color="darkorange", linestyle="--", - xmin=0.05, xmax=0.95, linewidth=3, label="Incidence") - - #Legend - ax.legend(loc="upper right") - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title("Cumulative response", fontsize=20) - - if path is not None: - plt.savefig(path, format="png", dpi=300, bbox_inches="tight") - - plt.show() - - def plot_lift_curve(self, path: str=None, dim: tuple=(12, 8)): - """Plot lift per decile - - Parameters - ---------- - path : str, optional - path to store the figure - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - if self.lift_curve is None: - msg = ("This {} instance is not fitted yet. Call 'fit' with " - "appropriate arguments before using this method.") - - raise NotFittedError(msg.format(self.__class__.__name__)) - - x_labels, lifts, _ = self.lift_curve - - fig, ax = plt.subplots(figsize=dim) - plt.style.use("default") - - plt.bar(x_labels[::-1], lifts, align="center", color="cornflowerblue") - plt.ylabel("lift", fontsize=16) - plt.xlabel("decile", fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=1, color="darkorange", linestyle="--", - xmin=0.05, xmax=0.95, linewidth=3, label="Baseline") - - #Legend - ax.legend(loc="upper right") - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title("Cumulative Lift", fontsize=20) - - if path is not None: - plt.savefig(path, format="png", dpi=300, bbox_inches="tight") - - plt.show() - - def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): - """Plot lift per decile - - Parameters - ---------- - path : str, optional - path to store the figure - dim : tuple, optional - tuple with width and lentgh of the plot - """ - plt.style.use('seaborn-darkgrid') - - fig, ax = plt.subplots(figsize=dim) - - ax.plot(self.cumulative_gains[0]*100, self.cumulative_gains[1]*100, - color='blue', linewidth=3, - label='cumulative gains') - ax.plot(ax.get_xlim(), ax.get_ylim(), linewidth=3, - ls="--", color="darkorange", label='random selection') - - ax.set_title('Cumulative Gains', fontsize=20) - - #Format axes - ax.set_xlim([0, 100]) - ax.set_ylim([0, 100]) - #Format ticks - ax.set_yticklabels(['{:3.0f}%'.format(x) - for x in ax.get_yticks()]) - ax.set_xticklabels(['{:3.0f}%'.format(x) - for x in ax.get_xticks()]) - #Legend - ax.legend(loc='lower right') - - if path is not None: - plt.savefig(path, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - @staticmethod - def find_optimal_cutoff(y_true: np.ndarray, - y_pred: np.ndarray) -> float: - """Find the optimal probability cut off point for a - classification model. Wrapper around _compute_optimal_cutoff - - Parameters - ---------- - y_true : np.ndarray - True binary target data labels - y_pred : np.ndarray - Target scores of the model - - Returns - ------- - float - Optimal cut off probability for the model - """ - return Evaluator._compute_optimal_cutoff(roc_curve(y_true=y_true, - y_score=y_pred)) - - @staticmethod - def _compute_optimal_cutoff(fpr: np.ndarray, tpr: np.ndarray, - thresholds: np.ndarray) -> float: - """Find the optimal probability cut off point for a - classification model - - Parameters - ---------- - fpr : np.ndarray - false positive rate for various thresholds - tpr : np.ndarray - true positive rate for various thresholds - thresholds : np.ndarray - list of thresholds for which fpr and tpr were computed - - Returns - ------- - float - Description - """ - - # The optimal cut off would be where tpr is high and fpr is low, hence - # tpr - (1-fpr) should be zero or close to zero for the optimal cut off - temp = np.absolute(tpr - (1-fpr)) - - # index for optimal value is the one for which temp is minimal - optimal_index = np.where(temp == min(temp))[0] - - return thresholds[optimal_index][0] - - @staticmethod - def _compute_cumulative_gains(y_true: np.ndarray, - y_pred: np.ndarray) -> tuple: - """Compute lift of the model per decile, returns x-labels, lifts and - the target incidence to create cummulative response curves - - Code from (https://github.com/reiinakano/scikit-plot/blob/ - 2dd3e6a76df77edcbd724c4db25575f70abb57cb/ - scikitplot/helpers.py#L157) - - Parameters - ---------- - y_true : np.ndarray - True binary target data labels - y_pred : np.ndarray - Target scores of the model - - Returns - ------- - tuple - x-labels, lifts per decile and target incidence - """ - - # make y_true a boolean vector - y_true = (y_true == 1) - - sorted_indices = np.argsort(y_pred)[::-1] - y_true = y_true[sorted_indices] - gains = np.cumsum(y_true) - - percentages = np.arange(start=1, stop=len(y_true) + 1) - - gains = gains / float(np.sum(y_true)) - percentages = percentages / float(len(y_true)) - - gains = np.insert(gains, 0, [0]) - percentages = np.insert(percentages, 0, [0]) - - return percentages, gains - - @staticmethod - def _compute_lift_per_decile(y_true: np.ndarray, - y_pred: np.ndarray) -> tuple: - """Compute lift of the model per decile, returns x-labels, lifts and - the target incidence to create cummulative response curves - - Parameters - ---------- - y_true : np.ndarray - True binary target data labels - y_pred : np.ndarray - Target scores of the model - - Returns - ------- - tuple - x-labels, lifts per decile and target incidence - """ - - lifts = [Evaluator._compute_lift(y_true=y_true, - y_pred=y_pred, - lift_at=perc_lift) - for perc_lift in np.arange(0.1, 1.1, 0.1)] - - x_labels = [len(lifts)-x for x in np.arange(0, len(lifts), 1)] - - return x_labels, lifts, y_true.mean() - - @staticmethod - def _compute_lift(y_true: np.ndarray, y_pred: np.ndarray, - lift_at: float=0.05) -> float: - """Calculates lift given two arrays on specified level - %timeit - 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, - 10000 loops each) - - Parameters - ---------- - y_true : np.ndarray - True binary target data labels - y_pred : np.ndarray - Target scores of the model - lift_at : float, optional - At what top level percentage the lift should be computed - - Returns - ------- - float - lift of the model - """ - - #Make sure it is numpy array - y_true_ = np.array(y_true) - y_pred_ = np.array(y_pred) - - #Make sure it has correct shape - y_true_ = y_true_.reshape(len(y_true_), 1) - y_pred_ = y_pred_.reshape(len(y_pred_), 1) - - #Merge data together - y_data = np.hstack([y_true_, y_pred_]) - - #Calculate necessary variables - nrows = len(y_data) - stop = int(np.floor(nrows*lift_at)) - avg_incidence = np.einsum("ij->j", y_true_)/float(len(y_true_)) - - #Sort and filter data - data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] - .reshape(stop, 1)) - - #Calculate lift (einsum is very fast way of summing, - # needs specific shape) - inc_in_top_n = np.einsum("ij->j", data_sorted)/float(len(data_sorted)) - - lift = np.round(inc_in_top_n/avg_incidence, 2)[0] - - return lift +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt +import seaborn as sns + +from sklearn.metrics import precision_score +from sklearn.metrics import recall_score +from sklearn.metrics import f1_score +from sklearn.metrics import accuracy_score +from sklearn.metrics import roc_curve +from sklearn.metrics import confusion_matrix +from sklearn.metrics import roc_auc_score +from sklearn.exceptions import NotFittedError + + +class Evaluator(): + + """Summary + + Attributes + ---------- + confusion_matrix : np.ndarray + Confusion matrix computed for a particular cut-off + cumulative_gains : tuple + data for plotting cumulative gains curve + evaluation_metrics : dict + map containing various scalar evaluation metics such as AUC, ... + lift_at : float + parameter to determine at which top level percentage the lift of the + model should be computed + lift_curve : tuple + data for plotting lift curve(s) + probability_cutoff : float + probability cut off to convert probability scores to a binary score + roc_curve : dict + map containing true-positive-rate, false-positve-rate at various + thresholds (also incl.) + """ + + def __init__(self, probability_cutoff: float=None, + lift_at: float=0.05): + + self.lift_at = lift_at + self.probability_cutoff = probability_cutoff + + # Placeholder to store fitted output + self._scalar_metrics = None + self.roc_curve = None + self.confusion_matrix = None + self.lift_curve = None + self.cumulative_gains = None + + def fit(self, y_true: np.ndarray, y_pred: np.ndarray): + + fpr, tpr, thresholds = roc_curve(y_true=y_true, y_score=y_pred) + + # if probability_cutoff is not set, take the optimal cut off + if not self.probability_cutoff: + self.probability_cutoff = (Evaluator. + _compute_optimal_cutoff(fpr, tpr, + thresholds)) + + # Transform probabilities to binary array using cut off: + y_pred_b = np.array([0 if pred <= self.probability_cutoff else 1 + for pred in y_pred]) + + # Compute the various evaluation metrics + self._scalar_metrics = Evaluator.compute_scalar_metrics( + y_true, + y_pred, + y_pred_b, + self.lift_at + ) + + self.roc_curve = {"fpr": fpr, "tpr": tpr, "thresholds": thresholds} + self.confusion_matrix = confusion_matrix(y_true, y_pred_b) + self.lift_curve = Evaluator._compute_lift_per_decile(y_true, y_pred) + self.cumulative_gains = Evaluator._compute_cumulative_gains(y_true, + y_pred) + + def get_scalar_scalar_metrics(self) -> pd.Series: + """Get the evaluation_metrics attribute as a pandas Series + + Returns + ------- + pd.Series + Score of various scalar evaluation metrics for the model + """ + return pd.Series(self._scalar_metrics) + + @staticmethod + def compute_scalar_metrics(y_true: np.ndarray, + y_pred: np.ndarray, + y_pred_b: np.ndarray, + lift_at: float) -> dict: + """Convenient function to compute various scalar performance measures + and return them in a dict + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + y_pred_b : np.ndarray + Predicted target data labels (binary) + lift_at : float + At what top level percentage the lift should be computed + + Returns + ------- + dict + contains various performance measures of the model + """ + return { + "accuracy": accuracy_score(y_true, y_pred_b), + "AUC": roc_auc_score(y_true, y_pred), + "precision": precision_score(y_true, y_pred_b), + "recall": recall_score(y_true, y_pred_b), + "F1": f1_score(y_true, y_pred_b, average=None)[1], + f"lift at {lift_at}": np.round(Evaluator + ._compute_lift(y_true=y_true, + y_pred=y_pred, + lift_at=lift_at), 2) + } + + def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): + """Plot ROC curves of the model + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.roc_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + auc = self._scalar_metrics["AUC"] + + fig, ax = plt.subplots(figsize=dim) + + ax.plot(self.roc_curve["fpr"], + self.roc_curve["tpr"], + color="darkorange", lw=2, + label="ROC curve (area = {s:.3})".format(s=auc)) + + ax.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") + ax.set_xlabel("False Positive Rate", fontsize=15) + ax.set_ylabel("True Positive Rate", fontsize=15) + ax.legend(loc="lower right") + ax.set_title("ROC Curve", fontsize=20) + + if path: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_confusion_matrix(self, path: str=None, dim: tuple=(12, 8), + labels: list=["0", "1"]): + """Plot the confusion matrix + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + labels : list, optional + Optional list of labels, default "0" and "1" + """ + + if self.confusion_matrix is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + fig, ax = plt.subplots(figsize=dim) + ax = sns.heatmap(self.confusion_matrix, + annot=self.confusion_matrix.astype(str), + fmt="s", cmap="Reds", + xticklabels=labels, yticklabels=labels) + ax.set_title("Confusion matrix", fontsize=20) + + if path: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_cumulative_response_curve(self, path: str=None, + dim: tuple=(12, 8)): + """Plot cumulative response curve + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.lift_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + x_labels, lifts, inc_rate = self.lift_curve + + lifts = np.array(lifts)*inc_rate*100 + + with plt.style.context("seaborn-ticks"): + fig, ax = plt.subplots(figsize=dim) + + plt.bar(x_labels[::-1], lifts, align="center", + color="cornflowerblue") + plt.ylabel("response (%)", fontsize=16) + plt.xlabel("decile", fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=inc_rate*100, color="darkorange", linestyle="--", + xmin=0.05, xmax=0.95, linewidth=3, label="Incidence") + + #Legend + ax.legend(loc="upper right") + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title("Cumulative response", fontsize=20) + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_lift_curve(self, path: str=None, dim: tuple=(12, 8)): + """Plot lift per decile + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + if self.lift_curve is None: + msg = ("This {} instance is not fitted yet. Call 'fit' with " + "appropriate arguments before using this method.") + + raise NotFittedError(msg.format(self.__class__.__name__)) + + x_labels, lifts, _ = self.lift_curve + + with plt.style.context("seaborn-ticks"): + fig, ax = plt.subplots(figsize=dim) + + plt.bar(x_labels[::-1], lifts, align="center", + color="cornflowerblue") + plt.ylabel("lift", fontsize=16) + plt.xlabel("decile", fontsize=16) + ax.set_xticks(x_labels) + ax.set_xticklabels(x_labels) + + plt.axhline(y=1, color="darkorange", linestyle="--", + xmin=0.05, xmax=0.95, linewidth=3, label="Baseline") + + #Legend + ax.legend(loc="upper right") + + ##Set Axis - make them pretty + sns.despine(ax=ax, right=True, left=True) + + #Remove white lines from the second axis + ax.grid(False) + + ##Description + ax.set_title("Cumulative Lift", fontsize=20) + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): + """Plot lift per decile + + Parameters + ---------- + path : str, optional + path to store the figure + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + with plt.style.context("seaborn-whitegrid"): + fig, ax = plt.subplots(figsize=dim) + + ax.plot(self.cumulative_gains[0]*100, self.cumulative_gains[1]*100, + color="blue", linewidth=3, + label="cumulative gains") + ax.plot(ax.get_xlim(), ax.get_ylim(), linewidth=3, + ls="--", color="darkorange", label="random selection") + + ax.set_title("Cumulative Gains", fontsize=20) + + #Format axes + ax.set_xlim([0, 100]) + ax.set_ylim([0, 100]) + #Format ticks + ax.set_yticklabels(["{:3.0f}%".format(x) + for x in ax.get_yticks()]) + ax.set_xticklabels(["{:3.0f}%".format(x) + for x in ax.get_xticks()]) + #Legend + ax.legend(loc="lower right") + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + + plt.show() + + @staticmethod + def find_optimal_cutoff(y_true: np.ndarray, + y_pred: np.ndarray) -> float: + """Find the optimal probability cut off point for a + classification model. Wrapper around _compute_optimal_cutoff + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + float + Optimal cut off probability for the model + """ + return Evaluator._compute_optimal_cutoff(roc_curve(y_true=y_true, + y_score=y_pred)) + + @staticmethod + def _compute_optimal_cutoff(fpr: np.ndarray, tpr: np.ndarray, + thresholds: np.ndarray) -> float: + """Find the optimal probability cut off point for a + classification model + + Parameters + ---------- + fpr : np.ndarray + false positive rate for various thresholds + tpr : np.ndarray + true positive rate for various thresholds + thresholds : np.ndarray + list of thresholds for which fpr and tpr were computed + + Returns + ------- + float + Description + """ + + # The optimal cut off would be where tpr is high and fpr is low, hence + # tpr - (1-fpr) should be zero or close to zero for the optimal cut off + temp = np.absolute(tpr - (1-fpr)) + + # index for optimal value is the one for which temp is minimal + optimal_index = np.where(temp == min(temp))[0] + + return thresholds[optimal_index][0] + + @staticmethod + def _compute_cumulative_gains(y_true: np.ndarray, + y_pred: np.ndarray) -> tuple: + """Compute lift of the model per decile, returns x-labels, lifts and + the target incidence to create cummulative response curves + + Code from (https://github.com/reiinakano/scikit-plot/blob/ + 2dd3e6a76df77edcbd724c4db25575f70abb57cb/ + scikitplot/helpers.py#L157) + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + tuple + x-labels, lifts per decile and target incidence + """ + + # make y_true a boolean vector + y_true = (y_true == 1) + + sorted_indices = np.argsort(y_pred)[::-1] + y_true = y_true[sorted_indices] + gains = np.cumsum(y_true) + + percentages = np.arange(start=1, stop=len(y_true) + 1) + + gains = gains / float(np.sum(y_true)) + percentages = percentages / float(len(y_true)) + + gains = np.insert(gains, 0, [0]) + percentages = np.insert(percentages, 0, [0]) + + return percentages, gains + + @staticmethod + def _compute_lift_per_decile(y_true: np.ndarray, + y_pred: np.ndarray) -> tuple: + """Compute lift of the model per decile, returns x-labels, lifts and + the target incidence to create cummulative response curves + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + + Returns + ------- + tuple + x-labels, lifts per decile and target incidence + """ + + lifts = [Evaluator._compute_lift(y_true=y_true, + y_pred=y_pred, + lift_at=perc_lift) + for perc_lift in np.arange(0.1, 1.1, 0.1)] + + x_labels = [len(lifts)-x for x in np.arange(0, len(lifts), 1)] + + return x_labels, lifts, y_true.mean() + + @staticmethod + def _compute_lift(y_true: np.ndarray, y_pred: np.ndarray, + lift_at: float=0.05) -> float: + """Calculates lift given two arrays on specified level + %timeit + 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, + 10000 loops each) + + Parameters + ---------- + y_true : np.ndarray + True binary target data labels + y_pred : np.ndarray + Target scores of the model + lift_at : float, optional + At what top level percentage the lift should be computed + + Returns + ------- + float + lift of the model + """ + + #Make sure it is numpy array + y_true_ = np.array(y_true) + y_pred_ = np.array(y_pred) + + #Make sure it has correct shape + y_true_ = y_true_.reshape(len(y_true_), 1) + y_pred_ = y_pred_.reshape(len(y_pred_), 1) + + #Merge data together + y_data = np.hstack([y_true_, y_pred_]) + + #Calculate necessary variables + nrows = len(y_data) + stop = int(np.floor(nrows*lift_at)) + avg_incidence = np.einsum("ij->j", y_true_)/float(len(y_true_)) + + #Sort and filter data + data_sorted = (y_data[y_data[:, 1].argsort()[::-1]][:stop, 0] + .reshape(stop, 1)) + + #Calculate lift (einsum is very fast way of summing, + # needs specific shape) + inc_in_top_n = np.einsum("ij->j", data_sorted)/float(len(data_sorted)) + + lift = np.round(inc_in_top_n/avg_incidence, 2)[0] + + return lift From 63b584ad0b5258348b12e68ef7e361b30bc20648 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 16:10:11 +0200 Subject: [PATCH 81/98] Add documentation to evaluation module --- cobra/evaluation/performance_curves.py | 10 ++++++++++ cobra/evaluation/pigs_tables.py | 9 +++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cobra/evaluation/performance_curves.py b/cobra/evaluation/performance_curves.py index 1e0032d..0ff5aa8 100644 --- a/cobra/evaluation/performance_curves.py +++ b/cobra/evaluation/performance_curves.py @@ -7,7 +7,17 @@ def plot_performance_curves(model_performance: pd.DataFrame, dim: tuple=(12, 8)): + """Plot performance curves generated by the forward feature selection + for the train-selection-validation sets + Parameters + ---------- + model_performance : pd.DataFrame + contains train-selection-validation performance for each model trained + in the forward feature selection + dim : tuple, optional + tuple with width and lentgh of the plot + """ highest_auc = np.round(max(max(model_performance['train_performance']), max(model_performance['selection_performance']), max(model_performance['validation_performance']) diff --git a/cobra/evaluation/pigs_tables.py b/cobra/evaluation/pigs_tables.py index 8afb4f7..c2e53a9 100644 --- a/cobra/evaluation/pigs_tables.py +++ b/cobra/evaluation/pigs_tables.py @@ -10,7 +10,9 @@ def generate_pig_tables(data: pd.DataFrame, id_column_name: str, target_column_name: str, preprocessed_predictors: list) -> pd.DataFrame: - """Summary + """Compute PIG tables for all predictors in preprocessed_predictors. The + output is a DataFrame with columns "variable", "label", "pop_size", + "avg_incidence" and "incidence" Parameters ---------- @@ -29,11 +31,6 @@ def generate_pig_tables(data: pd.DataFrame, DataFrame containing a PIG table for all predictors """ - # Based on the data, get column names by datatype - # threshold to decide whether a numeric column should be considered - # a categorical variable (if the number of distinct values is smaller - # or equal to the number of requested bins) - pigs = [compute_pig_table(data, column_name, target_column_name, id_column_name) for column_name in sorted(preprocessed_predictors) From 2c441f5361343d9f1c2b904e11ad278d7c0d0323 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 16:31:34 +0200 Subject: [PATCH 82/98] Update README --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 53ce70a..b723c29 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ This section contains detailed examples for each step on how to use COBRA for bu help(function_or_class_you_want_info_from) ``` -In the examples below, we assume the data for model building is available in a pandas DataFrame called `basetable`. +In the examples below, we assume the data for model building is available in a pandas DataFrame called `basetable`. This DataFrame should contain an ID columns (e.g. customernumber), a target column (e.g. "TARGET") and a number of candidate predictors to build or model with. ```python from cobra.preprocessing import PreProcessor @@ -104,11 +104,23 @@ basetable = preprocessor.transform(basetable, ``` -Once the preprocessing pipeline is fitted and applied to your data, it is time for the actual modelling. In this part of the process, -we first start with the _univariate preselection_: +Once the preprocessing pipeline is fitted and applied to your data, we are ready to start modelling. However, we could already compute the PIG tables here for later use: + +```python +from cobra.evaluation import generate_pig_tables + +pig_tables = generate_pig_tables(basetable[basetable["split"] == "selection"], + id_column_name, + target_column_name + preprocessed_predictors) +``` + +Once these PIG tables are computed, we can start with the _univariate preselection_: ```python from cobra.model_building import univariate_selection +from cobra.evaluation import plot_predictor_quality +from cobra.evaluation import plot_correlation_matrix # Get list of predictor names to use for univariate_selection preprocessed_predictors = [col for col in basetable.columns if col.endswith("_enc")] @@ -123,20 +135,28 @@ df_auc = univariate_selection.compute_univariate_preselection( preselect_overtrain_threshold=0.05 # if (auc_train - auc_selection) >= 0.05 --> overfitting! ) +# Plot df_auc to get a horizontal barplot: +plot_predictor_quality(df_auc) + # compute correlations between preprocessed predictors: df_corr = (univariate_selection .compute_correlations(basetable[basetable["split"] == "train"], preprocessed_predictors)) +# plot correlation matrix +plot_correlation_matrix(df_corr) + # get a list of predictors selection by the univariate selection preselected_predictors = (univariate_selection .get_preselected_predictors(df_auc)) ``` -After a preselection is done on the predictors, we can start the model building itself using _forward feature selection_ to choose the right set of predictors: +After a preselection is done on the predictors, we can start the model building itself using forward feature selection to choose the right set of predictors: ```python from cobra.model_building import ForwardFeatureSelection +from cobra.evaluation import plot_performance_curves +from cobra.evaluation import plot_variable_importance forward_selection = ForwardFeatureSelection(max_predictors=30, pos_only=True) @@ -151,15 +171,49 @@ forward_selection.fit(basetable[basetable["split"] == "train"], performances = (forward_selection .compute_model_performances(basetable, target_column_name)) +# plot performance curves +plot_performance_curves(performances) + # After plotting the performances and selecting the model, # we can extract this model from the forward_selection class: -model = forward_selection.get_model_from_step(5) # Python indexing starts from 0, so this model has 6 predictors +model = forward_selection.get_model_from_step(5) -# Note that model has 6 variables (python lists start with index 0), +# Note that chosen model has 6 variables (python lists start with index 0), # which can be obtained as follows: final_predictors = model.predictors -# We can also compute the importance of each predictor in the model (dict): -variable_importance = model.compute_variable_importance(basetable) +# We can also compute and plot the importance of each predictor in the model: +variable_importance = model.compute_variable_importance( + basetable[basetable["split"] == "selection"] +) +plot_variable_importance(variable_importance) +``` + +Now that we have build and selected a final model, it is time to evaluate it against various evaluation metrics: + +```python +from cobra.evaluation import Evaluator + +# get numpy array of True target labels and predicted scores: +y_true = basetable[basetable["split"] == "selection"][target_column_name].values +y_pred = model.score_model(basetable[basetable["split"] == "selection"]) + +evaluator = Evaluator() +evaluator.fit(y_true, y_pred) # Automatically find the best cut-off probability + +# Get various scalar metrics such as accuracy, AUC, precision, recall, ... +evaluator.get_scalar_scalar_metrics() + +# Plot non-scalar evaluation metrics: +evaluator.plot_roc_curve() + +evaluator.plot_confusion_matrix() + +evaluator.plot_cumulative_gains() + +evaluator.plot_lift_curve() + +evaluator.plot_cumulative_response_curve() + ``` ## Development From 748f6819ce237e93b92ea26592bb07f4ddb8b8e0 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 17:05:16 +0200 Subject: [PATCH 83/98] Change color scheme in Evaluator plots --- cobra/evaluation/evaluator.py | 31 +++++++++++++++++-------------- cobra/model_building/models.py | 3 ++- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index de09f77..39f8f7c 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -144,21 +144,24 @@ def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): auc = self._scalar_metrics["AUC"] - fig, ax = plt.subplots(figsize=dim) + with plt.style.context("seaborn-whitegrid"): - ax.plot(self.roc_curve["fpr"], - self.roc_curve["tpr"], - color="darkorange", lw=2, - label="ROC curve (area = {s:.3})".format(s=auc)) + fig, ax = plt.subplots(figsize=dim) - ax.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") - ax.set_xlabel("False Positive Rate", fontsize=15) - ax.set_ylabel("True Positive Rate", fontsize=15) - ax.legend(loc="lower right") - ax.set_title("ROC Curve", fontsize=20) + ax.plot(self.roc_curve["fpr"], + self.roc_curve["tpr"], + color="cornflowerblue", linewidth=3, + label="ROC curve (area = {s:.3})".format(s=auc)) - if path: - plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + ax.plot([0, 1], [0, 1], color="darkorange", linewidth=3, + linestyle="--") + ax.set_xlabel("False Positive Rate", fontsize=15) + ax.set_ylabel("True Positive Rate", fontsize=15) + ax.legend(loc="lower right") + ax.set_title("ROC Curve", fontsize=20) + + if path: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") plt.show() @@ -310,7 +313,7 @@ def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): fig, ax = plt.subplots(figsize=dim) ax.plot(self.cumulative_gains[0]*100, self.cumulative_gains[1]*100, - color="blue", linewidth=3, + color="cornflowerblue", linewidth=3, label="cumulative gains") ax.plot(ax.get_xlim(), ax.get_ylim(), linewidth=3, ls="--", color="darkorange", label="random selection") @@ -319,7 +322,7 @@ def plot_cumulative_gains(self, path: str=None, dim: tuple=(12, 8)): #Format axes ax.set_xlim([0, 100]) - ax.set_ylim([0, 100]) + ax.set_ylim([0, 105]) #Format ticks ax.set_yticklabels(["{:3.0f}%".format(x) for x in ax.get_yticks()]) diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index 597117a..a736c96 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -153,4 +153,5 @@ def compute_variable_importance(self, data: pd.DataFrame) -> pd.DataFrame: orient='index').reset_index() df.columns = ["predictor", "importance"] - return df.sort_values(by="importance", ascending=False) + return (df.sort_values(by="importance", ascending=False) + .reset_index(drop=True)) From 0030b3e9e0372aca3a742fe48cdaa24139fc8ba8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 30 Mar 2020 17:05:32 +0200 Subject: [PATCH 84/98] Update example notebook --- examples/examples.ipynb | 286 +++++++++++++++++++++++++++------------- 1 file changed, 192 insertions(+), 94 deletions(-) diff --git a/examples/examples.ipynb b/examples/examples.ipynb index 146ec0c..1cf1527 100644 --- a/examples/examples.ipynb +++ b/examples/examples.ipynb @@ -52,7 +52,8 @@ "from cobra.evaluation import plot_performance_curves\n", "from cobra.evaluation import plot_variable_importance\n", "from cobra.evaluation import plot_predictor_quality\n", - "from cobra.evaluation import plot_correlation_matrix" + "from cobra.evaluation import plot_correlation_matrix\n", + "from cobra.evaluation import Evaluator" ] }, { @@ -276,7 +277,7 @@ } ], "source": [ - "path = \"\"\n", + "path = \"\"\"\n", "\n", "basetable = pd.read_csv(path)\n", "\n", @@ -1072,7 +1073,7 @@ "forward_selection.fit(basetable[basetable[\"split\"] == \"train\"], \n", " \"TARGET\", \n", " preselected_predictors,\n", - " forced_predictors=[\"scont_1_enc\", \"scont_1_enc\"])" + " forced_predictors=[\"age_enc\"])" ] }, { @@ -1120,43 +1121,83 @@ " \n", " \n", " 0\n", - " [scont_1_enc]\n", - " scont_1_enc\n", - " 0.506035\n", - " 0.489155\n", - " 0.497746\n", + " [age_enc]\n", + " age_enc\n", + " 0.707488\n", + " 0.716277\n", + " 0.703346\n", " \n", " \n", " 1\n", - " [scont_1_enc, relationship_enc]\n", + " [age_enc, relationship_enc]\n", " relationship_enc\n", - " 0.779681\n", - " 0.775267\n", - " 0.787203\n", + " 0.817672\n", + " 0.823040\n", + " 0.823285\n", " \n", " \n", " 2\n", - " [scont_1_enc, relationship_enc, education_enc]\n", + " [age_enc, relationship_enc, education_enc]\n", " education_enc\n", - " 0.858048\n", - " 0.856376\n", - " 0.859707\n", + " 0.870507\n", + " 0.872161\n", + " 0.871720\n", " \n", " \n", " 3\n", - " [scont_1_enc, relationship_enc, education_enc,...\n", - " age_enc\n", - " 0.870537\n", - " 0.872149\n", - " 0.871496\n", + " [age_enc, relationship_enc, education_enc, occ...\n", + " occupation_enc\n", + " 0.878316\n", + " 0.882509\n", + " 0.880340\n", " \n", " \n", " 4\n", - " [scont_1_enc, relationship_enc, age_enc, educa...\n", - " occupation_enc\n", - " 0.878380\n", - " 0.882468\n", - " 0.880178\n", + " [age_enc, relationship_enc, occupation_enc, ed...\n", + " hours-per-week_enc\n", + " 0.882722\n", + " 0.885464\n", + " 0.885040\n", + " \n", + " \n", + " 5\n", + " [occupation_enc, age_enc, hours-per-week_enc, ...\n", + " fnlwgt_enc\n", + " 0.883650\n", + " 0.885744\n", + " 0.885099\n", + " \n", + " \n", + " 6\n", + " [fnlwgt_enc, occupation_enc, age_enc, hours-pe...\n", + " native-country_enc\n", + " 0.884048\n", + " 0.886110\n", + " 0.885772\n", + " \n", + " \n", + " 7\n", + " [fnlwgt_enc, occupation_enc, native-country_en...\n", + " marital-status_enc\n", + " 0.884377\n", + " 0.886766\n", + " 0.886017\n", + " \n", + " \n", + " 8\n", + " [fnlwgt_enc, occupation_enc, native-country_en...\n", + " scont_2_enc\n", + " 0.884610\n", + " 0.886622\n", + " 0.885594\n", + " \n", + " \n", + " 9\n", + " [fnlwgt_enc, occupation_enc, native-country_en...\n", + " workclass_enc\n", + " 0.884795\n", + " 0.886878\n", + " 0.885854\n", " \n", " \n", "\n", @@ -1164,18 +1205,28 @@ ], "text/plain": [ " predictors last_added_predictor \\\n", - "0 [scont_1_enc] scont_1_enc \n", - "1 [scont_1_enc, relationship_enc] relationship_enc \n", - "2 [scont_1_enc, relationship_enc, education_enc] education_enc \n", - "3 [scont_1_enc, relationship_enc, education_enc,... age_enc \n", - "4 [scont_1_enc, relationship_enc, age_enc, educa... occupation_enc \n", + "0 [age_enc] age_enc \n", + "1 [age_enc, relationship_enc] relationship_enc \n", + "2 [age_enc, relationship_enc, education_enc] education_enc \n", + "3 [age_enc, relationship_enc, education_enc, occ... occupation_enc \n", + "4 [age_enc, relationship_enc, occupation_enc, ed... hours-per-week_enc \n", + "5 [occupation_enc, age_enc, hours-per-week_enc, ... fnlwgt_enc \n", + "6 [fnlwgt_enc, occupation_enc, age_enc, hours-pe... native-country_enc \n", + "7 [fnlwgt_enc, occupation_enc, native-country_en... marital-status_enc \n", + "8 [fnlwgt_enc, occupation_enc, native-country_en... scont_2_enc \n", + "9 [fnlwgt_enc, occupation_enc, native-country_en... workclass_enc \n", "\n", " train_performance selection_performance validation_performance \n", - "0 0.506035 0.489155 0.497746 \n", - "1 0.779681 0.775267 0.787203 \n", - "2 0.858048 0.856376 0.859707 \n", - "3 0.870537 0.872149 0.871496 \n", - "4 0.878380 0.882468 0.880178 " + "0 0.707488 0.716277 0.703346 \n", + "1 0.817672 0.823040 0.823285 \n", + "2 0.870507 0.872161 0.871720 \n", + "3 0.878316 0.882509 0.880340 \n", + "4 0.882722 0.885464 0.885040 \n", + "5 0.883650 0.885744 0.885099 \n", + "6 0.884048 0.886110 0.885772 \n", + "7 0.884377 0.886766 0.886017 \n", + "8 0.884610 0.886622 0.885594 \n", + "9 0.884795 0.886878 0.885854 " ] }, "execution_count": 22, @@ -1184,17 +1235,17 @@ } ], "source": [ - "performances.head()" + "performances.head(n=10)" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtMAAAJHCAYAAABSA9pkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdd3wUdfoH8M/sbja9kwKhC0kIIbQQEqqEEoooXUSKCMchUiwn4FlQlBP19CflsBwnipzYAClRQAEhQAiQAyMoSAskQEJLL1u/vz82u2Szm2SzJGwCn/cLXrs75ZlndmY2z85+5zuSEEKAiIiIiIhqTOboBIiIiIiIGioW00REREREdmIxTURERERkJxbTRERERER2YjFNRERERGQnFtNERERERHZiMU313tq1azF06FBERUUhLCwMn332maNTIqoz169fx4IFC9CnTx+0a9cOYWFhyM/Pd3Ra9cKkSZMQFhZWo3n279+P8ePHIzo6GmFhYZg1a1YdZUe2CgsLw6RJkxydhklKSgrCwsKwYsUKR6dCDZTC0QlQ/VbxD5dMJoOXlxfCwsIwduxYDB8+vE6Xn5iYiCVLliAiIgJTpkyBUqlEp06d6nSZRI60cOFCHDhwAMOGDUOLFi0gSRKcnZ0dnVaDlJmZiVmzZsHLywujR4+Gh4cHWrdufdeWHxYWhpiYGHzxxRd3bZlkKTMzE/3798fIkSOxdOlSR6dD9yAW02ST2bNnAwC0Wi3Onz+PXbt2ISUlBSdOnMCLL75YZ8vds2cPAOCjjz5CUFBQnS2HqD5Qq9U4ePAgevTogffee8/R6TR4ycnJUKlUWLBgQZ1/8aeGKyoqCj/88AN8fX0dnQo1UCymySZz5swxe52cnIypU6fi888/x6RJk9C0adM6We61a9cAgIU03Rdu3LgBvV6PwMBAR6dyT8jOzgYAvp9UJVdXVzzwwAOOToMaMLaZJrvExcWhdevWEELgt99+Mw3PysrC4sWL0b9/f0RGRqJ79+6YOXMm0tLSLGKsWLECYWFhSElJwdatWzF27Fh07twZ8fHxZuMAw8+lxv/lJScnY9q0aYiJiUFkZCQSEhLwz3/+EwUFBRbLM7a3VKvVWLlyJRISEhAZGYmFCxeajddoNFi5ciUGDBiADh06ICEhAd98840pzvr16zF8+HBERUWhT58+WL58OfR6vcXyNm7ciDlz5qB///6IiopCly5dMH78eGzevNnqe2pcvlarxUcffYRBgwYhMjISffv2xbvvvgu1Wm11vnPnzuHFF19EfHw8IiMjERcXhwkTJuDLL7+0Ou3ChQvRt29fREZGokePHnj++edx/vx5q7Grsn//fsycORNxcXGmPJ966ikcPHjQ7D0ICwvDxo0brcaw1nayqv3i+PHjCAsLw9NPP11pXkOGDEFkZCRyc3PNhiclJeEvf/kLunfvjsjISAwYMABvv/221fbIp06dwnPPPWd6T2NjYzFy5EgsWbIEGo2mJm+TzeLj49GvXz8AwKZNm0z7u3H/BAxnrj/55BMMHz4cHTt2RJcuXTBhwgT88MMPFvEyMzNN81+4cAHPPPMM4uLiEB4ejpSUFDz33HMICwtDenq62XwLFixAWFgYpkyZYja8sLAQ7du3x+OPP24aVlBQgNWrV2Py5Mno06eP6b2aOXMmjh07ZnU9jdv8+vXreOmll9C7d2+0a9fObB9JTEzEqFGjEBUVhbi4OLzwwgumwtgWFdvATp482fR+Gj9TACA3NxfvvfcehgwZgqioKHTt2hVTpkzB/v37LWLWZF2N+z0AHD582Ozzy5hTde104+PjER8fbzXuxo0bsW/fPkyaNAldu3Y1+1zUarX473//i3HjxqFLly7o2LEjRowYgXXr1ln9nKpMRkYGXnnlFQwcOBBRUVGIiYnB8OHD8eqrryInJ8di+m3btmHSpEmIjo5Ghw4dMGTIEKxatarSzy1r7Mk9LS0NzzzzDHr37o3IyEj06tULTz75pOmYWLFiBfr37w/A/Lgq/7lU1bZIT0/H/PnzzeLPnz/f4rgxLsu4j23fvh1jxoxBx44dERMTg2effbZG+zA1LDwzTXYTQgAAJEkCAJw8eRJPPvkk8vLy0KtXLwwaNAg5OTn4+eefMWHCBPzrX/9C3759LeKsWbMGBw4cQL9+/dC9e3cUFBQgJiYGs2fPxqZNm3D58mVTM5PyvvrqK7z22mtwdXXF4MGD4e/vj8OHD+Pf//439uzZg/Xr18PLy8tivrlz5+K3335Dnz59MGDAAPj7+5uNf+655/Drr7+ib9++UCgU2LFjB1555RUoFAqcPn0a33//PR588EHExsZi9+7d+Ne//gUXFxfMmDHDLM5rr72GNm3aoFu3bggICEBubi727t2L+fPnm4oba55//nmkpqaid+/e6Nu3L/bt24fVq1fj1q1beOutt8ym/eWXXzBv3jyo1Wr07t0bw4YNQ35+Pk6fPo3Vq1djwoQJpmn37duHOXPmQKvVol+/fmjevDmys7Oxc+dO/PLLL1i7di3at29vNaeKli9fjn/9619wc3PDgAED0LhxY1y7dg3Hjh3Dli1b0KNHD5viVMXaftGpUye0atUKe/fuRU5OjsXPsmlpaTh//jwSEhLg4+NjGr5y5UqsWLECPj4+ePDBB+Hn54c///wTn376Kfbt24evv/4aHh4eAAyF9Lhx4yBJEuLj49G0aVMUFhbi0qVLWL9+PZ555hk4OTnd8fpVNHnyZFy+fBlr165FeHg4BgwYAABo164dAEMhPW3aNBw+fBitW7fGhAkTUFpaih07duDZZ581fQGo6NKlSxg3bhxatmyJ4cOHo7S0FB4eHoiLi0NiYiKSk5PRsmVL0/TJyckAgGPHjkGlUpnaax85cgRarRZxcXGmac+dO4cPPvgA0dHRePDBB+Hl5YWrV69i9+7dSEpKwocffog+ffpY5JSbm4tHH30Ubm5uGDRoECRJMh2Hn332Gd566y14eXlhxIgR8PT0xP79+/HYY4+ZtlF1QkJCMHv2bBw+fBiHDx/GyJEjERISYhoHAJcvX8akSZNw+fJlREdHo3fv3igpKcGePXswffp0LF68GOPGjbNrXdu1a4fZs2dj5cqVCAkJwciRI01xYmJibFqHquzYsQNJSUno06cPxo8fjytXrgAANBoNZs6cif3796NVq1Z46KGH4OzsjJSUFLzxxhv49ddf8e6771Yb/9q1axgzZgwKCwvRp08fDBo0CCqVCpmZmdiyZQsmTpxoduy9+OKL2LhxI4KDgzFo0CB4eXnh+PHjWLZsGZKTk7FmzRooFFWXG/bk/s033+C1116DTCZDfHw8WrZsiZs3b+LEiRNYv349hg4dipiYGEyePNniuAJuH1uVSUtLw9SpU1FUVIT4+Hi0adMG58+fx5YtW7Br1y6sWbMGUVFRFvN9+eWX2L17N+Lj49GtWzekpaXhhx9+wKlTp7B582YolcpqtwE1MIKoCqGhoSI0NNRi+IEDB0RYWJgICwsTmZmZQqPRiAEDBojIyEiRkpJiNm1WVpbo1auX6Nmzp1CpVKbhy5cvF6GhoaJjx47i5MmTVpc/ceJEq8vPzMwU7du3F507dxZnz541G7do0SIRGhoqXn75ZauxHnroIXHz5s1KlzVq1CiRl5dnGn7p0iXRvn17ER0dLfr16yeysrJM4/Ly8kRMTIzo3r270Gg0ZvEuXrxosQyVSiUmT54sIiIizOKUX/7IkSNFTk6OaXhRUZEYMGCACA8PF9euXTMNv3nzpujSpYto3769xXsuhBBXr141Pc/NzRXR0dEiJiZGnDlzxmy606dPi06dOokRI0ZYxLAmKSlJhIaGivj4eIt1qLjcDRs2iNDQULFhwwarsUJDQ8XEiRPNhlW3X3z00UciNDRUfPHFFxbjXnvtNREaGip27dplGpacnCxCQ0PFo48+arZdy+e3ZMkS07C33npLhIaGip9++skifm5urtDpdFbXpTZkZGSI0NBQsWDBAotxxvWePn262b5248YN0a9fPxEaGipSU1MtYoWGhor33nvPIt6lS5dEaGiomDNnjmnYuXPnRGhoqJg6daoIDQ0VBw8eNI1bsmSJCA0NFUeOHDENy8/Pt3osXb16VfTs2VMMHjzYYpwxpxdeeMHimMnIyBDt27cX3bp1ExkZGabhOp1OzJ49u9LPo8oY96VDhw5ZjJs4caIICwsT27ZtMxuel5cnHn74YdGhQwdx/fr1O17Xivu30aFDh0RoaKhYvny51fH9+vUT/fr1Mxtm3F/DwsLE3r17K13fxYsXC61Waxqu1WrFiy++WOl+XdHatWtFaGio+OyzzyzGFRUViZKSEoucnn76abPh5fOpGKeq497W3M+cOSMiIiJEt27dxJ9//mmRZ/nPoaqOKyGsbwu9Xi8GDx4sQkNDxebNm82mT0xMFKGhoSIhIcHs88C4Dp07dxanTp0ym+e5554ToaGhIjEx0WoO1LCxmQfZZMWKFVixYgX+7//+D3PnzsX06dMhhMCUKVMQEhKCX375BZcuXcLEiRMtzrwEBQVh+vTpuH79uumsV3njxo1DREREjfLZsmULNBoNJk6caNHW7dlnn4W7uzs2b95s9SfGefPmwc/Pr9LYf/vb38zOaDdr1gxdunRBfn4+Zs2aZdZ+28vLC/Hx8cjJybH4Ca958+YWsZVKJR5//HFotVqr74Vx+eXPqrq5uWH48OHQ6/U4ceKEafj333+PwsJCjB8/3urZruDgYLNp8/PzMXfuXLRp08ZsutDQUIwdOxa///47zp49W9nbYrJu3ToAhl4nrLVlL7/cO1HZfvHII49AJpNh06ZNZsPVajV++OEH+Pv7m50NNfak8MYbb1j8UjFq1Ci0a9cOW7dutViOi4uLxTBvb2/IZI752NywYQMkScLChQvNzvL5+/vjqaeeAgB8++23FvM1atTI6i87zZo1Q0hICFJSUky/Mhn3yblz50Iul5vto8nJyXBzc0PHjh1Nwzw9Pa0eS8HBwRg8eDDOnz9vOmtanpOTExYsWGBxtnLr1q2m47r8dRgymQzz58+vtff+1KlTOHz4MAYNGoRhw4aZjfPy8sKcOXOgUqmwY8cO03B717Uu9O/f3+KMv16vx7p16xAQEIAXX3wRcrncNE4ul2PhwoWQJMnqvl4Za8eAm5ub2fC1a9dCoVDgH//4h8X0s2bNgo+PT7XLtCf39evXQ6vVYtasWWjbtq1FzDv9HPrf//6H8+fPo3Pnznj44YfNxg0dOhRdu3bFhQsXkJqaajGvtS4cx44dCwBmzSLp3sFmHmSTlStXAjA06fDy8kLXrl0xZswYPPLIIwCA48ePAwCuXLlSabszwPBTacWmHtZ+JqvO77//DgCIjY21GOft7Y2IiAgcOXIE58+fR3h4eI2WFxkZaTHMeAFTVeOysrJMPyEDhvfi3//+N5KTk3H16lWUlpaazVdZ+zlry2jcuDEAIC8vzzTM+J5b+xm9IuO0p06dqnb7VCy2rcWSJAm9e/eudrl3orLtFBwcjLi4OBw4cABnz5415btnzx7k5ubiiSeeMCvSjh8/DicnJ2zfvh3bt2+3iKfRaHDr1i1Ts5GhQ4di7dq1ePrpp5GQkIAePXqgS5cuVr8cVcbaezxy5Ei7L9QtLCzExYsXERQUZPVCKeNx8Mcff1iMCw8Pr/Rn5djYWGzYsAF//PEHIiIikJKSgoCAAHTq1Ant27c3FdO3bt3CmTNn0LNnT4smLqmpqVi7di2OHz+OmzdvWrQpz87ORpMmTcyGhYSEWDSvAm4f1926dbMY16xZMzRu3BiXL1+2ui41YWzjXFhYaHVb3bp1CwAsriWwZ13rgrVj48KFC8jNzUXLli3x4YcfWp3PxcXFpusj4uPj8f7772Px4sXYv38/evXqhS5duqBNmzamZn0AUFJSglOnTsHX1xeff/651VhKpRLnzp2rcnn25G78TKurzyHjvti9e3er42NjY5Gamorff//dYn/t0KGDxfTWPsPp3sFimmxy+vTpKscbL/ayVqyUV1xcbDGsUaNGNc7HeIFhQECA1fHG4dYuLqtsHiNPT0+LYcbizFqbTeM4rVZrGpaRkYExY8YgPz8f0dHR6NWrFzw8PCCXy3H58mVs2rSp0gtzrLXzNp6pKX8RjvE9sKWnE+P2KX8hpTXWtk9FBQUF8Pb2tnrWqjZVtV+MHDkSBw4cwKZNm/DCCy8AgOlMdfn2qYBh3bVarekLYWWKi4vh6+uLqKgo/Pe//8VHH32EHTt2mC4YbdWqFWbPno2HHnqo2tytLSsmJuaOimmg8n3X+IXO2v5e1fsYFxeHDRs2IDk52XRhovHLWVxcHFavXo2CggIcOnQIQgiz9tIA8NNPP2Hu3LlwdnZGjx490Lx5c7i6ukImk5naK1vbzytbD+M+XVnOjRo1qpVi2ng8HDhwAAcOHKh0uvLHg73rWhesvT/GdUpPT69yXy8qKqo2fkhICL777jusWLECSUlJ2LlzJwBDQfjkk09i8uTJAAz7mxACt27dqvb4qoo9udfk888exviV9QRj3IetXexu7W+Itc9wunewmKZaYfzwWLVqlenKaVuVP9NR0+XduHHD6k98169fN5vuTpdXU2vWrEFubi7eeustjBo1ymzctm3bLJoo2MO4btnZ2dXeFc447ebNmy3O1Nuz3NzcXJSWllZbUBt/ltfpdBbjqrurX1XbaeDAgfDw8MCWLVvw3HPPITc3F0lJSQgPD7dYPw8PDwghcPjw4SqXV17nzp3x8ccfQ61W48SJE0hKSsK6devw/PPPw8/Pr9oLLKv78llTxi9xN27csDre2IVkTfd34xntgwcPIjY2Frm5uaaCOTY2Fh9//DFSUlJMZ6gr/hK0bNkyODk5YcOGDRZnzF999dVK3/PKcqruuK5s/WvKuJyXXnrJVBhWx951rYzx2Cj/Jby8/Px8q1+sAevvn3GdBg4ceEeFrdEDDzyADz74AFqtFqdOncLBgwexbt06LFmyBK6urhg7dqxpv4yIiLijzzR7ci//+Wfrhan25GT8W1KRcXhdLJsaHraZplphbEd59OjRu7I841XY5bu5MsrPz8cff/wBZ2dnh/UdevHiRQDAoEGDLMbV9I9uZYx3gty3b1+10xq3j7X2ffYsVwiBpKSkaqc1FgNXr161GFe+/XdNubi4YMiQIbh27RoOHjyIrVu3QqvVWpyVNuabl5eHM2fO1Hg5SqUSXbp0wbx58/DSSy8BAHbt2mV33vby8PAw9b5irUsu43FQ02sPAgIC0KZNG6Smppq2p7GY7tKlC5RKJZKTk3Ho0CFT86nyLl68iDZt2lgcZ3q93q59zRj/yJEjFuMyMjKs7kf2sOfzyp51lclkVr9IArePjaysLKvLsnbGsyqtW7c29aJRm903KhQKREZGYsaMGXj//fcB3D4G3N3d0bZtW5w5c8aiK8qasCd34+efLZ9DxrPClW0La4x/Yyr7vDYec7b2gET3NhbTVCv69++P5s2b48svv8TevXutTnPs2DGUlJTUyvIefvhhODk5Yd26dabC1WjZsmUoLCzEww8/7LAuiIxtpyt+ECclJeG7776rlWWMGDECHh4e+Oqrr6wWH+X/SI8aNQpeXl5YuXKl1T6/9Xq91S8m1kycOBEAsHTpUqvtvssPi4yMhEwmw7Zt28y2fW5urk1ddFXFWDh///332Lx5MxQKhdW73D3xxBMAgFdeecVqvsXFxab2l4DhwqOK7dsB4ObNmwCsX5R1N4wePRpCCLzzzjtmRcGtW7ewatUq0zQ1FRsbi5KSEqxduxYtW7Y0te10cXFBp06d8OOPP+LSpUuIiYmxuAAwJCQE6enpZu+rEAIrVqyw6WLWioYPH246rjMzM03D9Xo93nnnnVr7ibxDhw6Ijo7GTz/9VOnxePr0adM2B+xbVx8fH6vFMmAoID08PLBr1y6z5ZSWluLNN9+s8TopFApMnDgR169fx5tvvml1H7527ZpN2+XEiRNWi3njLwPlj4EnnngCGo0Gf//7363+2pSXl4eTJ0/Weu6PPfYYFAoFVq1aZXWdyr/vXl5ekCSpRl/GunbtilatWiE1NdWi+eL27dtx9OhRtGzZEl27drU5Jt272MyDaoWTkxNWrFiB6dOnY8aMGejcuTPatWsHFxcXZGVl4bfffkNGRgb2798PV1fXO15e06ZN8eKLL2Lx4sUYOXIkhgwZAj8/Pxw5cgTHjh1D69at8be//a0W1sw+EyZMwMaNGzFv3jwkJCQgMDAQZ86cQVJSEoYMGWL1Jhs15efnh/feew9z58413UgiLCwMhYWFOH36tKkPXADw9fXF8uXL8fTTT2PcuHGIi4szXUyUlZWFY8eOITc316YrzXv16oWnnnoKH374IYYMGWLqZ/rGjRtITU1Fp06dsHTpUgCG9obDhw/H5s2bMWLECPTt2xeFhYXYt28foqOjTRf52KNr165o0aIFduzYAY1Gg379+lm9qC0uLg7PP/883n//fSQkJKBPnz5o2rQpiouLceXKFRw5cgRdunTBf/7zHwDA6tWrcejQIURHR6Np06Zwc3PD2bNnsW/fPnh7e+PRRx+1O+c78eSTT2Lfvn3YtWsXHnnkEfTp0welpaXYvn07bt68ienTpyM6OrrGcePi4rBu3TrcvHkTAwcOtBhn/EJYsb00YCikFi1ahJEjR2LQoEFQKBT43//+h3PnzqFfv37Ys2dPjXJp2rQpnn/+eSxdutR0XBv7mS4oKEBYWFitNaF57733MGXKFLz00kv44osv0LFjR3h6eiIrKwt//vkn/vzzT3z99demfcqedTX25T1z5kxERERAoVCgW7du6NatG5ycnDB58mSsWrUKI0aMwMCBA6HVanHw4EEEBgbaddfGWbNm4dSpU/jqq6+wZ88exMbGIigoCDdv3sTFixfxv//9D88++2y1Fxlv3rwZX3/9Nbp27YpmzZrB29sbly5dwp49e6BUKs1u6DNmzBicPHkSX375JQYOHIhevXqhcePGyMvLQ2ZmJo4cOYJRo0Zh8eLFtZp7mzZtsGjRIixatAgjRoxA//790bJlS+Tk5ODEiRNwd3c39eTj7u6Ojh074ujRo3j++efRqlUrU9/UlTV7kyQJb7/9NqZOnYpnn30W27ZtQ+vWrXHhwgX8/PPPcHd3xzvvvOOw3n2ofmExTbUmPDwcmzdvxpo1a/DLL79g48aNkMlkCAgIQEREBObMmWNxk4078fjjj6NFixb49NNPsXPnTpSUlKBx48aYNm0aZs6cWWl7w7shPDwca9euxQcffIC9e/dCq9UiPDwcK1euhKenZ60U0wDw4IMPYsOGDaZeQw4cOAAvLy+0bt0af/3rX82mjYuLw5YtW/Dpp59i//79OHr0KJycnBAYGIjY2FgkJCTYvNxnnnkGnTt3xtq1a/HLL7+guLgY/v7+iIyMNPXwYvTmm2/C398fiYmJ+PLLL9G4cWNMmjQJ06ZNw48//nhH6z9ixAgsW7YMgOWFh+XNmDEDXbp0wRdffIHU1FTs3r0bHh4eCAoKwrhx48wuKpwwYQK8vb3x66+/IjU1FTqdDkFBQZgwYQKmTp1q1mPL3aRUKrFmzRqsWbMG27Ztw7p16yCXyxEeHo6///3vNl0YaY3xjLNer7doEx0XF2d6f631nDN+/HgolUp8/vnn+P777+Hs7Izo6Gi89dZb2LlzZ42LaQCYOnUqAgIC8J///AebNm2Cu7s7evXqhRdeeKFWvyAHBwdjw4YNWLduHXbu3ImtW7dCp9OhUaNGaNOmDSZOnIjQ0NA7WteXXnoJkiQhOTkZe/fuhV6vx+zZs029P8ydOxeurq745ptv8M0336BRo0YYOnQo5syZY9Flny2cnJywatUqbN68GZs2bTIdm76+vmjatCnmzZtn9debih566CGo1WocO3YMJ0+eRGlpKYKCgjBs2DBMnTrV7H0BgEWLFqFPnz746quvcPDgQdNFysbP44pdy9VW7uPGjUPbtm3x6aef4vDhw9i1axd8fHwQFhZm6orO6J133sFbb72F/fv3IzExEUIIBAcHV3kNSceOHfHdd9/hww8/RHJyMvbs2QNfX18MGzYMs2bNQuvWratdL7o/SMLYwSgREREREdUIf58gIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7sZgmIiIiIrITi2kiIiIiIjuxmCYiIiIishOLaSIiIiIiO7GYJiIiIiKyE4tpIiIiIiI7KRydwJ24fr3AIcv18HBGYaGKMRmTMRmTMRmTMRmTMe+TmAEBnlaH88y0HRQKOWMyJmMyJmMyJmMyJmMyZt0W0/v27UNCQgIGDhyITz75xGL85cuXMWXKFAwfPhyTJk1CVlaWadymTZswaNAgDBo0CJs2barLNImIiIiI7FJnxbROp8PixYuxevVqJCYmYtu2bTh79qzZNG+//TZGjBiBrVu3YtasWXjvvfcAALm5uVi5ciW++eYbfPvtt1i5ciXy8vLqKlUiIiIiIrvUWTGdlpaGFi1aoFmzZlAqlRg2bBh27dplNs25c+cQGxsLAIiNjTWN379/P3r27AkfHx94e3ujZ8+eSEpKqqtUiYiIiIjsUmfFdHZ2NoKDg02vg4KCkJ2dbTZNeHg4du7cCQD46aefUFRUhJycHJvmJSIiIiJyNIf25jF//ny88cYb2LRpE0lw8JYAACAASURBVKKjoxEUFAS53PZG4B4eznXSEL06crkMPj5ujMmYjMmYjMmYjMmYjHmfx6yzYjooKMjsgsLs7GwEBQVZTLNy5UoAQFFREXbu3AkvLy8EBQXh8OHDZvPGxMRYLKO2u0axlY+PG3JzixmTMRmTMRmTMRmTMRnzPol517vG69ChA9LT05GRkQG1Wo3ExETEx8ebTXPr1i3o9XoAwCeffILRo0cDAHr16oX9+/cjLy8PeXl52L9/P3r16lVXqRIRERER2aXOzkwrFAq8+uqrmD59OnQ6HUaPHo22bdti2bJliIyMRP/+/XH48GG8//77kCQJ0dHRWLRoEQDAx8cHs2bNwpgxYwAATz/9NHx8fOoqVSIiIiIiu9Rpm+m+ffuib9++ZsPmzZtnej548GAMHjzY6rxjxowxFdNERERERPUR74BIRERERGQnFtNERERERHZiMU1EREREZCeH9jNNRLVDlpcOt+OfwPn0RkiaIvg7uUMVNgrFnWZA793S0emZ1EWe93NMIiJyPEkIIRydhL2uXy9wyHLvtX4TGbNyFQsgUQsF0NWsFGxMexfb1ZkoliS4CYHByqYYFfUCGgd3r3E85cXdyPvpKXzu4YxEDzdTzGGFxZhSqIL3wA+hbhFffaAKanvd6yJP5cXd8Nr+V0CngSS0puFCUgByJ+QP/tiumA0hT6Bh7J/3e8y0s/uwMe1dHFVeR4kkwVUIRKsDMCrqBUS16cOYjMmYDopZ3p32M81i2g71ufi7n2M2hCI19cRHeCX9c2glQCtJpuEKIaAQwBstp6Br5Eyb48ny0nHy+2F43t8TWkmyElPgvZsFaD8isUbFlfLibuT89BTWujvjB8/b6z60oBiTi1TwreG610Wesrx0+H01EJnQ4HMvT2zzdDfl+VBBEabkF6ApnHBr/E81itkQ8gRqfxsBtb9/3u8xNyW9j3/nfVtpzL94j8XI3s8xJmMy5l2OWdGdFtPy11577bU7ysCBiovVDlmui4sTSks1jFmPYqae+AjP/P42/tAXQCWTAZIEjSThjC4f265sR7haiyaB0TbHk+Wl47ftEzEt0BsnnZVmMU8rnfCdhws6/vE9gloNg3CxrQ/0q1kpeOb3t1Eqk6Av94EAAPqywi0p5zjivTrA06OpTTFzkl/HXxXXUCqTVRpzt6sSQ69fgkvzBwGhN/yHAIQAIAEV5pPlpePXHx/HXwJ98LuL+br/6eyEDR4u6PD7JgS3HALh5AHoNYBODUlXCklTAklTDElTCJm6AJIqH5IqFzmHlmCGc261eQ67ehrujSIhleYa5lUXQFIXQtIUQtIUQdIUAdoSSNpSuKe8h+TCM5gWHICTLs7m28hZiW89PRCuUqFlUS40wV0hqQsh0xQaYqoKIKnyIKlyIZXmQCrNgaw0x5CnS54NeZ6Cu38EZKpcyEpzymLl3Y6tLoSkKYCkKYJ7yj+RXHS2+jxLC6Fu1qdsu8DqtqnxNnLw/nk/x0w7uw/vXF5WZcy00pPoIAtFkF8LxmRMxrxLMa2xtQ5xd3e2Opxnpu1Qn8/O3o8xr2alYNrRZ1Aqs154AICLXuA/0R/YfIY6d8+zmFCUjFJZ5dfouuj1WK/sCL+YBYBOA+i1kPTWH6HX4IPfl+N7Wb7ZN+uK5EJgqFqOv3p2hF6nhl5XCqFTQ6dTQ69XG4bp1dDrNNDr1fhMqcFedzfoqok5oKgYM3PzDd/kYfg2rxACThBwEoBcAApJBpkkIUMGjA4JrnbdN17OQjOt1my4AKAv+6+TJOgA6CTgXV9fbPV0rzbPhwqK8FxOLuQAJAjIhOEqaQmArOy14TmQqVDYnGdTrdaUl4DhQ1iUz1cCBCT809en2jwVQuCRgkLMv5ULebkcjXlWlKFQYJSd7ydgyAuSzPC/rMC+JAmMCQmsNua3WTlopvDA7cJcuv1cup2xkCT8Q1mMTe7O1e6fw4pUmK12gR566IXhv04ChBCG15KAXgjoocdqVwX2uLtU+X7KhECPEhVGFqsgAOhg2D6GR0APybA/QYJeAn50VSDN2dniD2vFmO1VGvTVCNzeKhKEhNuvpdvD98p1+N1ZUW3MSLUO8XoXSGXvpVQ2vwRAkmTGdxeAhB0iH785y6uNGaXSYZjcH+UzNeYFGPZR4zISddeRZkPMSJUeA+X+0JfNbdjPhWmfN+73AgJ7dbk45SyDqCZmmEqgr7IxAAmysv1RggySJEGC3PQakoTdRafxu7OwYRvJ0c8nxpCT6ZiUyv4b1tswDki6lYzTztpqY4aqnBDn17Nsbct9Ny3HOOhwzkH86ayxIaYCMT6xt+csH1OUHyBwOP8I/rQhzzCVAjHePWA6JiGZ9iuzTxNJwuGcPTjlrK42ZrhKiW5+A1H2qWHa1oaTJzC9JwBwNGc3TtsQM0ylRFffBy3GWZvrSM4em97PMJUTon0fhNkbWUkpeiR3X7UxFUKgpyoAi0ZtqXSa6rCZhwPUx4KyocWstbaeei1W7RiN7/XXqiwCZEIgRqXBcI0SQNnZWSEgTGdpy/7U6A0l1xY3Z6S6VP0HWxICoWo1OqvUUEsSVJJkelRJEjQSTM/VkoQMhaLKP1j1gazs40APVHpWFAAgDEW4E0RZwSyVFaV3ef2EqDbPKsfXMpkQkMFQdMoAyAGoYNgXqsvTTS/go9fbtJxcmQzFssrPXBtjOgsBFyGgLyskTV8mYPhza/gSUfa6nu+bRESVcdfrsfWhQ3bPf6fFNHvzoLvO2sVYkqYQLie/hMupby0vxtKWQp6fAXn+Rcjz0iHPuwB53kXI8tJRVHQZ20KCoK3iDB1gKPIOuShxyAW4fR7xzghJwmlnZ5x2tv6zT0NkczFc9mVBY/X8xF1UXb53uUDUl32pqOqLnVWShGK5hGJ5LfZWavxiV3sRiYjqpWIHnwxgMU13lSwvHV7b/2q4GMu3kouxfpgGVevBkJXcNBTNhVcglf1Ale6kwHFnZ/zq4ozjHkqc82vs6FWqdZIQUOpcDL9tCkPhL8o9CtOjHMLlatlP19XFBLTqQEiSDoAOkPSApDO8lgyvDePunBBlP1WW5QwhQZKprP8uaDEzoNe5QZLK/SBdyXPDNPbkVa65Q4XXkqzE5jyFcAKgN+VU03waLCEgacuajpi2c/lHCaLsZ2qZc5ZNX9DkQkBTEAHzbSKr8GhYhtL3sE1fVpz0ArpbPSs0ljA8N0S7PUzvfwgaW2IKAedbncoCGRsj3P6J37gPSBAo8vndpjwVQsArP7RcfrfzNQ0rW16u51mbYzYqCIdk/HCQDD9BSGUnEco3UMn2SrN53VsUhBkaYUjGxjfCdKxK5X73OON+xeY8I4v9yt5HqayBByy2jwSBVLc8m2PGFnmZ5jV/cpsE4KBb1c3uyseMK/YxNZ2wjHR7+CG3HJtjdi/xNs0pyu1Hhnfj9pAjrkU2x4wpcS2XVcUsbw895Fpsc8weJe7VTgcAB2uQZ5wpZtXTJ7sW2hTTzcGNLFhM013ldvwTJClleD4g2Ky3hCJJwgYvD2z2dMf7126g99mtKJEk/OqsxK/enjju4oxfnZXIlcvtXrZcCMjzw8u1ySv3Xxg/sg3/Fd7HobehqJLrJRRdGw4IBYReYXgUCkA4Afrbz4WQI6bRJzjtlV9te9zIPB8kXX3RpnVqFPwldD6/VvlhoxAC8txOyM96rJpohj+E/qEvQy2v/oNJqZNBf/kNyCQ5ZJBBJt3+L5cZ/lzLJAkyGZCnWG1znm2cZ0GSDPMaHw0tGsoeYXhM1j8FyKu/CFnSKTHI9d+meADK2nmWNR2GVPYIbM98F1qf49XmqcjrhNGtF5rNa6AHJD2EqbAQWJf9Fwh59Re2SHonTGmyvCxHyfjvdo6m58Cnl2ZCI7NhG+llmNf2C8N2KWuTKUkSZJBBKmsjbxy34dAk/OaVW+26d8jzxaQ+683ey7KMb78XZcM+3TPOpphReb54atBys3W9vf7m8f/vx1G25Znvi4WjlphyMcsPZQVl2es3vn8EaV5VF0HGmG+OX2m2/xhjo8KwxZsexkHnG9XG7KkKwKLHPq90mvJe2zjc9pjj19RqzB6qACwa/1nt5znWtnauNYo5rg5i1kWeYxiztmJGqwNtildXWEzTXXXt7Pd4PsjX6oVTxuJ6TlAAWqvVuKBUVv+NVMjgqgNK5boq23zeLlKn2pRnI6G3rfjL64jWToOgkEmG//KyR5mswmsJJ86NhZPnf6CrYpUUAriUMxovxD8AuUyCXJIMj2UxKg5b8OMQOHufNJxdroReKFCaNxjfT+8GuSSVFbgS5GUFqrys4DWOm70jBuniULXr3kLeDR/P7GfT+7nop1IcUFWfZ1zg43h9YAebYk7Z3gsZuj1lZ4YrISQ0deqNBQPa2hTz5k+P44DqRLV5dgt4HNNibbty/JftvW3LU9EHk7t0tCnmrkwbt5GsG4aEtbIpZsof0/GH/p/QVrN/NvWZhk5NvW2K2dTbtpghPtPwQCPbzn7VJGYjD9uaYIV4T8PvtsT0ngYXJ9u+0I+KegGHTy2oNubIqL/ZFI8xGZMxaydmXWDXeHaor93D1feY8usnsTpjI05Wc2GfkCTcUli/sl5o3aAregCavK5Q3+iP0qyH4VPQApLPsSoLC6UAiq8+jiEdo9CtuQ96tPJD7wf8Ed+2ERLCAzE0IgiPdAjGmI5NML5LCC7d9MdV8QtElQWQAnHez2LFyO4YEdUYD3cIxkPtgzE0IgiD2wViUHggBoQFID40AA+2bYQL+Z7QX7qFYo90AObtkxVCQCmAZlf6IrLtSPy1Z0tEBHsiPMgTYYEeaBvggQcauaO1vzta+ruhhZ8bmvm64nqeHCcveUPhcQIy6M2afMgEIIQCpVcmY3ibbhgSEQQPZwXcnRVwU8rh6iSHi5MczgoZlHIZFHLDGeUmXm3x05Ut1a77wq7/QBMPv8qnKaetfwC+SZFB5vFbpXlqrk7BPwYmwMvFyaaYzT1aYOeVrZCkyi/aE8IJr3RZdM/l2cSz9rdRs4BWSDt8EUXu6QCs758tsh7EzITpNq/7/RwzyK8F3LLzkVZ6stKYf/Eeiwc7j7cpHmMyJmPWTkxr7rRrPBbTdqhvRWp9j6m49is8fvk7PA+8jgWBjQx94tpIpwqEtiAC6pweUF0bAvX1IdDmd4aupBWExheAHDp5EFqXqGwqUl/o3wbdmvuic1NvdGjshfAgT7QJcEdLPzeE+LgiyNMZfu5KRAQG1XpR1dLPDR8d9YRnXjOEyS6gQFkCLQB3IdAu3xf5VybgkqYnXh8aXqOYG49qUJIbBSHpIVNeN7SH1jtDndsNJVcehZOmRY1iNvHwg07VFGm5+ytd94ktXsHQB7rZFA8AvFycEOrXEjtTQ6CHDlK5PLV53aC79hiWJgxGZGMvm2M28fCDUIXg19wDAGDWZlkIGSAUmNzipXsyz7raRr6+XXAs1RVtJfP9MyLfF6XZj2PGoOk1Wvf7OSYAtGsRhw6yUBRd+RU3ZIWmmLHqQMxpv8iuAoAxGZMx7zxmRexn2gHqc5dz9Smm4upRuB/9AMpLv0ALYJ+bK+YFNrKthwUBFPz5KqB3Mxvs5+aEiGBPRAR7on2wJyKCPFGo1uKxz1PhL/sDrf034oxXjumixrb5vjh/cxRu6tth/ZSuaOrjavO6HrhwCwt/TILkvRdyr2OATA3oldDld4bI64ulQ3qjZyvbzvqZxdzyO7R6PbTlTlQqZIBCJsPShyPqRUwASM06h+XHP8MlzX7Tujd36oW5nZ5A1+AHahwPADJzS/Dfo5n48Y9rKNbo4OYkx5B2gXg8ummNtg3zrLuYdbHu93PM8hz9mcyYjMmYdxaT/UzXontt56jtmE6Xk+F25AMoLx/AdbkMGzw98J2nB7IVNWiir1NCn74E7YI8EBHshfbBHogI9kSQp7PpgqTy6qqgbCh/sFkEMCZjMiZjMiZj1m1M9jNNdUsIOGUmwe3IMjhdTcERF2d8HeCP3e5ulm2ZLW/1VWG8BHVeF+yb3QMyG/uO7NnKD+undK31grKpjysWDGiLBQPa1toB3FBiEhERUfVYTFO1Kt6t0L/83Qq9WkB5cTfcjn6AkuvH8a2HO74OaYwLSsv2uXqtOzT5kVD6pEKSLG+bbCSEAoqCvjYX0kYsKImIiOhuYzFNVVJe3I2cn57Cv9yd8UMTHxRLvnATAkOvbMOUP79FiEsQ/iy9jK+9PPFjsxCUWOvyrrglNDmx0BZEAkIBXWEEXJuuK7tpyO02GYaLseRQX5mIh9tG3M3VJCIiIrILi2mqlCwvHcf2zMYLwb4WN1jZ5OWO773cEaJRId3f8i6EQucMTV5naHJjoVcFo4WvK4b1DEKnEC/M3SBD0flnoPRLgpP37Qv7NHmdob7VG84iAI9HN73bq0tERERUYyymqVIZh97DC428Kr3BCgCkV2jOoSsNhiYnDpr8TvBSuuOh8AA81D4I7YM9TRcOLn04wnCx4PURUGWPMM2rkAHOZRcL1sZFc0RERER1jcU0VWpzzn5oPV2qnU4SAur8LlDnxEIqbY64Vv54qHcQerf2h1JhWYjX1cWCRERERHcbi2mq1I8eztXfzhuAqxAI0jyJh7oHISE8EP7uymrn4cWCREREdC9gMU1WqXQqFNnYm0aJJGHb5K51nBERERFR/WP7fZ3pvqAXeuy6tA1Tdwy27U6FAJyEvI6zIiIiIqqfeGaaTNJuHcdHv72LU0UXbJ5HIQRayLrVYVZERERE9ReLaUJmUQY+ObUK+7P3mg330ulQJJNDV8UJar1QYEbX2XWcIREREVH9xGL6PpavzscXZ9dg88UN0IrbdyRU6gXG5xfh+s3hON+yA05LqyCTtNCXK6plwlBIP97iZXQNfsAB2RMRERE5Hovp+5Bap8bmixvwxdnPUKgtMBs3pLAII24qsdX/TYyfOBDNfF2RmtUNy49/hkua/aYbrIQ49cLcTk+wkCYiIqL7Govpe8zlokx8e2E9fr6yAyXaErgqXDGgSQLGtnoMTdxCsC9rDz45vQpXi6+Yzde5tBQv3MzFZW0cbvRfiudCm5nGdQ1+AJ8PfgMA2I0dERERUTkspu8hKdeS8fqxl6DRa6Era7ZRrC1GYsZWbM9MRGO3JrhYmG42TzONBs/dykWPIh32tvobOgz6K5yd2DsHERERkS1YTN8jLhdl4vVjL6FUV2oxTie00AmYFdJeOh1m5uZjfH4BriuaI/PhjxDdPOouZkxERETU8LGYvkd8e2E91DpNtdNJQmBifgH+mpsPb70emU0fgfPQd+Hr5HYXsiQiIiK6t7CYvkfsvLwDeuiqnc5VCMy/lQuNzAW5/ZbAOeLRu5AdERER0b2JxfQ9olRn20WBJZIElU8oioZ8BJ1faB1nRURERHRvYzF9j5D0SgiZutrplEKG/HGJgJPrXciKiIiI6N4mc3QCVDs65rtCIUSV0yiEQLt8LxbSRERERLWExfQ94qX88zYV03/PP3+XMiIiIiK697GYvkeE6Yrx/rUbVgtqhRBw0evx/rUbCLOxbTURERERVY/F9D1Cr3BD75JSRJWqbg8UAh56PcbkF2Lj5Sz0LimFcHJ3XJJERERE95g6vQBx3759WLJkCfR6PcaOHYsZM2aYjb9y5QoWLFiAgoIC6HQ6/O1vf0Pfvn2RmZmJoUOHolWrVgCAjh07YvHixXWZaoOnDh8N3e/r8ZuLs2nYzowraKy73V2eXlJAHTbaEekRERER3ZPqrJjW6XRYvHgx1qxZg6CgIIwZMwbx8fFo06aNaZoPP/wQQ4YMwYQJE3D27FnMmDEDu3fvBgA0b94cmzdvrqv07jnFnWbg1/TN0EgSAKCNWm1WSAOAJHdCcae/OCI9IiIiontSnTXzSEtLQ4sWLdCsWTMolUoMGzYMu3btMptGkiQUFhYCAAoKChAYGFhX6dzz9N4t8XPb/qbXfYpLTM+FpIBQuCJ/8MfQe7d0QHZERERE96Y6OzOdnZ2N4OBg0+ugoCCkpaWZTTN79mxMmzYN69atQ0lJCdasWWMal5mZiREjRsDDwwPPPPMMoqOj6yrVe4IQAklF6abXfYpLISBBKD2gCh2F4k5/YSFNREREVMscetOWxMREjBw5Ek8++SSOHTuG+fPnY9u2bQgMDMSePXvg6+uLEydO4Omnn0ZiYiI8PDzM5vfwcIZCIb/recvlMvj4uNWrmH/m/IlczQ0AgKdOj44qFcS836DzCIECgFc9yZMxGZMxGZMxGZMxGfNeillnxXRQUBCysrJMr7OzsxEUFGQ2zXfffYfVq1cDADp37gyVSoWcnBz4+/tDqVQCACIjI9G8eXNcuHABHTp0MJu/sFAFR/DxcUNubu12MXenMX8+t9v0vGdJCUpcm8HFI6Te5cmYjMmYjMmYjMmYjNkQYwYEeFodXmdtpjt06ID09HRkZGRArVYjMTER8fHxZtM0btwYycnJAIBz585BpVLBz88Pt27dgq7s4rmMjAykp6ejWbNmdZXqPeFg9kHT894lpdA37+3AbIiIiIjuD3V2ZlqhUODVV1/F9OnTodPpMHr0aLRt2xbLli1DZGQk+vfvj4ULF+Lll1/GZ599BkmSsHTpUkiShCNHjmD58uVQKBSQyWR4/fXX4ePjU1epNnj56nz8kXcCACAJgZ7FJUDLPg7OioiIiOjeV6dtpvv27Yu+ffuaDZs3b57peZs2bfDVV19ZzJeQkICEhIS6TO2ecvRGCgT0AIBIlRr+ej1uhMQ5OCsiIiKiex/vgHgPOHStfBOPEuR7hUG4+jswIyIiIqL7A4vpBk4ndEi5dsj0uk9xKWQt2F6aiIiI6G5gMd3Anc79AwXaPACAv1aHdmo1dLz4kIiIiOiuYDHdwKVcTzY971VSAkAOTZPujkuIiIiI6D7CYrqBK99euk9xCQr9O0AoPaqYg4iIiIhqC4vpBuxm6Q2cyT8NAFAIgbiSUsjYJR4RERHRXcNiugE7fP32hYedS1XwFALaZr0cmBERERHR/YXFdANWsYmHVuYMTVAXB2ZEREREdH9hMd1AafQaHL1x2PS6d0kJSoOiAYWLA7MiIiIiur+wmG6gfrv1K0p0xQCAEI0WrTVagP1LExEREd1VLKYbqIpd4kkANE17Oi4hIiIiovsQi+kGKqVCe2m1whPagCgHZkRERER0/2Ex3QBdKb6MS0UXAQDOej26laqgDYkFZHIHZ0ZERER0f2Ex3QClXLvdxCOmVAVXIaBnl3hEREREdx2L6QaofHvp3sUlAAB1UxbTRERERHcbi+kGplRXiuM3U02v+5SUQOXsD51fqAOzIiIiIro/sZhuYI7fTIVarwYAPKBWI0SrMzTxkCQHZ0ZERER0/2Ex3cCUv+th7+JSAOAtxImIiIgchMV0AyKEML+FeImxvTT7lyYiIiJyBBbTDUh64QVcK80GAHjo9ehUqoLKvSn0Xs0dnBkRERHR/YnFdANS/kYtcSWlcAKgb85biBMRERE5CovpBsRal3i8hTgRERGR47CYbiAKNQX4LSfN9Lo320sTERERORyL6Qbi6I0j0AsdACBCpUIjnR4q3zAItwAHZ0ZERER0/2Ix3UCUby/dp6xLPF0znpUmIiIiciQW0w2AXuhxuFx76T6m9tLsX5qIiIjIkVhMNwB/5p1CjjoHAOCr06G9Wg0BGTRNujs4MyIiIqL7G4vpBqBiLx4yAJrAKAhnb8clRUREREQsphsCs1uIl5TdQpxNPIiIiIgcjsV0PXdLdQun8/4AAMiEQBy7xCMiIiKqN1hM13NHrh8yPe+kUsFbL6CXKaFpHO3ArIiIiIgIYDFd7x0y6xLPcFZa2zgaULg6KiUiIiIiKsNiuh7T6rU4euOw6XXvsv6leQtxIiIiovqBxXQ9djLnNxRpCwEAAVo92mo0AAA1Lz4kIiIiqhdYTNdjKddvN/F4sLgIEgCdkwe0gR0dlxQRERERmbCYrscOWbmFuLZJd0CmcFRKRERERFQOi+l6KqvkKtILLwAAFHogptTYXppNPIiIiIjqCxbT9VTKtdt3PexcqoGbEADYvzQRERFRfcJiup4qfwvx/iUFAACdix90/uGOSomIiIiIKmAxXQ+pdCocu3HU9LpP+S7xJG4yIiIiovqClVk99OutY1DpVQCAQLUMzbRaAIAmhE08iIiIiOqTOi2m9+3bh4SEBAwcOBCffPKJxfgrV65g0qRJGDFiBIYPH469e/eaxn388ccYOHAgEhISkJSUVJdp1jvle/GILy40PWd7aSIiIqL6pc76WNPpdFi8eDHWrFmDoKAgjBkzBvHx8WjTpo1pmg8//BBDhgzBhAkTcPbsWcyYMQO7d+/G2bNnkZiYiMTERGRnZ2Pq1KnYsWMH5HJ5XaVbbwghzPqXNrWX9giB3rulg7IiIiIiImvq7Mx0WloaWrRogWbNmkGpVGLYsGHYtWuX2TSSJKGw0HDmtaCgAIGBgQCAXbt2YdiwYVAqlWjWrBlatGiBtLS0ukq1XskouoirxVcAAAq9DF1LDc09DO2lJUemRkREREQV1NmZ6ezsbAQHB5teBwUFWRTEs2fPxrRp07Bu3TqUlJRgzZo1pnk7duxoNm92dnZdpVqvlG/i0aFYglPZczbxICIiIqp/HHorvcTERIwcORJPPvkkjh07hvnz52Pbtm02z+/h4QyF4u43/ZDLZfDxcauTmKk5h03DhpXcND13jRgAV8+aLbMu82RMxmRMxmRMxmRMxmTMOiymg4KCkJWVZXqdnZ2NoKAgs2m+++47rF69GgDQuXNnqFQq5OTk2DQvABQWquoo+6r5+LghN7e41mNevn4d/7uWahrWr6QIAKD1bYNcnTdQw2XWVZ6MyZiMyZiMyZiMyZj3W8yAAE+rw+uszXSHDh2Qnp6OjIwMqNVqJCYmIj4+3myaxo0bjwxUQgAAIABJREFUIznZcHOSc+fOQaVSwc/PD/Hx8UhMTIRarUZGRgbS09MRFRVVV6nWG6k3j0AndACARqWuCNQZnmvYxIOIiIioXqqzM9MKhQKvvvoqpk+fDp1Oh9GjR6Nt27ZYtmwZIiMj0b9/fyxcuBAvv/wyPvvsM0iShKVLl0KSJLRt2xZDhgzB0KFDIZfL8eqrr94XPXmklGsv3aPk9ll3ddNejkiHiIiIiKpRp22m+/bti759+5oNmzdvnul5mzZt8NVXX1md96mnnsJTTz1Vl+nVK4Yu8W7fQnx08VXDcEjQNIl1VFpEREREVAXeAbGeOJVzCrdUhgsOFVolOqoNZ6a1AR0gXHwdmRoRERERVYLFdD1x4Mp+0/M2xS4wNmphe2kiIiKi+suhXePd7y4XZeLbC+vx85UdKNbevoq0uzrX9Jz9SxMRERHVXyymHSTlWjJeP/YSNHotdEJ7e4QAvvYDumtd0Eulg6ZxjOOSJCIiIqIqsZmHA1wuysTrx15Cqa7UvJAGAAkolcnwXGAjnA+OApxqt2NyIiIiIqo9LKYd4NsL66HRa6ucRitJ+MLL6y5lRERERET2YDHtAD9f2WF5RroCrSRhu+byXcqIiIiIiOzBYtoBSrQlNk1XrHfM7dKJiIiIyDYsph3AVeFq43RsL01ERERUn7GYdoABTRIgl6ruSEUBCQObDL5LGRERERGRPVhMO8DYVo/BSVZNMS1zwphWj96ljIiIiIjIHiymHSDEvSnGNlkIoXeCEJLZOIUQcNELjG3yIkLcmzooQyIiIiKyBYtpB8jMLcGnu91QdP4Z6EqbmIY76QXG5Bdi7qUAfLrHHZm5tl2oSERERESOwWLaAf57NBNavR5C4w99SXPT8OdycvHSrRycVneEVq/Hf49mOjBLIiIiIqoOi2kH+PGPa9DqgeZSNqKcTpiGe+t0AIB0fRC0esN0RERERFR/sZh2gGK1Dg/KjmO7ciF85NdNw330eggBfKJ8Hw/KjqNYrXNglkRERERUHRbTDhCmvI5VTsvgJqmQL799AaK3Xg9JAtwkNVY5LUOY8noVUYiIiIjI0VhMO8ArvruggOF24nkyuWm4j05veq6AFi/77r7ruRERERGR7VhMO0Bs0S4oJUMTjjz57U3grb/drEMp6RBXvOuu50ZEREREtmMx7QAybTEAQAOgUGbYBDIh4KkX5tNpiu52akRERERUAyymHUA4uQPA/7N359FR1ff/x193ZjJZyQxQHakEtBUEFUXr1oKmhq0CxcqitKC0FNCKihVFaRWUFqyt+ivVuoA7XxWxiCxxBbQIbkW02FqsINGwhSV7Jtvce39/pNwkLJksMxkyeT7O8Zx7Z+59z3syE3j54XPvR0Wu2h9/umUd9mHY3rRW7AoAAABNRZiOgcpTR8o2PCquM8Wj7nxpSbINjyp7jmzt1gAAANAEhOkYCPadIrkTVFjn4sN0q36YljtBwb6TW7kzAAAANAVhOgYs30kq/tFjKkxIch7z/y9M24ZHtidZxT96TJbvpBh1CAAAgMYgTMdIVfcs7fr+rc6+3zRlubyqOH2c8se+paruWTHsDgAAAI3hiXUD7Vmhx+tsp1uW8i+8Q/bZE2PYEQAAAJqCkekYKqgsdLb9lqWE1E4x7AYAAABNRZiOoQPldcK0aclOJkwDAAC0JYTpGMqvMzLtsyzZSR1j2A0AAACaijAdQ0WVRc62zzRlJRKmAQAA2hLCdAyVhEqcbb9lyWJkGgAAoE0hTMdQMFQ7Mp1quaWElBh2AwAAgKYiTMdQhVXqbCe5UiXDiGE3AAAAaCrCdIxUmpUyVS1JSrBtuRN8Me4IAAAATUWYjpHiqroXH1pcfAgAANAGEaZjpLi6Nkz7LZN7TAMAALRBhOkYKa4udrZ9piUjhTANAADQ1hCmY6TeNA/Lkietcwy7AQAAQHMQpmOk7si037KU2OFbMewGAAAAzUGYjpH8ijpLiZuWvIRpAACANocwHSP7y+uEacuUuAARAACgzfFEs/i6des0d+5cWZalMWPGaMqUKfWenzdvnj788ENJUkVFhQ4cOKCNGzdKknr37q2ePXtKkrp06aJHH300mq22uroj037TkpK5NR4AAEBbE7UwbZqm5syZo6eeekqBQECjR49WVlaWTjnlFOeY3/zmN872okWL9Pnnnzv7SUlJWr58ebTai7nCqrq3xrNkE6YBAADanKhN89i8ebO6d++ujIwMeb1eDRs2TGvWrDnq8dnZ2Ro+fHi02jnmlFTVXoCYbllM8wAAAGiDojYynZeXpxNOOMHZDwQC2rx58xGP3blzp3bs2KELL7zQeayyslIjR46Ux+PRlClTNHDgwMPOS0tLlMfjjnzzYbjdLvn9KS2qEbTq3M3DtORO6yy/IvteItEnNalJTWpSk5rUpCY1jy6qc6YbKzs7W0OGDJHbXRsm3377bQUCAeXm5mrChAnq2bOnunXrVu+80tLK1m5VkuT3p6iwMNiiGsFQbZhOVqJMuVtc81CR6JOa1KQmNalJTWpSk5rSccd1OOLjUZvmEQgEtGfPHmc/Ly9PgUDgiMe++uqrGjZs2GHnS1JGRobOP//8evOp2zrbtlVplzn7ia70GHYDAACA5opamO7Tp49ycnKUm5urqqoqZWdnKysr67Djtm3bpuLiYp199tnOY0VFRaqqqpIk5efna9OmTfUuXGzrykJlsmVJkpItS4bXH+OOAAAA0BxRm+bh8Xg0a9YsTZo0SaZpatSoUerRo4fmz5+vM844QwMGDJBUMyo9dOhQGYbhnLtt2zbNnj1bhmHItm1Nnjw5rsJ0ySGrH5qJhGkAAIC2KKpzpjMzM5WZmVnvsWnTptXbv+GGGw4775xzztHKlSuj2VpMFde5LZ7P5E4eAAAAbRUrIMZAcXWdMG1ZcqVwj2kAAIC2iDAdA8V1p3mYphJSGZkGAABoiwjTMZBfXruUuM+y5E7pHMNuAAAA0FyE6RjYW17gbPtYShwAAKDNIkzHQH5F7Zxpv2nJSiJMAwAAtEWE6RgoqKw/zcMmTAMAALRJhOkYKK6qfwEiI9MAAABtE2E6BkpC9W+Nx8g0AABA20SYjoHyUImznW4ashPSYtgNAAAAmqtRYXrjxo1aunSpJCk/P1+5ublRbSreVdq1YTrRnSrVWUodAAAAbUfYMP3QQw/p8ccf14IFCyRJ1dXVuvXWW6PeWLwyrZCqVS5JMmxbSe70GHcEAACA5gobpt966y098sgjSk5OliQFAgGVlZVFvbF4VRoqdbY7WJaUyHxpAACAtipsmE5ISJBhGDL+NxUhGAxGval4VlR16MWHLCUOAADQVnnCHXDppZdq1qxZKi4u1pIlS7R06VJdccUVrdFbXCqurntbPEtGSkfZMewHAAAAzRc2TP/yl7/Uhg0blJqaqu3bt+vGG29Uv379WqO3uFRS5x7TPsuSJ7WzqmLYDwAAAJovbJjOzc3Vueee6wToiooK7dixQ127do16c/HoQEWBs+2zLLlTmOYBAADQVoWdMz1t2jRnvrQkuVwuTZs2LapNxbO8YG2Y9puW7GQuQAQAAGirwoZp0zTl9Xqdfa/Xq+rq6qg2Fc8OlBc62z6LpcQBAADasrBhulOnTlqzZo2zv3r1anXsSABsroLKOnfzMFlKHAAAoC0LO2f67rvv1i233KLf/e53sm1bXbp00b333tsavcWlwqrakWm/ZclK9MewGwAAALRE2DDdrVs3LVmyxFmoJTU1NepNxbPS6tqlxP0WI9MAAABtWdgwXVVVpTfeeEM7d+5UKBRyHr/++uuj2li8KgvVuTWeaTFnGgAAoA0LG6Z/9atfqUOHDjr99NPrXYiI5qm0asN0irySm58pAABAWxU2TOfl5emJJ55ojV7ahSq7TPrfnQZT3B1i2wwAAABaJOzdPM4++2x98cUXrdFL3Ksyq2QaNesdemxb3gRfjDsCAABAS4Qdmf7444+1bNkynXjiifWmeaxcuTKqjcWj4ura2+KlW5aUxOqHAAAAbVnYML1w4cLW6KNdKKmunS/tNy0ZLCUOAADQpoUN0yeeeKIk6cCBA6qsrIx6Q/GsqO6CLZYpF2EaAACgTQsbptesWaN7771Xe/fuVadOnbRr1y5997vfVXZ2dmv0F1f2V9RZSty05E7htngAAABtWdgLEOfPn68XX3xRJ510ktauXaunn35aZ511Vmv0Fnf2BgucbRZsAQAAaPvChmmPx6OOHTvKsixZlqULL7xQ//rXv1qjt7izv7w2TPssFmwBAABo68JO80hPT1dZWZnOO+883XLLLerUqZNSUlJao7e4c6DONA+/ycg0AABAWxd2ZPrhhx9WUlKSZs6cqYsuukjdunXTI4880hq9xZ3CyjpLiVsmI9MAAABtXNiR6YOj0KWlpbrkkkui3lA8K6lzn2mfyTQPAACAti5smF68eLEefPBBJSYmyjAM2bYtwzC0Zs2a1ugvrpSFSpxtLkAEAABo+8KG6SeffFIrV65Up07cE7mlys3aaR7ppmR702PYDQAAAFoq7JzpjIwMJScnt0Yvca/Krh2ZTnanSoYRw24AAADQUmFHpqdPn66xY8fqrLPOktfrdR6/4447otpYvLFtW9Uqc/ZT3R1kx7AfAAAAtFzYMD1r1ixdeOGF6tmzp1yusAPZOIoKs1y2YUmSkixLCUnHqSrGPQEAAKBlwobpUCikmTNntkYvca2ozp080i1LRjIXHwIAALR1YYeaL774Yr344ovau3evCgsLnf8aY926dRoyZIgGDRqkBQsWHPb8vHnzdNlll+myyy7TkCFDdO655zrPLVu2TIMHD9bgwYO1bNmyJrylY1NRZW2Y9puWXCmdY9gNAAAAIiHsyPSqVaskSY899pjzWGNujWeapubMmaOnnnpKgUBAo0ePVlZWlk455RTnmN/85jfO9qJFi/T5559LkgoLC/XQQw9p6dKlMgxDI0eOVFZWlnw+X9Pe3THkQHmdMG1ZcjEyDQAA0OY1GKYty9Itt9yioUOHNrnw5s2b1b17d2VkZEiShg0bpjVr1tQL03VlZ2frhhtukCStX79e/fr1k9/vlyT169dP7777roYPH97kPo4Ve4L5zrbPsmQlc6tBAACAtq7BaR4ul0uPP/54swrn5eXphBNOcPYDgYDy8vKOeOzOnTu1Y8cOXXjhhU0+t63YFyxwtn2mKTvJH8NuAAAAEAlhp3n84Ac/0BNPPKGhQ4fWu9/0wVHjSMjOztaQIUPkdrubdF5aWqI8nqadEwlut0t+f0qTzimxSp1tv2UpufMJSqpTozk1w6EmNalJTWpSk5rUpGZ0a4YN06+++qok6bnnnnMea8yc6UAgoD179jj7eXl5CgQCR32NWbNm1Tv3o48+qnfu+eeff9h5paWV4dqPCr8/RYWFwSadk1dywNn2mZZKzRRV16nRnJrhUJOa1KQmNalJTWpSMzI1jzuuwxEfDxum165d2/SuJPXp00c5OTnKzc1VIBBQdna27r///sOO27Ztm4qLi3X22Wc7j/Xv318PPPCAiopqLtpbv369br755mb1caworqq9ANFnWbISmeYBAADQ1oUN09XV1XrhhRe0ceNGSdL555+vK6+8UgkJCQ0X9ng0a9YsTZo0SaZpatSoUerRo4fmz5+vM844QwMGDJBUMyo9dOhQGXWW1vb7/bruuus0evRoSdLUqVMjOq0kFkpDtUuJ+y1LdhJ38wAAAGjrwobpu+66S6FQSD/96U8lSStWrNBdd92luXPnhi2emZmpzMzMeo9Nmzat3v7BO3gcavTo0U6YjgdBs9jZ9pmWLMI0AABAmxc2TH/22WdasWKFs//9739fI0aMiGpT8ajKKpb+N/ieZnskT1JsGwIAAECLhV0B0e1265tvvnH2c3Nzm3zXDUjVdu3dPFI9R57ADgAAgLYl7Mj0jBkzdPXVVysjI0O2bWvXrl2aN29ea/QWNyzbUsgod/ZTvX5Vx7AfAAAARMZRw/Rrr72mSy+9VF27dtWbb76pr776SpL0ne98R16vt9UajAel1aW1UzwsS56kToRpAACAOHDUaR4LFiyQJN14443yer3q1auXevXqRZBuhqKqQmfbZ1pypbCUOAAAQDw46si03+/XxIkTlZubq2uvvfaw5x999NGoNhZP9pfXhmm/ZcogTAMAAMSFo4bpxx57TJ9//rlmzJihiRMntmZPcWd3ab6zzW3xAAAA4sdRw7TX61WfPn103nnnHXEpbzTe3mCBs+2zLNmsfggAABAXGrw1ntvt1tatW1url7i1v6LONA9GpgEAAOJG2Fvj9erVS9dee61+9KMfKSUlxXl88ODBUW0snhRU1rkAkaXEAQAA4kbYMF1VVaWOHTvqww8/rPc4YbrxiqpqlxL3W6asJKZ5AAAAxIOwYfqee+5pjT7iWkl1bZj2mYxMAwAAxIuwy4lv375dEyZM0PDhwyVJW7Zs0cMPPxz1xuJJMFQnTFvMmQYAAIgXYcP0nXfeqenTp8vjqRnE7tWrl1599dWoNxZPKqwSZzvdtGUn+mLYDQAAACIlbJguLy/XmWeeWe8xt9sdtYbiUbVdG6bTXCmSEfbHDgAAgDYgbKrr2LGjvvnmGxmGIUl6/fXXddxxx0W9sXgSUpmznZqQHsNOAAAAEElhL0CcPXu27rzzTn311Ve66KKL1LVrV913332t0VtcqLaqZbmqJEku21ZaYkeVx7gnAAAAREbYMJ2RkaGnn35awWBQlmUpLS2tNfqKG8VV9S8+dKd0jmE3AAAAiKSwYbqgoEB//etf9fHHH8swDJ1zzjmaOnWqOnbkjhSNUW8pcdOSkdYpht0AAAAgksLOmb755pvVsWNH/eUvf9H8+fPVqVMn/frXv26N3uLC7tIDzja3xQMAAIgvYUem9+3bp6lTpzr71113nV577bWoNhVP8oK1S4n7WUocAAAgroQdme7Xr5+ys7NlWZYsy9Krr76q/v37t0ZvcWF/ed1pHiYj0wAAAHEk7Mj0kiVL9Mwzz2jGjBmSJNM0lZycrMWLF8swDG3atCnqTbZlBypqR6aZ5gEAABBfwobpTz75pDX6iFtFlbV38/CbTPMAAACIJyzFF2XF1UXONiPTAAAA8YUwHWVlodqlxH2WJTvRH8NuAAAAEEmE6SgrN2vDtJ8LEAEAAOLKUedMFxYWHu0pSZLfzwhrY1TbtXOmO9geKSE5ht0AAAAgko4apkeOHCnDMGTb9mHPGYahNWvWRLWxeBFSqbOd5mEpdgAAgHhy1DC9du3a1uwjbplG0Nnu4PXLjGEvAAAAiKywc6Zt29by5cv117/+VZK0a9cubd68OeqNxYPyULlsV018TrBtJSczXxoAACCehA3Td911lz799FOtWrVKkpSamqq777476o3Fgz1l+c623zTlSu4cw24AAAAQaWHD9ObNmzV79mwlJiZKknw+n6qrq6PeWDzYXVpnKXHuMQ0AABB3woZpj8cj0zRlGIYkKT8/Xy4Xd9RrjD3BOmHaJEwDAADEm7DLiV911VWaOnWqDhw4oP/3//6fXn/9dd10002t0Vubt69OmPazYAsAAEDcCRumR4wYodNPP10ffPCBbNvWww8/rO9+97ut0Vubl19Re69uP9M8AAAA4k6jFm3p3Lmzhg0bVu85Fm0Jr6Cy9meYblqyCdMAAABxpVGLtuzevVvp6emSpOLiYnXp0oX7UDdCcVWRs+23WEocAAAg3oRdtOWOO+7QoEGDlJmZKUn6+9//zuqHjVRaXeJs+xmZBgAAiDthb8vxz3/+0wnSkpSZmalPPvkkqk3Fi6BZG6a5NR4AAED8CXsB4vHHH6+HH35YI0aMkCStXLlSxx9/fNQbiwcVVonzvyvpli070RfbhgAAABBRYcP0/fffr4ceekjXX3+9JOncc8/V/fff36ji69at09y5c2VZlsaMGaMpU6Ycdsyrr76qhx56SIZhqFevXk7t3r17q2fPnpKkLl266NFHH230mzpWmCp2tjsYyZLLHcNuAAAAEGlhw7Tf79cdd9yh0tJSGYah1NTURhU2TVNz5szRU089pUAgoNGjRysrK0unnHKKc0xOTo4WLFigF154QT6fTwcOHHCeS0pK0vLly5vxlo4dllHmbHdI6BDDTgAAABANYcP0F198odtuu01FRTV3pujYsaP+8Ic/OKPGR7N582Z1795dGRkZkqRhw4ZpzZo19cL0kiVLNG7cOPl8NdMfOnfu3Ow3cqyxLEumq8LZ9yd1VDCG/QAAACDywobp2bNn6/bbb9eFF14oSfrwww81a9YsLV68uMHz8vLydMIJJzj7gUBAmzdvrndMTk6OJGns2LGyLEvXX3+9Lr74YklSZWWlRo4cKY/HoylTpmjgwIFNemOxtjdYJBm2JCnFsuRJ6hTjjgAAABBpYcN0MBh0grQkXXDBBQoGIzPGapqmvv76ay1atEh79uzR+PHjtXLlSqWnp+vtt99WIBBQbm6uJkyYoJ49e6pbt271zk9LS5TH0/rzkN1ul/z+lAaP+W/pTmfbZ1lK8B3X4DmNqdlU1KQmNalJTWpSk5rUjG7NsGE6IyNDf/3rX3XZZZdJklasWOFM3WhIIBDQnj17nP28vDwFAoHDjjnrrLOUkJCgjIwMnXTSScrJydGZZ57pHJuRkaHzzz9fn3/++WFhurS0Mvw7jAK/P0WFhQ3/D8XWPbtqjzctVbg6qKyBcxpTs6moSU1qUpOa1KQmNakZmZrHHXfk69/C3md63rx5Kigo0A033KAbbrhB+fn5mjdvXtgX7NOnj3JycpSbm6uqqiplZ2crKyur3jEDBw7URx99JEnKz89XTk6OMjIyVFRUpKqqKufxTZs21Ztr3RbsDdYuJe6zTBZsAQAAiENhR6Z9Pp/uuOOOphf2eDRr1ixNmjRJpmlq1KhR6tGjh+bPn68zzjhDAwYM0EUXXaQNGzZo6NChcrvdmjFjhjp27KhNmzZp9uzZznLmkydPbnNhen9FnTBtsmALAABAPDpqmL722msbPLEx933OzMyst3qiJE2bNs3ZNgxDM2fO1MyZM+sdc84552jlypVh6x/LCiprw7TfYilxAACAeHTUMP3pp5+qS5cuGjZsmM466yzZtt2afbV5hZVFzjYj0wAAAPHpqGF6w4YN2rBhg7Kzs7Vq1SplZmZq+PDh6tGjR2v212aVhmpXP2RkGgAAID4d9QJEt9utiy++WPfee6+WLFmi7t2766qrrtL//d//tWZ/bVZZqMTZ9lmMTAMAAMSjBi9ArKqq0jvvvKNVq1Zp586duuqqqzRo0KDW6q1Nq7BKnP9V8ZumrER/bBsCAABAxB01TM+YMUNffvmlLr74Yl1//fVhlw9HfdV27ch0uu2SEiJ7g3EAAADE3lHD9IoVK5ScnKycnBwtWrTIedy2bRmGoU2bNrVKg22VpdownebpIBlGDLsBAABANBw1TG/ZsqU1+4grtm3LctWupONL9MWwGwAAAERL2BUQ0XRF5ZWy3TUrOBq2rbRELj4EAACIR4TpKNhVVuBsd7AsuZI6xbAbAAAARAthOgp2l+Y7235uiwcAABC3CNNRkFdnZNpvsmALAABAvCJMR8G+8townc7INAAAQNwiTEdBfkWRs+23LNks2AIAABCXCNNRUFhVJ0ybJiPTAAAAcYowHQUldcI00zwAAADiF2E6CkpDxc42FyACAADEL8J0FFRYpc42t8YDAACIX4TpKKi0Spxtn2XJTuICRAAAgHhEmI4Cy6id5tHBSJJcnhh2AwAAgGghTEeYbduyjTJn3+dNj2E3AAAAiCbCdISVVpqy3RXOfgcvUzwAAADiFWE6wvaWlcl2hSRJHttWCvOlAQAA4hZhOsJ2l+Y72z7Tkp3YKYbdAAAAIJoI0xG2p6xOmOa2eAAAAHGNMB1h+8oLnG2/ZbJgCwAAQBwjTEfYgfLapcR9JiPTAAAA8YwwHWEFlYXOds2CLYRpAACAeEWYjrCi6tqRaT8j0wAAAHGNMB1hZdWHLiVOmAYAAIhXhOkIC5q1S4n7LJORaQAAgDhGmI6wSqvU2fablmwWbQEAAIhbhOkIC6nONA/bkJ2QFsNuAAAAEE2E6QiybFsyasN0B0+aZBgx7AgAAADRRJiOoNLKkOQOOvu+BF8MuwEAAEC0EaYjqCBYLdtd6eynJXLxIQAAQDwjTEdQXlmxbMOSJCVZlhK5kwcAAEBcI0xH0J6yfGfbZ7FgCwAAQLwjTEfQ3mCBs11zWzzCNAAAQDwjTEfQ/vJCZ5uRaQAAgPhHmI6ggooiZ7tmKXEWbAEAAIhnhOkIKqquHZn2mywlDgAAEO+iGqbXrVunIUOGaNCgQVqwYMERj3n11Vc1dOhQDRs2TNOnT3ceX7ZsmQYPHqzBgwdr2bJl0WwzYkqr66x+aDFnGgAAIN55olXYNE3NmTNHTz31lAKBgEaPHq2srCydcsopzjE5OTlasGCBXnjhBfl8Ph04cECSVFhYqIceekhLly6VYRgaOXKksrKy5PMd24uglIWKJXfNtt+0ZHGfaQAAgLgWtZHpzZs3q3v37srIyJDX69WwYcO0Zs2aescsWbJE48aNc0Jy586dJUnr169Xv3795Pf75fP51K9fP7377rvRajViyq36I9NM8wAAAIhvURuZzsvL0wknnODsBwIBbd68ud4xOTk5kqSxY8fKsixdf/31uvjii494bl5e3mGvkZaWKI/HHZ030AC32yW/P+Wwx6vtUmfbb1rynfBtye1tUc2WoCY1qUlNalKTmtSkZnRrRi1MN4Zpmvr666+1aNEi7dmzR+PHj9fKlSsbfX5paWX4g6LA709RYWGw3mOWbcs2akemO7gSVVgSkhRqds2WoiY1qUlNalKTmtSkZmRqHndchyM+HrVpHoFAQHv27HH28/Lu4MpdAAAgAElEQVTyFAgEDjsmKytLCQkJysjI0EknnaScnJxGnXusKakIyeUuc/Z9CUf+gQMAACB+RC1M9+nTRzk5OcrNzVVVVZWys7OVlZVV75iBAwfqo48+kiTl5+crJydHGRkZ6t+/v9avX6+ioiIVFRVp/fr16t+/f7RajYjC8mrJXe7spycc2xdLAgAAoOWiNs3D4/Fo1qxZmjRpkkzT1KhRo9SjRw/Nnz9fZ5xxhgYMGKCLLrpIGzZs0NChQ+V2uzVjxgx17Fhz0d51112n0aNHS5KmTp0qv//YXgCloLxSlqt22klaUkeVNXA8AAAA2r6ozpnOzMxUZmZmvcemTZvmbBuGoZkzZ2rmzJmHnTt69GgnTLcFe8oKJaNmu4NpyZXUKbYNAQAAIOpYATFC8soKnG2fZbKUOAAAQDtAmI6Q/eV1wzQLtgAAALQHhOkIya8ocrb9JkuJAwAAtAeE6QgprKwN06x+CAAA0D4QpiOkpLrY2faZhGkAAID2gDAdIaWh2jDtt5jmAQAA0B4QpiOk3KpdStxnmYxMAwAAtAOE6QipskudbR8XIAIAALQLhOkIMC1bpuqMTNuS7e0Qw44AAADQGgjTEVBSGZLbVWdk2p0qGUYMOwIAAEBrIExHQFF5tVyeoLPfwZsew24AAADQWgjTEVBYXi3bVe7s+7wsJQ4AANAeEKYj4ECwXJY7JEly27ZSEjvFuCMAAAC0BsJ0BOSVFTjb6ZYlO4kwDQAA0B4QpiNgX3ltmK65LR7TPAAAANoDwnQEHKgodLb9FkuJAwAAtBeE6QgoqCxytn0sJQ4AANBuEKYjoLiqTpg2TVlM8wAAAGgXCNMRUFpdu/qhn5FpAACAdoMwHQFBs9jZ9pnMmQYAAGgvCNMRUGHXLiXOyDQAAED7QZhuIdOyFaoTpmvmTBOmAQAA2gPCdAuVVIbkcdeG6XTDK7kTY9gRAAAAWgthuoUKy6vlqhumPWkx7AYAAACtiTDdQkXl1TLc5c5+utcXw24AAADQmgjTLVQQrJLlrnD20xOZLw0AANBeEKZb6ECwTLbLkiR5LVtewjQAAEC7QZhuob3BAmfbb5lScqcYdgMAAIDWRJhuoX3lhc52usWCLQAAAO0JYbqFCiprw7TfZMEWAACA9oQw3UKFVUXOtt+yZCX5Y9gNAAAAWhNhuoVKQ8XOts+yZHMBIgAAQLtBmG6hslCJs81S4gAAAO0LYbqFKqzaMO3nAkQAAIB2hTDdAqZlq9quXUrcxwWIAAAA7QphugVKKkJyucucfZ9ty05Mj2FHAAAAaE2E6RYoLK+Wx11nZNqVIhn8SAEAANoLkl8LFJZX1xuZ7pDQIYbdAAAAoLURplugsLxacpc7++le7jENAADQnhCmW6CwvFKmu9LZ75DUKYbdAAAAoLV5oll83bp1mjt3rizL0pgxYzRlypR6z7/88sv64x//qEAgIEkaP368xowZI0nq3bu3evbsKUnq0qWLHn300Wi22iz7giWSUbOdalnyJBOmAQAA2pOohWnTNDVnzhw99dRTCgQCGj16tLKysnTKKafUO27o0KGaNWvWYecnJSVp+fLl0WovIvaVFzjbPtOSxeqHAAAA7UrUpnls3rxZ3bt3V0ZGhrxer4YNG6Y1a9ZE6+Vi4kBFobPts7jHNAAAQHsTtTCdl5enE044wdkPBALKy8s77Lg333xTP/7xj3XjjTdq9+7dzuOVlZUaOXKkrrjiCq1evTpabbZIUWWRs+23WEocAACgvYnqnOlwLrnkEg0fPlxer1eLFy/WbbfdpmeffVaS9PbbbysQCCg3N1cTJkxQz5491a1bt3rnp6UlyuNxt3rfbrdLfn+KyuosJe4zLSV3Pl5J/pQW1YwkalKTmtSkJjWpSU1qRrdm1MJ0IBDQnj17nP28vDznQsODOnasHckdM2aM/vSnP9U7X5IyMjJ0/vnn6/PPPz8sTJeWVioW/P4UFRYGVVxVJCXVPOazLJWaqaouDLaoZiRRk5rUpCY1qUlNalIzMjWPO+7I64lEbZpHnz59lJOTo9zcXFVVVSk7O1tZWVn1jtm7d6+zvXbtWn33u9+VJBUVFamqqkqSlJ+fr02bNh124eKxoNysHZn2mxbTPAAAANqZqI1MezwezZo1S5MmTZJpmho1apR69Oih+fPn64wzztCAAQO0aNEirV27Vm63Wz6fT/fcc48kadu2bZo9e7YMw5Bt25o8efIxF6ZDlq0qu1Te/+3XXIDIoi0AAADtSVTnTGdmZiozM7PeY9OmTXO2p0+frunTpx923jnnnKOVK1dGs7UWK6moluGu/ScBLkAEAABof1gBsZkKy0PyuEudfZ88kic5hh0BAACgtRGmm6mwvLpemO7gSYthNwAAAIgFwnQzFZXXn+aRnpAew24AAAAQC4TpZiosr5blrnD2071cfAgAANDeEKabKT9YIctdLUkybFspiZ1j3BEAAABaG2G6mfaVFzrb6ZYlVzJ38gAAAGhvCNPNlF9R5Gz7LRZsAQAAaI8I081UUFk7Mu0zLdmEaQAAgHaHMN1MxVW1I9M+RqYBAADaJcJ0M5WEip1tPyPTAAAA7RJhupnKzRJn28dS4gAAAO0SYboZQqalSrvOUuIWI9MAAADtEWG6GWpWPyx39v0mc6YBAADaI8J0M+QHq+Vylzn76ZYl28ty4gAAAO0NYboZCoNVSnDXmTPtTpZc7hh2BAAAgFggTDdDQbBa7roj0560GHYDAACAWCFMN0NBsEpyB539dK8vht0AAAAgVgjTzVBQViXLXensd0jsFMNuAAAAECuE6WbYHyyT5TIlSR7bVjJhGgAAoF0iTDfD3mC+s+0zLdnJhGkAAID2iDDdDAeChc623zJZsAUAAKCdIkw3Q2FlkbPts1iwBQAAoL0iTDdDSVWdMG1ashMJ0wAAAO0RYboZSkPFzrbfsmQl+WPYDQAAAGKFMN1EIdNSpVW7+qHftJgzDQAA0E4RppuoqCIkw13u7KczZxoAAKDd8sS6gbamqKJaRp2lxP2WSZgGAAAttqOwXM9t3KHX/rNXwSpTKV63Lu19vMad21Vd/cmxbg9Hwch0ExWWV8vtLnX2fbZb8vAFBwAAzbdhe75++szHeuWz3SqrMmVLKqsy9cpnu/XTZz7Whu35YWs0ZN26d9S//7n6+usc57FNmzZqxoyb6h03d+5devvt1ZKkUCikRx55UGPHXq6JE8fpmmt+offf33BY7SVLnldFRUWTe3r88Uf1j3982OTzjjWE6SYqLA8poU6Y7uBJkQwjhh0BAIC2bEdhuW5f8bkqQpZCVv3nQpZUEbJ0+4rPtaOw/MgFGmH16jd05pl9tXr1G40+Z+HCR3TgwH49++yLevLJ53TPPfcpGAwedtySJS8cNUybpnnU+pMmXavzzrug0f0cqwjTTVRYXi3DXftF8iWkx7AbAADQ1j23cYdCltXgMSHL0nMbdzSrfjAY1ObNn+r22+9sdJiuqKjQypWv6Ne/vlVer1eS1KlTZw0YMKjecS+9tFj79+/TjTdeoxtuuEaSNGjQRXrwwf+nkSMv17/+9ZmeemqhJk26WldddYXuvXeubNuWVH8UfPToH+uJJx7TxInjdPXVV9YbQT/WMWe6iYrKq6W6FyB6uS0eAAAI7/827tDC975WsProo7VHE7Kkv/1zt/72z931Hk9JcGvyD7pr/Lldj3ru+vV/1wUXfF/dunWXz+fXli3/Ua9evRt8vR07chUIBJSamtbgcWPGjNWLLz6nv/zlMfn9NZmovLxcp512hu6887cqLAzq5JNP1i9+MVmS9Lvf3akNG95V//4XH1bL5/PpySef08svv6QXXlik22+/s8HXPlYwMt1EBcEqme5KZ79DYqcYdgMAANqK5zbuaFaQbkiw2gw7Yr169RsaOHCwJGnAgMHO6LRxlGmqR3u8sdxut374wyxnf9OmjZo8eYKuvvpKffzxRm3f/tURz8vMrDnn1FN7a/fu3Uc85ljEyHQT5ZeXyDZq/nki2bKUkNpJlWHOAQAAGHdu12aPTB9NSoJb4xoYlS4uLtLHH/9D27ZtlWEYsv43nWTq1Gny+XwqKSk+7Hifz6+uXTOUl5ensrLSsKPTh/J6vXK73ZKkyspK3X//vXr88WcVCJygJ554TFVVR05OCQk100ncbpdMM9Sk14wlwnQT5VcWSYk12z6LBVsAAEDjjD+36xGnY9y7+ku98tnuwy4+rMvjkn7Sp4tuG9hDfn+KCgsPvxDwSN5+e42GDBmqGTN+6zx2/fVT9M9/fqLTTjtD+/fvV07OdvXte7r27NmtrVu/VI8epyopKUnDh4/Q/Pn369Zbf6OEhAQVFBTok08+VlbWwHqvkZKSomCwzJnmUVdVVZUkye/3KxgM6p131uiHPxzQqN7bCsJ0Ix289+Mnu3Yp+eSax/ympQI7Td7YtgYAANqwced21ap/5zV4EaLH5WpwBPpoVq9+Q+PGTaj3WGZmllavfkN9+56jO++co3nz7pZpVssw3Lr99juUllYzEj158nVauPBhjR8/Rl6vV0lJyZo06drDXmPEiMs1ffoN+ta3jtODDz5W77kOHTroxz/+ia666kp17txZvXuf3uT3cKwz7IOXVLZB+/aVhD8oAjZsz9ftKz5XyLJkJ/9XKd2elCRdUF6h43eO1Pk/vk79Tm7Z3Omm/F8mNalJTWpSk5rUjK+adbNG3RFqj6smSP9hxGlO1oi3995Wah53XIcjPs4FiGEceu/HerfFM03tNVNbfO9HAADQvvU7uZNemPA9/aRPF6V63TIkpXrd+kmfLnphwvdaPGiH6GGaRxiH3vvRqHNbPL9lKcdOc+79eNvAHrFoEQAAxIGu/mTdNrAHeaKNYWQ6jNf+s7feP7cY7jJn22dZKlSaQlbNcQAAAGhfCNNhBKvq376m/jQPSwV2hyMeBwAAgPhHmA4jxeuut++qE6b9lqUipR7xOAAAAMS/qIbpdevWaciQIRo0aJAWLFhw2PMvv/yyLrzwQl122WW67LLL9NJLLznPLVu2TIMHD9bgwYO1bNmyaLbZoEt7Hy9PnZ9Sgrv2DiIJZoJMueVx1RwHAADQXK6iHKX9/TfqvKCXvvXXDHVe0Etpf/+NXEU5sW4NDYhamDZNU3PmzNHjjz+u7OxsrVq1Slu3bj3suKFDh2r58uVavny5xowZI0kqLCzUQw89pCVLluill17SQw89pKKiomi12qBx53aVx+WSkXBAiYFXZKfWLoH5SlqKjIQDzb73IwAAgCR5v16rTosHKenfz8tVXSpDtlzVpUr69/PqtHiQvF+vbVH9deveUf/+5+rrr3OcxzZt2qgZM26qd9zcuXfp7bdXS5JCoZAeeeRBjR17uSZOHKdrrvmF3n9/Q4v6ePXVlXrggXubde6zzz5Zb//aaye2qJdIiVqY3rx5s7p3766MjAx5vV4NGzZMa9asadS569evV79+/eT3++Xz+dSvXz+9++670Wq1QV39yZqYFVTqd/6shI4fSUbtbbk/TPUo9Tt/1sSsoLr6k2PSHwAAaNtcRTlKf/0aGaFyGXb9ZbQNOyQjVK70169p0Qj16tVv6Mwz+2r16jcafc7ChY/owIH9evbZF/Xkk8/pnnvuUzAY2Xs8N8WiRU/V23/00SePcmTritqt8fLy8nTCCSc4+4FAQJs3bz7suDfffFP/+Mc/dPLJJ2vmzJnq0qXLEc/Ny8uLVqsN2lm2Qy/t+oMMV/Vhz1mGZBjVemnXHzT4O6fpxFRGpwEAQNOkfLpAMg/PGfWY1Ur5dKFKM+c2uX4wGNTmzZ/qL395VLfd9mv98pfXhD2noqJCK1e+opdeWi6vt2at506dOmvAgEGHHfvIIw9qw4Z1crvdOu+8C3X99TepoKBAd901Uzt27JQk3XjjzTrzzL71zisoKNB9981zMt7BY4LBoP785z9py5bPZRiGfvGLyfrPfz5XZWWlRo26XN26nazZs3+vQYMu0ltvvSvbtvXww3/RBx9skGEYmjDhlxowYLA2bdqoJ59cIL/fr6++2qZTT+2tWbN+J8MwmvwzbEhM7zN9ySWXaPjw4fJ6vVq8eLFuu+02Pfvss40+Py0tUR5PdC/8e+S/LylkhRo8JmSFtHLXUt1+3sxmv47b7ZLfn9Ls86lJTWpSk5rUpOaxXdP1wUNyvftHGVWlTa5j2CEl/+sZJf/rGUnScf973PamybpohqwLrz/quevXr9FFF12kM8/srU6dOmnnzu06/fTTlZaWJI/HLb8/xenT6/UoNTVRRUX79O1vd9GJJzZ8TVhhYaE2bPi7Vq7MlmEYKi4uVnp6iubNm62f//zn6tv3bO3evUtTpkzRypWrlJLiVWJigvz+mmN++cuJOuec79U75sknH1GnTn6tWLFSklRUVKSf/OTHevnlJXrlleUyzdp7Fvv9KXrrrTe1fftWvfLKchUUFGjs2Ct08cX9lJaWpK1b/6tXXlmh448/XlddNU7bt2/ROed8r957aOnnHrUwHQgEtGfPHmc/Ly9PgUCg3jEdO3Z0tseMGaM//elPzrkfffRRvXPPP//8w16jtLQy0m0fJjsnWyE7TJi2Q1q1fZWu7TGt2a8Tb0tuUpOa1KQmNalJzfo1O73/12YF6YYYVaXS+39VYa+jzx9esWKlxowZq8LCoH74w4Fatmy5TjzxZJWVVSoUMlVYGHT6rKoKKRisUklJhUzTCvvzCIVccrsTdNttt6tfv4v0gx9cpMLCoN577z1t27ZVplkzPbakpES7du1XMFilyspq55j//vdLp9bBY9av36C7755X57UTnO1DeyosDOr99z/UD384UCUllfJ4UnTWWWfrww83KiUlVb16naakpHQVF1fo5JNP0Zdfbtd3vtO73nto6XLiUQvTffr0UU5OjnJzcxUIBJSdna3777+/3jF79+7V8cfX/B/P2rVr9d3vfleS1L9/fz3wwAPORYfr16/XzTffHK1WG1Qeatwy4eWh2M0hAgAAx77yvlOU8o8H5KouC39wI1kJqSrvO+WozxcXF+njj/+hbdu2yjAMWf9b1Xnq1Gny+XwqKSk+7Hifz6+uXTOUl5ensrJSpaamHbW+x+PRwoXP6OOPP9Lbb6/R0qVL9Je/PCrbtvT884tVXn70dThs29Jjjz2lxMTEJr7rxjs4RUWSXC6XTDPy64JE7QJEj8ejWbNmadKkSRo6dKguvfRS9ejRQ/Pnz3cuRFy0aJGGDRumESNG6Nlnn9U999wjSfL7/bruuus0evRojR49WlOnTpXf749Wqw1K9jTuwsJkT2T/WQgAAMSX8rOv0YEpX2jf1B31/is/42rZRsPjm7bhUfkZE7Rv6g5V/zbfOffAlC9UfvbR50C//fYaDRkyVEuXrtLf/rZSL7+crW9/+0T985+fqGvXbtq/f79ycrZLkvbs2a2tW79Ujx6nKikpScOHj9D8+ferurpmPndBQYHWrl1dr34wGFRZWam+//3+uvHG6dq6tWak+bzzLtRzz/2fc9yXX35xWG/nnXehli598bBjzjvvAr38cu3tkouLawK/2+1xeqnrrLPO1tq1b8k0TRUUFOjTTz9R796nN/jzjKSozpnOzMxUZmZmvcemTaudCjF9+nRNnz79iOceDNKxNvDbQ5Sdu1JmA1M93IZHg779o1bsCgAAxItg3ylK2vKSFGpgWqk7QcG+k5tce/XqNzRu3IR6j2VmZmn16jfUt+85uvPOOZo3726ZZrUMw63bb79DaWk1I9GTJ1+nhQsf1vjxY+T1epWUlKxJk66t33swqJkzb1ZVVZVs29YNN/xaknTTTbfqwQfv0/LlY2Waps4662zdeutv6p1700236oEH7tWECfWPmTDhl3rggXt11VVXyOVya+LEycrMzNKIEZdr5MjLdcopPTV79u+dOhdffIn+9a/P9POf/1SGYei6625U587fqncbwGgybNu2wx92bNq3ryT8QS20s2yHJq+/WhVmxVGPSXInaWH/Z1t0N4+2OPeLmtSkJjWpSU1qRqam9+u1Sn/9Gsmsrnd7PNvwSO4EFf/oMVV1z4p5n+255tHmTLOceBgnpnbV7LPnKsmVeNgwvkdSkitRs8+ey23xAABAs1V1z1L+2LdUcfo4Wd4OsmXI8nZQxenjlD/2LSdI49gT01vjtRUXlZdr6Y5dejYtUdlpKSozDKXatoaVBnV16QH5eperKtZNAgCANs3ynaTSzLnNupc0YocwHcbBVYl8oXLdUVmmOw7kH3aM/fo1yh/7lizfSa3fIAAAAGKGaR5hNGVVIgAAALQvhOkwEr94ud6FAEdi2CEl/vflVuoIAAAAxwqmeYRhNPLG6pFe0QgAALQvO8t26KXtL2j1rjdUHipXsidZA789RGNO/ik3OjiGMTIdhp2Q2rjjvEdfHQgAAKAhH+59X5PXX63s3JUKhoKyZSsYCio7d6Umr79aH+59v0X11617R/37n1vv3subNm3UjBk31Ttu7ty79PbbNQuzhEIhPfLIgxo79nJNnDhO11zzC73//oYW9SFJ118/RVu2fC5JuuWWG1VScvitjp944jE9//yiBuusW/eOtm//ytl//PFH9Y9/fNji/pqKMB1G5akjG7UqUWXPka3UEQAAiCc7y3bo7k9+qwqz4rBF4kw7pAqzQnd/8lvtLNvR7NdYvfoNnXlmX61e/Uajz1m48BEdOLBfzz77op588jndc899CgYje4/n++77izp0OPL9m8N59913lJNTG6YnTbpW5513QaRaazTCdBjBvlMkd0LDBzVzVSIAAICXtr+gaqvh67OqrZD+tv3FBo85mmAwqM2bP9Xtt9/Z6DBdUVGhlStf0a9/fau8Xq8kqVOnzhowYFC94z744D3dccdtzn7d0e45c+7WL395lcaPv0JPPPHYEV9n9Ogfq7CwUJL0zDNPaOzYkfrVr36pb7752jlmxYplmjTpak2Y8FPddNM0VVRU6LPP/qn1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qqrFNQ3D0KZNm/Szn/1MW7dujUiYjvR33rbtet/RNWvWtDhQH/zc161bp4kTJ2rLli0t7lOSlixZonXr1mnBggXq0qWL87vaEm63W998841uvfVWffLJJxHoMjp/LkXrc7dtW7fccoveeeediHzuUs3nNH36dH322Wct7jMaNaXofO6SdNttt+npp5+OWL1ofZci+d7rfpemT5+uN998U+Xl5cdczYN/Xrz44ov67W9/qy+++KJF9aLVpxT5zz0av0fR+DOk1dioZ8eOHfbll19uv/jiixGt+9xzz9lXX321XVxcHJF6CxcutG+66SbbsqyI1LNt237//fft8ePH24WFhRGruXXrVvuKK66wH3jggYjV/PTTT+0RI0bYH330UYtrWZZlh0Ihe+7cufajjz4age7q154zZ449e/bsiNXct2+fPX78eHvt2rURqxmN73xlZaV988032zfffLNdWVkZkZobNmywL730UvuDDz6ISD3btu1nnnnGfuWVV2zbtu3S0lLbtu0W/05t27bN/slPfmIvXry4xf0dFI3PKBo1S0pK7Guuucb+3e9+F7GaGzdutEeNGmV/8803x3TNaHzuTz/9tH3jjTdGrJ5tR+dzj8Z7Lysrs2+44Qb797///TFd8+9//3vEv0vR6DMan3s0fo+i8WdIa2j3I9OHjhDv3LlTXbp00RVXXCGp+dMU7EPm/FRWVmr06NHq0KGDysrKjnhMY2tZlqUdO3Zo4MCBMgzjsOkDja17qNzcXJ144ony+Xyqrq6OyLylN998UwMGDNCvf/3rZtc40mfUu3dvnXfeeUd8Ppy6n6lhGM4oqt/vlyRnBLmp/0d8aB+GYcjr9WrIkCGS5HzuTRmhPvT753K51KVLF51//vkyTdOp1ZQpJNH4zh/6XcnPz1d1dbXuueceeb1ehUKhIx7X1D5HjRqlCy644LDvZ1Pq5ufnO7VDoZCef/55SVJqaqqkmn9Sb8q/9hz62jk5OerZs6euvPLKI76PxojGZ9Qan3txcbFSU1P129/+VpKcz70pP4NDj921a5d69uypjIwMVVdXR+TnGYma0fjcD+XxeHTaaadJkjM6GQqFWvx7FOnPPRLv/Uh/Z9q2rRtuuEGSVF1dfcTjWrvmoe8tNzdX3/ve95zvUnP+3myNPqPxZ0g0fo8i8WdILLTrMF13XtLBMHLcccfJ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\n", + "image/png": "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8Nlnn+HYsWPYvXs3PDw8oFKpMHnyZJOLCqdMmQJvb2/8/PPPOHbsGPR6PVQqFaZMmYKZM2ea9NhyNymVSqxZswZr1qzBtm3bsG7dOsjlckRGRuJPf/qTVRdGWmI842wwGMzaRMfHx0vb11LPOY888giUSiU+/fRTfPPNN3B2dkZsbCxeffVV7Ny587aLaQCYOXMmAgIC8NFHH2HTpk1wd3fHoEGD8PzzzzfpF+SgoCBs2LAB69atw86dO7F161bo9Xq0adMGYWFhmDp1KsLDw+9oXV988UUIgoD09HTs3bsXBoMB8+bNk3p/WLBgAVxdXfHVV1/hq6++Qps2bTB69GjMnz/frMs+azg5OeGdd97B5s2bsWnTJunY9PX1RUhICBYuXGjx15u6xo4dC61Wi+PHj+PUqVOorKyESqXCmDFjMHPmTJPtAgDLli3DkCFDsH79ehw8eFC6SNn4fly3a7mmyn3y5Mno2rUrPv74Yxw+fBi7du2Cj48PIiIipK7ojF577TW8+uqr2L9/P1JTUyGKIoKCghq8hqRXr174+uuv8e677yI9PR179uyBr68vxowZgzlz5qBz586NrhfdGwTR2MEoERERERHdFv4+QURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2YjFNRERERGQjFtNERERERDZiMU1EREREZCMW00RERERENmIxTURERERkIxbTREREREQ2smsxvW/fPiQlJWHEiBH44IMPzKZfvXoVM2bMwLhx4zBt2jTk5ORI0zZt2oSRI0di5MiR2LRpkz3TJCIiIiKyiSCKomiPwHq9HklJSVizZg1UKhUmTZqEN998E2FhYdI8CxYswNChQ5GSkoL09HRs3LgRr7/+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hyogIEC7d+9Wenq6BgwYoHXr1unnn3/W/v379eGHH+qWW25xG+/Sz3PYsGHavXu3Tp8+rVatWmn58uXy9fVVhQoVJEmvvfaabr31VudDpAoTsyiPUd5381//+pe++uornT9/Xi1bttQPP/ygVatWKTQ0VIZh6LXXXtPZs2d13333FXue0sUOg507d2rIkCH65ptvlJWVpfr168tisWjFihUKCgpSTEyMPv74Y7Vs2bJQx0mS5s+frzFjxig+Pl47d+5Uv3799OWXX2rbtm1KS0vTokWLtHnzZvXs2dP5x3Rx5untc6msnJ8TJ07UkiVLdOzYMbVo0UKHDx/WunXrVLNmTVWqVEnDhg3TmTNn1Lx58wKvIWUlT+niuRQTE6NXX31Va9aska+vr+6++24dO3ZMO3fuVJUqVbR161ZNnz5dLVu21E033fQ/93kW1XUp7/P0xjWkJFzX80xHR0frs88+09tvv60LFy7ovffeU8eOHdWsWTNt375d27Zt04ULFzRs2DBVrVo13169/KxYsUIxMTF6+umntXfvXu3fv1+NGzdW+/btNWrUKAUEBOj06dMaP368qlSp4jbesmXL9O233zq/bD/99JN69uypatWqadasWcrKylJWVpb+/ve/yzCMQuWZnZ2td999V4cOHVJwcLDq1Kmj5557TmvWrNGcOXMUHh6utWvXqmXLloUe9nH8+HF9+OGHaty4sZo0aaLevXtr0tf/0MkAACAASURBVKRJqlu3rpYsWaKdO3cqKytLffv21SOPPOI23rlz57Ro0SKlpKRo2LBhio+P14ABA/Tyyy8rNDRU69ev14EDB1SzZk117dpVUv49r/mZPn26cnNz1bVrV8XExOi7775Tp06dVKtWLX366afKyMjQDTfcoIiIiEK9971792rFihW666679MgjjygyMlI//fSTIiIidPz4cf3444+Ki4vTn/70J4WGhhYq5rlz5zRlyhTZ7Xb9+c9/Vt++fTV69Gg98MADWrBggX744QfZ7Xa1bdtWL7zwQqFiShd72xcsWKD7779fx48fV506dfT888/rxRdfVHBwsE6dOqUbb7xRH3zwQaE+y4MHD2rixInq0KGDEhMTtWPHDvXt21cBAQGaPXu20tLSdPLkSY0cOVItWrRwGy8tLU09e/bUTTfdpE8//VTZ2dlatWqV9uzZo9atW+vBBx90aX/phb4g586d09SpU5Wenq5HHnlEkyZN0siRI3XLLbdo7ty5OnHihFJTU/X444+rT58+buPlxfTGMVq5cqXWrVun5s2ba//+/apevbr69++vIUOGyNfXV7/99pvq16+vsWPHllieu3bt0rRp0zRo0CBZLBZNnDhRXbp0UaNGjbR161Zt27ZNmZmZev31190Wfnk+//xzJSUl6ZlnnlFmZqamTp2qunXr6oUXXtCiRYt0/PhxGYah4cOHSyrc990beXrrXCor5+c333yjb775Rk8++aSio6OVlZWld955R//4xz+UmJiokydPqlatWipsX11ZyXP37t2aMmWK+vfvr6ysLI0bN07vvPOOqlSporVr1+qXX37RhQsX9MYbbxT6XPJGnmXluuSN72aJKcLx12XKyZMnzXfeecds06aNmZycbJrmxRsJXn75ZXPt2rWXtc/Nzb1iLLvd7vw5NTXVfOKJJ8wXX3zRNM2LN3ctXbrUHDdunBkdHV2ouJfeVLVnzx6zf//+Zv/+/Z3rpkyZYo4cOdL8z3/+c1n7gvLMk5aWZvbp08f85z//aZqmacbGxpqTJk0yp0yZYjocDnPr1q1mZGSkuW7dunxz+uP7djgcZmJiojl48GCzT58+Zlpammmaprl582azadOm5r59+0zTNM3k5GTnDZgOh+OymH9cjomJMdu1a2eOHTvWzM7ONk3TNHfs2GG2a9fO/Pbbby/b5tLj8EeXtjt8+LBZr149829/+5szr8jISPP111933ih56eeY32f6x30tXLjQrFevnvMYHzt2zJw9e7Y5cuRI540Uee8hv/d66Tq73W6ePXvWHD16tPn888+b8fHxpmma5s8//2zee++95vbt203TvHgOnzhxolDvP++1cePGmW3atDETExNN07x4w+3IkSPNyMhI0+FwmCdOnDC///77At/7pbknJiaao0ePNp999lnnuo8++sh84403zIMHD5qmaZrp6ekFHvdL93X69Gnne3300UfNNWvWmKZ58Xs1a9Ys84033jCPHj1aqPect5/c3FwzNTXV7Nevnzl06FAzIyPDNE3T3Lp1qxkWFmb++uuvpmlePGZHjhzJ933+cZ03j9HIkSPN0NBQ53Vp27Zt5htvvGH+P3tXHpBT+v0/b2ihhUQjlOxpka2IbBnbWCKUhCRjDROlfjS20AiDJMJk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\n", "text/plain": [ "
    " ] @@ -1216,39 +1267,86 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "['age_enc', 'occupation_enc', 'scont_1_enc', 'relationship_enc', 'education_enc', 'hours-per-week_enc']\n" + "['age_enc', 'relationship_enc', 'education_enc', 'occupation_enc']\n" ] } ], "source": [ - "model = forward_selection.get_model_from_step(5) # Python starts to count from 0!\n", + "model = forward_selection.get_model_from_step(3) # Python starts to count from 0!\n", "print(model.predictors)" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    predictorimportance
    0relationship0.747456
    1education0.609713
    2occupation0.572291
    3age0.524466
    \n", + "
    " + ], "text/plain": [ - "{'age': 0.5172855940047185,\n", - " 'occupation': 0.5634536788555236,\n", - " 'scont_1': 0.012280802872168558,\n", - " 'relationship': 0.7363851709976446,\n", - " 'education': 0.5989073013361791,\n", - " 'hours-per-week': 0.44453822509112334}" + " predictor importance\n", + "0 relationship 0.747456\n", + "1 education 0.609713\n", + "2 occupation 0.572291\n", + "3 age 0.524466" ] }, - "execution_count": 28, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -1260,12 +1358,12 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 26, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwoAAAHsCAYAAAByulqXAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nO3de1iUdf7/8RcHQVESNDNN21VbNVMSw/KA2iLmgQBRMcswEzOtzDZLMdO0PFRWllSam+nmgb6usiKeF3WtTdfTmkjZZiye0CwTEjkzzO+Pfs33Sx/U0RxuwOfjuva6mHvu+cxr3hdr8+K+7xk3u91uFwAAAAD8H+5WBwAAAABQ+VAUAAAAABgoCgAAAAAMFAUAAAAABooCAAAAAANFAQAAAICBogAAcFpgYKBOnDhx2f1OnjypVq1aqaSkpNz74+Pj9dxzz11VhrCwMO3evfuqHgsAcB5FAQCqqdjYWL3zzjvG9pSUFHXt2vWib+Iv5cCBA2ratOm1iHfV1q9fr3vuucfSDL8ICQnRzp07rY4BAC5BUQCAaioqKkpr167Vr79Xc+3atQoPD5enp6fTa11NqajOmAeA6wFFAQCqqdDQUGVnZ2vfvn2ObT/99JO2b9+u/v37KzU1VQ888ICCgoIUHBysl19+WUVFRY59W7VqpeXLl+u+++7Tfffd59h27NgxSdI//vEP9e/fXx06dFCPHj0UHx9vZFi9erWCg4MVHBysRYsWXTTrF198oSFDhigoKEgRERGXPLXo//4VPz4+Xk8//bSee+45BQYGKjw8XBkZGfrggw/UuXNn9ejRQ//85z8dj42JidGbb76pQYMGqUOHDhozZoyys7Md92/dulVhYWEKCgpSTEyM0tPTyzzvwoULFR4ervbt2+vZZ5/VqVOnNHr0aAUGBurPf/6zJOnpp59W165dddddd2no0KE6cuSIY424uDhNnz5do0aNUmBgoKKjo3X8+HHH/UeOHNGjjz6qu+++W126dNGCBQskSaWlpVq4cKFCQ0N1zz33aNy4cWVyA4ArUBQAoJqqWbOm+vbtqzVr1ji2bdy4Uc2bN1fr1q3l7u6uSZMm6V//+pc++eQT7dq1SytWrCizRkpKilauXKkNGzYY69eqVUuvvfaa9u3bpw8++EAJCQlKSUkps8/u3bu1ZcsWLVq0SH/+85/LPU3nzJkzevzxxzVmzBjt2bNHEydO1NNPP61z58459Tq3b9+uyMhI7d27V7fffrtiY2NVWlqqTz/9VE8++aSmTp1aZv81a9Zo1qxZ+uc//ylPT0/NmDFDkpSRkaHx48frhRde0K5du9S9e3eNHj26THlav369Fi5cqH379umtt95S48aNtWDBAh04cECPPfaYJKl79+7avHmzdu3apTZt2hjXYmzYsEFPPfWU9u7dq1tvvVVz586VJF24cEGPPvqounXrps8++0xbtmxR586dJUlLly5VSkqKli1bps8++0x169bVyy+/7NR8AOBqURQAoBrr37+/Nm/erMLCQkk/v0mOioqSJLVt21bt27eXp6enmjRpogceeEB79+4t8/hRo0bJz89PNWvWNNa+55571KpVK7m7u6t169YKCwvTnj17yuzz5JNPysfHR61atdKAAQO0bt06Y52kpCR1795dPXr0kLu7u7p27aq2bdtqx44dTr3GoKAgdevWTZ6enurTp4+ysrI0atQo1ahRQ/369VNmZqbOnz/v2D8yMlItW7aUj4+Pxo0bp02bNslms2nDhg3q0aOHunbtqho1aig2NlYFBQU6cOCA47ExMTFq1KhRufP4xaBBg1SnTh15eXlp7Nix+vrrr5WTk+O4PzQ0VAEBAfL09FRERIQOHz4s6ecjNDfeeKNGjBghb29v1alTR3feeack6ZNPPtGf/vQn3XzzzfLy8tJTTz2lzZs3cwoUAJdy/gRVAECVExQUJH9/f6WkpKhdu3Y6dOiQ3n33XUk//wX91VdfVVpamvLz82Wz2XTHHXeUeXyjRo0uuvbBgwf1xhtv6MiRIyouLlZRUZH69Olz0cffcsst+uabb4x1Tp06pU2bNmn79u2ObSUlJU5fsFy/fn3HzzVr1pS/v788PDwctyUpLy9PN9xwg5GpcePGKi4uVlZWlr7//ns1btzYcZ+7u7saNWqkM2fOlPt6ymOz2TR37lxt2rRJ586dk7v7z3+Py8rKkq+vryTpxhtvLJM3Ly9PknT69Gndeuut5a576tQpPfnkk471fsn3448/qmHDhpfMBABXi6IAANVcZGSk1qxZo4yMDAUHBzveqE6bNk1t2rTRm2++qTp16mjJkiXavHlzmce6ublddN3x48fr4Ycf1ocffihvb2/NnDlTWVlZZfY5ffq0WrRoIennN7s33XSTsU6jRo0UGRnpOAXI1U6fPl3m5xo1asjf31833XRTmSJjt9t1+vTpMm/ELzUPSUpOTtbWrVu1ePFiNWnSRDk5OerYsaNxQXl5GjVqVO4pXpJ08803a9asWbrrrrsuuw4AXCucegQA1Vz//v21a9curVy5Uv3793dsz83NVe3atVW7dm2lp6crISHhitbNzc1V3bp15e3trdTU1HJPK3r//feVn5+vI0eOKDExUf369TP2iYiI0Pbt2/XZZ5/JZrOpsLBQu3fv1nfffXflL9YJa9eu1bfffqv8/Hy988476t27tzw8PNS3b1/t2LFDu3btUnFxsT766CN5eXkpMDDwomvdeOONZb5XIjc3V15eXvL391d+fr7eeustp3Pde++9+uGHH7RkyRIVFRXpwoULOnjwoCTpwQcf1Ntvv63MzExJ0rlz54zrQQDgWqMoAEA116RJEwUGBio/P189e/Z0bJ84caLWrVunDh06aMqUKeW+ib+Ul156SfPmzVNgYKDee+899e3b19jn7rvvVq9evTR8+HCNGDFCwcHBxj6NGjXS+++/X+aTihYtWqTS0tIrf7FOiIyMVFxcnLp27aqioiJNnjxZktS8eXPNmTNHr7zyijp16qTt27drwYIF8vLyuuhao0aN0vz58xUUFKRFixapf//+aty4sbp166awsDC1b9/e6Vx16tTRRx99pO3bt6tr167q3bu349Ofhg0bppCQEI0YMUKBgYEaPHiwUlNTf9sgAOAy3OzOHA8FAKAaiImJUUREhKKjo62OAgCVHkcUAAAAABgoCgAAAAAMnHoEAAAAwMARBQAAAAAGvkehksrPL9KFC4VWx6iW6tTxZrYuwFxdh9m6BnN1HWbrOszWNa73uTZo4Fvudo4oVFKenh5WR6i2mK1rMFfXYbauwVxdh9m6DrN1DeZaPooCAAAAAANFAQAAAICBogAAAADAQFEAAAAAYKAoAAAAADBQFAAAAAAY+B6FSqrTC8utjgAAAIAKsGlClNURysURBQAAAAAGigIAAAAAA0UBAAAAgIGiAAAAAMBAUQAAAABgoCgAAAAAMFAUAAAAABgoCgAAAAAMFAUAAAAABooCAAAAAANFAQAAAICBogAAAADAQFEAAAAAYKAoAAAAADBQFAAAAAAYKAoAAAAADBQFAAAAAAaKAgAAAAAD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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvgAAAHsCAYAAACnjdnzAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nO3de7iVdZ3//xebkxz2CJoSJM6IM2GGJopHTg5CKszmoGCYYY6Yaak0mYqZhmZamZlSYuZpMqWvAyRyEBiEtIKQHBUxm5RAEMhSIBCQw2b9/vBy/4ZQ3Og+wN3jcV1e117rvte93utzbeS5b+61doNSqVQKAABQCGX1PQAAAFBzBD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8gL8DnTt3zrJly95zv1deeSUdO3bM1q1b33H76NGj85WvfOV9zdCvX7/MmzfvfT0WgOoT+AC7meHDh+fWW2/d4f6ZM2ema9eu7xrfO/P000+nffv2NTHe+zZlypQce+yx9TrD23r16pU5c+bU9xgAtULgA+xmBg0alEceeSR/+3sIH3nkkVRUVKRRo0bVPtb7+WGgyKwH8PdA4APsZnr37p01a9bkt7/9bdV9f/3rXzN79uwMHDgwCxYsyKc+9al06dIl3bp1y3XXXZfNmzdX7duxY8c88MAD+eQnP5lPfvKTVfe9/PLLSZJf/OIXGThwYI488sj07Nkzo0eP3mGG8ePHp1u3bunWrVvuvvvud531mWeeydChQ9OlS5f0799/p5fg/N+z5qNHj84ll1ySr3zlK+ncuXMqKiqyePHi/OhHP8rxxx+fnj175le/+lXVY4cNG5abb745gwcPzpFHHpkLL7wwa9asqdr+2GOPpV+/funSpUuGDRuWRYsWbfe8d955ZyoqKnLEEUfky1/+clasWJELLrggnTt3zo9//OMkySWXXJKuXbvmqKOOyllnnZUXX3yx6hgjR47Mtddem/PPPz+dO3fOkCFDsnTp0qrtL774Yv793/89xxxzTE444YTccccdSZJt27blzjvvTO/evXPsscdmxIgR280NUBsEPsBuZq+99sqpp56ahx9+uOq+Rx99NB06dMghhxySsrKyXHnllfnNb36Tn/3sZ5k7d24efPDB7Y4xc+bMPPTQQ5k6deoOx2/WrFm+/e1v57e//W1+9KMfZezYsZk5c+Z2+8ybNy8zZszI3XffnR//+MfveDnLq6++ms9//vO58MIL8+STT+aKK67IJZdcklWrVlXrdc6ePTsDBgzI/Pnz87GPfSzDhw/Ptm3b8sQTT+SLX/xirrnmmu32f/jhh3PDDTfkV7/6VRo1apTrr78+SbJ48eJceuml+epXv5q5c+emR48eueCCC7b7oWfKlCm5884789vf/jbf+9730q5du9xxxx15+umn87nPfS5J0qNHj0yfPj1z587NoYceusN7DaZOnZqLLroo8+fPz4EHHphbbrklSfLGG2/k3//939O9e/f88pe/zIwZM3L88ccnSe6///7MnDkzP/3pT/PLX/4ye++9d6677rpqrQ/A+yXwAXZDAwcOzPTp07Np06Ykb8XtoEGDkiSdOnXKEUcckUaNGuWAAw7Ipz71qcyfP3+7x59//vlp1apV9tprrx2Ofeyxx6Zjx44pKyvLIYcckn79+uXJJ5/cbp8vfvGLad68eTp27JjTTjstkydP3uE4EydOTI8ePdKzZ8+UlZWla9eu6dSpUx5//PFqvcYuXbqke/fuadSoUU455ZSsXr06559/fho3bpy+fftm+fLlWbt2bdX+AwYMyEc/+tE0b948I0aMyLRp01JZWZmpU6emZ8+e6dq1axo3bpzhw4fnzTffzNNPP1312GHDhqVt27bvuB5vGzx4cFq2bJkmTZrk4osvzu9///usW7euanvv3r1z+OGHp1GjRunfv39eeOGFJG/9i8iHPvShnHvuuWnatGlatmyZT3ziE0mSn/3sZ/mP//iPfPjDH06TJk1y0UUXZfr06S4VAmpV9S/kBKDOdOnSJa1bt87MmTNz2GGH5bnnnssPfvCDJG+dsf7Wt76VhQsXZuPGjamsrMzHP/7x7R7ftm3bdz32s88+m+9+97t58cUXs2XLlmzevDmnnHLKuz7+Ix/5SP7whz/scJwVK1Zk2rRpmT17dtV9W7durfYbaffdd9+qr/faa6+0bt06DRs2rLqdJBs2bMg//MM/7DBTu3btsmXLlqxevTp//vOf065du6ptZWVladu2bV599dV3fD3vpLKyMrfcckumTZuWVatWpazsrfNfq1evTnl5eZLkQx/60HbzbtiwIUmycuXKHHjgge943BUrVuSLX/xi1fHenu/1119PmzZtdjoTwPsl8AF2UwMGDMjDDz+cxYsXp1u3blWBOWrUqBx66KG5+eab07Jly9x3332ZPn36do9t0KDBux730ksvzWc+85ncddddadq0ab75zW9m9erV2+2zcuXKHHzwwUneitT9999/h+O0bds2AwYMqLpUpratXLlyu68bN26c1q1bZ//999/uB5BSqZSVK1duF9A7W48kmTRpUh577LHce++9OeCAA7Ju3bocffTRO7zR+Z20bdv2HS+FSpIPf/jDueGGG3LUUUe953EAaopLdAB2UwMHDszcuXPz0EMPZeDAgVX3r1+/Pi1atEiLFi2yaNGijB07dpeOu379+uy9995p2rRpFixY8I6X39x+++3ZuHFjXnzxxUyYMCF9+/bdYZ/+/ftn9uzZ+eUvf5nKysps2rQp8+bNy5/+9Kddf7HV8Mgjj+Sll17Kxo0bc+utt+bkk09Ow4YNc+qpp+bxxx/P3Llzs2XLltxzzz1p0qRJOnfu/K7H+tCHPrTd7wVYv359mjRpktatW2fjxo353ve+V+25TjzxxPzlL3/Jfffdl82bN+eNN97Is88+myQ588wz8/3vfz/Lly9PkqxatWqH9zsA1DSBD7CbOuCAA9K5c+ds3LgxJ510UtX9V1xxRSZPnpwjjzwyV1999TvG9858/etfz2233ZbOnTvnhz/8YU499dQd9jnmmGPSp0+fnHPOOTn33HPTrVu3HfZp27Ztbr/99u0++ebuu+/Otm3bdv3FVsOAAQMycuTIdO3aNZs3b85VV12VJOnQoUNuuummfOMb38hxxx2X2bNn54477kiTJk3e9Vjnn39+xowZky5duuTuu+/OwIED065du3Tv3j39+vXLEUccUe25WrZsmXvuuSezZ89O165dc/LJJ1d9mtDZZ5+dXr165dxzz03nzp1zxhlnZMGCBR9sIQDeQ4NSdf79EQDq0bBhw9K/f/8MGTKkvkcB2O05gw8AAAUi8AEAoEBcogMAAAXiDD4AABSIz8GvYRs3bs4bb2yq7zEKpWXLpta0hlnT2mFda541rXnWtOZZ09phXXduv/3K33WbM/g1rFGjhvU9QuFY05pnTWuHda151rTmWdOaZ01rh3V9/wQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUSKP6HqBojvvqA/U9AgAAtWza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UCACHwAACkTgAwBAgQh8AAAokL/LwH/hhRfy+OOPV91+7LHHcuedd9bjRAAAUDMEfpKTTjop559/fj1OBAAANaNRXT7Zvffem/HjxydJBg8enHPOOScPP/xw7r777jRo0CAdO3bMTTfdlNdeey1f//rXs2zZsiTJqFGjsv/+++eCCy7I5MmTkyR33313NmzYkIsvvjjDhg1Lx44dM3/+/FRWVuaGG27I4YcfngULFuSb3/xmNm3alL322is33HBDDjjggNx22215880389RTT+Xzn/983nzzzSxcuDDXXHNNXnnllXz1q1/N6tWrs88+++TGG29Mu3btMnLkyLRs2TILFy7MX/7yl1x22WU55ZRT6nL5AADgPdVZ4C9cuDATJkzIQw89lFKplDPOOCOHHXZYxowZk7Fjx2afffbJmjVrkiTXX399jj766Pzwhz9MZWVlNmzYkL/+9a87Pf6bb76ZiRMnZv78+fnqV7+ayZMnp0OHDnnggQfSqFGjzJkzJ7fccktGjx6dSy65pCrok2TChAlVx7n++uszaNCgDBo0KOPGjcv111+f22+/PUny5z//OQ8++GD++Mc/5sILLxT4AADsduos8J966qn07t07zZs3T5L06dMnCxcuzCmnnJJ99tknSdKqVaskyW9+85t85zvfSZI0bNgw5eXl7xn4/fr1S5IcffTReeONN7J27dqsX78+V1xxRV5++eU0aNAgW7Zsec85n3766YwePTpJMmDAgNx0001V23r37p2ysrL88z//c1577bVdXAEAAKh9e8w1+I0aNcq2bduqbm/atGm77Q0aNNjh9q233ppjjz02kydPzpgxY7J58+YPNEOTJk0+0OMBAKC21Vngd+nSJTNnzszGjRuzYcOGzJw5M506dcq0adOyevXqJKm6ROf444/Pgw8+mCSprKzMunXrsu++++b111/P6tWrs3nz5vziF7/Y7vhTp05Nkvz2t79NeXl5ysvLs27durRp0yZJ8vOf/7xq3xYtWmT9+vXvOGfnzp0zZcqUJMmkSZPSpUuXmlsEAACoZXV2ic7HP/7xnHbaaRkyZEiSt95ke9RRR+WCCy7IsGHDUlZWlkMPPTTf+ta3ctVVV+Xqq6/O+PHjU1ZWllGjRqVz58754he/mCFDhqRNmzbp0KHDdsdv2rRpBg4cmK1bt+aGG25Ikpx33nkZOXJkxowZk549e1bte+yxx+bOO+/MgAED8vnPf36741x99dW58sorc/fdd1e9yRYAAPYUDUqlUqm+h/ighg0blssvvzyHHXZYfY+SrqO71vcIAMDfeHjotFo7dqtWzbNmzYZaO/7fK+u6c/vtV/6u2/aYa/ABAID3Vqefg19b7r///voeAQAAdgvO4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKP+aNRsAAA+DSURBVBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQBrV9wBF84sLnsiaNRvqe4xCadWquTWtYda0dljXmmdNa541heJzBh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIEIfAAAKBCBDwAABdKovgcomjkn9arvEQCgTh06fnJ9jwD8H87gAwBAgQh8AAAoEIEPAAAFIvABAKBABD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8AAAoEIEPAAAFIvABAKBABD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8AAAoEIEPAAAFIvABAKBABD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8AAAoEIEPAAAFIvABAKBABD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8AAAoEIEPAAAFIvABAKBABD4AABSIwAcAgAIR+AAAUCACHwAACkTgAwBAgQh8AAAokEb1PUB9+sIXvpA//elP2bRpU84+++x86lOfyn/913/lrrvuSnl5eQ455JA0adIk11xzTVatWpWvf/3rWbFiRZLkq1/9ao466qh6fgUAALC9v+vAv+GGG9KqVau8+eabGTx4cE488cSMGTMmEyZMSIsWLfLZz342hxxySJLkm9/8Zj772c+mS5cuWbFiRYYPH55HH320nl8BAABs7+868O+///7893//d5Jk5cqVmThxYo4++ui0atUqSXLKKadkyZIlSZI5c+bkpZdeqnrsG2+8kfXr16dFixZ1PjcAALybv9vAnzdvXubMmZP/9//+X5o1a5Zhw4alQ4cOWbRo0Tvuv23btjz00ENp2rRpHU8KAADV93f7Jtt169Zl7733TrNmzbJo0aI888wz2bBhQ+bPn5+//vWv2bp1a2bMmFG1f7du3XL//fdX3X7hhRfqY2wAANipv9vA79GjR7Zu3ZpTTz01N998c4444oi0adMmn//85zNkyJCceeaZ+chHPpLy8vIkyVVXXZWFCxemoqIiffv2zdixY+v5FQAAwI4alEqlUn0PsTt5+7r6rVu35qKLLsrpp5+ePn36VPvxj/foWYvTAcDu59Dxk+t7hHrVqlXzrFmzob7HKBzrunP77Vf+rtv+bq/Bfzc/+MEPMmfOnGzatCndunVL796963skAACoNoH/N6644or6HgEAAN63v9tr8AEAoIgEPgAAFIjABwCAAhH4AABQIAIfAAAKROADAECBCHwAACgQgQ8AAAUi8AEAoEAEPgAAFIjABwCAAhH4AABQIAIfAAAKROADAECBCHwAACgQgQ8AAAUi8AEAoEAEPgAAFIjABwCAAhH4AABQIAIfAAAKROADAECBCHwAACgQgQ8AAAUi8AEAoEAEPgAAFIjABwCAAhH4AABQIAIfAAAKROADAECBCHwAACgQgQ8AAAUi8AEAoEAEPgAAFIjABwCAAmlU3wMUzQmPzcqaNRvqe4xCadWquTWtYda0dljXmmdNa541heJzBh8AAApE4AMAQIEIfAAAKBCBDwAABSLwAQCgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AMAQIE0KJVKpfoeAgAAqBnO4AMAQIEIfAAAKBCBDwAABSLwAQCgQAT++/DEE0/k5JNPTp8+fXLnnXfusH3z5s350pe+lD59+mTIkCF55ZVX6mHKPc97rev8+fMzaNCgHHrooZk2bVo9TLjnea81vffee9O3b99UVFTks5/9bJYvX14PU+5Z3mtNx44dm4qKigwYMCBnnnlmXnrppXqYcs/zXuv6tunTp6djx4557rnn6nC6PdN7remECRNy3HHHZcCAARkwYED+67/+qx6m3LNU5/t06tSp6du3b/r165dLL720jifc87zXmt5www1V36Mnn3xyunTpUg9T7oFK7JKtW7eWTjrppNLSpUtLmzZtKlVUVJRefPHF7fb56U9/Wrr66qtLpVKpNHny5NKIESPqY9Q9SnXWddmyZaUXXnihdNlll5UeffTRepp0z1GdNZ07d25pw4YNpVKpVHrggQd8r76H6qzpunXrqr6eOXNm6dxzz63rMfc41VnXUumttf30pz9dGjJkSGnBggX1MOmeozprOn78+NK1115bTxPueaqzposXLy4NGDCgtGbNmlKpVCq99tpr9THqHqO6f/bf9pOf/KQ0cuTIOpxwz+UM/i5asGBB/vEf/zHt27dPkyZN0q9fvzz22GPb7TNr1qwMGjQoSXLyySdn7ty5Kfk00p2qzroecMABOeSQQ1JW5tu2Oqqzpscdd1yaNWuWJDniiCPypz/9qT5G3WNUZ01btmxZ9fXGjRvToEGDuh5zj1OddU2SW2+9NZ/73OfStGnTephyz1LdNaX6qrOmDz30UM4666zsvffeSZJ99923PkbdY+zq9+mUKVPyb//2b3U44Z5LKe2iV199NR/+8Ierbrdp0yavvvrqDvu0bds2SdKoUaOUl5dn9erVdTrnnqY668qu2dU1HTduXHr06FEXo+2xqrumDzzwQHr37p2bbropX/va1+pyxD1Sddb1+eefz5/+9KeceOKJdTzdnqm636szZsxIRUVFLrnkkqxcubIuR9zjVGdNlyxZksWLF2fo0KE544wz8sQTT9T1mHuUXfl7avny5XnllVdy3HHH1dV4ezSBD2TixIlZuHBhzjvvvPoepRDOOuuszJw5M1/5ylcyZsyY+h5nj7dt27Z861vfyhVXXFHfoxTKv/7rv2bWrFmZNGlSTjjhBOtbAyorK/Pyyy/n/vvvz80335yrr746a9eure+xCmHKlCk5+eST07Bhw/oeZY8g8HdRmzZttruM4dVXX02bNm122OftMyFbt27NunXr0rp16zqdc09TnXVl11R3TefMmZM77rgjY8aMSZMmTepyxD3Orn6f9uvXLzNnzqyL0fZo77Wu69evzx/+8IecffbZ6dWrV5555plceOGF3mi7E9X5Xm3dunXVn/khQ4bk+eefr9MZ9zTV/fu/V69eady4cdq3b59/+qd/ypIlS+p40j3Hrvw/derUqenXr19djbbHE/i76LDDDsuSJUuybNmybN68OVOmTEmvXr2226dXr175+c9/nuStT3w47rjjXIf7Hqqzruya6qzp7373u1xzzTUZM2aMa0WroTpr+n//Mv/FL36Rf/zHf6zjKfc877Wu5eXlmTdvXmbNmpVZs2bliCOOyJgxY3LYYYfV49S7t+p8r/75z3+u+nrWrFk5+OCD63rMPUp11rR379558sknkySrVq3KkiVL0r59+/oYd49Q3b/7Fy1alLVr16Zz5871MOWeqVF9D7CnadSoUa655pqcd955qayszOmnn55/+Zd/ya233ppOnTrlpJNOyuDBg3PZZZelT58+2XvvvXPLLbfU99i7veqs64IFC3LRRRdl7dq1mT17dkaPHp0pU6bU9+i7reqs6Xe+851s2LAhI0aMSJK0bds2d9xxRz1Pvvuqzpr+9Kc/zdy5c9OoUaP8wz/8Q7797W/X99i7veqsK7umOmt6//33Z9asWWnYsGH23nvv3HjjjfU99m6tOmvavXv3/PrXv07fvn3TsGHDXH755f4Ffyeq+2f/7Y8edbK0+hqUfLwLAAAUhkt0AACgQAQ+AAAUiMAHAIACEfgAAFAgAh8AAApE4AOwg6FDh9bp873yyiuZNGlSnT4nQFEJfAB28LOf/azOnmvr1q1Zvnx5Jk+eXGfPCVBkPgcfgB107tw5Tz/9dObNm5fRo0envLw8f/jDH3Lqqafmox/9aH7yk59k06ZN+eEPf5gDDzwwI0eOTJMmTbJw4cKsX78+I0eOzL/+679m06ZNGTVqVBYuXJiGDRtm5MiROe644zJhwoTMmDEjGzZsyLZt27J58+YsWrQoBxxwQAYNGpTevXvn8ssvz8aNG5MkV199dY488sjMmzcvP/jBD9K6dev84Q9/yMc//vF897vfTYMGDbJgwYLccMMN2bBhQ5o0aZL77rsvzZo1y3e/+908+eST2bx5c84666w6/9cJgLrmN9kCsFO///3vM3Xq1LRq1SonnXRShgwZknHjxuU///M/c//99+eqq65Kkixfvjzjxo3L0qVLc/bZZ+eEE07IAw88kCSZNGlSFi1alOHDh2f69OlJkt/97nd55JFH0qpVq8ybNy/33HNPfvSjHyVJNm7cmHvvvTdNmzbNkiVL8uUvfzkTJkyoetyUKVOy//7758wzz8xTTz2Vww8/PP/xH/+RW265JYcffnjeeOON7LXXXhk3blzKy8szfvz4bN68OUOHDk3Xrl3Tvn37elhJgLoh8AHYqcMOOyz7779/kuTAAw9M165dkyQf/ehHM2/evKr9Tj311JSVleWf/umf0r59+/zxj3/MU089lc985jNJkoMPPjjt2rXL4sWLkyRdu3ZNq1at3vE5t27dmuuuuy6///3vU1ZWliVLllRtO/zww/PhD384SXLIIYdk+fLlKS8vz3777ZfDDz88SdKyZcskya9//ev87//+b9UPFevWrcvLL78s8IFCE/gA7FSTJk2qvi4rK6u6XVZWlsrKyqptDRo02O5xf3v7bzVr1uxdt91333350Ic+lIkTJ2bbtm1V4f638zRs2HC7Gf5WqVTK1772tXTv3n2nswAUiTfZAlAjpk2blm3btmXp0qVZtmxZDjrooHTp0qXq03EWL16clStXpkOHDjs8tkWLFlm/fn3V7XXr1mW//fZLWVlZJk6cuNOIT5KDDjoof/nLX7JgwYIkyRtvvJGtW7emW7duGTt2bLZs2VI1w4YNG2rqJQPslpzBB6BGtG3bNoMHD8769etz7bXXpmnTpvn0pz+dUaNGpaKiIg0bNsyNN9643Rn4t3Xs2DFlZWXp379/TjvttHz605/OxRdfnIcffjjdu3dP8+bNd/rcTZo0yS233JLrr78+b775Zvbaa6/ce++9GTJkSJYvX57TTjstpVIprVu3zu23315bSwCwW/ApOgB8YCNHjsyJJ56YU045pb5HAfi75xIdAAAoEGfwAQCgQJzBBwCAAhH4AABQIAIfAAAKROADAECBCHwAACiQ/w/fkfQasjkongAAAABJRU5ErkJggg==\n", "text/plain": [ "
    " ] @@ -1280,89 +1378,89 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ - "from cobra.evaluation.model_evaluator import Evaluator" + "y_true = basetable[basetable[\"split\"] == \"selection\"][\"TARGET\"].values\n", + "y_pred = model.score_model(basetable[basetable[\"split\"] == \"selection\"])" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ - "y_true = basetable[basetable[\"split\"] == \"selection\"][\"TARGET\"].values\n", - "y_pred = model.score_model(basetable[basetable[\"split\"] == \"selection\"])" + "evaluator = Evaluator()\n", + "evaluator.fit(y_true, y_pred)" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 29, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "accuracy 0.798628\n", + "AUC 0.882509\n", + "precision 0.555125\n", + "recall 0.797176\n", + "F1 0.654488\n", + "lift at 0.05 3.420000\n", + "dtype: float64" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "evaluator = Evaluator(y_true, y_pred)" + "# Get various scalar metrics such as accuracy, AUC, precision, recall, ...\n", + "evaluator.get_scalar_scalar_metrics()" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 36, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
    " ] }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best cutoff value for probability is: 0.28123688299785216\n" - ] } ], "source": [ - "evaluator.plotROCCurve()" + "evaluator.plot_roc_curve(dim=(8, 5))" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 31, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
    " ] }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Precision: 0.712\n", - "Accuracy: 0.836\n", - "Recall: 0.526\n", - "F1 Score: 0.605\n", - "Lift at top 10.0%: 3.32\n", - "AUC: 0.885\n" - ] } ], "source": [ - "evaluator.plotConfusionMatrix()" + "evaluator.plot_confusion_matrix(dim=(8, 5))" ] }, { @@ -1372,7 +1470,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfoAAAFACAYAAABURL5aAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nOzdeZxN9f/A8de5+52FMWOsEdkjZR1ShCghS6gUpfyMokyWSioKKYS+lkqWRFHEVFRkq0gzRkKWFmUZMZjFLHe/9/z+uLnXNdYx487yfj4eHjWfc88973vmzn3fz66oqqoihBBCiGJJE+wAhBBCCFFwJNELIYQQxZgkeiGEEKIYk0QvhBBCFGOS6IUQQohiTBK9EEIIUYxJoheikKpTpw79+vUr0GskJCRQp04dZs6cWaDXKaxK+usXJYMu2AEIkRcHDx7kk08+ISEhgePHj2O324mIiODmm2+mQ4cOdOvWDYPBEOwwgy45OZn27dvTo0cP3nzzzWCHc1X++ecfli1bRmJiIseOHSMnJ4fQ0FBuvPFGmjZtSufOnWnQoEGwwxSi0JNEL4qcWbNmMXv2bDweD40aNaJHjx6EhIRw+vRpEhMTefnll1m6dCkrV64MdqiFXsOGDfn6668pU6ZMsEPxUVWV2bNn+37H9evXp1OnTkRERJCTk8Pvv//O4sWLWbBgAa+++iqPPPJInq9VGF+/EPlNEr0oUt577z1mzpxJxYoVeeedd7j11ltzPWbTpk0sWLAgCNEVPWazmRo1agQ7jACzZ8/2/Y7ffvttmjRpkusxqampLFq0iKysrGu6VmF8/ULkN+mjF0VGcnIys2bNQq/XM3fu3AsmeYC2bdsyf/5838+X64dt164d7dq1CyhbuXIlderUYeXKlWzdupW+ffvSqFEjWrRowejRo8nMzARg3759xMbG0qxZMxo1asTgwYNJTk6+omucNXPmTOrUqUNCQsJl70FKSgqzZs3ioYceolWrVjRo0IA77riDESNG8Ndff+V63vbt2wOwatUq6tSp4/t3trXjQvfm3nvvpUGDBqSlpV0whrlz51KnTh2WLFkSUH7ixAlef/112rdvT4MGDYiJiWHw4MHs3r37sq/rrKNHj/Luu++i1+v54IMPLpjkAaKiohg+fDgDBw4MKP/nn3+YOnUqPXv2pEWLFjRo0IC2bdvyyiuvcOLEiVzPc7H3Rr9+/ahTpw4ul4v33nuPjh070qBBA9q0acOUKVNwOBy5nispKYnBgwfTunVrGjRoQKtWrejTpw+zZs264tcvREGQRC+KjJUrV+J0OunYsSO1a9e+5GPzq39+48aNxMbGEhkZyUMPPUS1atVYuXIlQ4YM4ddff6Vv3764XC569epF48aN2bRpE4MHD8bj8eTL9c+XlJTEBx98QKlSpejYsSOPPfYYt912G2vXrqV3794cOHDA99jmzZvTv39/AOrWrcvQoUN9/+rVq3fRa/To0QOn08maNWsueDw+Ph69Xk+XLl18ZXv37qVbt2588sknVK9enX79+tG2bVuSkpLo27cv33///RW9vs8//xyXy8U999xDrVq1Lvt4nS6wUfK7775j2bJlVKxYkS5dutCvXz9q1KjB8uXL6dWrFykpKVcUx1kjRoxgyZIlNGnShIcffhiTycS8efMYO3ZswON++OEH+vXrx44dO2jZsiVPPPEE7du3x2Aw8Mknn1zVNYXIb9J0L4qMHTt2ANCyZcvrds2NGzfy4Ycf0rx5cwA8Hg9PPvkkP/30E4MGDeL111/n/vvv9z3+pZde4vPPP2fjxo3cfffd+R5PixYt2Lp1K2FhYQHlBw4c4OGHH2bq1KnMmzcPgJiYGCpXrsxHH31EvXr1eOaZZ67oGt26dWPGjBmsWrUq16j/3bt3c/DgQTp27EhERAQALpeLuLg4LBYLH330ke9egbcFolevXowZM4aNGzde9gvYL7/84nudedGtWzcef/zxXNfZsmUL//d//8ecOXN47bXXrvj5jh49yurVq32v9bnnnqNbt27Ex8czfPhwoqOjAVi+fDkej4fFixdTt27dgOe4WMuIENeL1OhFkXHq1CkAypcvf92u2blz54DEpdFo6NatGwC1atUKSPIA3bt3BwioWeenqKioXEkevDX2mJgYEhIScDqd13SNChUq0LJlS/bu3cuff/4ZcCw+Ph7wv06AzZs3c+TIER599NGAewXe39XAgQM5deoU27Ztu+y1T58+7TvvfMnJycycOTPg34cffpjrehf6MnHHHXdQs2ZNtmzZctkYzjVy5EhfkgcICQmha9eueDwefvvtt1yPNxqNucoiIyOv6ppC5Dep0QtxCReavlWuXDkA6tevn+vY2QR1of7g/LJ5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\n", + "image/png": "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\n", "text/plain": [ "
    " ] @@ -1382,17 +1480,17 @@ } ], "source": [ - "evaluator.plotCumulativeGains()" + "evaluator.plot_cumulative_gains(dim=(8, 5))" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 33, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfkAAAFZCAYAAACbqlYKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8GearUAAAgAElEQVR4nO3deVyVdaLH8e9hURlxHwGXMnFBB4p4qankSMCgFiKukeVWmterQqWTa1fNsbyVaeUyudwarRlDb4hNetOCVNJySzOXsuxa4gIqmDtw8Ll/dGU6HURUOAd+ft6vl688z/M7z/M9J+R7nuc8i82yLEsAAMA4Hu4OAAAAygclDwCAoSh5AAAMRckDAGAoSh4AAENR8gAAGIqSB1AkKChIAwcOLNd1bN26VUFBQZo7d265rqe8XOs9OnnypMaPH6/OnTurdevWCgoK0tmzZ92QEPgXL3cHACqLQ4cO6R//+Ie2bt2q48ePKy8vT7Vr19Yf/vAHxcTEKD4+XlWqVHF3TLfLzMxUdHS0evXqpf/8z/90d5xSCQoKkiR9++23N72MCRMmaPPmzYqNjVWTJk1ks9lUtWpVDRw4UNu2bbulZQM3i5IHSmHevHmaP3++rly5orCwMPXq1Uu/+93vdOrUKW3btk3PPfecli9frpSUFHdHrfDuuecerV27VnXq1HF3lJuydu1a+fj4OEzLz8/Xli1bFB4erldffdVNyQBnlDxwHW+++abmzp2rBg0a6PXXX1doaKjTmE8//VRvvfWWG9JVPj4+PmrWrJm7Y9y04rKfOnVKV65ckZ+fnxsSAdfGd/JACTIzMzVv3jx5e3tr0aJFxRa8JEVGRuq//uu/ih5f73vnqKgoRUVFOUxLSUlRUFCQUlJStHnzZj366KMKCwtThw4dNHHixKLvd/fv369/+7d/U7t27RQWFqYRI0YoMzOzVOu4au7cuQoKCtLWrVuv+x5kZWVp3rx5euSRR3T//fcrJCREnTp10tixY/X99987LTc6OlqStGrVKgUFBRX9ubqXo7j3plu3bgoJCVFOTk6xGRYtWqSgoCC9++67DtNPnDih6dOnKzo6WiEhIWrfvr1GjBihPXv2XPd13azfficfFRWlyMhISY6vecKECQoKCtK2bduKnnf1T3kf9wBcxZY8UIKUlBQVFBQoNjZWLVu2LHFsWX0fn56erg0bNuiBBx7QI488ol27diklJUWZmZkaO3ashgwZojZt2qhv3746ePCgPv30U2VmZuqDDz6Qh0fZf27fsWOHFi9erPbt26tLly763e9+px9//FHr1q1Tenq6li9frlatWkmS7rvvPg0aNEjLli1Tq1at9Kc//aloOa1bt77mOnr16qXZs2drzZo1xRZgamqqvL291b1796Jp+/bt0xNPPKGff/5ZnTp1UpcuXZSbm6tPPvlEjz76qObPn6+IiIgyfCeKN2jQIB09etTpNbdu3VqNGjXSqlWrdPToUY0ePbroOY0aNSr3XIBEyQMl2rlzpySpY8eOLltnenq6/va3v+m+++6TJF25ckVDhw7Vli1bNHz4cE2fPl09evQoGj9p0iS9//77Sk9PdyjVstKhQwdt3rxZvr6+DtO/+eYb9e/fX7NmzdKSJUskSe3bt1ejRo20bNkytW7dWomJiaVaR3x8vF577TWtWrXKqeT37NmjQ4cOqUuXLqpdu7YkyW636+mnn9bFixe1bNmyovdK+mXPQ9++fTV58mSlp6eX+8GQQ4YMUWZmZrGv+U9/+pO2bdumo0ePlvq9AMoSu+uBEpw8eVKS5O/v77J1xsbGOpSWh4eH4uPjJUktWrRwKHhJ6tmzp6RfSrc81KtXz6ngJalVq1Zq3769tm7dqoKCgltaR0BAgDp27Kh9+/bpu+++c5iXmpoq6V+vU5I2bNign376SQMGDHB4r6Rf/l8NGzZMJ0+e1Oeff35LuYDKji15oIIJCQlxmnb1gK7g4GCneVc/gJw4caLcMm3YsEHvvfee9u7dq9zcXNntdof5ubm5t3zQWa9evbR582atWrVK48aNk/TLUetr1qxRvXr1HHa97969W5J07NixYo97OHz4sKRfTnt0xS57oKKi5IES1K9fX4cOHVJWVpbL1lmjRg2naZ6ented99viLStLly7Viy++qFq1aik8PFwNGjSQj4+PbDabPvnkE33zzTfKz8+/5fXExMTI19dXH3zwgcaOHStPT09t2LBBZ86c0eDBg+Xl9a9fV2fOnJEkffTRRyUu8+LFi7ecC6jMKHmgBG3atNEXX3yhL774Qv369Sv1864eAHet4j179qxq1qxZJhmvxWazXXM3emmvxGa32zVv3jzVr19fKSkpTlvrV7eoy0K1atX04IMP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ef/7v/+rQYMGlUkoAABw626o5C3Luua8/Px8eXp63nIgAABQNq57dH1mZqaOHDlS9Pjrr7922mWfl5en999/Xw0aNCj7hAAA4KZct+RTU1M1b9482Ww22Ww2zZgxw2GL3mazybIseXl5acqUKeUaFgAAlN51S75379667777ZFmWBg8erClTpqh58+YOY6pUqaK77rpLtWvXLregAADgxly35Hv27Km3335bwcHBatiwodq0aaOgoCBXZAMAALfgugfeXbx4Ufn5+ZKkY8eOKS8vr9xDAQCAW3fdLflGjRppxYoVRUW/f//+Eou+Xbt2ZZcOAADctOuW/JNPPqmpU6cqNTVVNptNzz//fLHjLMuSzWbTgQMHyjwkAAC4cdct+b59+6pz5846fPiwBg0apOeee07NmjVzRTYAAHALSnUXOj8/P/n5+alXr16KiIjQHXfcUd65AADALbqhW83OnDmzvHIAAIAydt2Snzdvnvr16yd/f3/NmzevxLE2m02jRo0qs3AAAODmlarkO3fuTMkDAFDJXLfkv/nmm2L/DgAleXJBjrsjaPHIuu6OALjVDd2FDgAAVB7X3ZJv1aqVbDZbqRZms9m0f//+Ww4FAABu3XVLftSoUaUueQAAUHFct+QTExNdkQMAAJQxvpMHAMBQlDwAAIZyaclPnDhRHTt2VPfu3Yudv3XrVrVp00bx8fGKj4+/7nn5AADg2m7osra3qnfv3howYIDGjx9/zTFt27bVwoULXZgKAAAzuXRLvl27dqpVq5YrVwkAwG3LpVvypbF792716NFDfn5+Gj9+vFq0aFHsuOTkZCUnJztNT0hIUEJCQnnHBACgwqtQJR8cHKz09HRVr15dGzdu1KhRo7R+/fpix1LmAACUrEIdXe/r66vq1atLkiIiImS325WT4/7rXwMAUBlVqJI/efKkLMuSJO3Zs0dXrlxRnTp13JwKAIDKyaW768eMGaNt27YpNzdXnTt3VmJioux2uySpf//+WrdunZYvXy5PT09Vq1ZNs2fP5pK6AADcJJeW/OzZs0ucP2DAAA0YMMBFaQAAMFuF2l0PAADKToU6uh4AXOnJBRXjwN7FI+u6OwIMxZY8AACGouQBADAUJQ8AgKEoeQAADEXJAwBgKEoeAABDUfIAABiKkgcAwFCUPAAAhqLkAQAwFCUPAIChKHkAAAzFDWoA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zmn79u1F72OVKlVUs2ZNN6cq2eeff6477rhDjRo1cncUJ4WFhbp8+bLsdrsuX74sPz8/d0dycOjQId1zzz3y8fGRl5eX2rVrp/Xr17s7lqTif4+npaWpZ8+ekqSePXvqk08+cUe0UqPkUSQzM1MHDhxQaGiou6M4KSwsVHx8vMLDwxUeHl4hM7744ot69tln5eFR8f9ZDR06VL1793a6yVNFkJmZqbp162rixInq2bOnJk+erIsXy/6uXWVpzZo16t69u7tjOPH399cTTzyhyMhIderUSb6+vurUqZO7Yzlo2bKldu7cqdzcXF26dEmbNm2qkPcguer06dNFH5Tq16+v06dPuzlRySr+byO4xIULF5SUlKRJkybJ19fX3XGceHp6avXq1dq4caP27NmjgwcPujuSg08//VR169atFOfPLl++XKtWrdLixYv197//Xdu3b3d3JAd2u1379+9X//79lZqaKh8fHy1atMjdsa4pPz9f6enp6tatm7ujOPn555+VlpamtLQ0ZWRk6NKlS1q9erW7Yzlo1qyZhg0bpqFDh2rYsGFq1apVpfigLP2yl7Ei7lX8tcrxTqJcFRQUKCkpSXFxcerSpYu745SoZs2aat++vTIyMtwdxcGXX36p9PR0RUVFacyYMfriiy/05z//2d2xinX1plD16tVTTEyM0+2e3S0gIEABAQFFe2u6deum/fv3uznVtW3atEnBwcH6/e9/7+4oTrZs2aLGjRurbt268vb2VpcuXbRr1y53x3LSr18/paSk6O9//7tq1aqlu+66y92RrqlevXrKzs6WJGVnZzvdBr2ioeRvc5ZlafLkyQoMDNTjjz/u7jjFysnJ0dmzZyVJly9f1pYtWxQYGOjmVI7Gjh2rTZs2KT09XbNnz1aHDh00a9Ysd8dycvHiRZ0/f77o75s3b1aLFi3cnMpR/fr1FRAQoB9++EHSL993N2vWzM2prm3NmjWKjY11d4xiNWzYUF999ZUuXboky7Iq7Ht5dZf3sWPHtH79esXFxbk50bVFRUUpNTVVkpSamqro6Gg3JyoZp9CVkzFjxmjbtm3Kzc1V586dlZiYqH79+rk7lpOdO3dq9erVatmypeLj4yX9kv239xdwp+zsbE2YMEGFhYWyLEvdunVTZGSku2NVSqdPn9aoUaMk/XKcQ/fu3dW5c2c3p3L2H//xH/rzn/+sgoIC3XHHHZo5c6a7IxXr4sWL2rJli6ZPn+7uKMUKDQ1V165d1atXL3l5eal169ZKSEhwdywniYmJOnPmjLy8vDR16tQKc6Blcb/Hhw8frqefflr//d//rYYNG+q1115zd8wScVlbAAAMxe56AAAMRckDAGAoSh4AAENR8gAAGIqSBwDAUJQ8AEnSwIEDNXDgwDJf7oQJExQVFVX0ODMzU0FBQRXy7neAaThPHkC5GjlypAYNGuTuGMBtiZIHUK7uvPNOd0cAblvsrgduQ2vWrFG3bt0UEhKi2NhYffzxx05jcnJyNGXKFP3xj39USEiIunXrVuxd644cOaJnn31W999/v0JCQhQdHa0ZM2YUzf/t7vpr2bZtmwYPHqywsDDde++9Gjp0aIW7ERFQ2bAlD9xmtmzZorFjx+qBBx7QhAkTlJOToxdeeEF2u11NmzaVJJ0/f179+/dXXl6eEhMT1bhxY2VkZGjatGnKz88v+u7+yJEj6tevn3x8fJSUlKQmTZro+PHj+uyzz24o04YNGzRy5EhFRETolVdekSQtWbJEjz32mD744AM1aNCgbN8E4DZByQO3mTfeeEOBgYFasGBB0S09AwMDlZCQUFTyS5cu1bFjx/TPf/6z6I5g4eHhOnfunObNm6f+/fvLy8tLc+fOVV5enlavXl10dztJ6tWr1w1leuGFF9SuXTv99a9/LZrWoUMHRUdH66233tLkyZNv8VUDtyd21wO3kcLCQu3du1ddu3Z1uGf3vffeq0aNGhU9zsjIUGhoqBo3biy73V70p1OnTjpz5oy+//57SdLmzZv1wAMPOBT8jTp8+LB++uknxcXFOayrWrVqCgsL044dO27+BQO3ObbkgdtIbm6uCgoKir33+a+n5eTk6Mcff1RwcHCxyzlz5kzRfwMCAm4p09XbjE6ePLnYLfaGDRve0vKB2xklD9xG6tSpI29vb506dcpp3qlTp4q25mvXrq26deteczf51d36derUUVZW1i1lql27tiRp7Nix6tixo9N8b2/vW1o+cDuj5IHbiKenp0JCQrRu3TolJiYW7bL/6quvdPTo0aKS/+Mf/6h3331XDRs2VL169a65vPvvv1/r169Xdna2/Pz8bipTYGCgGjVqpO+++07Dhw+/qWUAKB4lD9xmkpKS9MQTT2jkyJF65JFHlJOTo7lz56p+/fpFY4YMGaK1a9fq0Ucf1ZAhQ9S0aVNdunRJP/zwg3bs2FF0gFxiYqI2btyoRx55RCNGjNCdd96prKwsZWRkaNasWaXKY7PZNHXqVI0cOVIFBQV68MEHVadOHZ06dUq7du1Sw4YN9fjjj5fLewGYjpIHbjPh4eGaNWuW5s6dq9GjR6tJkyaaNGmSli1bVjSmRo0aeu+99zR//nwtXrxY2dnZqlGjhpo2baouXboUjWvcuLFWrFih1157Ta+++qouXrwof39/RUdH31CmiIgIvfvuu3rzzTf13HPP6fLly6pfv75CQ0P10EMPldlrB243NsuyLHeHAAAAZY9T6AAAMBQlDwCAoSh5AAAMRckDAGAoSh4AAENR8gAAGIqSBwDAUJQ8AACGouQBADDU/wFklTkwkWsPCAAAAABJRU5ErkJggg==\n", "text/plain": [ "
    " ] @@ -1402,19 +1500,19 @@ } ], "source": [ - "evaluator.plotCumulativeResponse()" + "evaluator.plot_lift_curve(dim=(8, 5))" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 34, "metadata": {}, "outputs": [ { "data": { - "image/png": "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c+cK/CUUt6qcn7nkup87gKIh5AIolJCQEM2cOVPSHyGsU6dOTr9+9nr/+c9/1KtXL02dOtVhfeXKle2P9YqPj3f65/ft27drxowZLqr+5kVERNjnxE6bNk2GYeRqM3XqVO3evbvI58h5hNn27dt15MiRXNvPnDmjRx555IbHCQ4OliT7kw9ulpeXl/7nf/5HkrR27VotXrzY/gSKvKYqSNLQoUNVq1YtSdKTTz6Z68a1P9u+ffsNf6fcVc7PXHLdzx1A0TAnF0ChRUVF6cSJE3r++ed1+vRpde7cWT169FD//v3VsGFDVapUSWlpaTp8+LDWr1+vTz/9VFlZWbm+6lWSYmJi9H//939KTU3VXXfdpeeee05hYWFKS0vT+vXrNXv2bLVp00Y7duwwoae5BQQEaMCAAVqyZIk2btyo++67TzExMQoMDNTx48e1cOFCrVixQhEREUWueciQIVq7dq39Q8Szzz5r/4a5HTt26PXXX1dKSorCw8PzfZ5sRESEtmzZoj179mjKlCnq3bu3/fnGvr6+RfqK2cGDB2v+/Pm6fPmynnjiCUlSYGCg7rnnnjz38fb21rJly9S5c2ddvHhRXbt21cCBAxUZGam6desqOztbp06d0r59+7Ry5UolJyfrzTfftN/cVhQXL14s0BdK1K5dW127di3yef4sIiLC/t9jx47VhAkTFBwcbJ/GUKdOHfuHJADFzACAIlqxYoVRp04dQ9INl8aNGxsbN27MdYysrCwjMjIyz/2aNm1qnDp1yv564sSJuY4xceJE+/b8DB061JBkhIaG5tuuU6dOhiSjU6dOTrenpKQY9evXz7PmgQMHGps2bbK/3rJlS6FriYqKyvP4Hh4exvTp02/Y7xMnThhVqlRxeow/9y2/n+/1srOzjVq1ajkca/To0fnuk2Pnzp259s1ree+99wp0zD8LDQ0t0PFzlv79+9v3vdHPMz4+3r796NGjedbw17/+Nc/z5bcfANdiugKAInvggQd06NAhLVq0SA8//LDCwsJUuXJllS1bVlWqVFGrVq30+OOP6/PPP1dycrJ69OiR6xhlypTR8uXLNWvWLN15550qX768ypcvr2bNmumll17Srl27FBQUZELv8hYYGKhdu3bpmWeeUf369eXt7a0qVaro7rvv1gcffKAlS5bIw8Pjps7x7rvvauHChbrrrrvk5+cn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SkhI0MyZMzV37txirxkAAADuwy1Hcv/s3LlzkqQqVark2Wbnzp3q1q2bw7qePXtq586dTttnZGTo/PnzDktGRobrigYAAIBp3D7kZmdna8yYMerQoYOaNGmSZ7uUlBQFBgY6rAsMDFRKSorT9nFxcfL393dY4uLiXFo7AAAAzOGW0xWuFxMTowMHDmj79u0uPe748eM1btw4h3Xe3t4uPQcAAADM4dYhd9SoUVq3bp22bdummjVr5ts2KChIqampDutSU1MVFBTktL23tzehFgAAwKLccrqCYRgaNWqUVq5cqc8//1x169a94T7h4eHavHmzw7qEhASFh4cXV5kAAABwU245khsTE6PFixdr9erV8vPzs8+r9ff3l6+vryRpyJAhCgkJsc+jHT16tDp16qTXXntNffr00dKlS7V3717NmzfPtH4AAADAHG45kjtnzhydO3dOnTt3VnBwsH358MMP7W2OHz+uU6dO2V9HRERo8eLFmjdvnpo3b67ly5dr1apV+d6sBgAAAGtyy5FcwzBu2CYxMTHXugEDBmjAgAHFUBEAAABuJW45kgsAAADcDEIuAAAALIeQCwAAAMsh5AIAAMByCLkAAACwHEIuAAAALIeQCwAAAMsh5AIAAMBy3PLLIADATNGz08wuoUjmP17F7BIAwG0wkgsAAADLIeQCAADAcm4q5L7//vvasWPHDdt99dVXev/992/mVAAAAECB3VTIHTZsmN5+++0btnvnnXcUFRV1M6cCAAAACqxEpitkZ2fLZrOVxKkAAACAkgm5P/74oypWrFgSpwIAAAAK/wixSZMmObxOSkrKtS7HtWvXdOjQIW3btk3du3cvWoUAAABAIRU65MbGxspms8kwDNlsNiUlJSkpKSnffQICAvSvf/2ryEUCAAAAhVHokBsfHy9JMgxDw4cPV8eOHTVixAinbb28vFSjRg21b99e3t7eN1cpAAAAUECFCrmTJk1SixYtdN9990mS3nvvPfXu3VtDhw4tluIAAACAoijUjWexsbFatWqV/fXWrVv13XffubwoAAAA4GYUKuR6eHjo6tWrxVULAAAA4BKFCrnBwcHas2ePLl++XFz1AAAAADetUHNyIyMjNXPmTFWvXl0BAQGSpOXLlysxMfGG+9psNv3www9FKhIAAAAojEKF3ClTpkiSVq9erWPHjslms+nixYu6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\n", + "image/png": "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\n", "text/plain": [ - "
    " + "
    " ] }, "metadata": {}, @@ -1422,7 +1520,7 @@ } ], "source": [ - "evaluator.plotLift()" + "evaluator.plot_cumulative_response_curve(dim=(8, 5))" ] } ], From c1fb00276f4a7180bc129d7c14d8a5628de507ff Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 31 Mar 2020 14:36:44 +0200 Subject: [PATCH 85/98] Clean up setup and requirements.txt --- requirements.txt | 12 ++++++------ setup.py | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index 784dea5..c5d375c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -pandas==0.25.1 -matplotlib==3.0.2 -scipy==1.2.0 -seaborn==0.9.0 -numpy==1.17.2 -scikit_learn==0.22.1 +numpy>=1.17.2 +pandas>=0.25.1 +scipy>=1.2.0 +scikit_learn>=0.22.1 +matplotlib>=3.0.2 +seaborn>=0.9.0 diff --git a/setup.py b/setup.py index a13d0d8..5944027 100644 --- a/setup.py +++ b/setup.py @@ -10,11 +10,9 @@ "cobra.model_building", "cobra.evaluation"], url="https://github.com/PythonPredictions", - #long_description=long_description, # TO DO - #long_description_content_type="text/markdown", install_requires=[ - "pandas>=0.25.1", "numpy>=1.17.2", + "pandas>=0.25.1", "scipy>=1.2.0", "scikit_learn>=0.22.1", "matplotlib>=3.0.2", From 6774f99498a15f1db0a73e0fa460945643887999 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 31 Mar 2020 16:04:58 +0200 Subject: [PATCH 86/98] Refactor plotting functions from evaluation - Rename performance_curves.py to plotting_utils.py - Remove predictor_quality.py and move content to plotting_utils - Make style of plots compatible with the ones from Evaluator - README and examples.ipynb are modified accordingly --- README.md | 4 +- cobra/evaluation/__init__.py | 11 +- cobra/evaluation/performance_curves.py | 45 -------- cobra/evaluation/plotting_utils.py | 142 +++++++++++++++++++++++++ cobra/evaluation/predictor_quality.py | 80 -------------- examples/examples.ipynb | 6 +- 6 files changed, 153 insertions(+), 135 deletions(-) delete mode 100644 cobra/evaluation/performance_curves.py create mode 100644 cobra/evaluation/plotting_utils.py delete mode 100644 cobra/evaluation/predictor_quality.py diff --git a/README.md b/README.md index b723c29..a6ac9d7 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Once these PIG tables are computed, we can start with the _univariate preselecti ```python from cobra.model_building import univariate_selection -from cobra.evaluation import plot_predictor_quality +from cobra.evaluation import plot_univariate_predictor_quality from cobra.evaluation import plot_correlation_matrix # Get list of predictor names to use for univariate_selection @@ -136,7 +136,7 @@ df_auc = univariate_selection.compute_univariate_preselection( ) # Plot df_auc to get a horizontal barplot: -plot_predictor_quality(df_auc) +plot_univariate_predictor_quality(df_auc) # compute correlations between preprocessed predictors: df_corr = (univariate_selection diff --git a/cobra/evaluation/__init__.py b/cobra/evaluation/__init__.py index b7e5f8e..c648cb2 100644 --- a/cobra/evaluation/__init__.py +++ b/cobra/evaluation/__init__.py @@ -2,11 +2,12 @@ from .pigs_tables import compute_pig_table from .pigs_tables import plot_pig_graph -from .performance_curves import plot_performance_curves +from .plotting_utils import plot_performance_curves +from .plotting_utils import plot_variable_importance + +from .plotting_utils import plot_univariate_predictor_quality +from .plotting_utils import plot_correlation_matrix -from .predictor_quality import plot_variable_importance -from .predictor_quality import plot_predictor_quality -from .predictor_quality import plot_correlation_matrix from .evaluator import Evaluator @@ -15,6 +16,6 @@ "plot_pig_graph", "plot_performance_curves", "plot_variable_importance", - "plot_predictor_quality", + "plot_univariate_predictor_quality", "plot_correlation_matrix", "Evaluator"] diff --git a/cobra/evaluation/performance_curves.py b/cobra/evaluation/performance_curves.py deleted file mode 100644 index 0ff5aa8..0000000 --- a/cobra/evaluation/performance_curves.py +++ /dev/null @@ -1,45 +0,0 @@ -# third party imports -import numpy as np -import pandas as pd - -import matplotlib.pyplot as plt - - -def plot_performance_curves(model_performance: pd.DataFrame, - dim: tuple=(12, 8)): - """Plot performance curves generated by the forward feature selection - for the train-selection-validation sets - - Parameters - ---------- - model_performance : pd.DataFrame - contains train-selection-validation performance for each model trained - in the forward feature selection - dim : tuple, optional - tuple with width and lentgh of the plot - """ - highest_auc = np.round(max(max(model_performance['train_performance']), - max(model_performance['selection_performance']), - max(model_performance['validation_performance']) - ), 1) - - fig, ax = plt.subplots(figsize=dim) - - plt.plot(model_performance['train_performance'], marker=".", markersize=20, - linewidth=3, label='AUC train') - plt.plot(model_performance['selection_performance'], marker=".", - markersize=20, linewidth=3, label='AUC selection') - plt.plot(model_performance['validation_performance'], marker=".", - markersize=20, linewidth=3, label='AUC validation') - # Set x/yticks - ax.set_xticks(np.arange(len(model_performance['last_added_predictor']) - + 1)) - ax.set_xticklabels(model_performance['last_added_predictor'].tolist(), - rotation=40, ha='right') - ax.set_yticks(np.arange(0.5, highest_auc + 0.02, 0.05)) - #Make Pretty - ax.legend(loc='lower right') - fig.suptitle('Performance curves - forward feature selection', - fontsize=20) - plt.ylabel('Model performance') - plt.show() diff --git a/cobra/evaluation/plotting_utils.py b/cobra/evaluation/plotting_utils.py new file mode 100644 index 0000000..8b71ab7 --- /dev/null +++ b/cobra/evaluation/plotting_utils.py @@ -0,0 +1,142 @@ +# third party imports +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt +import seaborn as sns + + +def plot_univariate_predictor_quality(df_auc: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot univariate quality of the predictors + + Parameters + ---------- + df_auc : pd.DatFrame + Contains for each variable the train auc and selection auc allong with + a boolean indicating whether or not it is selected based on the + criteria + dim : tuple, optional + tuple with width and lentgh of the plot + """ + + df = (df_auc[df_auc["preselection"]] + .sort_values(by='AUC train', ascending=False)) + + df = pd.melt(df, id_vars=["predictor"], + value_vars=["AUC train", "AUC selection"], + var_name="split", + value_name="AUC") + + # plot data + with plt.style.context("seaborn-ticks"): + fig, ax = plt.subplots(figsize=dim) + + ax = sns.barplot(x="AUC", y="predictor", hue="split", data=df) + ax.set_title('Univariate Quality of Predictors') + + # Set Axis - make them pretty + sns.despine(ax=ax, right=True) + + # Remove white lines from the second axis + ax.grid(False) + + plt.show() + + +def plot_correlation_matrix(df_corr: pd.DataFrame, + dim: tuple=(12, 8)): + """Plot correlation matrix amongst the predictors + + Parameters + ---------- + df_corr : pd.DataFrame + Correlation matrix + dim : tuple, optional + tuple with width and lentgh of the plot + """ + fig, ax = plt.subplots(figsize=dim) + ax = sns.heatmap(df_corr, cmap='Blues') + ax.set_title('Correlation Matrix') + plt.show() + + +def plot_performance_curves(model_performance: pd.DataFrame, + dim: tuple=(12, 8), + colors: dict={"train": "#0099bf", + "selection": "#ff9500", + "validation": "#8064a2"}): + """Plot performance curves generated by the forward feature selection + for the train-selection-validation sets + + Parameters + ---------- + model_performance : pd.DataFrame + contains train-selection-validation performance for each model trained + in the forward feature selection + dim : tuple, optional + tuple with width and lentgh of the plot + """ + highest_auc = np.round(max(max(model_performance['train_performance']), + max(model_performance['selection_performance']), + max(model_performance['validation_performance']) + ), 1) + + with plt.style.context("seaborn-whitegrid"): + + fig, ax = plt.subplots(figsize=dim) + + plt.plot(model_performance['train_performance'], marker=".", + markersize=20, linewidth=3, label='AUC train', + color=colors["train"]) + plt.plot(model_performance['selection_performance'], marker=".", + markersize=20, linewidth=3, label='AUC selection', + color=colors["selection"]) + plt.plot(model_performance['validation_performance'], marker=".", + markersize=20, linewidth=3, label='AUC validation', + color=colors["validation"]) + # Set x/yticks + ax.set_xticks(np.arange(len(model_performance['last_added_predictor']) + + 1)) + ax.set_xticklabels(model_performance['last_added_predictor'].tolist(), + rotation=40, ha='right') + ax.set_yticks(np.arange(0.5, highest_auc + 0.02, 0.05)) + #Make Pretty + ax.legend(loc='lower right') + fig.suptitle('Performance curves - forward feature selection', + fontsize=20) + plt.ylabel('Model performance') + plt.show() + + +def plot_variable_importance(df_variable_importance: pd.DataFrame, + title: str=None, + dim: tuple=(12, 8)): + """Plot variable importance of a given model + + Parameters + ---------- + df_variable_importance : pd.DataFrame + DataFrame containing columns predictor and importance + title : str, optional + Title of the plot + dim : tuple, optional + tuple with width and lentgh of the plot + """ + with plt.style.context("seaborn-ticks"): + fig, ax = plt.subplots(figsize=dim) + ax = sns.barplot(x="importance", y="predictor", + data=df_variable_importance, + color="cornflowerblue") + if title: + ax.set_title(title) + else: + ax.set_title("Variable importance") + + # Set Axis - make them pretty + sns.despine(ax=ax, right=True) + + # Remove white lines from the second axis + ax.grid(False) + + plt.show() diff --git a/cobra/evaluation/predictor_quality.py b/cobra/evaluation/predictor_quality.py deleted file mode 100644 index 3fac91f..0000000 --- a/cobra/evaluation/predictor_quality.py +++ /dev/null @@ -1,80 +0,0 @@ -# third party imports -import pandas as pd - -import matplotlib.pyplot as plt -import seaborn as sns - - -def plot_variable_importance(df_variable_importance: pd.DataFrame, - title: str=None, - dim: tuple=(12, 8)): - """Plot variable importance of a given model - - Parameters - ---------- - df_variable_importance : pd.DataFrame - DataFrame containing columns predictor and importance - title : str, optional - Title of the plot - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - # plot data - fig, ax = plt.subplots(figsize=dim) - ax = sns.barplot(x="importance", y="predictor", - data=df_variable_importance) - if title: - ax.set_title(title) - else: - ax.set_title("Variable importance") - plt.show() - - -def plot_predictor_quality(df_auc: pd.DataFrame, - dim: tuple=(12, 8)): - """Plot univariate quality of the predictors - - Parameters - ---------- - df_auc : pd.DatFrame - Contains for each variable the train auc and selection auc allong with - a boolean indicating whether or not it is selected based on the - criteria - dim : tuple, optional - tuple with width and lentgh of the plot - """ - - plt.style.use('seaborn-darkgrid') - - df = (df_auc[df_auc["preselection"]] - .sort_values(by='AUC train', ascending=False)) - - df = pd.melt(df, id_vars=["predictor"], - value_vars=["AUC train", "AUC selection"], - var_name="partition", - value_name="AUC") - - # plots - fig, ax = plt.subplots(figsize=dim) - - ax = sns.barplot(x="AUC", y="predictor", hue="partition", data=df) - ax.set_title('Univariate Quality of Predictors') - plt.show() - - -def plot_correlation_matrix(df_corr: pd.DataFrame, - dim: tuple=(12, 8)): - """Plot correlation matrix amongst the predictors - - Parameters - ---------- - df_corr : pd.DataFrame - Correlation matrix - dim : tuple, optional - tuple with width and lentgh of the plot - """ - fig, ax = plt.subplots(figsize=dim) - ax = sns.heatmap(df_corr, cmap='Blues') - ax.set_title('Correlation Matrix') - plt.show() diff --git a/examples/examples.ipynb b/examples/examples.ipynb index 1cf1527..71fe896 100644 --- a/examples/examples.ipynb +++ b/examples/examples.ipynb @@ -51,7 +51,7 @@ "from cobra.evaluation import generate_pig_tables\n", "from cobra.evaluation import plot_performance_curves\n", "from cobra.evaluation import plot_variable_importance\n", - "from cobra.evaluation import plot_predictor_quality\n", + "from cobra.evaluation import plot_univariate_predictor_quality\n", "from cobra.evaluation import plot_correlation_matrix\n", "from cobra.evaluation import Evaluator" ] @@ -853,7 +853,7 @@ } ], "source": [ - "plot_predictor_quality(df_auc)" + "plot_univariate_predictor_quality(df_auc)" ] }, { @@ -1420,7 +1420,7 @@ ], "source": [ "# Get various scalar metrics such as accuracy, AUC, precision, recall, ...\n", - "evaluator.get_scalar_scalar_metrics()" + "evaluator.scalar_metrics" ] }, { From a68ef3814a4c01c879ac6771e3f3dbd241494bc3 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 31 Mar 2020 16:25:38 +0200 Subject: [PATCH 87/98] Change datatype of Evaluator.scalar_metrics to pd.Series --- cobra/evaluation/evaluator.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 39f8f7c..04b95c1 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -45,7 +45,7 @@ def __init__(self, probability_cutoff: float=None, self.probability_cutoff = probability_cutoff # Placeholder to store fitted output - self._scalar_metrics = None + self.scalar_metrics = None self.roc_curve = None self.confusion_matrix = None self.lift_curve = None @@ -66,7 +66,7 @@ def fit(self, y_true: np.ndarray, y_pred: np.ndarray): for pred in y_pred]) # Compute the various evaluation metrics - self._scalar_metrics = Evaluator.compute_scalar_metrics( + self.scalar_metrics = Evaluator.compute_scalar_metrics( y_true, y_pred, y_pred_b, @@ -79,23 +79,13 @@ def fit(self, y_true: np.ndarray, y_pred: np.ndarray): self.cumulative_gains = Evaluator._compute_cumulative_gains(y_true, y_pred) - def get_scalar_scalar_metrics(self) -> pd.Series: - """Get the evaluation_metrics attribute as a pandas Series - - Returns - ------- - pd.Series - Score of various scalar evaluation metrics for the model - """ - return pd.Series(self._scalar_metrics) - @staticmethod def compute_scalar_metrics(y_true: np.ndarray, y_pred: np.ndarray, y_pred_b: np.ndarray, - lift_at: float) -> dict: + lift_at: float) -> pd.Series: """Convenient function to compute various scalar performance measures - and return them in a dict + and return them in a pd.Series Parameters ---------- @@ -110,10 +100,10 @@ def compute_scalar_metrics(y_true: np.ndarray, Returns ------- - dict + pd.Series contains various performance measures of the model """ - return { + return pd.Series({ "accuracy": accuracy_score(y_true, y_pred_b), "AUC": roc_auc_score(y_true, y_pred), "precision": precision_score(y_true, y_pred_b), @@ -123,7 +113,7 @@ def compute_scalar_metrics(y_true: np.ndarray, ._compute_lift(y_true=y_true, y_pred=y_pred, lift_at=lift_at), 2) - } + }) def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): """Plot ROC curves of the model @@ -142,7 +132,7 @@ def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): raise NotFittedError(msg.format(self.__class__.__name__)) - auc = self._scalar_metrics["AUC"] + auc = float(self.scalar_metrics.loc["AUC"]) with plt.style.context("seaborn-whitegrid"): @@ -188,7 +178,7 @@ def plot_confusion_matrix(self, path: str=None, dim: tuple=(12, 8), fig, ax = plt.subplots(figsize=dim) ax = sns.heatmap(self.confusion_matrix, annot=self.confusion_matrix.astype(str), - fmt="s", cmap="Reds", + fmt="s", cmap="Blues", xticklabels=labels, yticklabels=labels) ax.set_title("Confusion matrix", fontsize=20) From ac42d57c98e9921462ba177b98f23af20e818ba0 Mon Sep 17 00:00:00 2001 From: JanBenisek Date: Mon, 6 Apr 2020 08:06:51 +0200 Subject: [PATCH 88/98] updated gitignore Added .vscode/ settings to ignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5a98460..a24d78a 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ ENV/ # mypy .mypy_cache/ + +# vscode settings +.vscode/ From 69041d3da31b426cd2fa24b030587b69dd662720 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 20 Apr 2020 08:43:07 +0200 Subject: [PATCH 89/98] Fix typo in README --- README.md | 2 +- cobra/evaluation/evaluator.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6ac9d7..2a517b7 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ evaluator = Evaluator() evaluator.fit(y_true, y_pred) # Automatically find the best cut-off probability # Get various scalar metrics such as accuracy, AUC, precision, recall, ... -evaluator.get_scalar_scalar_metrics() +evaluator.scalar_metrics # Plot non-scalar evaluation metrics: evaluator.plot_roc_curve() diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 04b95c1..b2f2531 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -109,10 +109,10 @@ def compute_scalar_metrics(y_true: np.ndarray, "precision": precision_score(y_true, y_pred_b), "recall": recall_score(y_true, y_pred_b), "F1": f1_score(y_true, y_pred_b, average=None)[1], - f"lift at {lift_at}": np.round(Evaluator - ._compute_lift(y_true=y_true, - y_pred=y_pred, - lift_at=lift_at), 2) + "lift at".format(lift_at): np.round(Evaluator + ._compute_lift(y_true=y_true, + y_pred=y_pred, + lift_at=lift_at), 2) }) def plot_roc_curve(self, path: str=None, dim: tuple=(12, 8)): From 0b45b2bb452e9f99293712481f64478cc7533ff1 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Mon, 20 Apr 2020 08:44:33 +0200 Subject: [PATCH 90/98] Clean up repo --- cobra/metrics/__init__.py | 3 - cobra/metrics/all_metrics_plots.py | 527 ----------------------------- 2 files changed, 530 deletions(-) delete mode 100644 cobra/metrics/__init__.py delete mode 100644 cobra/metrics/all_metrics_plots.py diff --git a/cobra/metrics/__init__.py b/cobra/metrics/__init__.py deleted file mode 100644 index 67656f7..0000000 --- a/cobra/metrics/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .all_metrics_plots import Evaluator - -__all__ = ['Evaluator'] \ No newline at end of file diff --git a/cobra/metrics/all_metrics_plots.py b/cobra/metrics/all_metrics_plots.py deleted file mode 100644 index d18bb64..0000000 --- a/cobra/metrics/all_metrics_plots.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -====================================================================================== ---------------------------------------- Evaluation Class code ------------------------ -====================================================================================== -author: jan.benisek@pythonpredictins.com - benoit.vandekerkhove@pythonpredictions.com -date: 23/09/2019 -purpose: library for model evaluation class - -""" -#%% -import numpy as np -import pandas as pd -import seaborn as sns -import matplotlib.pyplot as plt -import sklearn.metrics as mt -from typing import Tuple -#%% - - -class Evaluator(): - ''' - Class to evaluate models - - Parameters - ----------- - y_true : array, shape = [1, n_features] - array with true values - - y_pred_p : array, shape = [1, n_features] - array with predicted values (probabilities) - - lift_at : int , default=0.05 - calculate lift at given level (0-1) - - save_pth : str, default=None - path to where save the plot - - binary_cutoff : float, default=0.5 - cutoff to convert predictions to binary - - ''' - - def __init__(self, y_true: np.ndarray, y_pred_p: np.ndarray, - lift_at: float=0.05, save_pth: str=None, binary_cutoff: int=0.5): - - self.y_true = y_true.flatten() - self.y_pred_p = y_pred_p.flatten() #As probability - self.lift_at = lift_at - self.save_pth = save_pth - self.binary_cutoff = binary_cutoff - - self.y_pred_b = np.where(self.y_pred_p > self.binary_cutoff,1,0) - - - - - '''============================================================= - ----------------------------- PLOTS ---------------------------- - =============================================================''' - def plotROCCurve(self, desc: str=None): - ''' - Plot ROC curve and print best cutoff value - Transform probabilities predictions to bool based on best AUC based cutoff - - Parameters - ---------- - desc : str, default=None - description of the plot, used also as a name of saved plot - - ''' - if desc is None: - desc = '' - - fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) - - #--------------------------- - #Calculate AUC - #-------------------------- - score = mt.roc_auc_score(self.y_true, self.y_pred_p) - - fig, ax = plt.subplots(figsize=(8,5)) - ax.plot(fpr,tpr, color='darkorange', lw=2, label='ROC curve (area = {s:.3})'.format(s=score)) - ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') - ax.set_xlabel('False Positive Rate', fontsize=15) - ax.set_ylabel('True Positive Rate', fontsize=15) - ax.legend(loc="lower right") - ax.set_title('ROC Curve {}' .format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - '''============================================================= - ---------------------------- METRICS --------------------------- - =============================================================''' - - def printPerformance(self): - ''' - Print out performance measures - - EV.printPerformance() - %timeit 2min 19s ± 784 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) - ''' - - if self.threshold != np.nan : - out_perfo = self._evaluation() - - print('=== Test on', self.test_on, '===') - print('Precision: {s:.3}'.format(s=out_perfo['precision'])) #If we mark customer as a churner, how often we are correct - print('Accuracy: {s:.3}'.format(s=out_perfo['accuracy'])) #Overall performance - print('Recall: {s:.3}'.format(s=out_perfo['recall'])) #How many churners can the model detect - print('F1 Score: {s:.3}'.format(s=out_perfo['F1'])) # 2 * (precision * recall) / (precision + recall) - print('Lift at top {l}%: {s:.3}'.format(l=self.lift_at*100, s=out_perfo['lift'])) # 2 * (precision * recall) / (precision + recall) - print('AUC: {s:.3}'.format(s=out_perfo['AUC'])) # 2 * (precision * recall) / (precision + recall) - - else : - raise ValueError('Please call .plotROCCurve() method first to get the best threshold for probabilities, and try again') - - def plotLift(self, desc : str=None, save_pth : str=None): - ''' - Method plots lift per decile - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- -# inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) - for perc_lift in np.arange(0.05,1.05,0.05)] - - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - plt.style.use('seaborn-darkgrid') - - nrows = len(lifts) - x_labels = [nrows/2-x/2 for x in np.arange(0,nrows,1)] - - #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") - plt.bar(x_labels[::-1], lifts, align='center', color="green", width=0.2) - plt.ylabel('lift', fontsize=15) - plt.xlabel('decile', fontsize=15) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=1, color='darkorange', linestyle='--', - xmin=0.05, xmax=0.9, linewidth=3, label='Baseline') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative Lift {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - - - '''------------------------------------------------------------------- - -------------------------------- UTILS ------------------------------- - -------------------------------------------------------------------''' - def estimateCutoff(self) -> float: - ''' - Estimates optimal cutoff based on maximization of AUC curve - https://stackoverflow.com/questions/28719067/roc-curve-and-cut-off-point-python - - Parameters - ---------- - None - - Returns - ------- - best_cutoff : float - optimal cutoff as a float <0;1> - - ''' - fpr,tpr,thresholds = mt.roc_curve(self.y_true,self.y_pred_p) - i = np.arange(len(tpr)) - roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), - 'threshold' : pd.Series(thresholds, index=i)}) - roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]] - - best_cutoff = list(roc_t['threshold']) - - return best_cutoff[0] - - - def _testA(self, test : np.ndarray, pred : np.ndarray, train_M : np.ndarray) -> Tuple[np.ndarray, np.ndarray]: - ''' - Limits the evaluation to potential A offers - (that a customer has not purchase in the train timeframe) - - Parameters - ---------- - test: true values -> array - pred: predictions as probabilities -> array - train_M : train matrix of interactions -> ndarray - - Output - ------ - testA: vector of interaction on potential A offers -> array - predA: vector of predictions on potential A offers -> array - ''' - - train = train_M.flatten() - testA = np.where(train>0, np.nan, test) - predA = np.where(train>0, np.nan, pred) - testA = testA[testA>=0] - predA = predA[predA>=0] - - return testA, predA - - def _evaluation(self): - ''' - Convenient function, returns various performance measures in a dict - - Parameters - ---------- - y_true: true values - y_pred: predictions as booleans - - Output - ------ - Returns dictionary with the measures - ''' - - dict_perfo = {'precision': mt.precision_score(self.y_true, self.y_pred_b), - 'accuracy': mt.accuracy_score(self.y_true, self.y_pred_b), - 'recall': mt.recall_score(self.y_true, self.y_pred_b), - 'F1': mt.f1_score(self.y_true, self.y_pred_b, average=None)[1], - 'lift': np.round(Evaluator.liftCalculator(y_true=self.y_true, - y_pred=self.y_pred_p, - lift_at=self.lift_at),2), - 'AUC': mt.roc_auc_score(self.y_true, self.y_pred_p) - } - return dict_perfo - - @staticmethod - def liftCalculator(y_true : np.ndarray, y_pred : np.ndarray, lift_at : float=0.05, **kwargs) -> float: - ''' - Calculates lift given two arrays on specified level - - Parameters - ---------- - y_true: numpy array with true values - y_pred: numpy array with predictions (probabilities) - lift_at: lift at what top percentage - - Output - ------ - Scalar value, lift. - - 50.3 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) - ''' - #Make sure it is numpy array - y_true_ = np.array(y_true) - y_pred_ = np.array(y_pred) - - #Make sure it has correct shape - y_true_ = y_true_.reshape(len(y_true_),1) - y_pred_ = y_pred_.reshape(len(y_pred_),1) - - #Merge data together - y_data = np.hstack([y_true_, y_pred_]) - - #Calculate necessary variables - nrows = len(y_data) - stop = int(np.floor(nrows*lift_at)) - avg_incidence = np.einsum('ij->j',y_true_)/float(len(y_true_)) - - #Sort and filter data - data_sorted = y_data[y_data[:,1].argsort()[::-1]][:stop,0].reshape(stop, 1) - - #Calculate lift (einsum is very fast way of summing, needs specific shape) - inc_in_top_n = np.einsum('ij->j',data_sorted)/float(len(data_sorted)) - - lift = np.round(inc_in_top_n/avg_incidence,2)[0] - - return lift - - '''------------------------------------------------------------------- - ------------------------JUST IN CASE ------------------------------- - -------------------------------------------------------------------''' - - def plotConfusionMatrix(self, labels : list=None, color : str='Reds', - save_pth : str=None, desc : str=None): - ''' - Plot Confusion matrix - - Parameters - ---------- - y_test: True values of target y - pred: Predicted values of target y, boolean - labels: labels for the matrix, if empty, values from y_test_ are used - color: Color of the matrix, its a cmap, so many values possible - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if labels is None: - labels = [str(lab) for lab in np.unique(self.y_true)] - - if desc is None: - desc = '' - - cm = mt.confusion_matrix(self.y_true, self.y_pred_b) - - fig, ax = plt.subplots(figsize=(8,5)) - ax = sns.heatmap(cm, annot=cm.astype(str), fmt="s", cmap=color, xticklabels=labels, yticklabels=labels) - ax.set_title('Confusion matrix {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeGains(self, save_pth : str=None, desc : str=None): - ''' - Functions plot cumulative gains - - Parameters - ---------- - save: whether plot should be saved (if yes, then now shown) - desc: description of the plot, used also as a name of saved plot - ''' - if desc is None: - desc = '' - - #--------------------------- - #Calculate cumulative gains - #-------------------------- - nrows = len(self.y_true) - npositives = self.y_true.sum() - df_y_pred = pd.DataFrame({"y":self.y_true, "y_pred":self.y_pred_p}).sort_values(by='y_pred', ascending=False).reset_index(drop=True) - cgains = [0] - for stop in (np.linspace(0.01,1,100)*nrows).astype(int): - cgains.append(round(df_y_pred.loc[:stop,'y'].sum()/npositives*max(100,1),2)) - - #--------------------------- - #Plot it - #--------------------------- - plt.style.use('seaborn-darkgrid') - fig, ax_cgains = plt.subplots(figsize=(8,5)) - ax_cgains.plot(cgains, color='blue', linewidth=3, label='cumulative gains') - ax_cgains.plot(ax_cgains.get_xlim(), ax_cgains.get_ylim(), linewidth=3, ls="--", color="darkorange", label='random selection') - ax_cgains.set_title('Cumulative Gains ' + desc, fontsize=20) - - ax_cgains.set_title('Cumulative Gains {}' .format(desc), fontsize=20) - #Format axes - ax_cgains.set_xlim([0,100]) - ax_cgains.set_ylim([0,100]) - #Format ticks - ax_cgains.set_yticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_yticks()]) - ax_cgains.set_xticklabels(['{:3.0f}%'.format(x) for x in ax_cgains.get_xticks()]) - #Legend - ax_cgains.legend(loc='lower right') - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - - def plotCumulativeResponse(self, desc : str=None, save_pth : str=None): - #--------------------- - #-- CALCULATE LIFT --- - #--------------------- - inc_rate = self.y_true.mean() - lifts = [Evaluator.liftCalculator(y_true=self.y_true, y_pred=self.y_pred_p, lift_at=perc_lift) - for perc_lift in np.arange(0.1,1.1,0.1)] - lifts = np.array(lifts)*inc_rate*100 - #--------------------- - #------- PLOT -------- - #--------------------- - if desc is None: - desc = '' - - fig, ax = plt.subplots(figsize=(8,5)) - #plt.style.use('seaborn-darkgrid') - plt.style.use('default') - - nrows = len(lifts) - x_labels = [nrows-x for x in np.arange(0,nrows,1)] - - #plt.bar(x_labels[::-1], df['lift'].values.tolist(), align='center', color="cornflowerblue") - plt.bar(x_labels[::-1], lifts, align='center', color="#00ccff") - plt.ylabel('response (%)', fontsize=16) - plt.xlabel('decile', fontsize=16) - ax.set_xticks(x_labels) - ax.set_xticklabels(x_labels) - - plt.axhline(y=inc_rate*100, color='#ff9500', linestyle='--', - xmin=0.05, xmax=0.95, linewidth=3, label='Incidence') - - #Legend - ax.legend(loc='upper right') - - ##Set Axis - make them pretty - sns.despine(ax=ax, right=True, left=True) - - #Remove white lines from the second axis - ax.grid(False) - - ##Description - ax.set_title('Cumulative response {}'.format(desc), fontsize=20) - - if save_pth is not None: - plt.savefig(save_pth, format='png', dpi=300, bbox_inches='tight') - - plt.show() - -def plotIncidence(df, variable, dim=(12,8)): - ''' - Method plots Incidence plot on train partition - Returns plot - ---------------------------------------------------- - df: dataframe with cleaned, binned, partitioned and prepared data - variable: variable for which the incidence plot will be shown` - dim: tuple with width and lentgh of the plot - ---------------------------------------------------- - ''' - def masterOfOrder(x): - ''' - Function converts interval or string (category) to a number, so the incidence plot can be orderd. - In case of interval -> '(151, 361]' to integer 151. - In case of string -> order is alphabetical - Missings and Non-significants are always put at the end - - Parameters - ---------- - x: value to be converted - - Output - ------ - Order of given value - ''' - x_split = x.split(',')[0] - replace_strings = (('...', '0'),('Missing','999999999999'), ('Non-significants','999999999999')) - for repl_str in replace_strings: - x_split = x_split.replace(repl_str[0], repl_str[1]) - x_split = x_split.strip("()[]") - - try: - order = float(x_split) - except: - LETTERS = {letter: index for index, letter in enumerate(ascii_lowercase, start=1)} - order = LETTERS[x[0].lower()] - - return order - - plt.style.use('seaborn-darkgrid') - - #---------------------------------- - #------ Prepare the data -------- - #---------------------------------- - #Set up the variable and dataframe - var_prefix = 'B_' + variable - df_plt = df[['TARGET', var_prefix]][df['PARTITION'] == 'train'].copy() - - #Aggregate the data - avg_inc_rate = df_plt['TARGET'].mean() - - aggregations = { - 'bin_inc_rate': 'mean', - 'bin_size': 'count' - } - df_plt = df_plt.groupby(var_prefix, as_index=False)['TARGET'].agg(aggregations) - df_plt['avg_inc_rate'] = avg_inc_rate - - #create a sort column and sort by it - df_plt['sort_by'] = df_plt[var_prefix].apply(lambda x: masterOfOrder(x)) - df_plt.sort_values(by='sort_by', ascending=True, inplace=True) - df_plt.reset_index(inplace=True) - - #---------------------------------- - #----- Plot the incidence ------- - #---------------------------------- - fig, ax = plt.subplots(figsize=dim) - ##First Axis - #Bin size - y_pos = np.arange(len(df_plt[var_prefix])) - plt.bar(y_pos, df_plt['bin_size'].values.tolist(), align='center', color="cornflowerblue") - plt.xticks(y_pos, df_plt[var_prefix]) - plt.ylabel('Bin Size') - plt.xlabel(variable + ' Bins') - - max_inc = max(df_plt['bin_inc_rate']) - - ##Second Axis - ax2 = ax.twinx() - #incidence rate per bin - plt.plot(df_plt['bin_inc_rate'], color="darkorange", marker=".", markersize=20, linewidth=3, label='incidence rate per bin') - plt.plot(df_plt['avg_inc_rate'], color="dimgrey", linewidth=4, label='average incidence rate') - ax2.plot(np.nan, "cornflowerblue", linewidth=6, label = 'bin size') #dummy line to have label on second axis from first - ax2.set_yticks(np.arange(0, max_inc+0.05, 0.05)) - ax2.set_yticklabels(['{:3.1f}%'.format(x*100) for x in ax2.get_yticks()]) - plt.ylabel('Incidence') - - ##Set Axis - sns.despine(ax=ax, right=True, left=True) - sns.despine(ax=ax2, left=True, right=False) - ax2.spines['right'].set_color('white') - - #remove white line from second grid axes - #the white lines are reguler, Spyder sometimes fails to visualize it (try to export the pic!) - ax2.grid(False) - - ##Description - fig.suptitle('Incidence Plot - ' + variable, fontsize=20, y=1.02) - ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=1, mode="expand", borderaxespad=0.) - plt.show() From 9a5299ac771ede3fc48d8c67110fd0b5c10abe46 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 21 Apr 2020 10:06:35 +0200 Subject: [PATCH 91/98] Fix random state in models, bug fixing in evaluator --- cobra/evaluation/evaluator.py | 5 +++-- cobra/model_building/models.py | 2 +- cobra/utils.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index b2f2531..2a6a657 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -109,8 +109,9 @@ def compute_scalar_metrics(y_true: np.ndarray, "precision": precision_score(y_true, y_pred_b), "recall": recall_score(y_true, y_pred_b), "F1": f1_score(y_true, y_pred_b, average=None)[1], - "lift at".format(lift_at): np.round(Evaluator - ._compute_lift(y_true=y_true, + "lift at {}".format(lift_at): np.round(Evaluator + ._compute_lift( + y_true=y_true, y_pred=y_pred, lift_at=lift_at), 2) }) diff --git a/cobra/model_building/models.py b/cobra/model_building/models.py index a736c96..3ce6964 100644 --- a/cobra/model_building/models.py +++ b/cobra/model_building/models.py @@ -24,7 +24,7 @@ class LogisticRegressionModel: def __init__(self): self.logit = LogisticRegression(fit_intercept=True, C=1e9, - solver='liblinear') + solver='liblinear', random_state=42) # placeholder to keep track of a list of predictors self.predictors = [] self._eval_metrics_by_split = {} diff --git a/cobra/utils.py b/cobra/utils.py index 8c55b7d..b21e6d9 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -70,4 +70,5 @@ def clean_predictor_name(predictor: str) -> str: Returns: str: Description """ - return predictor.replace("_enc", "").replace("_bin", "") + return (predictor.replace("_enc", "").replace("_bin", "") + .replace("_processed", "")) From 8d8d5534b04f0adfc9b199d06146b1c17af3b0e6 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 21 Apr 2020 14:05:26 +0200 Subject: [PATCH 92/98] Fix bug in KBinsDiscretizer.set_attributes_from_dict --- cobra/preprocessing/kbins_discretizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/preprocessing/kbins_discretizer.py b/cobra/preprocessing/kbins_discretizer.py index 4ad153d..dc5baba 100644 --- a/cobra/preprocessing/kbins_discretizer.py +++ b/cobra/preprocessing/kbins_discretizer.py @@ -161,7 +161,7 @@ def set_attributes_from_dict(self, params: dict): self.set_params(**params) self._bins_by_column = { - key: [tuple(l) for l in value] + key: ([tuple(l) for l in value] if value else None) for key, value in _bins_by_column.items() } From eeddacea6d222d9f24da7038928ca453eea1fdec Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 21 Apr 2020 14:06:29 +0200 Subject: [PATCH 93/98] Modify CategoricalDataProcessor to avoid regrouping of dummy variables --- cobra/preprocessing/categorical_data_processor.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cobra/preprocessing/categorical_data_processor.py b/cobra/preprocessing/categorical_data_processor.py index 5b3a4fc..45af0d6 100644 --- a/cobra/preprocessing/categorical_data_processor.py +++ b/cobra/preprocessing/categorical_data_processor.py @@ -191,6 +191,13 @@ def _fit_column(self, data: pd.DataFrame, column_name: str, unique_categories = list(X.unique()) + # do not merge categories in case of dummies, i.e. 0 and 1 + # (and possibly "Missings") + if (len(unique_categories) == 2 + or (len(unique_categories) == 3 + and "Missing" in unique_categories)): + return set(unique_categories) + # get small categories and add them to the merged category list small_categories = (CategoricalDataProcessor ._get_small_categories( @@ -420,7 +427,8 @@ def _compute_p_value(X: pd.Series, y: pd.Series, category: str, @staticmethod def _replace_categories(data: pd.Series, categories: set) -> pd.Series: - """replace categories in set with "Other" + """replace categories in set with "Other" and transform the remaining + categories to strings to avoid type errors later on in the pipeline Parameters ---------- @@ -434,4 +442,4 @@ def _replace_categories(data: pd.Series, categories: set) -> pd.Series: pd.Series Description """ - return data.apply(lambda x: x if x in categories else "Other") + return data.apply(lambda x: str(x) if x in categories else "Other") From 581f7b4daf753051465700c78bc4aca7410d4cd5 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 28 Apr 2020 14:19:22 +0200 Subject: [PATCH 94/98] Add option to save figure to plotting_utils --- cobra/evaluation/evaluator.py | 6 +++--- cobra/evaluation/plotting_utils.py | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 2a6a657..979753b 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -381,8 +381,8 @@ def _compute_optimal_cutoff(fpr: np.ndarray, tpr: np.ndarray, @staticmethod def _compute_cumulative_gains(y_true: np.ndarray, y_pred: np.ndarray) -> tuple: - """Compute lift of the model per decile, returns x-labels, lifts and - the target incidence to create cummulative response curves + """Compute cumulative gains of the model, returns percentages and + gains cummulative gains curves Code from (https://github.com/reiinakano/scikit-plot/blob/ 2dd3e6a76df77edcbd724c4db25575f70abb57cb/ @@ -398,7 +398,7 @@ def _compute_cumulative_gains(y_true: np.ndarray, Returns ------- tuple - x-labels, lifts per decile and target incidence + x-labels, gains """ # make y_true a boolean vector diff --git a/cobra/evaluation/plotting_utils.py b/cobra/evaluation/plotting_utils.py index 8b71ab7..35a56e7 100644 --- a/cobra/evaluation/plotting_utils.py +++ b/cobra/evaluation/plotting_utils.py @@ -45,7 +45,8 @@ def plot_univariate_predictor_quality(df_auc: pd.DataFrame, def plot_correlation_matrix(df_corr: pd.DataFrame, - dim: tuple=(12, 8)): + dim: tuple=(12, 8), + path: str=None): """Plot correlation matrix amongst the predictors Parameters @@ -54,10 +55,16 @@ def plot_correlation_matrix(df_corr: pd.DataFrame, Correlation matrix dim : tuple, optional tuple with width and lentgh of the plot + path : str, optional + path to store the figure """ fig, ax = plt.subplots(figsize=dim) ax = sns.heatmap(df_corr, cmap='Blues') ax.set_title('Correlation Matrix') + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + plt.show() @@ -111,7 +118,8 @@ def plot_performance_curves(model_performance: pd.DataFrame, def plot_variable_importance(df_variable_importance: pd.DataFrame, title: str=None, - dim: tuple=(12, 8)): + dim: tuple=(12, 8), + path: str=None): """Plot variable importance of a given model Parameters @@ -122,6 +130,8 @@ def plot_variable_importance(df_variable_importance: pd.DataFrame, Title of the plot dim : tuple, optional tuple with width and lentgh of the plot + path : str, optional + path to store the figure """ with plt.style.context("seaborn-ticks"): fig, ax = plt.subplots(figsize=dim) @@ -139,4 +149,7 @@ def plot_variable_importance(df_variable_importance: pd.DataFrame, # Remove white lines from the second axis ax.grid(False) + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + plt.show() From 0a5a7ffcd6db360312eb628f7227f5c031910b0a Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 28 Apr 2020 17:12:19 +0200 Subject: [PATCH 95/98] hotfix of missing value imputation --- cobra/preprocessing/preprocessor.py | 7 ++ cobra/preprocessing/target_encoder.py | 96 +++++++++++++++++----- tests/preprocessing/test_target_encoder.py | 16 ++-- 3 files changed, 92 insertions(+), 27 deletions(-) diff --git a/cobra/preprocessing/preprocessor.py b/cobra/preprocessing/preprocessor.py index 8299b8f..5fb9774 100644 --- a/cobra/preprocessing/preprocessor.py +++ b/cobra/preprocessing/preprocessor.py @@ -80,6 +80,7 @@ def from_params(cls, scale_contingency_table: bool=True, forced_categories: dict={}, weight: float=0.0, + imputation_strategy: str="mean", serialization_path: Optional[str]=None): """Constructor to instantiate PreProcessor from all the parameters that can be set in all its required (attribute) classes. @@ -130,6 +131,12 @@ def from_params(cls, parameter, the bigger the contribution of the overall mean. When set to zero, there is no smoothing (e.g. the pure target incidence is used). + imputation_strategy : str, optional + in case there is a particular column which contains new categories, + the encoding will lead to NULL values which should be imputed. + Valid strategies are to replace with the global mean of the train + set or the min (resp. max) incidence of the categories of that + particular variable. serialization_path : str, optional path to save the pipeline to diff --git a/cobra/preprocessing/target_encoder.py b/cobra/preprocessing/target_encoder.py index ea63e13..9dac042 100644 --- a/cobra/preprocessing/target_encoder.py +++ b/cobra/preprocessing/target_encoder.py @@ -34,22 +34,37 @@ class TargetEncoder(BaseEstimator): Attributes ---------- - columns : list - A list of columns to encode, if None, all string columns will be - encoded. + imputation_strategy : str + in case there is a particular column which contains new categories, + the encoding will lead to NULL values which should be imputed. + Valid strategies are to replace with the global mean of the train + set or the min (resp. max) incidence of the categories of that + particular variable. weight : float Smoothing parameters (non-negative). The higher the value of the parameter, the bigger the contribution of the overall mean. When set to zero, there is no smoothing (e.g. the pure target incidence is used). """ - def __init__(self, weight: float=0.0): + valid_strategies = ("mean", "min", "max") + + def __init__(self, weight: float=0.0, + imputation_strategy: str="mean"): if weight < 0: raise ValueError("The value of weight cannot be smaller than zero") + elif imputation_strategy not in self.valid_strategies: + raise ValueError("Valid options for 'imputation_strategy' are {}." + " Got imputation_strategy={!r} instead" + .format(self.valid_strategies, + imputation_strategy)) self.weight = weight + self.imputation_strategy = imputation_strategy + self._mapping = {} # placeholder for fitted output + # placeholder for the global incidence of the data used for fitting + self._global_mean = None # not implemented yet! # randomized: bool=False, sigma=0.05 @@ -72,6 +87,8 @@ def attributes_to_dict(self) -> dict: for key, value in self._mapping.items() } + params["_global_mean"] = self._global_mean + return params def set_attributes_from_dict(self, params: dict): @@ -88,6 +105,14 @@ def set_attributes_from_dict(self, params: dict): if "weight" in params and type(params["weight"]) == float: self.weight = params["weight"] + if ("imputation_strategy" in params and + params["imputation_strategy"] in self.valid_strategies): + + self.imputation_strategy = params["imputation_strategy"] + + if "_global_mean" in params and type(params["_global_mean"]) == float: + self._global_mean = params["_global_mean"] + _mapping = {} if "_mapping" in params and type(params["_mapping"]) == dict: _mapping = params["_mapping"] @@ -121,7 +146,7 @@ def fit(self, data: pd.DataFrame, column_names: list, # compute global mean (target incidence in case of binary target) y = data[target_column] - global_mean = y.sum() / y.count() + self._global_mean = y.sum() / y.count() for column in column_names: if column not in data.columns: @@ -129,11 +154,9 @@ def fit(self, data: pd.DataFrame, column_names: list, "skipped in fitting" .format(column)) continue - self._mapping[column] = self._fit_column(data[column], y, - global_mean) + self._mapping[column] = self._fit_column(data[column], y) - def _fit_column(self, X: pd.Series, y: pd.Series, - global_mean: float) -> pd.Series: + def _fit_column(self, X: pd.Series, y: pd.Series) -> pd.Series: """Summary Parameters @@ -143,8 +166,6 @@ def _fit_column(self, X: pd.Series, y: pd.Series, categorical variable. y : pd.Series series containing the targets for each observation - global_mean : float - Global mean of the target Returns ------- @@ -158,7 +179,9 @@ def _fit_column(self, X: pd.Series, y: pd.Series, # Q: do we need to do this here or during the transform phase??? # Note if self.weight = 0, we have the ordinary incidence replacement - numerator = stats["count"]*stats["mean"] + self.weight*global_mean + numerator = (stats["count"]*stats["mean"] + + self.weight * self._global_mean) + denominator = stats["count"] + self.weight return numerator/denominator @@ -187,13 +210,12 @@ def transform(self, data: pd.DataFrame, method """ - if len(self._mapping) == 0: + if (len(self._mapping) == 0) or (self._global_mean is None): msg = ("This {} instance is not fitted yet. Call 'fit' with " "appropriate arguments before using this method.") raise NotFittedError(msg.format(self.__class__.__name__)) - new_columns = [] for column in column_names: if column not in data.columns: @@ -205,15 +227,47 @@ def transform(self, data: pd.DataFrame, "and will be skipped".format(column)) continue - new_column = TargetEncoder._clean_column_name(column) + data = self._transform_column(data, column) + + return data + + def _transform_column(self, data: pd.DataFrame, + column_name: str) -> pd.DataFrame: + """Replace (e.g. encode) categories of each column with its average + incidence which was computed when the fit method was called - # Convert dtype to float because when the original dtype - # is of type "category", the resulting dtype is also of type - # "category" - data[new_column] = (data[column].map(self._mapping[column]) - .astype("float")) + Parameters + ---------- + X : pd.DataFrame + data to encode + column_name : str + Name of the column in data to be encoded - new_columns.append(new_column) + Returns + ------- + pd.DataFrame + transformed data + """ + new_column = TargetEncoder._clean_column_name(column_name) + + # Convert dtype to float because when the original dtype + # is of type "category", the resulting dtype is also of type + # "category" + data[new_column] = (data[column_name].map(self._mapping[column_name]) + .astype("float")) + + # In case of categorical data, it could be that new categories will + # emerge which were not present in the train set, so this will result + # in missing values (which should be replaced) + if data[new_column].isnull().sum() > 0: + if self.imputation_strategy == "mean": + data[new_column].fillna(self._global_mean, inplace=True) + elif self.imputation_strategy == "min": + data[new_column].fillna(data[new_column].min(), + inplace=True) + elif self.imputation_strategy == "max": + data[new_column].fillna(data[new_column].max(), + inplace=True) return data diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py index 25b5f3b..2441935 100644 --- a/tests/preprocessing/test_target_encoder.py +++ b/tests/preprocessing/test_target_encoder.py @@ -21,9 +21,13 @@ def test_target_encoder_attributes_to_dict(self): encoder._mapping["variable"] = mapping_data + encoder._global_mean = 0.5 + actual = encoder.attributes_to_dict() expected = {"weight": 0.0, + "imputation_strategy": "mean", + "_global_mean": 0.5, "_mapping": {"variable": { "negative": 0.333333, "neutral": 0.50000, @@ -58,6 +62,7 @@ def test_target_encoder_set_attributes_from_dict(self): encoder = TargetEncoder() data = {"weight": 0.0, + "_global_mean": 0.5, "_mapping": {"variable": { "negative": 0.333333, "neutral": 0.50000, @@ -85,8 +90,8 @@ def test_target_encoder_fit_column(self): 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) encoder = TargetEncoder() - actual = encoder._fit_column(X=df.variable, y=df.target, - global_mean=0.0) + encoder._global_mean = 0.0 + actual = encoder._fit_column(X=df.variable, y=df.target) expected = pd.Series(data=[0.333333, 0.50000, 0.666667], index=["negative", "neutral", "positive"]) @@ -103,11 +108,10 @@ def test_target_encoder_fit_column_global_mean(self): 'neutral'], 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) - global_mean = df.target.sum() / df.target.count() # is 0.5 - encoder = TargetEncoder(weight=1) - actual = encoder._fit_column(X=df.variable, y=df.target, - global_mean=global_mean) + encoder._global_mean = df.target.sum() / df.target.count() # is 0.5 + + actual = encoder._fit_column(X=df.variable, y=df.target) expected = pd.Series(data=[0.375, 0.500, 0.625], index=["negative", "neutral", "positive"]) From f2c71edc9922da31f96e18f7b55919e8c6d346a8 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Tue, 5 May 2020 15:49:24 +0200 Subject: [PATCH 96/98] Add additional unittest for TargetEncoder --- cobra/evaluation/plotting_utils.py | 15 ++++++++++- tests/preprocessing/test_target_encoder.py | 29 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/cobra/evaluation/plotting_utils.py b/cobra/evaluation/plotting_utils.py index 35a56e7..83090b2 100644 --- a/cobra/evaluation/plotting_utils.py +++ b/cobra/evaluation/plotting_utils.py @@ -7,7 +7,8 @@ def plot_univariate_predictor_quality(df_auc: pd.DataFrame, - dim: tuple=(12, 8)): + dim: tuple=(12, 8), + path: str=None): """Plot univariate quality of the predictors Parameters @@ -18,6 +19,8 @@ def plot_univariate_predictor_quality(df_auc: pd.DataFrame, criteria dim : tuple, optional tuple with width and lentgh of the plot + path : str, optional + path to store the figure """ df = (df_auc[df_auc["preselection"]] @@ -41,6 +44,9 @@ def plot_univariate_predictor_quality(df_auc: pd.DataFrame, # Remove white lines from the second axis ax.grid(False) + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + plt.show() @@ -70,6 +76,7 @@ def plot_correlation_matrix(df_corr: pd.DataFrame, def plot_performance_curves(model_performance: pd.DataFrame, dim: tuple=(12, 8), + path: str=None, colors: dict={"train": "#0099bf", "selection": "#ff9500", "validation": "#8064a2"}): @@ -83,6 +90,8 @@ def plot_performance_curves(model_performance: pd.DataFrame, in the forward feature selection dim : tuple, optional tuple with width and lentgh of the plot + path : str, optional + path to store the figure """ highest_auc = np.round(max(max(model_performance['train_performance']), max(model_performance['selection_performance']), @@ -113,6 +122,10 @@ def plot_performance_curves(model_performance: pd.DataFrame, fig.suptitle('Performance curves - forward feature selection', fontsize=20) plt.ylabel('Model performance') + + if path is not None: + plt.savefig(path, format="png", dpi=300, bbox_inches="tight") + plt.show() diff --git a/tests/preprocessing/test_target_encoder.py b/tests/preprocessing/test_target_encoder.py index 2441935..b924bb6 100644 --- a/tests/preprocessing/test_target_encoder.py +++ b/tests/preprocessing/test_target_encoder.py @@ -90,7 +90,7 @@ def test_target_encoder_fit_column(self): 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) encoder = TargetEncoder() - encoder._global_mean = 0.0 + encoder._global_mean = 0.5 actual = encoder._fit_column(X=df.variable, y=df.target) expected = pd.Series(data=[0.333333, 0.50000, 0.666667], @@ -164,6 +164,33 @@ def test_target_encoder_transform(self): pd.testing.assert_frame_equal(actual, expected, check_less_precise=5) + def test_target_encoder_transform_new_category(self): + + df = pd.DataFrame({'variable': ['positive', 'positive', 'negative', + 'neutral', 'negative', 'positive', + 'negative', 'neutral', 'neutral', + 'neutral'], + 'target': [1, 1, 0, 0, 1, 0, 0, 0, 1, 1]}) + + df_appended = df.append({"variable": "new", "target": 1}, + ignore_index=True) + + # inputs of TargetEncoder will be of dtype category + df["variable"] = df["variable"].astype("category") + df_appended["variable"] = df_appended["variable"].astype("category") + + expected = df_appended.copy() + expected["variable_enc"] = [0.666667, 0.666667, 0.333333, 0.50000, + 0.333333, 0.666667, 0.333333, 0.50000, + 0.50000, 0.50000, 0.333333] + + encoder = TargetEncoder(imputation_strategy="min") + encoder.fit(data=df, column_names=["variable"], target_column="target") + actual = encoder.transform(data=df_appended, column_names=["variable"]) + + pd.testing.assert_frame_equal(actual, expected, + check_less_precise=5) + # Tests for _clean_column_name def test_target_encoder_clean_column_name(self): From 3865b36bbf9961fdc5b8682e93aa4b5c48fa3b9a Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Thu, 18 Jun 2020 16:10:45 +0200 Subject: [PATCH 97/98] Add matthews correlation coeff as evaluation metric --- cobra/evaluation/evaluator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cobra/evaluation/evaluator.py b/cobra/evaluation/evaluator.py index 979753b..819f2f5 100644 --- a/cobra/evaluation/evaluator.py +++ b/cobra/evaluation/evaluator.py @@ -11,6 +11,7 @@ from sklearn.metrics import roc_curve from sklearn.metrics import confusion_matrix from sklearn.metrics import roc_auc_score +from sklearn.metrics import matthews_corrcoef from sklearn.exceptions import NotFittedError @@ -109,6 +110,7 @@ def compute_scalar_metrics(y_true: np.ndarray, "precision": precision_score(y_true, y_pred_b), "recall": recall_score(y_true, y_pred_b), "F1": f1_score(y_true, y_pred_b, average=None)[1], + "matthews_corrcoef": matthews_corrcoef(y_true, y_pred_b), "lift at {}".format(lift_at): np.round(Evaluator ._compute_lift( y_true=y_true, From f980a7ddd47e20abf5987bcbbb83291ab885edd2 Mon Sep 17 00:00:00 2001 From: Matthias Roels Date: Fri, 26 Jun 2020 10:23:31 +0200 Subject: [PATCH 98/98] Bug fix in setup.py --- README.md | 8 +++++--- setup.py | 10 +++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2a517b7..e15b01b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ or using conda conda install requirements.txt ``` +__Note__: if you want to install cobra with e.g. pip, you don't have to install all of these requirements as these are automatically installed with cobra itself. + ### Installation As this package is an internal package that is not open-sourced, it is not available through `pip` or `conda`. As a result, the package has to be installed manually using the following steps: @@ -110,9 +112,9 @@ Once the preprocessing pipeline is fitted and applied to your data, we are ready from cobra.evaluation import generate_pig_tables pig_tables = generate_pig_tables(basetable[basetable["split"] == "selection"], - id_column_name, - target_column_name - preprocessed_predictors) + id_column_name=id_column_name, + target_column_name=target_column_name, + preprocessed_predictors=preprocessed_predictors) ``` Once these PIG tables are computed, we can start with the _univariate preselection_: diff --git a/setup.py b/setup.py index 5944027..e92bba1 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,11 @@ -from distutils.core import setup +from setuptools import setup, find_packages setup( name="cobra", version="1.0.0", description="Python Prediction's methodology for predictive analytics", - packages=["cobra", - "cobra.preprocessing", - "cobra.model_building", - "cobra.evaluation"], + packages=find_packages(include=['cobra', 'cobra.*']), url="https://github.com/PythonPredictions", install_requires=[ "numpy>=1.17.2", @@ -16,6 +13,5 @@ "scipy>=1.2.0", "scikit_learn>=0.22.1", "matplotlib>=3.0.2", - "seaborn>=0.9.0"], - python_requires=">=3.6", + "seaborn>=0.9.0"] )