@@ -52,17 +52,18 @@ def to_scalar_or_list(v):
5252 return v
5353
5454
55- def copy_to_readonly_numpy_array (v , dtype = None , force_numeric = False ):
55+ def copy_to_readonly_numpy_array (v , kind = None , force_numeric = False ):
5656 """
5757 Convert an array-like value into a read-only numpy array
5858
5959 Parameters
6060 ----------
6161 v : array like
6262 Array like value (list, tuple, numpy array, pandas series, etc.)
63- dtype : str
64- If specified, the numpy dtype that the array should be forced to
65- have. If not specified then let numpy infer the datatype
63+ kind : str or tuple of str
64+ If specified, the numpy dtype kind (or kinds) that the array should
65+ have, or be converted to if possible.
66+ If not specified then let numpy infer the datatype
6667 force_numeric : bool
6768 If true, raise an exception if the resulting numpy array does not
6869 have a numeric dtype (i.e. dtype.kind not in ['u', 'i', 'f'])
@@ -74,30 +75,53 @@ def copy_to_readonly_numpy_array(v, dtype=None, force_numeric=False):
7475
7576 assert np is not None
7677
77- # Copy to numpy array and handle dtype param
78- # ------------------------------------------
79- # If dtype was not specified then it will be passed to the numpy array
80- # constructor as None and the data type will be inferred automatically
78+ # ### Process kind ###
79+ if not kind :
80+ kind = ()
81+ elif isinstance (kind , string_types ):
82+ kind = (kind ,)
83+
84+ first_kind = kind [0 ] if kind else None
8185
82- # TODO: support datetime dtype here and in widget serialization
8386 # u: unsigned int, i: signed int, f: float
84- numeric_kinds = ['u' , 'i' , 'f' ]
87+ numeric_kinds = {'u' , 'i' , 'f' }
88+ kind_default_dtypes = {
89+ 'u' : 'uint32' , 'i' : 'int32' , 'f' : 'float64' , 'O' : 'object' }
8590
86- # Unwrap data types that have a `values` property that might be a numpy
87- # array. If this values property is a numeric numpy array then we
88- # can take the fast path below
91+ # Handle pandas Series and Index objects
8992 if pd and isinstance (v , (pd .Series , pd .Index )):
90- v = v .values
93+ if v .dtype .kind in numeric_kinds :
94+ # Get the numeric numpy array so we use fast path below
95+ v = v .values
96+ elif v .dtype .kind == 'M' :
97+ # Convert datetime Series/Index to numpy array of datetimes
98+ if isinstance (v , pd .Series ):
99+ v = v .dt .to_pydatetime ()
100+ else :
101+ # DatetimeIndex
102+ v = v .to_pydatetime ()
91103
92104 if not isinstance (v , np .ndarray ):
105+ # v is not homogenous array
93106 v_list = [to_scalar_or_list (e ) for e in v ]
107+
108+ # Lookup dtype for requested kind, if any
109+ dtype = kind_default_dtypes .get (first_kind , None )
110+
111+ # construct new array from list
94112 new_v = np .array (v_list , order = 'C' , dtype = dtype )
95113 elif v .dtype .kind in numeric_kinds :
96- if dtype :
114+ # v is a homogenous numeric array
115+ if kind and v .dtype .kind not in kind :
116+ # Kind(s) were specified and this array doesn't match
117+ # Convert to the default dtype for the first kind
118+ dtype = kind_default_dtypes .get (first_kind , None )
97119 new_v = np .ascontiguousarray (v .astype (dtype ))
98120 else :
121+ # Either no kind was requested or requested kind is satisfied
99122 new_v = np .ascontiguousarray (v .copy ())
100123 else :
124+ # v is a non-numeric homogenous array
101125 new_v = v .copy ()
102126
103127 # Handle force numeric param
@@ -106,7 +130,7 @@ def copy_to_readonly_numpy_array(v, dtype=None, force_numeric=False):
106130 raise ValueError ('Input value is not numeric and'
107131 'force_numeric parameter set to True' )
108132
109- if dtype != 'unicode' :
133+ if 'U' not in kind :
110134 # Force non-numeric arrays to have object type
111135 # --------------------------------------------
112136 # Here we make sure that non-numeric arrays have the object
@@ -116,12 +140,6 @@ def copy_to_readonly_numpy_array(v, dtype=None, force_numeric=False):
116140 if new_v .dtype .kind not in ['u' , 'i' , 'f' , 'O' ]:
117141 new_v = np .array (v , dtype = 'object' )
118142
119- # Convert int64 arrays to int32
120- # -----------------------------
121- # JavaScript doesn't support int64 typed arrays
122- if new_v .dtype == 'int64' :
123- new_v = new_v .astype ('int32' )
124-
125143 # Set new array to be read-only
126144 # -----------------------------
127145 new_v .flags ['WRITEABLE' ] = False
@@ -749,10 +767,13 @@ def validate_coerce(self, v):
749767 # Pass None through
750768 pass
751769 elif self .array_ok and is_homogeneous_array (v ):
752- if v .dtype .kind not in ['i' , 'u' ]:
753- self .raise_invalid_val (v )
754770
755- v_array = copy_to_readonly_numpy_array (v , dtype = 'int32' )
771+ v_array = copy_to_readonly_numpy_array (v ,
772+ kind = ('i' , 'u' ),
773+ force_numeric = True )
774+
775+ if v_array .dtype .kind not in ['i' , 'u' ]:
776+ self .raise_invalid_val (v )
756777
757778 # Check min/max
758779 if self .has_min_max :
@@ -875,7 +896,7 @@ def validate_coerce(self, v):
875896
876897 if is_homogeneous_array (v ):
877898 # If not strict, let numpy cast elements to strings
878- v = copy_to_readonly_numpy_array (v , dtype = 'unicode ' )
899+ v = copy_to_readonly_numpy_array (v , kind = 'U ' )
879900
880901 # Check no_blank
881902 if self .no_blank :
@@ -1057,10 +1078,10 @@ def validate_coerce(self, v, should_raise=True):
10571078 # ### Check that elements have valid colors types ###
10581079 elif self .numbers_allowed () or invalid_els :
10591080 v = copy_to_readonly_numpy_array (
1060- validated_v , dtype = 'object ' )
1081+ validated_v , kind = 'O ' )
10611082 else :
10621083 v = copy_to_readonly_numpy_array (
1063- validated_v , dtype = 'unicode ' )
1084+ validated_v , kind = 'U ' )
10641085 elif self .array_ok and is_simple_array (v ):
10651086 validated_v = [
10661087 self .validate_coerce (e , should_raise = False )
@@ -1509,7 +1530,7 @@ def validate_coerce(self, v):
15091530 self .raise_invalid_elements (invalid_els )
15101531
15111532 if is_homogeneous_array (v ):
1512- v = copy_to_readonly_numpy_array (validated_v , dtype = 'unicode ' )
1533+ v = copy_to_readonly_numpy_array (validated_v , kind = 'U ' )
15131534 else :
15141535 v = to_scalar_or_list (v )
15151536 else :
@@ -1559,7 +1580,7 @@ def validate_coerce(self, v):
15591580 # Pass None through
15601581 pass
15611582 elif self .array_ok and is_homogeneous_array (v ):
1562- v = copy_to_readonly_numpy_array (v , dtype = 'object ' )
1583+ v = copy_to_readonly_numpy_array (v , kind = 'O ' )
15631584 elif self .array_ok and is_simple_array (v ):
15641585 v = to_scalar_or_list (v )
15651586 return v
0 commit comments