Repository: andersbogsnes/ml_tooling

Issue:
ENH: Get result parameters
**Is your feature request related to a problem? Please describe.**
When doing a grid-search, I want to be able to see what parameters generated the result from the result.

**Describe the solution you'd like**
A `Result.parameters` attribute would likely be enough

**Describe alternatives you've considered**
The question is whether or not to include the feature pipeline parameters. Currently, I would not - since mostly we are interested in gridsearching model parameters. 

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=h1) Report
> Merging [#765](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=desc) (1e7ba71) into [develop](https://codecov.io/gh/andersbogsnes/ml_tooling/commit/18e5b480bea980347fde64ccbdb81b00dcc04232?el=desc) (18e5b48) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/graphs/tree.svg?width=650&height=150&src=pr&token=ucKtL3BlxO)](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           develop      #765    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           55        64     +9     
  Lines         1424      1712   +288     
  Branches       166       192    +26     
==========================================
+ Hits          1424      1712   +288     
```


| [Impacted Files](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ml\_tooling/plots/utils.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvcGxvdHMvdXRpbHMucHk=) | `100.00% <ø> (ø)` | |
| [src/ml\_tooling/transformers/binner.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvdHJhbnNmb3JtZXJzL2Jpbm5lci5weQ==) | `100.00% <ø> (ø)` | |
| [src/ml\_tooling/transformers/feature\_union.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvdHJhbnNmb3JtZXJzL2ZlYXR1cmVfdW5pb24ucHk=) | `100.00% <ø> (ø)` | |
| [src/ml\_tooling/transformers/renamer.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvdHJhbnNmb3JtZXJzL3JlbmFtZXIucHk=) | `100.00% <ø> (ø)` | |
| [src/ml\_tooling/transformers/to\_categorical.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvdHJhbnNmb3JtZXJzL3RvX2NhdGVnb3JpY2FsLnB5) | `100.00% <ø> (ø)` | |
| [src/ml\_tooling/\_\_init\_\_.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [src/ml\_tooling/baseclass.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvYmFzZWNsYXNzLnB5) | `100.00% <100.00%> (ø)` | |
| [src/ml\_tooling/config.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvY29uZmlnLnB5) | `100.00% <100.00%> (ø)` | |
| [src/ml\_tooling/data/\_\_init\_\_.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvZGF0YS9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/ml\_tooling/data/base\_data.py](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree#diff-c3JjL21sX3Rvb2xpbmcvZGF0YS9iYXNlX2RhdGEucHk=) | `100.00% <100.00%> (ø)` | |
| ... and [51 more](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=footer). Last update [74cb93a...1e7ba71](https://codecov.io/gh/andersbogsnes/ml_tooling/pull/765?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2645563..91f3331 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # v.0.12.1
 - Dataset features can now be easily accessed with the property dataset.features
 - `model.test_estimators` with CV will keep using CV if it's refitting the best estimator.
+- `Result` now has a `.parameters` attribute to show what parameters generated the result
 
 # v0.12.0
 - Permutation importance and Feature importance are now two different plotting methods.
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 1fd6497..eb36e5d 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -72,6 +72,15 @@ We can see that the results are sorted and shows us a nice repr of each model's
     >>> results
     ResultGroup(results=[<Result RandomForestClassifier: {'accuracy': 0.95, 'roc_auc': 0.98}>, <Result LogisticRegression: {'accuracy': 0.71, 'roc_auc': 0.79}>, <Result DummyClassifier: {'accuracy': 0.55, 'roc_auc': 0.52}>])
 
+
+You can access the parameters via the :attr:`Result.parameters <ml_tooling.result.result.Result.parameters>` attribute.
+
+.. doctest::
+
+    >>> results.parameters
+    {'bootstrap': True, 'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': None, 'max_features': 'auto', 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 10, 'n_jobs': None, 'oob_score': False, 'random_state': 42, 'verbose': 0, 'warm_start': False}
+
+
 From our results, the :class:`~sklearn.ensemble.RandomForestClassifier` looks the most promising, so we want to see if
 we can tune it a bit more. We can run a gridsearch over the hyperparameters using the :meth:`~Model.gridsearch` method.
 We also want to log the results, so we can examine each potential model in depth, so we use the :meth:`~Model.log`
diff --git a/src/ml_tooling/result/result.py b/src/ml_tooling/result/result.py
index 648f623..d380318 100644
--- a/src/ml_tooling/result/result.py
+++ b/src/ml_tooling/result/result.py
@@ -41,12 +41,18 @@ class Result:
 
     @property
     def model(self):
+        """Return the model used in the result"""
         from ml_tooling import Model
 
         model = Model(self.estimator)
         model.result = self
         return model
 
+    @property
+    def parameters(self):
+        """Return the estimator params"""
+        return self.estimator.get_params()
+
     @classmethod
     def from_estimator(
         cls,
@@ -57,6 +63,30 @@ class Result:
         n_jobs: int = None,
         verbose: int = 0,
     ) -> "Result":
+        """
+        Create a result from an estimator
+
+        Parameters
+        ----------
+        estimator : Estimator
+            The trained estimator
+        data : Dataset
+            The dataset used to create the result
+        metrics : List[str]
+            What metrics to calculate
+        cv : Any
+            If CV is an int, run with that many CV iterations. If a sklearn-compatible CV object
+            is passed, use that object to generate the CV splits
+        n_jobs : int
+            How many jobs to parallelize with
+        verbose : int
+            How verbose should the parallelization be
+
+        Returns
+        -------
+        Result
+            An instance of Result
+        """
         metrics = Metrics.from_list(metrics)
         if cv:
             metrics.score_metrics_cv(
@@ -73,6 +103,21 @@ class Result:
         return cls(metrics=metrics, estimator=estimator, data=data)
 
     def log(self, saved_estimator_path=None, savedir=None) -> Log:
+        """
+        Generate a Log object from the result
+
+        Parameters
+        ----------
+        saved_estimator_path : Union[str, pathlib.Path, None]
+            Optionally, where the saved estimator is
+        savedir : Union[str, pathlib.Path, None]
+            An optional path of where to save the log
+
+        Returns
+        -------
+        Log
+            An instance of Log
+        """
         log = Log.from_result(result=self, estimator_path=saved_estimator_path)
         if savedir:
             log.save_log(savedir)


Test patch:
diff --git a/tests/test_results/test_result.py b/tests/test_results/test_result.py
index 326e2cd..e9ef3d8 100644
--- a/tests/test_results/test_result.py
+++ b/tests/test_results/test_result.py
@@ -110,3 +110,9 @@ class TestResult:
         log = Log.from_result(result)
         log2 = result.log()
         assert log == log2
+
+    def test_result_can_show_model_parameters(self, result: Result):
+        """Expect a log to be able to show what parameters were used to generate it"""
+        parameters = result.parameters
+        expected = result.estimator.get_params()
+        assert parameters == expected