# Copyright 2015 Bloomberg Finance L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
============
Traits Types
============
.. currentmodule:: bqplot.traits
.. autosummary::
:toctree: _generate/
Date
"""
from traitlets import Instance, TraitError, TraitType, Undefined
import traittypes as tt
import numpy as np
import pandas as pd
import warnings
import datetime as dt
# Date
def date_to_json(value, obj):
if value is None:
return value
else:
return value.strftime('%Y-%m-%dT%H:%M:%S.%f')
def date_from_json(value, obj):
if value:
return dt.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
else:
return value
date_serialization = dict(to_json=date_to_json, from_json=date_from_json)
[docs]class Date(TraitType):
"""
A datetime trait type.
Converts the passed date into a string format that can be used to
construct a JavaScript datetime.
"""
def validate(self, obj, value):
try:
if isinstance(value, dt.datetime):
return value
if isinstance(value, dt.date):
return dt.datetime(value.year, value.month, value.day)
if np.issubdtype(np.dtype(value), np.datetime64):
# TODO: Fix this. Right now, we have to limit the precision
# of time to microseconds because np.datetime64.astype(datetime)
# returns date values only for precision <= 'us'
value_truncated = np.datetime64(value, 'us')
return value_truncated.astype(dt.datetime)
except Exception:
self.error(obj, value)
self.error(obj, value)
[docs] def __init__(self, default_value=dt.datetime.today(), **kwargs):
args = (default_value,)
self.default_value = default_value
super(Date, self).__init__(args=args, **kwargs)
self.tag(**date_serialization)
def convert_to_date(array, fmt='%m-%d-%Y'):
# If array is a np.ndarray with type == np.datetime64, the array can be
# returned as such. If it is an np.ndarray of dtype 'object' then conversion
# to string is tried according to the fmt parameter.
if(isinstance(array, np.ndarray) and np.issubdtype(array.dtype, np.datetime64)):
# no need to perform any conversion in this case
return array
elif(isinstance(array, list) or (isinstance(array, np.ndarray) and array.dtype == 'object')):
return_value = []
# Pandas to_datetime handles all the cases where the passed in
# data could be any of the combinations of
# [list, nparray] X [python_datetime, np.datetime]
# Because of the coerce=True flag, any non-compatible datetime type
# will be converted to pd.NaT. By this comparison, we can figure
# out if it is date castable or not.
if(len(np.shape(array)) == 2):
for elem in array:
temp_val = pd.to_datetime(
elem, errors='coerce', box=False, infer_datetime_format=True)
temp_val = elem if (
temp_val[0] == np.datetime64('NaT')) else temp_val
return_value.append(temp_val)
elif(isinstance(array, list)):
temp_val = pd.to_datetime(
array, errors='coerce', box=False, infer_datetime_format=True)
return_value = array if (
temp_val[0] == np.datetime64('NaT')) else temp_val
else:
temp_val = pd.to_datetime(
array, errors='coerce', box=False, infer_datetime_format=True)
temp_val = array if (
temp_val[0] == np.datetime64('NaT')) else temp_val
return_value = temp_val
return return_value
elif(isinstance(array, np.ndarray)):
warnings.warn("Array could not be converted into a date")
return array
# Array
def array_from_json(value, obj=None):
if value is not None:
if value.get('values') is not None:
dtype = {
'date': np.datetime64,
'float': np.float64
}.get(value.get('type'), object)
return np.asarray(value['values'], dtype=dtype)
def array_to_json(a, obj=None):
if a is not None:
if np.issubdtype(a.dtype, np.floating):
# replace nan with None
dtype = 'float'
a = np.where(np.isnan(a), None, a)
elif a.dtype in (int, np.int64):
dtype = 'float'
a = a.astype(np.float64)
elif np.issubdtype(a.dtype, np.datetime64):
dtype = 'date'
a = a.astype(np.str).astype('object')
for x in np.nditer(a, flags=['refs_ok'], op_flags=['readwrite']):
# for every element in the nd array, forcing the conversion into
# the format specified here.
temp_x = pd.to_datetime(x.flatten()[0])
if pd.isnull(temp_x):
x[...] = None
else:
x[...] = temp_x.to_pydatetime().strftime(
'%Y-%m-%dT%H:%M:%S.%f')
else:
dtype = a.dtype
return dict(values=a.tolist(), type=str(dtype))
else:
return dict(values=a, type=None)
array_serialization = dict(to_json=array_to_json, from_json=array_from_json)
# array validators
def array_squeeze(trait, value):
if len(value.shape) > 1:
return np.squeeze(value)
else:
return value
def array_dimension_bounds(mindim=0, maxdim=np.inf):
def validator(trait, value):
dim = len(value.shape)
if dim < mindim or dim > maxdim:
raise TraitError('Dimension mismatch for trait %s of class %s: expected an \
array of dimension comprised in interval [%s, %s] and got an array of shape %s'\
% (trait.name, trait.this_class, mindim, maxdim, value.shape))
return value
return validator
# DataFrame
def dataframe_from_json(value, obj):
if value is None:
return None
else:
return pd.DataFrame(value)
def dataframe_to_json(df, obj):
if df is None:
return None
else:
return df.to_dict(orient='records')
dataframe_serialization = dict(to_json=dataframe_to_json, from_json=dataframe_from_json)
# dataframe validators
def dataframe_warn_indexname(trait, value):
if value.index.name is not None:
warnings.warn("The '%s' dataframe trait of the %s instance disregards the index name" % (trait.name, trait.this_class))
value = value.reset_index()
return value
# Series
def series_from_json(value, obj):
return pd.Series(value)
def series_to_json(value, obj):
return value.to_dict()
series_serialization = dict(to_json=series_to_json, from_json=series_from_json)