Repository: scikit-learn/scikit-learn
Pull request: [MRG] Improve the error message of export_graphviz if a not-fitted decision tree is provided
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/master/CONTRIBUTING.md#Contributing-Pull-Requests
-->
#### Reference Issue
<!-- Example: Fixes #1234 -->
Fixes #8775 

#### What does this implement/fix? Explain your changes.
This pull request aims to improve the error message of export_graphviz if a not-fitted decision tree is provided.
I think for this special case, it is unnecessary to use sklearn.utils.validation.check_is_fitted, so I simply add an 'if' statement at the beginning of the function and output a NotFittedError.

#### Any other comments?
Thanks for your precious time.

<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
http://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->


Problem statement:
export_graphviz should fail with a better error message if a not-fitted decision tree is provided.
#### Description
export_graphviz should fail with a better error message if a not-fitted decision tree is provided.

#### Steps/Code to Reproduce

```python
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
export_graphviz(dtc)
AttributeError: 'DecisionTreeClassifier' object has no attribute 'tree_'
```

Hints from issue discussion:
I am new to your codebase but happy to try if mentoring/support is available. 

Is this a matter of changing the wording, or adding a string to the standard error message for this case? 


Hi @morrme, a good place to start is by looking at the code for `export_graphviz()` [here](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/tree/export.py#L70) and the [usage example](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/tree/export.py#L152-L163). You'll see that `export_graphviz()` requires a fitted decision tree as input as opposed to simply the initialized class object of `DecisionTreeClassifier()`. One approach would be to check `decision_tree` to see if it is a fitted decision tree. If it isn't, raise an error telling the user that the input must be a fitted decision tree.
Thanks for summarizing. You can make use of the `check_is_fitted` function imported from `sklearn.utils.validation` for this purpose. Just doing a git grep would give you some occurrences of its usage.

Commit message: Update export.py

Patch to review:
diff --git a/sklearn/tree/export.py b/sklearn/tree/export.py
index 43e8aa11b9611..5462cca0676b2 100644
--- a/sklearn/tree/export.py
+++ b/sklearn/tree/export.py
@@ -14,6 +14,7 @@
 import warnings
 
 from ..externals import six
+from ..exceptions import NotFittedError
 
 from . import _criterion
 from . import _tree
@@ -380,6 +381,12 @@ def recurse(tree, node_id, criterion, parent=None, depth=0):
     own_file = False
     return_string = False
     try:
+        if decision_tree.tree_ is None:
+            raise NotFittedError("This %(name)s instance is not fitted yet. "
+                                 "Call 'fit' with appropriate arguments "
+                                 "before using this method."
+                                 % {'name': type(decision_tree).__name__})
+
         if out_file == SENTINEL:
             warnings.warn("out_file can be set to None starting from 0.18. "
                           "This will be the default in 0.20.",


Code review comments:
diff_hunk: @@ -380,6 +381,12 @@ def recurse(tree, node_id, criterion, parent=None, depth=0):
     own_file = False
     return_string = False
     try:
+        if decision_tree.tree_ is None:
path: sklearn/tree/export.py
text: @user1:
You can import `check_is_fitted` from `sklearn.utils.validation` and then do `check_is_fitted(decision_tree, tree_)`