Repository: DistrictDataLabs/yellowbrick

Issue:
Detect continuous/discrete target vals for color util
Right now the ParallelCoordinates and RadViz do not work for regressions since it supports a class based visualization. 

Give these classes the opportunity to do a continuous space by using a sequential colormap and coloring the instance lines according to target value.


Discussion:
Now that we have the `color_sequence`  function, we can create a binning method to assign continuous valued points to the number of classes specified by the colormap.
Finding a solution to this has been tricky because discrete classes has been embedded into the logic of both RadViz and ParallelCoordinates. There are two primary features that continuous data requires:

1. A colormap that will bin values into a consistent space. 
2. Instead of a label legend, a colorbar that shows the ranges of the instances.

To handle this for now, I'm going to do add a principle argument to both visualizers called "target_type" whose values should be "auto", "discrete", or "continuous" or None. Discrete or continuous will select the correct methodology. For auto or None values. I will create two utility functions: 

- `is_continuous(y)`
- `is_discrete(y)`

Which will accept a vector and use rules to decide if the data is continuous or discrete. Not exactly sure how to implement this. 

This should solve the issue for now, but we should revisit it when we develop RadViz and ParallelCoordinates with more features and functionality.
Ok, this ticket has been extremely difficult to work on -- the RadViz and Parallel Coordinates classes are entirely set up for class based visualization only (thanks to their being pulled from matplotlib. 

This needs to be looked at a bit more closely, and I stashed my changes (which touched nearly every module in yellowbrick). We'll have to push this back to another sprint.
Some notes here:
http://bbengfort.github.io/snippets/2017/01/17/resolving-matplotlib-colors.html
Sklearn apparently does have a type of target checker that returns continuous or classification as one of it's outputs:

https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/multiclass.py#L175
A very lightweight version of this has been implemented in #399 
See #334 for package where this should be placed. 

Resolving patch:
diff --git a/yellowbrick/utils/target.py b/yellowbrick/utils/target.py
new file mode 100644
--- /dev/null
+++ b/yellowbrick/utils/target.py
@@ -0,0 +1,76 @@
+# yellowbrick.utils.target
+# Helper functions related to the target variable.
+#
+# Author:   Benjamin Bengfort <benjamin@bengfort.com>
+# Created:  Thu Dec 27 20:16:18 2018 -0500
+#
+# For license information, see LICENSE.txt
+#
+# ID: target.py [] benjamin@bengfort.com $
+
+"""
+Helper functions related to the target variable.
+"""
+
+##########################################################################
+## Imports and Module Variables
+##########################################################################
+
+import numpy as np
+
+from sklearn.utils.multiclass import type_of_target
+
+
+__all__ = [
+    'CONTINUOUS', 'DISCRETE', 'UNKNOWN', 'MAX_DISCRETE_CLASSES', 'target_color_type'
+]
+
+CONTINUOUS = "continuous"
+DISCRETE   = "discrete"
+UNKNOWN    = "unknown"
+MAX_DISCRETE_CLASSES = 12
+
+
+##########################################################################
+## Helper Functions
+##########################################################################
+
+def target_color_type(y):
+    """
+    Determines the type of color space that will best represent the target
+    variable y, e.g. either a discrete (categorical) color space or a
+    continuous color space that requires a colormap. This function can handle
+    both 1D or column vectors as well as multi-output targets.
+
+    Parameters
+    ----------
+    y : array-like
+        Must be a valid array-like data structure that can be passed to a
+        scikit-learn supervised estimator.
+
+    Returns
+    -------
+    color_type : string
+        One of:
+
+        * 'discrete': `y` is either a binary target or a multiclass target
+          with <= 12 discrete classes.
+        * 'continuous': `y` is an array-like of floats that are not all
+          integers or a multiclass target with > 12 discrete classes.
+        * 'unknown': `y` is array-like but none of the above. For example
+          a multilabel-indicator or a 3D array. No exception is raised.
+    """
+    ttype = type_of_target(y)
+
+    if ttype.startswith(CONTINUOUS):
+        return CONTINUOUS
+
+    if ttype.startswith("binary"):
+        return DISCRETE
+
+    if ttype.startswith("multiclass"):
+        if len(np.unique(y)) > MAX_DISCRETE_CLASSES:
+            return CONTINUOUS
+        return DISCRETE
+
+    return UNKNOWN


Test patch:
diff --git a/tests/test_utils/test_target.py b/tests/test_utils/test_target.py
new file mode 100644
--- /dev/null
+++ b/tests/test_utils/test_target.py
@@ -0,0 +1,65 @@
+# tests.test_utils.test_target
+# Tests for the target helper functions module.
+#
+# Author:   Benjamin Bengfort <benjamin@bengfort.com>
+# Created:  Thu Dec 27 20:43:31 2018 -0500
+#
+# For license information, see LICENSE.txt
+#
+# ID: test_target.py [] benjamin@bengfort.com $
+
+"""
+Tests for the target helper functions module.
+"""
+
+##########################################################################
+## Imports
+##########################################################################
+
+import pytest
+import numpy as np
+
+from yellowbrick.utils.target import *
+from sklearn.datasets import make_regression, make_classification
+
+
+##########################################################################
+## Target Color Type Tests
+##########################################################################
+
+@pytest.mark.parametrize("value,expected", [
+    (['a', 'b', 'a', 'b', 'c'], DISCRETE),
+    ([1, 2, 1, 2, 3], DISCRETE),
+    ([.23, 0.94, 1.3, -1.02, 0.11], CONTINUOUS),
+    ([1, 2, 0.2, 0.5, 1], CONTINUOUS),
+    (np.array([0.2, 2.2, 1.2, -3.1]), CONTINUOUS),
+    (np.array([[1, 2], [0, 2], [2, 1]]), DISCRETE),
+    (np.array([[[1,2], [1,2]], [[1,2], [1,2]]]), UNKNOWN),
+], ids=['list str', 'list int', 'list float', 'mixed list', 'float array', 'multioutput', 'cube'])
+def test_target_color_type(value, expected):
+    """
+    Test the target_color_type helper function with a variety of data types
+    """
+    assert target_color_type(value) == expected
+
+
+@pytest.mark.parametrize("n_classes,expected", [
+    (2, DISCRETE),
+    (4, DISCRETE),
+    (MAX_DISCRETE_CLASSES, DISCRETE),
+    (MAX_DISCRETE_CLASSES+3, CONTINUOUS),
+], ids=["binary", "multiclass", "max discrete", "too many discrete"])
+def test_binary_target_color_type(n_classes, expected):
+    """
+    Test classification target color type
+    """
+    _, y = make_classification(n_classes=n_classes, n_informative=n_classes+2)
+    assert target_color_type(y) == expected
+
+
+def test_regression_target_color_type():
+    """
+    Test regression target color type
+    """
+    _, y = make_regression()
+    assert target_color_type(y) == CONTINUOUS