Repository: bokeh/bokeh

Issue:
Support passing a pandas.Series as x_range for Figure
Bokeh 0.12.14dev3

Currently, running this code (taken from https://stackoverflow.com/questions/48152251/unable-to-create-charts-using-bokeh/48170111):
```python
import pandas as pd
from bokeh.plotting import figure, show

car = pd.DataFrame([l.split() for l in """Jan 1808
Feb 1251
Mar 3023""".splitlines()], columns=['Month', 'Sales'])

p = figure(title="Bokeh Scatter Plot Example", x_axis_label='Month',
           y_axis_label='Sales', x_range=car['Month'])

p.circle(car['Month'], car['Sales'], size=5, alpha=0.5)

show(p)
```
results in 
```python
Traceback (most recent call last):
  File "/home/p-himik/dev/git/bokeh/test.py", line 29, in <module>
    y_axis_label='Sales', x_range=car['Month'])
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/figure.py", line 793, in figure
    return Figure(**kwargs)
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/figure.py", line 140, in __init__
    self.x_range = _get_range(opts.x_range)
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/helpers.py", line 355, in _get_range
    raise ValueError("Unrecognized range input: '%s'" % str(range_input))
ValueError: Unrecognized range input: '0    Jan
1    Feb
2    Mar
Name: Month, dtype: object'
```

The workaround is to replace `x_range=car['Month']` with `x_range=list(car['Month'])`.
Support passing a pandas.Series as x_range for Figure
Bokeh 0.12.14dev3

Currently, running this code (taken from https://stackoverflow.com/questions/48152251/unable-to-create-charts-using-bokeh/48170111):
```python
import pandas as pd
from bokeh.plotting import figure, show

car = pd.DataFrame([l.split() for l in """Jan 1808
Feb 1251
Mar 3023""".splitlines()], columns=['Month', 'Sales'])

p = figure(title="Bokeh Scatter Plot Example", x_axis_label='Month',
           y_axis_label='Sales', x_range=car['Month'])

p.circle(car['Month'], car['Sales'], size=5, alpha=0.5)

show(p)
```
results in 
```python
Traceback (most recent call last):
  File "/home/p-himik/dev/git/bokeh/test.py", line 29, in <module>
    y_axis_label='Sales', x_range=car['Month'])
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/figure.py", line 793, in figure
    return Figure(**kwargs)
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/figure.py", line 140, in __init__
    self.x_range = _get_range(opts.x_range)
  File "/home/p-himik/dev/git/bokeh/bokeh/plotting/helpers.py", line 355, in _get_range
    raise ValueError("Unrecognized range input: '%s'" % str(range_input))
ValueError: Unrecognized range input: '0    Jan
1    Feb
2    Mar
Name: Month, dtype: object'
```

The workaround is to replace `x_range=car['Month']` with `x_range=list(car['Month'])`.


Discussion:
Probably reasonable to just check for a `Series` and cast to list internally
Probably reasonable to just check for a `Series` and cast to list internally

Resolving patch:
diff --git a/bokeh/plotting/helpers.py b/bokeh/plotting/helpers.py
--- a/bokeh/plotting/helpers.py
+++ b/bokeh/plotting/helpers.py
@@ -346,6 +346,8 @@ def _get_range(range_input):
         return FactorRange(factors=sorted(list(range_input.groups.keys())))
     if isinstance(range_input, Range):
         return range_input
+    if pd and isinstance(range_input, pd.Series):
+        range_input = range_input.values
     if isinstance(range_input, (Sequence, np.ndarray)):
         if all(isinstance(x, string_types) for x in range_input):
             return FactorRange(factors=list(range_input))


Test patch:
diff --git a/bokeh/plotting/tests/test_helpers.py b/bokeh/plotting/tests/test_helpers.py
--- a/bokeh/plotting/tests/test_helpers.py
+++ b/bokeh/plotting/tests/test_helpers.py
@@ -1,7 +1,13 @@
 import datetime
 from mock import mock
-
-import pandas as pd
+from unittest import skipIf
+
+import numpy as np
+try:
+    import pandas as pd
+    is_pandas = True
+except ImportError as e:
+    is_pandas = False
 import pytest
 
 from bokeh.models import ColumnDataSource, CDSView, Marker
@@ -134,6 +140,7 @@ def test__stack_broadcast_name_list_overrides():
         assert kw['bar'] == "baz"
         assert kw['name'] == names[i]
 
+@skipIf(not is_pandas, "pandas not installed")
 def test__graph_will_convert_dataframes_to_sources():
     node_source = pd.DataFrame(data=dict(foo=[]))
     edge_source = pd.DataFrame(data=dict(start=[], end=[], bar=[]))
@@ -275,19 +282,33 @@ def test__get_range_with_Range():
         assert r is rng
 
 def test__get_range_with_ndarray():
-    import numpy as np
     r = _get_range(np.array([10, 20]))
     assert isinstance(r, Range1d)
     assert r.start == 10
     assert r.end == 20
 
+def test__get_range_with_too_long_ndarray():
+    with pytest.raises(ValueError):
+        _get_range(np.array([10, 20, 30]))
+
 def test__get_range_with_ndarray_factors():
-    import numpy as np
     f = np.array(["Crosby", "Stills", "Nash", "Young"])
     r = _get_range(f)
     assert isinstance(r, FactorRange)
     assert r.factors == list(f)
 
+@skipIf(not is_pandas, "pandas not installed")
+def test__get_range_with_series():
+    r = _get_range(pd.Series([20, 30]))
+    assert isinstance(r, Range1d)
+    assert r.start == 20
+    assert r.end == 30
+
+@skipIf(not is_pandas, "pandas not installed")
+def test__get_range_with_too_long_series():
+    with pytest.raises(ValueError):
+        _get_range(pd.Series([20, 30, 40]))
+
 def test__get_range_with_string_seq():
     f = ["foo" ,"end", "baz"]
     for t in [list, tuple]: