We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[FEAT] Add force permutation option in UnusedFeatures check
`UnusedFeatures` looks for the feature importance attr if possible, which is bias in some [sklearn models](https://explained.ai/rf-importance/).
I suggest adding an option to override this behaviour. This is already an option in `.utils.features.calculate_feature_importance`.




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `deepchecks/checks/methodology/unused_features.py`
Content:
```
1 # ----------------------------------------------------------------------------
2 # Copyright (C) 2021 Deepchecks (https://www.deepchecks.com)
3 #
4 # This file is part of Deepchecks.
5 # Deepchecks is distributed under the terms of the GNU Affero General
6 # Public License (version 3 or later).
7 # You should have received a copy of the GNU Affero General Public License
8 # along with Deepchecks.  If not, see <http://www.gnu.org/licenses/>.
9 # ----------------------------------------------------------------------------
10 #
11 """The UnusedFeatures check module."""
12 from typing import Tuple
13 
14 import plotly.graph_objects as go
15 import numpy as np
16 import pandas as pd
17 from sklearn.base import TransformerMixin
18 from sklearn.compose import ColumnTransformer
19 from sklearn.decomposition import PCA
20 from sklearn.impute import SimpleImputer
21 from sklearn.pipeline import Pipeline
22 from sklearn.preprocessing import RobustScaler, OrdinalEncoder
23 
24 from deepchecks.base.check_context import CheckRunContext
25 from deepchecks import Dataset, CheckResult, TrainTestBaseCheck, ConditionResult, ConditionCategory
26 
27 
28 __all__ = ['UnusedFeatures']
29 
30 
31 class UnusedFeatures(TrainTestBaseCheck):
32     """Detect features that are nearly unused by the model.
33 
34     The check uses feature importance (either internally computed in appropriate models or calculated by permutation
35     feature importance) to detect features that are not used by the model. From this list, the check sorts the features
36     by their variance (as calculated by a PCA transformation). High variance unused features may be containing
37     information that is ignored by the model.
38 
39     Parameters
40     ----------
41     feature_importance_threshold : float , default: 0.2
42         A cutoff value for the feature importance, measured by the ratio of
43         each features' feature importance to the mean feature importance. Features with lower importance
44         are not shown in the check display.
45     feature_variance_threshold : float , default: 0.4
46         A cutoff value for the feature variance, measured by the ratio of
47         each features' feature variance to the mean feature variance. Unused features with lower variance
48         are not shown in the check display.
49     n_top_fi_to_show : int , default: 5
50         The max number of important features to show in the check display.
51     n_top_unused_to_show : int , default: 15
52         The max number of unused features to show in the check display, from among
53         unused features that have higher variance then is defined by feature_variance_threshold.
54     random_state : int , default: 42
55         The random state to use for permutation feature importance and PCA.
56     """
57 
58     def __init__(self, feature_importance_threshold: float = 0.2, feature_variance_threshold: float = 0.4,
59                  n_top_fi_to_show: int = 5, n_top_unused_to_show: int = 15, random_state: int = 42):
60         super().__init__()
61         self.feature_importance_threshold = feature_importance_threshold
62         self.feature_variance_threshold = feature_variance_threshold
63         self.n_top_fi_to_show = n_top_fi_to_show
64         self.n_top_unused_to_show = n_top_unused_to_show
65         self.random_state = random_state
66 
67     def run_logic(self, context: CheckRunContext) -> CheckResult:
68         """Run check.
69 
70         Returns
71         -------
72         CheckResult
73             value is a dataframe with metrics as indexes, and scores per training and test in the columns.
74             display data is a bar graph of the metrics for training and test data.
75 
76         Raises
77         ------
78         DeepchecksValueError
79             If neither train dataset nor test dataset exist, or either of the dataset objects are
80             not a Dataset instance with a label.
81         """
82         if context.have_test():
83             dataset = context.test
84         else:
85             dataset = context.train
86 
87         feature_importance = context.features_importance
88         features = context.features
89 
90         # Calculate normalized variance per feature based on PCA decomposition
91         pre_pca_transformer, var_col_order = naive_encoder(dataset)
92         pca_trans = PCA(n_components=len(dataset.features) // 2, random_state=self.random_state)
93         n_samples = min(10000, dataset.n_samples)
94         pca_trans.fit(pre_pca_transformer.fit_transform(
95             dataset.data[features].sample(n_samples, random_state=self.random_state)
96         ))
97 
98         feature_normed_variance = pd.Series(np.abs(pca_trans.components_).sum(axis=0), index=var_col_order)
99         feature_normed_variance = feature_normed_variance / feature_normed_variance.sum()
100 
101         feature_df = pd.concat([feature_importance, feature_normed_variance], axis=1)
102         feature_df.columns = ['Feature Importance', 'Feature Variance']
103         feature_df.sort_values(by='Feature Importance', ascending=False, inplace=True)
104 
105         # For feature importance and variance, calculate their "ratio to average" per feature. The ratio to average
106         # is, for example, the amount of feature importance a feature has, divided by the the amount he would have
107         # if all features where equally important (which is basically 1 / n_of_features).
108         feature_ratio_to_avg_df = feature_df / (1 / len(feature_importance))
109 
110         # Find last viable feature (not unused). All features from there on are sorted by variance
111         last_important_feature_index = sum(
112             feature_ratio_to_avg_df['Feature Importance'] > self.feature_importance_threshold
113         ) - 1
114 
115         unviable_feature_df = feature_df.iloc[(last_important_feature_index + 1):]
116         # Only display if there are features considered unimportant
117         if not unviable_feature_df.empty:
118             unviable_feature_df.sort_values(by='Feature Variance', ascending=False, inplace=True)
119             unviable_feature_ratio_to_avg_df = unviable_feature_df / (1 / len(feature_df))
120             last_variable_feature_index = sum(
121                 unviable_feature_ratio_to_avg_df['Feature Variance'] > self.feature_variance_threshold
122             )
123 
124             # limit display to n_top_to_show params
125             display_feature_df = pd.concat(
126                 [feature_df.iloc[:(last_important_feature_index + 1)].head(self.n_top_fi_to_show),
127                  unviable_feature_df.iloc[:last_variable_feature_index].head(self.n_top_unused_to_show)],
128                 axis=0)
129 
130             fig = go.Figure()
131             fig.add_trace(go.Bar(
132                 y=display_feature_df.index,
133                 x=display_feature_df['Feature Importance'].multiply(100).values.flatten(),
134                 name='Feature Importance %',
135                 marker_color='indianred',
136                 orientation='h'
137             ))
138             fig.add_trace(go.Bar(
139                 y=display_feature_df.index,
140                 x=display_feature_df['Feature Variance'].multiply(100).values.flatten(),
141                 name='Feature Variance %',
142                 marker_color='lightsalmon',
143                 orientation='h'
144             ))
145 
146             fig.update_yaxes(autorange='reversed')
147             fig.update_layout(title_text='Unused features compared to top important features',
148                               width=800, height=500)
149 
150             last_important_feature_index_to_plot = min(last_important_feature_index, self.n_top_fi_to_show - 1)
151 
152             if last_important_feature_index_to_plot < len(display_feature_df) - 1:
153                 last_important_feature_line_loc = last_important_feature_index_to_plot + 0.5
154                 fig.add_hline(y=last_important_feature_line_loc, line_width=2, line_dash='dash', line_color='green',
155                               annotation_text='Last shown significant feature')
156 
157             # display only if high variance features exist (as set by self.feature_variance_threshold)
158             if not last_variable_feature_index:
159                 display_list = []
160             else:
161                 display_list = [
162                     'Features above the line are a sample of the most important features, while the features '
163                     'below the line are the unused features with highest variance, as defined by check'
164                     ' parameters', fig]
165 
166         else:
167             display_list = []
168 
169         return_value = {
170             'used features': feature_df.index[:(last_important_feature_index + 1)].values.tolist(),
171             'unused features': {
172                 'high variance': (
173                     [] if unviable_feature_df.empty
174                     else unviable_feature_df.index[:last_variable_feature_index].values.tolist()
175                 ),
176                 'low variance': (
177                     [] if unviable_feature_df.empty
178                     else unviable_feature_df.index[last_variable_feature_index:].values.tolist()
179                 )
180             }}
181 
182         return CheckResult(return_value, header='Unused Features', display=display_list)
183 
184     def add_condition_number_of_high_variance_unused_features_not_greater_than(
185             self, max_high_variance_unused_features: int = 5):
186         """Add condition - require number of high variance unused features to be not greater than a given number.
187 
188         Parameters
189         ----------
190         max_high_variance_unused_features : int , default: 5
191             Maximum allowed number of high variance unused features.
192         """
193         def max_high_variance_unused_features_condition(result: dict) -> ConditionResult:
194             high_var_features = result['unused features']['high variance']
195             if len(high_var_features) > max_high_variance_unused_features:
196                 return ConditionResult(
197                     False,
198                     f'Found number of unused high variance features above threshold: {high_var_features}',
199                     category=ConditionCategory.WARN)
200             else:
201                 return ConditionResult(True)
202 
203         return self.add_condition(f'Number of high variance unused features is not greater than'
204                                   f' {max_high_variance_unused_features}',
205                                   max_high_variance_unused_features_condition)
206 
207 
208 def naive_encoder(dataset: Dataset) -> Tuple[TransformerMixin, list]:
209     """Create a naive encoder for categorical and numerical features.
210 
211     The encoder handles nans for all features and uses label encoder for categorical features. Then, all features are
212     scaled using RobustScaler.
213 
214     Parameters
215     ----------
216     dataset : Dataset
217         The dataset to encode.
218 
219     Returns
220     -------
221     Tuple[TransformerMixin, list]
222         A transformer object, a list of columns returned
223     """
224     numeric_features = [col for col in dataset.features if col not in dataset.cat_features]
225 
226     return ColumnTransformer(
227         transformers=[
228             ('num', Pipeline([
229                 ('nan_handling', SimpleImputer()),
230                 ('norm', RobustScaler())
231             ]),
232              numeric_features),
233             ('cat',
234              Pipeline([
235                  ('nan_handling', SimpleImputer(strategy='most_frequent')),
236                  ('encode', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)),
237                  ('norm', RobustScaler())
238              ]),
239              dataset.cat_features)
240         ]
241     ), numeric_features + dataset.cat_features
242
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/deepchecks/checks/methodology/unused_features.py b/deepchecks/checks/methodology/unused_features.py
--- a/deepchecks/checks/methodology/unused_features.py
+++ b/deepchecks/checks/methodology/unused_features.py
@@ -23,6 +23,7 @@
 
 from deepchecks.base.check_context import CheckRunContext
 from deepchecks import Dataset, CheckResult, TrainTestBaseCheck, ConditionResult, ConditionCategory
+from deepchecks.utils.typing import BasicModel
 
 
 __all__ = ['UnusedFeatures']
@@ -64,8 +65,26 @@
         self.n_top_unused_to_show = n_top_unused_to_show
         self.random_state = random_state
 
-    def run_logic(self, context: CheckRunContext) -> CheckResult:
-        """Run check.
+    def run(self,
+            train_dataset: Dataset,
+            test_dataset: Dataset,
+            model: BasicModel = None,
+            feature_importance_force_permutation: bool = False,
+            feature_importance_timeout: int = None) -> CheckResult:
+        """Run the check.
+
+        Parameters
+        ----------
+        train_dataset : Dataset
+            dataset representing data an estimator was fitted on
+        test_dataset : Dataset
+            dataset representing data an estimator predicts on
+        model : BasicModel
+            A scikit-learn-compatible fitted estimator instance
+        feature_importance_force_permutation : bool , default: False
+            force calculation of permutation features importance
+        feature_importance_timeout : int , default: None
+            timeout in second for the permutation features importance calculation
 
         Returns
         -------
@@ -79,6 +98,13 @@
             If neither train dataset nor test dataset exist, or either of the dataset objects are
             not a Dataset instance with a label.
         """
+        c = CheckRunContext(train_dataset, test_dataset, model,
+                            feature_importance_force_permutation=feature_importance_force_permutation,
+                            feature_importance_timeout=feature_importance_timeout)
+        return self.run_logic(c)
+
+    def run_logic(self, context: CheckRunContext) -> CheckResult:
+        """Run check."""
         if context.have_test():
             dataset = context.test
         else:

{"golden_diff": "diff --git a/deepchecks/checks/methodology/unused_features.py b/deepchecks/checks/methodology/unused_features.py\n--- a/deepchecks/checks/methodology/unused_features.py\n+++ b/deepchecks/checks/methodology/unused_features.py\n@@ -23,6 +23,7 @@\n \n from deepchecks.base.check_context import CheckRunContext\n from deepchecks import Dataset, CheckResult, TrainTestBaseCheck, ConditionResult, ConditionCategory\n+from deepchecks.utils.typing import BasicModel\n \n \n __all__ = ['UnusedFeatures']\n@@ -64,8 +65,26 @@\n         self.n_top_unused_to_show = n_top_unused_to_show\n         self.random_state = random_state\n \n-    def run_logic(self, context: CheckRunContext) -> CheckResult:\n-        \"\"\"Run check.\n+    def run(self,\n+            train_dataset: Dataset,\n+            test_dataset: Dataset,\n+            model: BasicModel = None,\n+            feature_importance_force_permutation: bool = False,\n+            feature_importance_timeout: int = None) -> CheckResult:\n+        \"\"\"Run the check.\n+\n+        Parameters\n+        ----------\n+        train_dataset : Dataset\n+            dataset representing data an estimator was fitted on\n+        test_dataset : Dataset\n+            dataset representing data an estimator predicts on\n+        model : BasicModel\n+            A scikit-learn-compatible fitted estimator instance\n+        feature_importance_force_permutation : bool , default: False\n+            force calculation of permutation features importance\n+        feature_importance_timeout : int , default: None\n+            timeout in second for the permutation features importance calculation\n \n         Returns\n         -------\n@@ -79,6 +98,13 @@\n             If neither train dataset nor test dataset exist, or either of the dataset objects are\n             not a Dataset instance with a label.\n         \"\"\"\n+        c = CheckRunContext(train_dataset, test_dataset, model,\n+                            feature_importance_force_permutation=feature_importance_force_permutation,\n+                            feature_importance_timeout=feature_importance_timeout)\n+        return self.run_logic(c)\n+\n+    def run_logic(self, context: CheckRunContext) -> CheckResult:\n+        \"\"\"Run check.\"\"\"\n         if context.have_test():\n             dataset = context.test\n         else:\n", "issue": "[FEAT] Add force permutation option in UnusedFeatures check\n`UnusedFeatures` looks for the feature importance attr if possible, which is bias in some [sklearn models](https://explained.ai/rf-importance/).\r\nI suggest adding an option to override this behaviour. This is already an option in `.utils.features.calculate_feature_importance`.\r\n\r\n\r\n\n", "before_files": [{"content": "# ----------------------------------------------------------------------------\n# Copyright (C) 2021 Deepchecks (https://www.deepchecks.com)\n#\n# This file is part of Deepchecks.\n# Deepchecks is distributed under the terms of the GNU Affero General\n# Public License (version 3 or later).\n# You should have received a copy of the GNU Affero General Public License\n# along with Deepchecks.  If not, see <http://www.gnu.org/licenses/>.\n# ----------------------------------------------------------------------------\n#\n\"\"\"The UnusedFeatures check module.\"\"\"\nfrom typing import Tuple\n\nimport plotly.graph_objects as go\nimport numpy as np\nimport pandas as pd\nfrom sklearn.base import TransformerMixin\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.decomposition import PCA\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import RobustScaler, OrdinalEncoder\n\nfrom deepchecks.base.check_context import CheckRunContext\nfrom deepchecks import Dataset, CheckResult, TrainTestBaseCheck, ConditionResult, ConditionCategory\n\n\n__all__ = ['UnusedFeatures']\n\n\nclass UnusedFeatures(TrainTestBaseCheck):\n    \"\"\"Detect features that are nearly unused by the model.\n\n    The check uses feature importance (either internally computed in appropriate models or calculated by permutation\n    feature importance) to detect features that are not used by the model. From this list, the check sorts the features\n    by their variance (as calculated by a PCA transformation). High variance unused features may be containing\n    information that is ignored by the model.\n\n    Parameters\n    ----------\n    feature_importance_threshold : float , default: 0.2\n        A cutoff value for the feature importance, measured by the ratio of\n        each features' feature importance to the mean feature importance. Features with lower importance\n        are not shown in the check display.\n    feature_variance_threshold : float , default: 0.4\n        A cutoff value for the feature variance, measured by the ratio of\n        each features' feature variance to the mean feature variance. Unused features with lower variance\n        are not shown in the check display.\n    n_top_fi_to_show : int , default: 5\n        The max number of important features to show in the check display.\n    n_top_unused_to_show : int , default: 15\n        The max number of unused features to show in the check display, from among\n        unused features that have higher variance then is defined by feature_variance_threshold.\n    random_state : int , default: 42\n        The random state to use for permutation feature importance and PCA.\n    \"\"\"\n\n    def __init__(self, feature_importance_threshold: float = 0.2, feature_variance_threshold: float = 0.4,\n                 n_top_fi_to_show: int = 5, n_top_unused_to_show: int = 15, random_state: int = 42):\n        super().__init__()\n        self.feature_importance_threshold = feature_importance_threshold\n        self.feature_variance_threshold = feature_variance_threshold\n        self.n_top_fi_to_show = n_top_fi_to_show\n        self.n_top_unused_to_show = n_top_unused_to_show\n        self.random_state = random_state\n\n    def run_logic(self, context: CheckRunContext) -> CheckResult:\n        \"\"\"Run check.\n\n        Returns\n        -------\n        CheckResult\n            value is a dataframe with metrics as indexes, and scores per training and test in the columns.\n            display data is a bar graph of the metrics for training and test data.\n\n        Raises\n        ------\n        DeepchecksValueError\n            If neither train dataset nor test dataset exist, or either of the dataset objects are\n            not a Dataset instance with a label.\n        \"\"\"\n        if context.have_test():\n            dataset = context.test\n        else:\n            dataset = context.train\n\n        feature_importance = context.features_importance\n        features = context.features\n\n        # Calculate normalized variance per feature based on PCA decomposition\n        pre_pca_transformer, var_col_order = naive_encoder(dataset)\n        pca_trans = PCA(n_components=len(dataset.features) // 2, random_state=self.random_state)\n        n_samples = min(10000, dataset.n_samples)\n        pca_trans.fit(pre_pca_transformer.fit_transform(\n            dataset.data[features].sample(n_samples, random_state=self.random_state)\n        ))\n\n        feature_normed_variance = pd.Series(np.abs(pca_trans.components_).sum(axis=0), index=var_col_order)\n        feature_normed_variance = feature_normed_variance / feature_normed_variance.sum()\n\n        feature_df = pd.concat([feature_importance, feature_normed_variance], axis=1)\n        feature_df.columns = ['Feature Importance', 'Feature Variance']\n        feature_df.sort_values(by='Feature Importance', ascending=False, inplace=True)\n\n        # For feature importance and variance, calculate their \"ratio to average\" per feature. The ratio to average\n        # is, for example, the amount of feature importance a feature has, divided by the the amount he would have\n        # if all features where equally important (which is basically 1 / n_of_features).\n        feature_ratio_to_avg_df = feature_df / (1 / len(feature_importance))\n\n        # Find last viable feature (not unused). All features from there on are sorted by variance\n        last_important_feature_index = sum(\n            feature_ratio_to_avg_df['Feature Importance'] > self.feature_importance_threshold\n        ) - 1\n\n        unviable_feature_df = feature_df.iloc[(last_important_feature_index + 1):]\n        # Only display if there are features considered unimportant\n        if not unviable_feature_df.empty:\n            unviable_feature_df.sort_values(by='Feature Variance', ascending=False, inplace=True)\n            unviable_feature_ratio_to_avg_df = unviable_feature_df / (1 / len(feature_df))\n            last_variable_feature_index = sum(\n                unviable_feature_ratio_to_avg_df['Feature Variance'] > self.feature_variance_threshold\n            )\n\n            # limit display to n_top_to_show params\n            display_feature_df = pd.concat(\n                [feature_df.iloc[:(last_important_feature_index + 1)].head(self.n_top_fi_to_show),\n                 unviable_feature_df.iloc[:last_variable_feature_index].head(self.n_top_unused_to_show)],\n                axis=0)\n\n            fig = go.Figure()\n            fig.add_trace(go.Bar(\n                y=display_feature_df.index,\n                x=display_feature_df['Feature Importance'].multiply(100).values.flatten(),\n                name='Feature Importance %',\n                marker_color='indianred',\n                orientation='h'\n            ))\n            fig.add_trace(go.Bar(\n                y=display_feature_df.index,\n                x=display_feature_df['Feature Variance'].multiply(100).values.flatten(),\n                name='Feature Variance %',\n                marker_color='lightsalmon',\n                orientation='h'\n            ))\n\n            fig.update_yaxes(autorange='reversed')\n            fig.update_layout(title_text='Unused features compared to top important features',\n                              width=800, height=500)\n\n            last_important_feature_index_to_plot = min(last_important_feature_index, self.n_top_fi_to_show - 1)\n\n            if last_important_feature_index_to_plot < len(display_feature_df) - 1:\n                last_important_feature_line_loc = last_important_feature_index_to_plot + 0.5\n                fig.add_hline(y=last_important_feature_line_loc, line_width=2, line_dash='dash', line_color='green',\n                              annotation_text='Last shown significant feature')\n\n            # display only if high variance features exist (as set by self.feature_variance_threshold)\n            if not last_variable_feature_index:\n                display_list = []\n            else:\n                display_list = [\n                    'Features above the line are a sample of the most important features, while the features '\n                    'below the line are the unused features with highest variance, as defined by check'\n                    ' parameters', fig]\n\n        else:\n            display_list = []\n\n        return_value = {\n            'used features': feature_df.index[:(last_important_feature_index + 1)].values.tolist(),\n            'unused features': {\n                'high variance': (\n                    [] if unviable_feature_df.empty\n                    else unviable_feature_df.index[:last_variable_feature_index].values.tolist()\n                ),\n                'low variance': (\n                    [] if unviable_feature_df.empty\n                    else unviable_feature_df.index[last_variable_feature_index:].values.tolist()\n                )\n            }}\n\n        return CheckResult(return_value, header='Unused Features', display=display_list)\n\n    def add_condition_number_of_high_variance_unused_features_not_greater_than(\n            self, max_high_variance_unused_features: int = 5):\n        \"\"\"Add condition - require number of high variance unused features to be not greater than a given number.\n\n        Parameters\n        ----------\n        max_high_variance_unused_features : int , default: 5\n            Maximum allowed number of high variance unused features.\n        \"\"\"\n        def max_high_variance_unused_features_condition(result: dict) -> ConditionResult:\n            high_var_features = result['unused features']['high variance']\n            if len(high_var_features) > max_high_variance_unused_features:\n                return ConditionResult(\n                    False,\n                    f'Found number of unused high variance features above threshold: {high_var_features}',\n                    category=ConditionCategory.WARN)\n            else:\n                return ConditionResult(True)\n\n        return self.add_condition(f'Number of high variance unused features is not greater than'\n                                  f' {max_high_variance_unused_features}',\n                                  max_high_variance_unused_features_condition)\n\n\ndef naive_encoder(dataset: Dataset) -> Tuple[TransformerMixin, list]:\n    \"\"\"Create a naive encoder for categorical and numerical features.\n\n    The encoder handles nans for all features and uses label encoder for categorical features. Then, all features are\n    scaled using RobustScaler.\n\n    Parameters\n    ----------\n    dataset : Dataset\n        The dataset to encode.\n\n    Returns\n    -------\n    Tuple[TransformerMixin, list]\n        A transformer object, a list of columns returned\n    \"\"\"\n    numeric_features = [col for col in dataset.features if col not in dataset.cat_features]\n\n    return ColumnTransformer(\n        transformers=[\n            ('num', Pipeline([\n                ('nan_handling', SimpleImputer()),\n                ('norm', RobustScaler())\n            ]),\n             numeric_features),\n            ('cat',\n             Pipeline([\n                 ('nan_handling', SimpleImputer(strategy='most_frequent')),\n                 ('encode', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)),\n                 ('norm', RobustScaler())\n             ]),\n             dataset.cat_features)\n        ]\n    ), numeric_features + dataset.cat_features\n", "path": "deepchecks/checks/methodology/unused_features.py"}], "after_files": [{"content": "# ----------------------------------------------------------------------------\n# Copyright (C) 2021 Deepchecks (https://www.deepchecks.com)\n#\n# This file is part of Deepchecks.\n# Deepchecks is distributed under the terms of the GNU Affero General\n# Public License (version 3 or later).\n# You should have received a copy of the GNU Affero General Public License\n# along with Deepchecks.  If not, see <http://www.gnu.org/licenses/>.\n# ----------------------------------------------------------------------------\n#\n\"\"\"The UnusedFeatures check module.\"\"\"\nfrom typing import Tuple\n\nimport plotly.graph_objects as go\nimport numpy as np\nimport pandas as pd\nfrom sklearn.base import TransformerMixin\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.decomposition import PCA\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import RobustScaler, OrdinalEncoder\n\nfrom deepchecks.base.check_context import CheckRunContext\nfrom deepchecks import Dataset, CheckResult, TrainTestBaseCheck, ConditionResult, ConditionCategory\nfrom deepchecks.utils.typing import BasicModel\n\n\n__all__ = ['UnusedFeatures']\n\n\nclass UnusedFeatures(TrainTestBaseCheck):\n    \"\"\"Detect features that are nearly unused by the model.\n\n    The check uses feature importance (either internally computed in appropriate models or calculated by permutation\n    feature importance) to detect features that are not used by the model. From this list, the check sorts the features\n    by their variance (as calculated by a PCA transformation). High variance unused features may be containing\n    information that is ignored by the model.\n\n    Parameters\n    ----------\n    feature_importance_threshold : float , default: 0.2\n        A cutoff value for the feature importance, measured by the ratio of\n        each features' feature importance to the mean feature importance. Features with lower importance\n        are not shown in the check display.\n    feature_variance_threshold : float , default: 0.4\n        A cutoff value for the feature variance, measured by the ratio of\n        each features' feature variance to the mean feature variance. Unused features with lower variance\n        are not shown in the check display.\n    n_top_fi_to_show : int , default: 5\n        The max number of important features to show in the check display.\n    n_top_unused_to_show : int , default: 15\n        The max number of unused features to show in the check display, from among\n        unused features that have higher variance then is defined by feature_variance_threshold.\n    random_state : int , default: 42\n        The random state to use for permutation feature importance and PCA.\n    \"\"\"\n\n    def __init__(self, feature_importance_threshold: float = 0.2, feature_variance_threshold: float = 0.4,\n                 n_top_fi_to_show: int = 5, n_top_unused_to_show: int = 15, random_state: int = 42):\n        super().__init__()\n        self.feature_importance_threshold = feature_importance_threshold\n        self.feature_variance_threshold = feature_variance_threshold\n        self.n_top_fi_to_show = n_top_fi_to_show\n        self.n_top_unused_to_show = n_top_unused_to_show\n        self.random_state = random_state\n\n    def run(self,\n            train_dataset: Dataset,\n            test_dataset: Dataset,\n            model: BasicModel = None,\n            feature_importance_force_permutation: bool = False,\n            feature_importance_timeout: int = None) -> CheckResult:\n        \"\"\"Run the check.\n\n        Parameters\n        ----------\n        train_dataset : Dataset\n            dataset representing data an estimator was fitted on\n        test_dataset : Dataset\n            dataset representing data an estimator predicts on\n        model : BasicModel\n            A scikit-learn-compatible fitted estimator instance\n        feature_importance_force_permutation : bool , default: False\n            force calculation of permutation features importance\n        feature_importance_timeout : int , default: None\n            timeout in second for the permutation features importance calculation\n\n        Returns\n        -------\n        CheckResult\n            value is a dataframe with metrics as indexes, and scores per training and test in the columns.\n            display data is a bar graph of the metrics for training and test data.\n\n        Raises\n        ------\n        DeepchecksValueError\n            If neither train dataset nor test dataset exist, or either of the dataset objects are\n            not a Dataset instance with a label.\n        \"\"\"\n        c = CheckRunContext(train_dataset, test_dataset, model,\n                            feature_importance_force_permutation=feature_importance_force_permutation,\n                            feature_importance_timeout=feature_importance_timeout)\n        return self.run_logic(c)\n\n    def run_logic(self, context: CheckRunContext) -> CheckResult:\n        \"\"\"Run check.\"\"\"\n        if context.have_test():\n            dataset = context.test\n        else:\n            dataset = context.train\n\n        feature_importance = context.features_importance\n        features = context.features\n\n        # Calculate normalized variance per feature based on PCA decomposition\n        pre_pca_transformer, var_col_order = naive_encoder(dataset)\n        pca_trans = PCA(n_components=len(dataset.features) // 2, random_state=self.random_state)\n        n_samples = min(10000, dataset.n_samples)\n        pca_trans.fit(pre_pca_transformer.fit_transform(\n            dataset.data[features].sample(n_samples, random_state=self.random_state)\n        ))\n\n        feature_normed_variance = pd.Series(np.abs(pca_trans.components_).sum(axis=0), index=var_col_order)\n        feature_normed_variance = feature_normed_variance / feature_normed_variance.sum()\n\n        feature_df = pd.concat([feature_importance, feature_normed_variance], axis=1)\n        feature_df.columns = ['Feature Importance', 'Feature Variance']\n        feature_df.sort_values(by='Feature Importance', ascending=False, inplace=True)\n\n        # For feature importance and variance, calculate their \"ratio to average\" per feature. The ratio to average\n        # is, for example, the amount of feature importance a feature has, divided by the the amount he would have\n        # if all features where equally important (which is basically 1 / n_of_features).\n        feature_ratio_to_avg_df = feature_df / (1 / len(feature_importance))\n\n        # Find last viable feature (not unused). All features from there on are sorted by variance\n        last_important_feature_index = sum(\n            feature_ratio_to_avg_df['Feature Importance'] > self.feature_importance_threshold\n        ) - 1\n\n        unviable_feature_df = feature_df.iloc[(last_important_feature_index + 1):]\n        # Only display if there are features considered unimportant\n        if not unviable_feature_df.empty:\n            unviable_feature_df.sort_values(by='Feature Variance', ascending=False, inplace=True)\n            unviable_feature_ratio_to_avg_df = unviable_feature_df / (1 / len(feature_df))\n            last_variable_feature_index = sum(\n                unviable_feature_ratio_to_avg_df['Feature Variance'] > self.feature_variance_threshold\n            )\n\n            # limit display to n_top_to_show params\n            display_feature_df = pd.concat(\n                [feature_df.iloc[:(last_important_feature_index + 1)].head(self.n_top_fi_to_show),\n                 unviable_feature_df.iloc[:last_variable_feature_index].head(self.n_top_unused_to_show)],\n                axis=0)\n\n            fig = go.Figure()\n            fig.add_trace(go.Bar(\n                y=display_feature_df.index,\n                x=display_feature_df['Feature Importance'].multiply(100).values.flatten(),\n                name='Feature Importance %',\n                marker_color='indianred',\n                orientation='h'\n            ))\n            fig.add_trace(go.Bar(\n                y=display_feature_df.index,\n                x=display_feature_df['Feature Variance'].multiply(100).values.flatten(),\n                name='Feature Variance %',\n                marker_color='lightsalmon',\n                orientation='h'\n            ))\n\n            fig.update_yaxes(autorange='reversed')\n            fig.update_layout(title_text='Unused features compared to top important features',\n                              width=800, height=500)\n\n            last_important_feature_index_to_plot = min(last_important_feature_index, self.n_top_fi_to_show - 1)\n\n            if last_important_feature_index_to_plot < len(display_feature_df) - 1:\n                last_important_feature_line_loc = last_important_feature_index_to_plot + 0.5\n                fig.add_hline(y=last_important_feature_line_loc, line_width=2, line_dash='dash', line_color='green',\n                              annotation_text='Last shown significant feature')\n\n            # display only if high variance features exist (as set by self.feature_variance_threshold)\n            if not last_variable_feature_index:\n                display_list = []\n            else:\n                display_list = [\n                    'Features above the line are a sample of the most important features, while the features '\n                    'below the line are the unused features with highest variance, as defined by check'\n                    ' parameters', fig]\n\n        else:\n            display_list = []\n\n        return_value = {\n            'used features': feature_df.index[:(last_important_feature_index + 1)].values.tolist(),\n            'unused features': {\n                'high variance': (\n                    [] if unviable_feature_df.empty\n                    else unviable_feature_df.index[:last_variable_feature_index].values.tolist()\n                ),\n                'low variance': (\n                    [] if unviable_feature_df.empty\n                    else unviable_feature_df.index[last_variable_feature_index:].values.tolist()\n                )\n            }}\n\n        return CheckResult(return_value, header='Unused Features', display=display_list)\n\n    def add_condition_number_of_high_variance_unused_features_not_greater_than(\n            self, max_high_variance_unused_features: int = 5):\n        \"\"\"Add condition - require number of high variance unused features to be not greater than a given number.\n\n        Parameters\n        ----------\n        max_high_variance_unused_features : int , default: 5\n            Maximum allowed number of high variance unused features.\n        \"\"\"\n        def max_high_variance_unused_features_condition(result: dict) -> ConditionResult:\n            high_var_features = result['unused features']['high variance']\n            if len(high_var_features) > max_high_variance_unused_features:\n                return ConditionResult(\n                    False,\n                    f'Found number of unused high variance features above threshold: {high_var_features}',\n                    category=ConditionCategory.WARN)\n            else:\n                return ConditionResult(True)\n\n        return self.add_condition(f'Number of high variance unused features is not greater than'\n                                  f' {max_high_variance_unused_features}',\n                                  max_high_variance_unused_features_condition)\n\n\ndef naive_encoder(dataset: Dataset) -> Tuple[TransformerMixin, list]:\n    \"\"\"Create a naive encoder for categorical and numerical features.\n\n    The encoder handles nans for all features and uses label encoder for categorical features. Then, all features are\n    scaled using RobustScaler.\n\n    Parameters\n    ----------\n    dataset : Dataset\n        The dataset to encode.\n\n    Returns\n    -------\n    Tuple[TransformerMixin, list]\n        A transformer object, a list of columns returned\n    \"\"\"\n    numeric_features = [col for col in dataset.features if col not in dataset.cat_features]\n\n    return ColumnTransformer(\n        transformers=[\n            ('num', Pipeline([\n                ('nan_handling', SimpleImputer()),\n                ('norm', RobustScaler())\n            ]),\n             numeric_features),\n            ('cat',\n             Pipeline([\n                 ('nan_handling', SimpleImputer(strategy='most_frequent')),\n                 ('encode', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)),\n                 ('norm', RobustScaler())\n             ]),\n             dataset.cat_features)\n        ]\n    ), numeric_features + dataset.cat_features\n", "path": "deepchecks/checks/methodology/unused_features.py"}]}