From bcea25e42459730dd41adc3e56bc3641ff481d9a Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Mon, 22 Mar 2021 18:54:53 -0400 Subject: [PATCH 1/9] initial commit Signed-off-by: Kleynhans, Bernard --- feature/selector.py | 98 +++++++++++++++++++-- feature/tree_based.py | 4 +- tests/test_benchmark.py | 186 ++++++++++++++++++++++++++-------------- 3 files changed, 214 insertions(+), 74 deletions(-) diff --git a/feature/selector.py b/feature/selector.py index 59510d6..1ab3df2 100644 --- a/feature/selector.py +++ b/feature/selector.py @@ -22,6 +22,7 @@ from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from sklearn.ensemble import ExtraTreesClassifier, ExtraTreesRegressor from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor +from sklearn.model_selection import KFold from xgboost import XGBClassifier, XGBRegressor from feature.base import _BaseDispatcher, _BaseSupervisedSelector, _BaseUnsupervisedSelector @@ -475,6 +476,7 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, SelectionMethod.Variance]], data: pd.DataFrame, labels: Optional[pd.Series] = None, + cv: Optional[int] = None, output_filename: Optional[str] = None, drop_zero_variance_features: Optional[bool] = True, verbose: bool = False) \ @@ -495,6 +497,8 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, Data of shape (n_samples, n_features) used for feature selection. labels: pd.Series, optional (default=None) The target values (class labels in classification, real numbers in regression). + cv: int, optional (default=None) + Number of folds to use for cross-validation. output_filename: str, optional (default=None) If not None, benchmarking output is saved. If file exists, results are appended, otherwise file is created. @@ -511,6 +515,78 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, check_true(selectors is not None, ValueError("Benchmark selectors cannot be none.")) check_true(data is not None, ValueError("Benchmark data cannot be none.")) + if cv is None: + return _bench(selectors=selectors, + data=data, + labels=labels, + output_filename=output_filename, + drop_zero_variance_features=drop_zero_variance_features, + verbose=verbose) + else: + + # Create K-Fold object + kf = KFold(n_splits=cv, shuffle=True, random_state=Constants.default_seed) + + # Initialize variables + t0 = time() + train_labels, test_labels = None, None + score_df, selected_df, runtime_df = pd.DataFrame(), pd.DataFrame(), pd.DataFrame() + + # Split data into cv-folds and run _bench for each fold + if verbose: + print("\n>>> Running") + for fold, (train_index, _) in enumerate(kf.split(data)): + + if verbose: + print("\tFold", fold, "...") + + # Split data, labels into folds + train_data = data.iloc[train_index] + if labels is not None: + train_labels = labels.iloc[train_index] + + # Run benchmark + score_cv_df, selected_cv_df, runtime_cv_df = _bench(selectors=selectors, + data=train_data, + labels=train_labels, + output_filename=output_filename, + drop_zero_variance_features=drop_zero_variance_features, + verbose=False) + + # Concatenate data frames + score_df = pd.concat((score_df, score_cv_df)) + selected_df = pd.concat((selected_df, selected_cv_df)) + runtime_df = pd.concat((runtime_df, runtime_cv_df)) + + if verbose: + print(f"<<< Done! Time taken: {(time() - t0) / 60:.2f} minutes") + + return score_df, selected_df, runtime_df + + +def _bench(selectors: Dict[str, Union[SelectionMethod.Correlation, + SelectionMethod.Linear, + SelectionMethod.TreeBased, + SelectionMethod.Statistical, + SelectionMethod.Variance]], + data: pd.DataFrame, + labels: Optional[pd.Series] = None, + output_filename: Optional[str] = None, + drop_zero_variance_features: Optional[bool] = True, + verbose: bool = False) \ + -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: + """ + Benchmark with a given set of feature selectors. + Return a tuple of data frames with scores, runtime and selected features for each method. + + Returns + ------- + Tuple of data frames with scores, selected features and runtime for each method. + """ + + check_true(selectors is not None, ValueError("Benchmark selectors cannot be none.")) + check_true(data is not None, ValueError("Benchmark data cannot be none.")) + # Output files if output_filename is not None: output_file = open(output_filename, "a") @@ -552,7 +628,7 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, if verbose: print(f"<<< Done! Time taken: {(time() - t0) / 60:.2f} minutes") - # Convert to series + # Format runtime_df = pd.Series(method_to_runtime).to_frame("runtime").rename_axis("method").reset_index() return score_df, selected_df, runtime_df @@ -568,8 +644,10 @@ def calculate_statistics(scores: pd.DataFrame, ---------- scores: pd.DataFrame Data frame with scores for each feature (index) and selector (columns). + Each feature could have multiple rows from different cross-validation folds. selected: pd.DataFrame Data frame with selection flag for each feature (index) and selector (columns). + Each feature could have multiple rows from different cross-validation folds. columns: list (default=None) List of methods (columns) to include in statistics. If None, all methods (columns) will be used. @@ -584,9 +662,9 @@ def calculate_statistics(scores: pd.DataFrame, check_true(isinstance(scores, pd.DataFrame), ValueError("scores must be a data frame.")) check_true(isinstance(selected, pd.DataFrame), ValueError("selection must be a data frame.")) check_true(scores.shape == selected.shape, ValueError("Shapes of scores and selected data frames must match.")) - check_true(len(scores.index.intersection(selected.index)) == selected.shape[0], + check_true(np.all(scores.index == selected.index), ValueError("Index of score and selection data frames must match.")) - check_true(len(scores.columns.intersection(selected.columns)) == selected.shape[1], + check_true(np.all(scores.columns == selected.columns), ValueError("Columns of score and selection data frames must match.")) # Get columns to use @@ -597,18 +675,18 @@ def calculate_statistics(scores: pd.DataFrame, scores_df = scores[columns].copy() selected_df = selected[columns].copy() + # Group by feature + scores_df = scores_df.groupby(scores_df.index).mean() + selected_df = selected_df.groupby(selected_df.index).mean() + # Drop methods with constant scores if ignore_constant: mask = ~np.isclose(np.var(scores_df, axis=0), 0) scores_df = scores_df.loc[:, mask] selected_df = selected_df.loc[:, mask] - # Sort by index - scores_df.sort_index(inplace=True) - selected_df.sort_index(inplace=True) - # Calculate statistics - stats_df = pd.DataFrame(index=scores.index) + stats_df = pd.DataFrame(index=scores_df.index) stats_df["_score_mean"] = scores_df.mean(axis=1) stats_df["_score_mean_norm"] = normalize_columns(scores_df).mean(axis=1) stats_df["_selection_freq"] = selected_df.sum(axis=1) @@ -632,6 +710,7 @@ def plot_importance(scores: pd.DataFrame, ---------- scores: pd.DataFrame Data frame with scores for each feature (index) and method (columns). + Each feature could have multiple rows from different cross-validation folds. columns: list (default=None) List of methods (columns) to include in statistics. If None, all methods (columns) will be used. @@ -663,6 +742,9 @@ def plot_importance(scores: pd.DataFrame, df = scores[columns].copy() df.fillna(0, inplace=True) + # Group by feature + df = df.groupby(df.index).mean() + # Get normalized scores such that scores for each method sums to 1 if normalize: df = normalize_columns(df) diff --git a/feature/tree_based.py b/feature/tree_based.py index 69ea6d5..d31ccf5 100644 --- a/feature/tree_based.py +++ b/feature/tree_based.py @@ -50,14 +50,14 @@ def dispatch_model(self, labels: pd.Series, *args): # Custom estimator should be compatible with the task if "classification_" in task_str: if isinstance(self.estimator, CatBoost): - if self.estimator._estimator_type is not 'classifier': + if self.estimator._estimator_type != 'classifier': raise TypeError(str(self.estimator) + " cannot be used for task: " + task_str) else: if not isinstance(self.estimator, ClassifierMixin): raise TypeError(str(self.estimator) + " cannot be used for task: " + task_str) else: if isinstance(self.estimator, CatBoost): - if self.estimator._estimator_type is not 'regressor': + if self.estimator._estimator_type != 'regressor': raise TypeError(str(self.estimator) + " cannot be used for task: " + task_str) else: if not isinstance(self.estimator, RegressorMixin): diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 33511d1..2f69c0d 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -17,43 +17,42 @@ class TestBenchmark(BaseTest): - def test_benchmark_regression(self): + num_features = 3 + corr_threshold = 0.5 + alpha = 1000 + tree_params = {"random_state": 123, "n_estimators": 100} + + selectors = { + "corr_pearson": SelectionMethod.Correlation(corr_threshold, method="pearson"), + "corr_kendall": SelectionMethod.Correlation(corr_threshold, method="kendall"), + "corr_spearman": SelectionMethod.Correlation(corr_threshold, method="spearman"), + "univ_anova": SelectionMethod.Statistical(num_features, method="anova"), + "univ_chi_square": SelectionMethod.Statistical(num_features, method="chi_square"), + "univ_mutual_info": SelectionMethod.Statistical(num_features, method="mutual_info"), + "linear": SelectionMethod.Linear(num_features, regularization="none"), + "lasso": SelectionMethod.Linear(num_features, regularization="lasso", alpha=alpha), + "ridge": SelectionMethod.Linear(num_features, regularization="ridge", alpha=alpha), + "random_forest": SelectionMethod.TreeBased(num_features), + "xgboost_clf": SelectionMethod.TreeBased(num_features, estimator=XGBClassifier(**tree_params)), + "xgboost_reg": SelectionMethod.TreeBased(num_features, estimator=XGBRegressor(**tree_params)), + "extra_clf": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesClassifier(**tree_params)), + "extra_reg": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesRegressor(**tree_params)), + "lgbm_clf": SelectionMethod.TreeBased(num_features, estimator=LGBMClassifier(**tree_params)), + "lgbm_reg": SelectionMethod.TreeBased(num_features, estimator=LGBMRegressor(**tree_params)), + "gradient_clf": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingClassifier(**tree_params)), + "gradient_reg": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingRegressor(**tree_params)), + "adaboost_clf": SelectionMethod.TreeBased(num_features, estimator=AdaBoostClassifier(**tree_params)), + "adaboost_reg": SelectionMethod.TreeBased(num_features, estimator=AdaBoostRegressor(**tree_params)), + "catboost_clf": SelectionMethod.TreeBased(num_features, estimator=CatBoostClassifier(**tree_params, silent=True)), + "catboost_reg": SelectionMethod.TreeBased(num_features, estimator=CatBoostRegressor(**tree_params, silent=True)) + } + def test_benchmark_regression(self): data, label = get_data_label(load_boston()) data = data.drop(columns=["CHAS", "NOX", "RM", "DIS", "RAD", "TAX", "PTRATIO", "INDUS"]) - num_features = 3 - corr_threshold = 0.5 - alpha = 1000 - tree_params = {"random_state": 123, "n_estimators": 100} - - selectors = { - "corr_pearson": SelectionMethod.Correlation(corr_threshold, method="pearson"), - "corr_kendall": SelectionMethod.Correlation(corr_threshold, method="kendall"), - "corr_spearman": SelectionMethod.Correlation(corr_threshold, method="spearman"), - "univ_anova": SelectionMethod.Statistical(num_features, method="anova"), - "univ_chi_square": SelectionMethod.Statistical(num_features, method="chi_square"), - "univ_mutual_info": SelectionMethod.Statistical(num_features, method="mutual_info"), - "linear": SelectionMethod.Linear(num_features, regularization="none"), - "lasso": SelectionMethod.Linear(num_features, regularization="lasso", alpha=alpha), - "ridge": SelectionMethod.Linear(num_features, regularization="ridge", alpha=alpha), - "random_forest": SelectionMethod.TreeBased(num_features), - "xgboost_clf": SelectionMethod.TreeBased(num_features, estimator=XGBClassifier(**tree_params)), - "xgboost_reg": SelectionMethod.TreeBased(num_features, estimator=XGBRegressor(**tree_params)), - "extra_clf": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesClassifier(**tree_params)), - "extra_reg": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesRegressor(**tree_params)), - "lgbm_clf": SelectionMethod.TreeBased(num_features, estimator=LGBMClassifier(**tree_params)), - "lgbm_reg": SelectionMethod.TreeBased(num_features, estimator=LGBMRegressor(**tree_params)), - "gradient_clf": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingClassifier(**tree_params)), - "gradient_reg": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingRegressor(**tree_params)), - "adaboost_clf": SelectionMethod.TreeBased(num_features, estimator=AdaBoostClassifier(**tree_params)), - "adaboost_reg": SelectionMethod.TreeBased(num_features, estimator=AdaBoostRegressor(**tree_params)), - "catboost_clf": SelectionMethod.TreeBased(num_features, estimator=CatBoostClassifier(**tree_params, silent=True)), - "catboost_reg": SelectionMethod.TreeBased(num_features, estimator=CatBoostRegressor(**tree_params, silent=True)) - } - # Benchmark - score_df, selected_df, runtime_df = benchmark(selectors, data, label, output_filename=None) + score_df, selected_df, runtime_df = benchmark(self.selectors, data, label, output_filename=None) _ = calculate_statistics(score_df, selected_df) self.assertListAlmostEqual([0.4787777784012165, 0.47170429073431874, 0.5596288196730658, 0.4400410275414326, 0.5674082968785575], @@ -86,42 +85,61 @@ def test_benchmark_regression(self): self.assertListAlmostEqual([0.10947144861974874, 0.020211076089938374, 0.08416074180466389, 0.045604950489313435, 0.7405517829963355], score_df["random_forest"].to_list()) - def test_benchmark_classification(self): + def test_benchmark_regression_cv(self): + data, label = get_data_label(load_boston()) + data = data.drop(columns=["CHAS", "NOX", "RM", "DIS", "RAD", "TAX", "PTRATIO", "INDUS"]) - data, label = get_data_label(load_iris()) + # Benchmark + score_df, selected_df, runtime_df = benchmark(self.selectors, data, label, cv=5, output_filename=None) + _ = calculate_statistics(score_df, selected_df) + + # Aggregate scores from different cv-folds + score_df = score_df.groupby(score_df.index).mean() + + self.assertListAlmostEqual( + [0.5598624197527886, 0.43999689309372514, 0.47947203347292133, 0.5677393697964164, 0.4718904343871402], + score_df["corr_pearson"].to_list()) + + self.assertListAlmostEqual( + [0.5133150872001859, 0.33830236220280874, 0.5355471187677026, 0.4944995007684703, 0.4812959438381611], + score_df["corr_kendall"].to_list()) + + self.assertListAlmostEqual( + [0.6266784101694156, 0.3922216387923788, 0.6538541627239243, 0.598348546553966, 0.5537572894805117], + score_df["corr_spearman"].to_list()) - num_features = 3 - corr_threshold = 0.5 - alpha = 1000 - tree_params = {"random_state": 123, "n_estimators": 100} - - selectors = { - "corr_pearson": SelectionMethod.Correlation(corr_threshold, method="pearson"), - "corr_kendall": SelectionMethod.Correlation(corr_threshold, method="kendall"), - "corr_spearman": SelectionMethod.Correlation(corr_threshold, method="spearman"), - "univ_anova": SelectionMethod.Statistical(num_features, method="anova"), - "univ_chi_square": SelectionMethod.Statistical(num_features, method="chi_square"), - "univ_mutual_info": SelectionMethod.Statistical(num_features, method="mutual_info"), - "linear": SelectionMethod.Linear(num_features, regularization="none"), - "lasso": SelectionMethod.Linear(num_features, regularization="lasso", alpha=alpha), - "ridge": SelectionMethod.Linear(num_features, regularization="ridge", alpha=alpha), - "random_forest": SelectionMethod.TreeBased(num_features), - "xgboost_clf": SelectionMethod.TreeBased(num_features, estimator=XGBClassifier(**tree_params)), - "xgboost_reg": SelectionMethod.TreeBased(num_features, estimator=XGBRegressor(**tree_params)), - "extra_clf": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesClassifier(**tree_params)), - "extra_reg": SelectionMethod.TreeBased(num_features, estimator=ExtraTreesRegressor(**tree_params)), - "lgbm_clf": SelectionMethod.TreeBased(num_features, estimator=LGBMClassifier(**tree_params)), - "lgbm_reg": SelectionMethod.TreeBased(num_features, estimator=LGBMRegressor(**tree_params)), - "gradient_clf": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingClassifier(**tree_params)), - "gradient_reg": SelectionMethod.TreeBased(num_features, estimator=GradientBoostingRegressor(**tree_params)), - "adaboost_clf": SelectionMethod.TreeBased(num_features, estimator=AdaBoostClassifier(**tree_params)), - "adaboost_reg": SelectionMethod.TreeBased(num_features, estimator=AdaBoostRegressor(**tree_params)), - "catboost_clf": SelectionMethod.TreeBased(num_features, estimator=CatBoostClassifier(**tree_params, silent=True)), - "catboost_reg": SelectionMethod.TreeBased(num_features, estimator=CatBoostRegressor(**tree_params, silent=True)) - } + self.assertListAlmostEqual( + [66.9096213925407, 50.470199216622746, 71.84642313219175, 481.0566386481166, 60.5346993182466], + score_df["univ_anova"].to_list()) + + self.assertListAlmostEqual([0, 0, 0, 0, 0], + score_df["univ_chi_square"].to_list()) + + self.assertListAlmostEqual( + [0.31315151982855777, 0.16552049446241074, 0.3376809619388398, 0.681986210957143, 0.18450178283973817], + score_df["univ_mutual_info"].to_list()) + + self.assertListAlmostEqual( + [0.06157747888912044, 0.006445566885590223, 0.06693250180688959, 0.9576028432508157, 0.053796504696545476], + score_df["linear"].to_list()) + + self.assertListAlmostEqual( + [0.05329389111187177, 0.007117077997740284, 0.054563375238215125, 0.9260391103473467, 0.05071613235478144], + score_df["lasso"].to_list()) + + self.assertListAlmostEqual( + [0.061567603158881413, 0.006446613222308434, 0.06694625250225411, 0.9575175129470551, 0.05379855880797472], + score_df["ridge"].to_list()) + + self.assertListAlmostEqual( + [0.07819877553940296, 0.04385018441841779, 0.11432712180337742, 0.7401304941703286, 0.023493424068473153], + score_df["random_forest"].to_list()) + + def test_benchmark_classification(self): + data, label = get_data_label(load_iris()) # Benchmark - score_df, selected_df, runtime_df = benchmark(selectors, data, label, output_filename=None) + score_df, selected_df, runtime_df = benchmark(self.selectors, data, label, output_filename=None) _ = calculate_statistics(score_df, selected_df) self.assertListAlmostEqual([0.7018161715727902, 0.47803395524999537, 0.8157648279049796, 0.7867331225527027], @@ -153,3 +171,43 @@ def test_benchmark_classification(self): self.assertListAlmostEqual([0.09210348279677849, 0.03045933928742506, 0.4257647994615192, 0.45167237845427727], score_df["random_forest"].to_list()) + + def test_benchmark_classification_cv(self): + data, label = get_data_label(load_iris()) + + # Benchmark + score_df, selected_df, runtime_df = benchmark(self.selectors, data, label, cv=5, output_filename=None) + _ = calculate_statistics(score_df, selected_df) + + # Aggregate scores from different cv-folds + score_df = score_df.groupby(score_df.index).mean() + + self.assertListAlmostEqual([0.8161221983271784, 0.7871883928143776, 0.7020705184086643, 0.4793198034473529], + score_df["corr_pearson"].to_list()) + + self.assertListAlmostEqual([0.6780266710547757, 0.6550828618428932, 0.6125815664695313, 0.35594860548691776], + score_df["corr_kendall"].to_list()) + + self.assertListAlmostEqual([0.78225620681015, 0.7652859083343029, 0.7201874607448919, 0.44222588698925963], + score_df["corr_spearman"].to_list()) + + self.assertListAlmostEqual([946.9891701851375, 781.7441886012473, 95.65931730842011, 39.49994604080157], + score_df["univ_anova"].to_list()) + + self.assertListAlmostEqual([92.9884264821005, 53.62326775665224, 8.659084856298207, 2.9711267637858163], + score_df["univ_chi_square"].to_list()) + + self.assertListAlmostEqual([0.994113677302704, 0.9907696444894937, 0.4998955427118911, 0.2298786031192229], + score_df["univ_mutual_info"].to_list()) + + self.assertListAlmostEqual([0.22327603204146848, 0.03543066514916661, 0.26254667473769594, 0.506591069316828], + score_df["linear"].to_list()) + + self.assertListAlmostEqual([0.280393459805252, 0.9489351779830099, 0.6627768115497065, 0.4761878539373159], + score_df["lasso"].to_list()) + + self.assertListAlmostEqual([1.1049393460379105e-15, 2.0872192862952944e-15, 6.504056552595708e-16, 4.218847493575594e-16], + score_df["ridge"].to_list()) + + self.assertListAlmostEqual([0.4185294825699565, 0.4472560913161835, 0.10091608418224696, 0.03329834193161316], + score_df["random_forest"].to_list()) \ No newline at end of file From 5bc533473e8f1ba8738e2ff71623bc675adc7214 Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Mon, 22 Mar 2021 19:03:17 -0400 Subject: [PATCH 2/9] initial commit Signed-off-by: Kleynhans, Bernard --- CHANGELOG.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 03a9123..92b3bab 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,12 @@ CHANGELOG ========= +------------------------------------------------------------------------------- +March, 23, 2021 1.0.1 +------------------------------------------------------------------------------- + +- Add cross-validation (cv) capability to benchmark function. + ------------------------------------------------------------------------------- February, 1, 2021 1.0.0 ------------------------------------------------------------------------------- From d1b54fea36ba4b8bb72cb24232021a9861e6222b Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Mon, 29 Mar 2021 14:25:50 -0400 Subject: [PATCH 3/9] update wheel/version Signed-off-by: Kleynhans, Bernard --- ...y.whl => selective-1.0.1-py3-none-any.whl} | Bin 34046 -> 34169 bytes feature/_version.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename dist/{selective-1.0.0-py3-none-any.whl => selective-1.0.1-py3-none-any.whl} (55%) diff --git a/dist/selective-1.0.0-py3-none-any.whl b/dist/selective-1.0.1-py3-none-any.whl similarity index 55% rename from dist/selective-1.0.0-py3-none-any.whl rename to dist/selective-1.0.1-py3-none-any.whl index 9a653f5c58edceb09e14c5b4f51f44986f4f8f2f..447507b7ab36d9fca68d25d7f0003c1f2acb9b70 100644 GIT binary patch delta 13563 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⪙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 F{U4!x0m=XX diff --git a/feature/_version.py b/feature/_version.py index 76221c0..6670214 100644 --- a/feature/_version.py +++ b/feature/_version.py @@ -2,4 +2,4 @@ # Copyright FMR LLC # SPDX-License-Identifier: GNU GPLv3 -__version__ = "1.0.0" +__version__ = "1.0.1" From b60e556fbf0bf65d6a8fdf44921bad7435e15e30 Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Mon, 29 Mar 2021 14:26:06 -0400 Subject: [PATCH 4/9] update readme Signed-off-by: Kleynhans, Bernard --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b68d52..be3e088 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The library provides: * Automated task detection. No need to know what feature selection method works with what machine learning task -* Benchmarking with multiple selectors +* Benchmarking multiple selectors using cross-validation * Inspection of results and feature importance @@ -91,7 +91,7 @@ selectors = { } # Benchmark -score_df, selected_df, runtime_df = benchmark(selectors, data, label) +score_df, selected_df, runtime_df = benchmark(selectors, data, label, cv=5) print(score_df, "\n\n", selected_df, "\n\n", runtime_df) # Get benchmark statistics by feature From f92f0de5d005ba7359d39d1f1e3be101237c03fa Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Tue, 30 Mar 2021 08:51:53 -0400 Subject: [PATCH 5/9] add seed argument Signed-off-by: Kleynhans, Bernard --- feature/selector.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/feature/selector.py b/feature/selector.py index 1ab3df2..519b33b 100644 --- a/feature/selector.py +++ b/feature/selector.py @@ -479,7 +479,8 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, cv: Optional[int] = None, output_filename: Optional[str] = None, drop_zero_variance_features: Optional[bool] = True, - verbose: bool = False) \ + verbose: bool = False, + seed: int = Constants.default_seed) \ -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: """ Benchmark with a given set of feature selectors. @@ -506,6 +507,8 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, Whether to drop features with zero variance before running feature selector methods or not. verbose: bool, optional (default=False) Whether to print progress messages or not. + seed: int, optional (default=Constants.default_seed) + The random seed to initialize the random number generator. Returns ------- @@ -525,7 +528,7 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, else: # Create K-Fold object - kf = KFold(n_splits=cv, shuffle=True, random_state=Constants.default_seed) + kf = KFold(n_splits=cv, shuffle=True, random_state=seed) # Initialize variables t0 = time() From 281c4f3a95d7764ccd3ea9fbf4a785fe820940b9 Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Tue, 30 Mar 2021 09:01:23 -0400 Subject: [PATCH 6/9] add return comment Signed-off-by: Kleynhans, Bernard --- feature/selector.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feature/selector.py b/feature/selector.py index 519b33b..bd1d56a 100644 --- a/feature/selector.py +++ b/feature/selector.py @@ -640,8 +640,10 @@ def _bench(selectors: Dict[str, Union[SelectionMethod.Correlation, def calculate_statistics(scores: pd.DataFrame, selected: pd.DataFrame, columns: Optional[list] = None, - ignore_constant: Optional[bool] = True): - """Calculate statistics for each feature using scores/selections from list of methods. + ignore_constant: Optional[bool] = True) -> pd.DataFrame: + """ + Calculate statistics for each feature using scores/selections from list of methods. + Returns data frame with calculated statistics for each feature. Parameters ---------- From 65c53462b58b6aa13e3670d5e3fe6fef00abf5b4 Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Tue, 30 Mar 2021 09:28:50 -0400 Subject: [PATCH 7/9] edits Signed-off-by: Kleynhans, Bernard --- feature/selector.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/feature/selector.py b/feature/selector.py index bd1d56a..2edf22b 100644 --- a/feature/selector.py +++ b/feature/selector.py @@ -513,6 +513,7 @@ def benchmark(selectors: Dict[str, Union[SelectionMethod.Correlation, Returns ------- Tuple of data frames with scores, selected features and runtime for each method. + If cv is not None, the data frames will contain the concatenated results from each fold. """ check_true(selectors is not None, ValueError("Benchmark selectors cannot be none.")) @@ -680,7 +681,7 @@ def calculate_statistics(scores: pd.DataFrame, scores_df = scores[columns].copy() selected_df = selected[columns].copy() - # Group by feature + # Group by feature for CV results scores_df = scores_df.groupby(scores_df.index).mean() selected_df = selected_df.groupby(selected_df.index).mean() @@ -692,13 +693,13 @@ def calculate_statistics(scores: pd.DataFrame, # Calculate statistics stats_df = pd.DataFrame(index=scores_df.index) - stats_df["_score_mean"] = scores_df.mean(axis=1) - stats_df["_score_mean_norm"] = normalize_columns(scores_df).mean(axis=1) - stats_df["_selection_freq"] = selected_df.sum(axis=1) - stats_df["_selection_freq_norm"] = normalize_columns(selected_df).sum(axis=1) + stats_df["score_mean"] = scores_df.mean(axis=1) + stats_df["score_mean_norm"] = normalize_columns(scores_df).mean(axis=1) + stats_df["selection_freq"] = selected_df.sum(axis=1) + stats_df["selection_freq_norm"] = normalize_columns(selected_df).sum(axis=1) # Sort - stats_df.sort_values(by="_score_mean_norm", ascending=False, inplace=True) + stats_df.sort_values(by="score_mean_norm", ascending=False, inplace=True) return stats_df @@ -747,7 +748,7 @@ def plot_importance(scores: pd.DataFrame, df = scores[columns].copy() df.fillna(0, inplace=True) - # Group by feature + # Group by feature for CV results df = df.groupby(df.index).mean() # Get normalized scores such that scores for each method sums to 1 From 9733bdfdc58ee165bb943ae498c2d4772084e035 Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Tue, 30 Mar 2021 09:31:44 -0400 Subject: [PATCH 8/9] remove wheel Signed-off-by: Kleynhans, Bernard --- dist/selective-1.0.1-py3-none-any.whl | Bin 34169 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dist/selective-1.0.1-py3-none-any.whl diff --git a/dist/selective-1.0.1-py3-none-any.whl b/dist/selective-1.0.1-py3-none-any.whl deleted file mode 100644 index 447507b7ab36d9fca68d25d7f0003c1f2acb9b70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34169 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�+e!JCSdbV)U7-iT zn$&MlFpMJ6o_8P2`}ZhGLrWV;Nr-D&9Lt{Sngl~TJ!8;vbl_Ehxq=XdY62>=;u~A5 zrO+8D`%#8X%Z-=SPO#tpsf<)mnG0fZ#^p-l(Zsb^T|=cf26?d! z)L8Pl&CtV&akPtBY5agl2FuqnX!E5LUPMYfZuvnDS;SPd2h9gtRZPW0609n1n!S4a zQSwPjPb0AR9x&Z>ORsuKS9kg(+LlPjU$fB?a>isZEq91 z5UuwTjeTTk=}?8PYc>s-KP*nImk=;KCzm;-q9pS;M5s^pSuz;z-d3(h(CL1ON6xs_%^3}b>=v-tykuM|3+^lXE0wT)L88J<`hf=0PMev zVhA^D^;fM@)~jN#;ZShMC)V{2$)ADLbU{gnuLStDJ4ePK;QTEaMV}LHd%hAdUGh}o zT#{6c*l!6Ea?36#SqZhD$&qZc)RBjXGk3dh7PKOiYzn`br#4PU3tHf zQNCxT43TpMc_`y^7Y<4tSyZUJrAEKok9h=h$z$`I(+1J z)j59`$bH8lw!rfAwwAJj7sK&sTge}3n0Fu?t`%TxxYDf|C5|{xopK|iX`-mQSbSqJ z?OO`>?g`PD3qKdkcdpv!2={lq6+1l@o-^qyx+Ts-_k3&qEcjL(tn6JW})f;(PCL%hyT0qnnNe5}Z-p;VHZP3FEh~uXKX8Cg65exZ#h*6d@`WmwW zpOID8!X1JB`fT#W5I(5dR_?Hk%78r3P2AhV{XasQWLFG4Li~S8rG}`}4jFG$b--*w z4@+Akryn@1@o=-V*XJUghaUT{jbi2!!>Hh&s}6@%LnWvyZcH_loWXr|bcih(r;L77 z8C|bh$r}fLslQoZV=vWhwHTZR~>{Z@t|(z8F)y(+~R)wFjZ9m*DMWAA?FpiFMk_pS^6At zbw_x3K8RvI`<%A=D=qJ6nym>P7OFjSRsqP4J1k00M@TvBaaraPz0j3}3Jqt}3Fu0v>0cJGN5T$z zH~&eZzadpNC+Qr5767kO0AFj_>+`R2$dG;56FEQtfP0|-zj8=v8O1*ZkqSZa?7q8 zEGd=v9?-w?^oSoA$xweBg7`B@{;Ok- zHnuvJM$Sf-)Q+x>5)x9BWK`mZ|Hjer1d+Y``OdBWDdoofuQ+)j0U3Egule!#dRBno zT`wKz7YA<2FHneZ2#Ea6v0xxbB*EH<&T?8HCO&|}1) zb*{RA2``JJPIMB&Ah5^rdIm)s%av(b)i5YJgXDQSOiLG5)dksv}ViInuIN#m#6kI=f)(#(I`XJ^+fYwBu@0MKMxjIGu4~*a6{i9e!8gCbTz?^VXLm%#Alu~? zSWvwo`0CGxFONxgk8jJ=D=z68e#)O)ixgtpvXsN)I^UCUBD@U@#zRx}!S<9;Nustla^>9xb269P!2^;?Q1}-ZB2!sNR@@IuW0Q@C^@aF>H&$9Vn<1Yz> z|BOPvzpjjp^ceU?}p|7u`cw3`P0Ju zKkom}_T~R};NKmk|7O&H|IPS+`%C}R{QuUI^>6daKT3fAap4~wTK@_7>ofdYW%X|W zCe=Rx|3h>2pN#*T_~73RQTo3b|3ixKpNPNQ__uV~-v}w@zY+ftGW)mnzoj((wtnaN z+xj2E8vg|R-*goI2JrCzx3KkpR2BY>`}a1`-?(YJ|Aza|rqI86|ITav=1F_}H{Sor za{i6`cUb-#x8?cYaQ`bv{~Px2Q2IB_<Qi|3Di7hyE+{e}`9D V2~e=VED-)YO#X!A58uC@{Xgc2Bz^z@ From e5230016d08978852d59bdf871065150d7880adc Mon Sep 17 00:00:00 2001 From: "Kleynhans, Bernard" Date: Wed, 31 Mar 2021 10:11:01 -0400 Subject: [PATCH 9/9] update README Signed-off-by: Kleynhans, Bernard --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index be3e088..f677928 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,18 @@ plot_importance(df) Selective is available to install as `pip install selective`. +## Source + +Alternatively, you can build a wheel package on your platform from scratch using the source code: + +```bash +git clone https://github.com/fidelity/selective.git +cd selective +pip install setuptools wheel # if wheel is not installed +python setup.py sdist bdist_wheel +pip install dist/selective-X.X.X-py3-none-any.whl +``` + ## Support Please submit bug reports and feature requests as [Issues](https://github.com/fidelity/selective/issues).