Repository: bokeh/bokeh

Issue:
Ranges using NumPy types, when creating a new figure, fail
It appears that when providing `x_range` or `y_range` a `numpy.float32` that the follow traceback is generated. Converting the `numpy.float32` values to either `numpy.float64` or `float` resolves the issue. However, it would be nice not to get this error in the first place.

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-134-a923ef6fd65e> in <module>()
    102 tr_srcs = ColumnDataSource(data=dict(times_sel=[], traces_sel=[], colors_sel=[]))
    103 plot_tr = bp.figure(plot_width=950, plot_height=500, x_range=(numpy.float32(time[0]), numpy.float32(time[-1])), y_range=(float(traces.min()), float(traces.max())),
--> 104                  tools=["pan", "box_zoom", "resize", "wheel_zoom", "save", "reset"], title="ROI traces")
    105 plot_tr.multi_line("times_sel", "traces_sel", source=tr_srcs, alpha=0.6, color="colors_sel")
    106 

/opt/conda/lib/python2.7/site-packages/bokeh/plotting.pyc in figure(**kwargs)
    273         kwargs['plot_width'] = kwargs.pop('width')
    274 
--> 275     fig = Figure(**kwargs)
    276     curdoc()._current_plot = fig
    277     if curdoc().autoadd:

/opt/conda/lib/python2.7/site-packages/bokeh/plotting.pyc in __init__(self, *arg, **kw)
     67         super(Figure, self).__init__(*arg, **kw)
     68 
---> 69         self.x_range = _get_range(x_range)
     70         self.y_range = _get_range(y_range)
     71 

/opt/conda/lib/python2.7/site-packages/bokeh/plotting_helpers.pyc in _get_range(range_input)
    171             except ValueError: # @mattpap suggests ValidationError instead
    172                 pass
--> 173     raise ValueError("Unrecognized range input: '%s'" % str(range_input))
    174 
    175 def _get_axis_class(axis_type, range_input):

ValueError: Unrecognized range input: '(0.0, 149.0)'
```



Discussion:
If that is failing with `numpy.float32` but working with `numpy.float64` than that means this `ininstance` check against `Sequence` 

https://github.com/bokeh/bokeh/blob/master/bokeh/plotting_helpers.py#L165

is failing with `numpy.float32` but working with `numpy.float64`? That seems like it would be an upstream problem with numpy.

Here is an example of the problem ( http://nbviewer.ipython.org/gist/jakirkham/38c51016d2a3731fe3d1 ). Code in this gist ( https://gist.github.com/jakirkham/38c51016d2a3731fe3d1 ).

It's also possible a subscripting error could trigger that exception:

https://github.com/bokeh/bokeh/blob/master/bokeh/plotting_helpers.py#L170

but your example obviously has two elements. 

I don't see how `np.float64` could work:

```
In [4]: a = np.array([1,2], dtype=np.float64)

In [5]: b = np.array([1,2], dtype=np.float32)

In [6]: isinstance(a, Sequence)
Out[6]: False

