Repository: DistrictDataLabs/yellowbrick

Issue:
pca_decomposition returns sometimes an exception 
**Describe the bug**
Some time pca_decomposition finishes with an exception (AttributeError: 'PCA' object has no attribute 'random_state').
More precisely, the picture is well generated, but there is an exception just after (cf. traceback below).

**To Reproduce**

```python
from yellowbrick.datasets import load_credit
from yellowbrick.features import pca_decomposition

# Specify the features of interest and the target
X, y = load_credit()
classes = ['account in default', 'current with bills']

# Create, fit, and show the visualizer
pca_decomposition(
    X, y, scale=True, classes=classes
)
```


**Expected behavior**
The picture is generated without exception

**Traceback**

```
----------------------------------------------------------------------
AttributeError                       Traceback (most recent call last)
~/.local/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

~/.local/lib/python3.8/site-packages/sklearn/base.py in _repr_mimebundle_(self, **kwargs)
    462     def _repr_mimebundle_(self, **kwargs):
    463         """Mime bundle used by jupyter kernels to display estimator"""
--> 464         output = {"text/plain": repr(self)}
    465         if get_config()["display"] == 'diagram':
    466             output["text/html"] = estimator_html_repr(self)

~/.local/lib/python3.8/site-packages/sklearn/base.py in __repr__(self, N_CHAR_MAX)
    258             n_max_elements_to_show=N_MAX_ELEMENTS_TO_SHOW)
    259 
--> 260         repr_ = pp.pformat(self)
    261 
    262         # Use bruteforce ellipsis when there are a lot of non-blank characters

~/anaconda3/lib/python3.8/pprint.py in pformat(self, object)
    151     def pformat(self, object):
    152         sio = _StringIO()
--> 153         self._format(object, sio, 0, 0, {}, 0)
    154         return sio.getvalue()
    155 

~/anaconda3/lib/python3.8/pprint.py in _format(self, object, stream, indent, allowance, context, level)
    168             self._readable = False
    169             return
--> 170         rep = self._repr(object, context, level)
    171         max_width = self._width - indent - allowance
    172         if len(rep) > max_width:

~/anaconda3/lib/python3.8/pprint.py in _repr(self, object, context, level)
    402 
    403     def _repr(self, object, context, level):
--> 404         repr, readable, recursive = self.format(object, context.copy(),
    405                                                 self._depth, level)
    406         if not readable:

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in format(self, object, context, maxlevels, level)
    178 
    179     def format(self, object, context, maxlevels, level):
--> 180         return _safe_repr(object, context, maxlevels, level,
    181                           changed_only=self._changed_only)
    182 

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in _safe_repr(object, context, maxlevels, level, changed_only)
    423         recursive = False
    424         if changed_only:
--> 425             params = _changed_params(object)
    426         else:
    427             params = object.get_params(deep=False)

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in _changed_params(estimator)
     89     estimator with non-default values."""
     90 
---> 91     params = estimator.get_params(deep=False)
     92     init_func = getattr(estimator.__init__, 'deprecated_original',
     93                         estimator.__init__)

~/.local/lib/python3.8/site-packages/sklearn/base.py in get_params(self, deep)
    193         out = dict()
    194         for key in self._get_param_names():
--> 195             value = getattr(self, key)
    196             if deep and hasattr(value, 'get_params'):
    197                 deep_items = value.get_params().items()

AttributeError: 'PCA' object has no attribute 'random_state'

----------------------------------------------------------------------
AttributeError                       Traceback (most recent call last)
~/.local/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

~/.local/lib/python3.8/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    392                         if cls is not object \
    393                                 and callable(cls.__dict__.get('__repr__')):
--> 394                             return _repr_pprint(obj, self, cycle)
    395 
    396             return _default_pprint(obj, self, cycle)

~/.local/lib/python3.8/site-packages/IPython/lib/pretty.py in _repr_pprint(obj, p, cycle)
    698     """A pprint that just redirects to the normal repr function."""
    699     # Find newlines and replace them with p.break_()
--> 700     output = repr(obj)
    701     lines = output.splitlines()
    702     with p.group():

~/.local/lib/python3.8/site-packages/sklearn/base.py in __repr__(self, N_CHAR_MAX)
    258             n_max_elements_to_show=N_MAX_ELEMENTS_TO_SHOW)
    259 
--> 260         repr_ = pp.pformat(self)
    261 
    262         # Use bruteforce ellipsis when there are a lot of non-blank characters

~/anaconda3/lib/python3.8/pprint.py in pformat(self, object)
    151     def pformat(self, object):
    152         sio = _StringIO()
--> 153         self._format(object, sio, 0, 0, {}, 0)
    154         return sio.getvalue()
    155 

~/anaconda3/lib/python3.8/pprint.py in _format(self, object, stream, indent, allowance, context, level)
    168             self._readable = False
    169             return
--> 170         rep = self._repr(object, context, level)
    171         max_width = self._width - indent - allowance
    172         if len(rep) > max_width:

~/anaconda3/lib/python3.8/pprint.py in _repr(self, object, context, level)
    402 
    403     def _repr(self, object, context, level):
--> 404         repr, readable, recursive = self.format(object, context.copy(),
    405                                                 self._depth, level)
    406         if not readable:

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in format(self, object, context, maxlevels, level)
    178 
    179     def format(self, object, context, maxlevels, level):
--> 180         return _safe_repr(object, context, maxlevels, level,
    181                           changed_only=self._changed_only)
    182 

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in _safe_repr(object, context, maxlevels, level, changed_only)
    423         recursive = False
    424         if changed_only:
--> 425             params = _changed_params(object)
    426         else:
    427             params = object.get_params(deep=False)

~/.local/lib/python3.8/site-packages/sklearn/utils/_pprint.py in _changed_params(estimator)
     89     estimator with non-default values."""
     90 
---> 91     params = estimator.get_params(deep=False)
     92     init_func = getattr(estimator.__init__, 'deprecated_original',
     93                         estimator.__init__)

~/.local/lib/python3.8/site-packages/sklearn/base.py in get_params(self, deep)
    193         out = dict()
    194         for key in self._get_param_names():
--> 195             value = getattr(self, key)
    196             if deep and hasattr(value, 'get_params'):
    197                 deep_items = value.get_params().items()

AttributeError: 'PCA' object has no attribute 'random_state'


```

