Repository: bokeh/bokeh

Issue:
Passing an index for factors throws value error
Python 3.6
Bokeh 0.12.9
Pandas 0.20.3

Using an index from a Pandas DataFrame as factors throws an error. 

[Example](https://bokeh.pydata.org/en/latest/docs/user_guide/categorical.html#userguide-categorical):
```
from bokeh.io import show, output_notebook
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.palettes import Spectral6

output_notebook()

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ['2015', '2016', '2017']

data = {'2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 3, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

df = pd.DataFrame(data, index=fruits)
fruits = df.index
years = list(df.columns) # this works
years = df.columns #this is broken


x = [ (fruit, year) for fruit in fruits for year in years ]
counts = sum(zip(data['2015'], data['2016'], data['2017']), ())

source = ColumnDataSource(data=dict(x=x, counts=counts))

p = figure(x_range=FactorRange(*x), plot_height=250, title="Fruit Counts by Year", toolbar_location=None, tools="")

p.vbar(x='x', top='counts', width=0.9, source=source, line_color="white", fill_color=factor_cmap('x', palette=Spectral6, factors=years, start=1, end=2))

p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None

show(p)
```

Error:
```
~\Anaconda3\envs\bilbo\lib\site-packages\bokeh\models\mappers.py in __init__(self, **kwargs)
     77         palette = self.palette
     78         factors = self.factors
---> 79         if palette and factors:
     80             if len(palette) < len(factors):
     81                 extra_factors = factors[len(palette):]

~\Anaconda3\envs\bilbo\lib\site-packages\pandas\core\indexes\base.py in __nonzero__(self)
   1620         raise ValueError("The truth value of a {0} is ambiguous. "
   1621                          "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
-> 1622                          .format(self.__class__.__name__))
   1623 
   1624     __bool__ = __nonzero__

ValueError: The truth value of a Index is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```




Discussion:

Resolving patch:
diff --git a/bokeh/models/mappers.py b/bokeh/models/mappers.py
--- a/bokeh/models/mappers.py
+++ b/bokeh/models/mappers.py
@@ -76,7 +76,7 @@ def __init__(self, **kwargs):
         super(ColorMapper, self).__init__(**kwargs)
         palette = self.palette
         factors = self.factors
-        if palette and factors:
+        if palette is not None and factors is not None:
             if len(palette) < len(factors):
                 extra_factors = factors[len(palette):]
                 warnings.warn("Palette length does not match number of factors. %s will be assigned to `nan_color` %s" % (extra_factors, self.nan_color))


Test patch:
diff --git a/bokeh/models/tests/test_mappers.py b/bokeh/models/tests/test_mappers.py
--- a/bokeh/models/tests/test_mappers.py
+++ b/bokeh/models/tests/test_mappers.py
@@ -48,3 +48,19 @@ def test_warning_if_categorical_color_mapper_with_short_palette(recwarn):
 def test_no_warning_if_categorical_color_mapper_with_long_palette(recwarn):
     CategoricalColorMapper(factors=["a", "b", "c"], palette=["red", "green", "orange", "blue"])
     assert len(recwarn) == 0
+
+def test_categorical_color_mapper_with_pandas_index():
+    import pandas as pd
+    from bokeh.palettes import Spectral6
+    fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
+    years = ['2015', '2016', '2017']
+    data = {'2015'   : [2, 1, 4, 3, 2, 4],
+            '2016'   : [5, 3, 3, 2, 4, 6],
+            '2017'   : [3, 2, 4, 4, 5, 3]}
+
+    df = pd.DataFrame(data, index=fruits)
+    fruits = df.index
+    years = df.columns
+    m = CategoricalColorMapper(palette=Spectral6, factors=years, start=1, end=2)
+    assert list(m.factors) == list(years)
+    assert isinstance(m.factors, pd.Index)