In [7]: isinstance(b, Sequence)
Out[7]: False
```

what version of numpy? maybe this is something they changed/fixed recently?

So, I tried using `ctypes` and didn't have the same issue. Maybe your theory is correct. Currently on the latest stable `numpy` release, `1.9.2`.

I changed the example to show how I was using `numpy.float64`. They are lists. Not arrays.

**_OH**_, you mean creating array scalars, as the items in a list? I'm guessing the items fail the `Range1d` validation type check. Just naively checking for and allowing Numpy array scalars there is problematic, it would introduce a hard dependency on NumPy at the lowest levels of the library, affecting everything, which is not acceptable. Not sure it's worth the effort to try and be clever somehow. Why would you need to specifically pass array scalars like that?

If you'd care to try and work up a PR that allows array scalars without a hard NumPy dependence, I would suggest starting here:

https://github.com/bokeh/bokeh/blob/master/bokeh/properties.py#L505

I imagine a possible solution is to conditionally define `numpy_integer_types`, `numpy_float_types` etc. that are just empty tuples if NumPy is not installed, and full of the apropropriate array scalar types if NumPy is installed. 

I think it could be done without introducing a hard dependency on NumPy. See below.

```
>>> import numbers
>>> import numpy
>>> a = numpy.float32(2)
>>> isinstance(a, numbers.Real)
True
>>> isinstance(a, numbers.Number)
True
```

That might work too. ping @mattpap any thoughts?

Regarding why I am doing this. Of course, this example is a bit contrived. In real life, I have some data of type `numpy.float32` and I take the min and max of such data to make sure it will fit within the plot. It would be nice not to have to cast it to some sort of double first. However, that is not the worst workaround.

Sure that makes more sense. We are pretty slammed with a release right now if you wanted to work up a small PR it could definitely go in the next dev build after the release.  

Just looking through the code, it looks like it is already dependent on `numpy` as shown by these lines ( https://github.com/bokeh/bokeh/blob/master/bokeh/properties.py#L58 ) ( https://github.com/bokeh/bokeh/blob/master/bokeh/properties.py#L107 ). Switching to `numbers` would free you up from this constraint in `bokeh.properties` and aid you in the goal of softening the `numpy` dependency ( https://github.com/bokeh/bokeh/issues/1280 ).

@jakirkham yes, less numpy dependence (ideally no hard dependency) is the goal, I do believe you are correct that there are some places that have slipped through currently. 


Resolving patch:
diff --git a/bokeh/properties.py b/bokeh/properties.py
--- a/bokeh/properties.py
+++ b/bokeh/properties.py
@@ -52,10 +52,10 @@ class Model(HasProps):
 from warnings import warn
 import inspect
 import logging
+import numbers
 logger = logging.getLogger(__name__)
 
-from six import integer_types, string_types, add_metaclass, iteritems
-import numpy as np
+from six import string_types, add_metaclass, iteritems
 
 from . import enums
 from .util.string import nice_join
@@ -104,7 +104,14 @@ def value(val):
     '''
     return dict(value=val)
 
-bokeh_integer_types = (np.int8, np.int16, np.int32, np.int64) + integer_types
+bokeh_bool_types = (bool,)
+try:
+    import numpy as np
+    bokeh_bool_types += (np.bool8,)
+except ImportError:
+    pass
+
+bokeh_integer_types = (numbers.Integral,)
 
 # used to indicate properties that are not set (vs null, None, etc)
 class _NotSet(object):
@@ -500,7 +507,7 @@ def from_json(self, json, models=None):
 
 class Bool(PrimitiveProperty):
     """ Boolean type property. """
-    _underlying_type = (bool, np.bool_)
+    _underlying_type = bokeh_bool_types
 
 class Int(PrimitiveProperty):
     """ Signed integer type property. """
@@ -508,11 +515,11 @@ class Int(PrimitiveProperty):
 
 class Float(PrimitiveProperty):
     """ Floating point type property. """
-    _underlying_type = (float, ) + bokeh_integer_types
+    _underlying_type = (numbers.Real,)
 
 class Complex(PrimitiveProperty):
     """ Complex floating point type property. """
-    _underlying_type = (complex, float) + bokeh_integer_types
+    _underlying_type = (numbers.Complex,)
 
 class String(PrimitiveProperty):
     """ String type property. """
@@ -646,6 +653,7 @@ def _is_seq(self, value):
         return isinstance(value, np.ndarray)
 
     def _new_instance(self, value):
+        import numpy as np
         return np.array(value)
 
 
@@ -1046,7 +1054,14 @@ def __init__(self, default=datetime.date.today(), help=None):
     def validate(self, value):
         super(Datetime, self).validate(value)
 
-        if (isinstance(value, (datetime.datetime, datetime.date, np.datetime64))):
+        datetime_types = (datetime.datetime, datetime.date)
+        try:
+            import numpy as np
+            datetime_types += (np.datetime64,)
+        except ImportError:
+            pass
+
+        if (isinstance(value, datetime_types)):
             return
         try:
             import pandas


Test patch:
diff --git a/bokeh/tests/test_properties.py b/bokeh/tests/test_properties.py
--- a/bokeh/tests/test_properties.py
+++ b/bokeh/tests/test_properties.py
@@ -402,6 +402,38 @@ def test_Bool(self):
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+        try:
+            import numpy as np
+            self.assertTrue(prop.is_valid(np.bool8(False)))
+            self.assertTrue(prop.is_valid(np.bool8(True)))
+            self.assertFalse(prop.is_valid(np.int8(0)))
+            self.assertFalse(prop.is_valid(np.int8(1)))
+            self.assertFalse(prop.is_valid(np.int16(0)))
+            self.assertFalse(prop.is_valid(np.int16(1)))
+            self.assertFalse(prop.is_valid(np.int32(0)))
+            self.assertFalse(prop.is_valid(np.int32(1)))
+            self.assertFalse(prop.is_valid(np.int64(0)))
+            self.assertFalse(prop.is_valid(np.int64(1)))
+            self.assertFalse(prop.is_valid(np.uint8(0)))
+            self.assertFalse(prop.is_valid(np.uint8(1)))
+            self.assertFalse(prop.is_valid(np.uint16(0)))
+            self.assertFalse(prop.is_valid(np.uint16(1)))
+            self.assertFalse(prop.is_valid(np.uint32(0)))
+            self.assertFalse(prop.is_valid(np.uint32(1)))
+            self.assertFalse(prop.is_valid(np.uint64(0)))
+            self.assertFalse(prop.is_valid(np.uint64(1)))
+            self.assertFalse(prop.is_valid(np.float16(0)))
+            self.assertFalse(prop.is_valid(np.float16(1)))
+            self.assertFalse(prop.is_valid(np.float32(0)))
+            self.assertFalse(prop.is_valid(np.float32(1)))
+            self.assertFalse(prop.is_valid(np.float64(0)))
+            self.assertFalse(prop.is_valid(np.float64(1)))
+            self.assertFalse(prop.is_valid(np.complex64(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex128(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex256(1.0+1.0j)))
+        except ImportError:
+            pass
+
     def test_Int(self):
         prop = Int()
 
@@ -419,6 +451,38 @@ def test_Int(self):
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+        try:
+            import numpy as np
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(False)))
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(True)))
+            self.assertTrue(prop.is_valid(np.int8(0)))
+            self.assertTrue(prop.is_valid(np.int8(1)))
+            self.assertTrue(prop.is_valid(np.int16(0)))
+            self.assertTrue(prop.is_valid(np.int16(1)))
+            self.assertTrue(prop.is_valid(np.int32(0)))
+            self.assertTrue(prop.is_valid(np.int32(1)))
+            self.assertTrue(prop.is_valid(np.int64(0)))
+            self.assertTrue(prop.is_valid(np.int64(1)))
+            self.assertTrue(prop.is_valid(np.uint8(0)))
+            self.assertTrue(prop.is_valid(np.uint8(1)))
+            self.assertTrue(prop.is_valid(np.uint16(0)))
+            self.assertTrue(prop.is_valid(np.uint16(1)))
+            self.assertTrue(prop.is_valid(np.uint32(0)))
+            self.assertTrue(prop.is_valid(np.uint32(1)))
+            self.assertTrue(prop.is_valid(np.uint64(0)))
+            self.assertTrue(prop.is_valid(np.uint64(1)))
+            self.assertFalse(prop.is_valid(np.float16(0)))
+            self.assertFalse(prop.is_valid(np.float16(1)))
+            self.assertFalse(prop.is_valid(np.float32(0)))
+            self.assertFalse(prop.is_valid(np.float32(1)))
+            self.assertFalse(prop.is_valid(np.float64(0)))
+            self.assertFalse(prop.is_valid(np.float64(1)))
+            self.assertFalse(prop.is_valid(np.complex64(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex128(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex256(1.0+1.0j)))
+        except ImportError:
+            pass
+
     def test_Float(self):
         prop = Float()
 
@@ -436,6 +500,38 @@ def test_Float(self):
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+        try:
+            import numpy as np
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(False)))
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(True)))
+            self.assertTrue(prop.is_valid(np.int8(0)))
+            self.assertTrue(prop.is_valid(np.int8(1)))
+            self.assertTrue(prop.is_valid(np.int16(0)))
+            self.assertTrue(prop.is_valid(np.int16(1)))
+            self.assertTrue(prop.is_valid(np.int32(0)))
+            self.assertTrue(prop.is_valid(np.int32(1)))
+            self.assertTrue(prop.is_valid(np.int64(0)))
+            self.assertTrue(prop.is_valid(np.int64(1)))
+            self.assertTrue(prop.is_valid(np.uint8(0)))
+            self.assertTrue(prop.is_valid(np.uint8(1)))
+            self.assertTrue(prop.is_valid(np.uint16(0)))
+            self.assertTrue(prop.is_valid(np.uint16(1)))
+            self.assertTrue(prop.is_valid(np.uint32(0)))
+            self.assertTrue(prop.is_valid(np.uint32(1)))
+            self.assertTrue(prop.is_valid(np.uint64(0)))
+            self.assertTrue(prop.is_valid(np.uint64(1)))
+            self.assertTrue(prop.is_valid(np.float16(0)))
+            self.assertTrue(prop.is_valid(np.float16(1)))
+            self.assertTrue(prop.is_valid(np.float32(0)))
+            self.assertTrue(prop.is_valid(np.float32(1)))
+            self.assertTrue(prop.is_valid(np.float64(0)))
+            self.assertTrue(prop.is_valid(np.float64(1)))
+            self.assertFalse(prop.is_valid(np.complex64(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex128(1.0+1.0j)))
+            self.assertFalse(prop.is_valid(np.complex256(1.0+1.0j)))
+        except ImportError:
+            pass
+
     def test_Complex(self):
         prop = Complex()
 
@@ -453,6 +549,38 @@ def test_Complex(self):
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+        try:
+            import numpy as np
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(False)))
+            # TODO: self.assertFalse(prop.is_valid(np.bool8(True)))
+            self.assertTrue(prop.is_valid(np.int8(0)))
+            self.assertTrue(prop.is_valid(np.int8(1)))
+            self.assertTrue(prop.is_valid(np.int16(0)))
+            self.assertTrue(prop.is_valid(np.int16(1)))
+            self.assertTrue(prop.is_valid(np.int32(0)))
+            self.assertTrue(prop.is_valid(np.int32(1)))
+            self.assertTrue(prop.is_valid(np.int64(0)))
+            self.assertTrue(prop.is_valid(np.int64(1)))
+            self.assertTrue(prop.is_valid(np.uint8(0)))
+            self.assertTrue(prop.is_valid(np.uint8(1)))
+            self.assertTrue(prop.is_valid(np.uint16(0)))
+            self.assertTrue(prop.is_valid(np.uint16(1)))
+            self.assertTrue(prop.is_valid(np.uint32(0)))
+            self.assertTrue(prop.is_valid(np.uint32(1)))
+            self.assertTrue(prop.is_valid(np.uint64(0)))
+            self.assertTrue(prop.is_valid(np.uint64(1)))
+            self.assertTrue(prop.is_valid(np.float16(0)))
+            self.assertTrue(prop.is_valid(np.float16(1)))
+            self.assertTrue(prop.is_valid(np.float32(0)))
+            self.assertTrue(prop.is_valid(np.float32(1)))
+            self.assertTrue(prop.is_valid(np.float64(0)))
+            self.assertTrue(prop.is_valid(np.float64(1)))
+            self.assertTrue(prop.is_valid(np.complex64(1.0+1.0j)))
+            self.assertTrue(prop.is_valid(np.complex128(1.0+1.0j)))
+            self.assertTrue(prop.is_valid(np.complex256(1.0+1.0j)))
+        except ImportError:
+            pass
+
     def test_String(self):
         prop = String()