**Desktop (please complete the following information):**
 - OS: linux
 - Python Version: 3.8
 - Yellowbrick Version: 1.3

**Additional information**
This bug is easy to correct, in  yellowbrick/features/pca.py:
actually : 
```
            colorbar=colorbar,
            **kwargs
        )

        # Data Parameters
        self.scale = scale
        self.proj_features = proj_features
```

correction :
```
            colorbar=colorbar,
            **kwargs
        )

        self.random_state=random_state
        # Data Parameters
        self.scale = scale
        self.proj_features = proj_features
```

 

ROC/AUC AttributeError: 'LogisticRegression' object has no attribute 'binary'
**Describe the bug**
Using logistic regression with the ROC/AUC functions throws `AttributeError: 'LogisticRegression' object has no attribute 'binary'`, however, the plot still shows up and it seems like most things work.

**To Reproduce**
This is the demo from the docs (https://www.scikit-yb.org/en/latest/api/classifier/rocauc.html):

```python
from yellowbrick.classifier.rocauc import roc_auc
from yellowbrick.datasets import load_credit
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

#Load the classification dataset
X, y = load_credit()

#Create the train and test data
X_train, X_test, y_train, y_test = train_test_split(X,y)

# Instantiate the visualizer with the classification model
model = LogisticRegression()
roc_auc(model, X_train, y_train, X_test=X_test, y_test=y_test, classes=['not_defaulted', 'defaulted'])
```

**Dataset**
From the demo.

**Expected behavior**
Should not throw an AttributeError.

**Traceback**

```python
AttributeError                            Traceback (most recent call last)
~\Anaconda3\envs\datasci\lib\site-packages\IPython\core\formatters.py in __call__(self, obj, include, exclude)
    968
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\base.py in _repr_mimebundle_(self, **kwargs)
    462     def _repr_mimebundle_(self, **kwargs):
    463         """Mime bundle used by jupyter kernels to display estimator"""
--> 464         output = {"text/plain": repr(self)}
    465         if get_config()["display"] == 'diagram':
    466             output["text/html"] = estimator_html_repr(self)

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\base.py in __repr__(self, N_CHAR_MAX)
    258             n_max_elements_to_show=N_MAX_ELEMENTS_TO_SHOW)
    259
--> 260         repr_ = pp.pformat(self)
    261
    262         # Use bruteforce ellipsis when there are a lot of non-blank characters

~\Anaconda3\envs\datasci\lib\pprint.py in pformat(self, object)
    151     def pformat(self, object):
    152         sio = _StringIO()
--> 153         self._format(object, sio, 0, 0, {}, 0)
    154         return sio.getvalue()
    155

~\Anaconda3\envs\datasci\lib\pprint.py in _format(self, object, stream, indent, allowance, context, level)
    168             self._readable = False
    169             return
--> 170         rep = self._repr(object, context, level)
    171         max_width = self._width - indent - allowance
    172         if len(rep) > max_width:

~\Anaconda3\envs\datasci\lib\pprint.py in _repr(self, object, context, level)
    402
    403     def _repr(self, object, context, level):
--> 404         repr, readable, recursive = self.format(object, context.copy(),
    405                                                 self._depth, level)
    406         if not readable:

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in format(self, object, context, maxlevels, level)
    178
    179     def format(self, object, context, maxlevels, level):
--> 180         return _safe_repr(object, context, maxlevels, level,
    181                           changed_only=self._changed_only)
    182

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in _safe_repr(object, context, maxlevels, level, changed_only)
    423         recursive = False
    424         if changed_only:
--> 425             params = _changed_params(object)
    426         else:
    427             params = object.get_params(deep=False)

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in _changed_params(estimator)
     89     estimator with non-default values."""
     90
---> 91     params = estimator.get_params(deep=False)
     92     init_func = getattr(estimator.__init__, 'deprecated_original',
     93                         estimator.__init__)

~\Anaconda3\envs\datasci\lib\site-packages\yellowbrick\base.py in get_params(self, deep)
    340         the estimator params.
    341         """
--> 342         params = super(ModelVisualizer, self).get_params(deep=deep)
    343         for param in list(params.keys()):
    344             if param.startswith("estimator__"):

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\base.py in get_params(self, deep)
    193         out = dict()
    194         for key in self._get_param_names():
--> 195             value = getattr(self, key)
    196             if deep and hasattr(value, 'get_params'):
    197                 deep_items = value.get_params().items()

~\Anaconda3\envs\datasci\lib\site-packages\yellowbrick\utils\wrapper.py in __getattr__(self, attr)
     40     def __getattr__(self, attr):
     41         # proxy to the wrapped object
---> 42         return getattr(self._wrapped, attr)

AttributeError: 'LogisticRegression' object has no attribute 'binary'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\Anaconda3\envs\datasci\lib\site-packages\IPython\core\formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

~\Anaconda3\envs\datasci\lib\site-packages\IPython\lib\pretty.py in pretty(self, obj)
    392                         if cls is not object \
    393                                 and callable(cls.__dict__.get('__repr__')):
--> 394                             return _repr_pprint(obj, self, cycle)
    395
    396             return _default_pprint(obj, self, cycle)

~\Anaconda3\envs\datasci\lib\site-packages\IPython\lib\pretty.py in _repr_pprint(obj, p, cycle)
    698     """A pprint that just redirects to the normal repr function."""
    699     # Find newlines and replace them with p.break_()
--> 700     output = repr(obj)
    701     lines = output.splitlines()
    702     with p.group():

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\base.py in __repr__(self, N_CHAR_MAX)
    258             n_max_elements_to_show=N_MAX_ELEMENTS_TO_SHOW)
    259
--> 260         repr_ = pp.pformat(self)
    261
    262         # Use bruteforce ellipsis when there are a lot of non-blank characters

~\Anaconda3\envs\datasci\lib\pprint.py in pformat(self, object)
    151     def pformat(self, object):
    152         sio = _StringIO()
--> 153         self._format(object, sio, 0, 0, {}, 0)
    154         return sio.getvalue()
    155

~\Anaconda3\envs\datasci\lib\pprint.py in _format(self, object, stream, indent, allowance, context, level)
    168             self._readable = False
    169             return
--> 170         rep = self._repr(object, context, level)
    171         max_width = self._width - indent - allowance
    172         if len(rep) > max_width:

~\Anaconda3\envs\datasci\lib\pprint.py in _repr(self, object, context, level)
    402
    403     def _repr(self, object, context, level):
--> 404         repr, readable, recursive = self.format(object, context.copy(),
    405                                                 self._depth, level)
    406         if not readable:

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in format(self, object, context, maxlevels, level)
    178
    179     def format(self, object, context, maxlevels, level):
--> 180         return _safe_repr(object, context, maxlevels, level,
    181                           changed_only=self._changed_only)
    182

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in _safe_repr(object, context, maxlevels, level, changed_only)
    423         recursive = False
    424         if changed_only:
--> 425             params = _changed_params(object)
    426         else:
    427             params = object.get_params(deep=False)

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\utils\_pprint.py in _changed_params(estimator)
     89     estimator with non-default values."""
     90
---> 91     params = estimator.get_params(deep=False)
     92     init_func = getattr(estimator.__init__, 'deprecated_original',
     93                         estimator.__init__)

~\Anaconda3\envs\datasci\lib\site-packages\yellowbrick\base.py in get_params(self, deep)
    340         the estimator params.
    341         """
--> 342         params = super(ModelVisualizer, self).get_params(deep=deep)
    343         for param in list(params.keys()):
    344             if param.startswith("estimator__"):

~\Anaconda3\envs\datasci\lib\site-packages\sklearn\base.py in get_params(self, deep)
    193         out = dict()
    194         for key in self._get_param_names():
--> 195             value = getattr(self, key)
    196             if deep and hasattr(value, 'get_params'):
    197                 deep_items = value.get_params().items()

~\Anaconda3\envs\datasci\lib\site-packages\yellowbrick\utils\wrapper.py in __getattr__(self, attr)
     40     def __getattr__(self, attr):
     41         # proxy to the wrapped object
---> 42         return getattr(self._wrapped, attr)

AttributeError: 'LogisticRegression' object has no attribute 'binary'
```

**Desktop (please complete the following information):**
 - OS: Windows 10
 - Python Version 3.8 Anaconda
 - Yellowbrick Version current latest (1.3.post1)
 - sklearn version: 0.24.1

**Additional context**
I ran this in IPython, noticed a similar/the same bug in Jupyter.



Discussion:
Hi @phbillet thank you for the bug report, this is probably a result of the changes we made to `get_params` and `set_params` in #1147. Your solution is also a good one; the `random_state` gets used in the `PCATransformer` but I can't think of a reason why it's not a good idea to also set it on the visualizer. To keep the random state in sync, however, I suggest a property:

```python
def __init__(self, ...):
    ...
    self._random_state = random_state

@property
def random_state(self):
    return self._random_state

@random_state.setter(self, val):
    self._random_state = val
    self.pca_transformer.set_params(pca__random_state=val)
```
I get the same error with a GaussianNB model: `AttributeError: 'GaussianNB' object has no attribute 'binary'`. Seems like it is happening for all models most likely.
@nateGeorge cheers, this is a regression and related to #1160 - thank you for finding it and bringing it to our attention! 

To fix this - we need to set `self.binary = binary` in the `__init__` rather than simply relying on the `if/else` to set the `macro/micro` flags. This is because scikit-learn uses `inspect` to determine the class signature for `get_params` and `set_params`. 

Resolving patch:
diff --git a/yellowbrick/classifier/rocauc.py b/yellowbrick/classifier/rocauc.py
--- a/yellowbrick/classifier/rocauc.py
+++ b/yellowbrick/classifier/rocauc.py
@@ -208,7 +208,10 @@ def __init__(
         # Set the visual parameters for ROCAUC
         # NOTE: the binary flag breaks our API since it's really just a meta parameter
         # for micro, macro, and per_class. We knew this going in, but did it anyway.
-        if binary:
+        
+        self.binary = binary
+        
+        if self.binary:
             self.micro = False
             self.macro = False
             self.per_class = False
diff --git a/yellowbrick/features/pca.py b/yellowbrick/features/pca.py
--- a/yellowbrick/features/pca.py
+++ b/yellowbrick/features/pca.py
@@ -200,6 +200,16 @@ def __init__(
             raise YellowbrickValueError(
                 "heatmap and colorbar are not compatible with 3d projections"
             )
+        self._random_state = random_state
+
+    @property
+    def random_state(self):
+        return self._random_state
+
+    @random_state.setter
+    def random_state(self, val):
+        self._random_state = val
+        self.pca_transformer.set_params(pca__random_state=val)
 
     @property
     def uax(self):


Test patch: