class GraphLayout(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Abstract interface for algorithms that compute coordinates for graph vertices and edges.""" def graph(self, vcoordinates, edges): """Compute vertex and edge coordinates for a graph. Parameters ---------- vcoordinates : :math:`V \\times 2` masked array Coordinates for every graph vertex, in vertex order. Where practical, only masked coordinates will have values assigned by the underlying algorithm. edges : :math:`E \\times 2` matrix Contains the integer vertex indices for every graph edge in edge order. The first and second matrix columns contain the source and target vertices respectively. Returns ------- vcoordinates : :math:`V \\times 2` matrix Contains coordinates for every graph vertex, in vertex order. eshapes : array of :math:`E` strings Contains a shape string for each edge, in edge order. The shape string contains drawing codes that define an arbitrary-complexity path for the edge, using a set of current coordinates and a turtle drawing model. The following codes are currently allowed: * `M` - change the current coordinates without drawing (requires one set of coordinates). * `L` - draw a straight line segment (requires one set of coordinates). * `Q` - draw a quadratic Bezier curve (requires two sets of coordinates). * `C` - draw a cubic Bezier curve (requires three sets of coordinates). ecoordinates : matrix containing two columns Contains coordinates for each of the edge shape strings, in drawing-code order. """ raise NotImplementedError() # pragma: no cover
class BaseEstimator(object, metaclass=ci.DocInheritMeta(style="numpy_with_merge_dedup") ): """Base Estimator class for both Stan and Pyro Estimator Parameters ---------- seed : int seed number for initial random values verbose : bool If True, output all diagnostics messages from estimators """ def __init__(self, seed=8888, verbose=False): self.seed = seed self.verbose = verbose # set random state np.random.seed(self.seed) @abstractmethod def fit(self, model_name, model_param_names, data_input, fitter=None, init_values=None): """ Parameters ---------- model_name : str name of model - used in mapping the right sampling file (stan/pyro/...) model_param_names : list list of strings of model parameters names to extract data_input : dict key-value pairs of data input as required by definition in samplers (stan/pyro/...) fitter : model object used for fitting; this will be used instead of model_name if supplied to search for model object init_values : float or np.array initial sampler value. If None, 'random' is used Returns ------- OrderedDict key: value pairs in which key is the model parameter name and value is `num_sample` x posterior values """ raise NotImplementedError('Concrete fit() method must be implemented')
class Library(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Abstract interface for objects that manage a collection of fonts.""" def font(self, style): """Lookup a font using CSS style information and return a corresponding Font object. Parameters ---------- style: dict containing CSS style information Returns ------- font: instance of :class:`toyplot.font.Font` """ raise NotImplementedError() # pragma: no cover
class Projection(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Abstract interface for objects that can map between one-dimensional domain and range spaces. .. automethod:: __call__ """ def __call__(self, domain_values): """Map domain values to range values. Parameters ---------- domain_values: :class:`numpy.ndarray` or compatible. The domain values to convert. Returns ------- range_values: :class:`numpy.ndarray` The domain values projected into range values. Examples -------- >>> projection = toyplot.projection.linear(0, 1, -1, 1) >>> projection(0.75) 0.5 """ raise NotImplementedError() # pragma: no cover def inverse(self, range_values): """Map range values to domain values. Parameters ---------- range_values: :class:`numpy.ndarray` or compatible. The range values to convert. Returns ------- domain_values: :class:`numpy.ndarray` The range values projected into domain values. Examples -------- >>> projection = toyplot.projection.linear(0, 1, -1, 1) >>> projection.inverse(-0.5) 0.25 """ raise NotImplementedError() # pragma: no cover
class Formatter(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon") ): """Abstract interface for formatters - objects that compute text representations from data.""" def format(self, value): """Return a text representation of the given value. Parameters ---------- value: value to be formatted Returns ------- prefix : string Formatted data to be displayed before the separator. separator : string Separator between formatted data, or empty string. suffix : string Formatted data to be displayed after the separator, or empty string. """ raise NotImplementedError() # pragma: no cover
class TickLocator( object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Base class for tick locators - objects that compute the position and format of axis tick labels.""" def ticks(self, domain_min, domain_max): """Return a set of ticks for the given domain. Parameters ---------- domain_min, domain_max: number Returns ------- locations : sequence of numbers Axis locations where ticks should be displayed. labels : sequence of strings Labels for each tick location. titles : sequence of strings Titles for each tick location. Typically, backends render titles as tooltips. """ raise NotImplementedError() # pragma: no cover
class Font(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Abstract interface for objects that return information about a specific combination of typeface and size.""" @property def ascent(self): """Font ascent (maximum height above the baseline). Returns ------- ascent: :class:`number<numbers.Number>` ascent of the font in CSS pixels. """ raise NotImplementedError() # pragma: no cover @property def descent(self): """Font descent (maximum height below the baseline). Returns ------- descent: :class:`number<numbers.Number>` descent of the font in CSS pixels. """ raise NotImplementedError() # pragma: no cover def width(self, string): """Return the width of a string if rendered using the font. Parameters ---------- string: str, required The text to be measured. Returns ------- width: :class:`number<numbers.Number>` Width of the string in CSS pixels, if rendered using the given font and font size. """ raise NotImplementedError() # pragma: no cover
def _log(x, base): return numpy.log10(numpy.abs(x)) / numpy.log10(base) def _in_range(a, x, b): left = min(a, b) right = max(a, b) old_settings = numpy.seterr(invalid="ignore") result = numpy.logical_and(left <= x, x <= right) numpy.seterr(**old_settings) return result @six.add_metaclass(custom_inherit.DocInheritMeta(style="numpy_napoleon")) class Projection(object): """Abstract interface for objects that can map between one-dimensional domain and range spaces. .. automethod:: __call__ """ def __call__(self, domain_values): """Map domain values to range values. Parameters ---------- domain_values: :class:`numpy.ndarray` or compatible. The domain values to convert. Returns -------
class Mark(object, metaclass=custom_inherit.DocInheritMeta(style="numpy_napoleon")): """Abstract interface for Toyplot marks. Marks are data objects that are added to a coordinate system for display on a :class:`canvas <toyplot.canvas.Canvas>`. Marks carry no explicit visual representation of their own - it is up to the coordinate system and :ref:`rendering backend<backends>` to determine how to render the data. For example, a :class:`scatterplot <toyplot.mark.Point>` mark is rendered using points by a :class:`cartesian <toyplot.coordinates.Cartesian>` coordinate system, but could be rendered using lines by a hypothetical parallel coordinate system. """ def __init__(self, annotation=False): self._annotation = False self.annotation = annotation @property def annotation(self): return self._annotation @annotation.setter def annotation(self, value): self._annotation = True if value else False def _finalize(self): return self def domain(self, axis): # pylint: disable=no-self-use """Return minimum and maximum domain values for the mark along the given axis. Parameters ---------- axis: string, required Name of an axis along which to return domain values. Returns ------- minimum: minimum domain value along the given axis, or `None`. maximum: maximum domain value along the given axis, or `None`. """ return (None, None) def extents(self, axes): # pylint: disable=no-self-use """Return range extents for the mark using the given axes. Parameters ---------- axes: sequence of strings, required Specifies the order in which domain coordinates must be returned. Returns ------- coordinates: tuple containing arrays of coordinates, in the order specified by the `axes` parameter. extents: (left, right, top, bottom) tuple of arrays containing the extents of each datum in range-space, relative to the domain coordinates. """ empty = numpy.array([]) return tuple([empty] * len(axes)), tuple([empty] * 4) @property def markers(self): # pylint: disable=no-self-use """Return an ordered set of markers used by this mark, if any. Returns ------- markers: list of :class:`toyplot.marker.Marker` objects. """ return [] def __format__(self, format_spec): return "".join([format(marker) for marker in self.markers])
class BaseTemplate(object, metaclass=ci.DocInheritMeta(style="numpy_with_merge_dedup") ): """Base module for model creation `BaseModule` will instantiate an estimator class of `estimator_type`. Each model defines its own `_supported_estimator_types` to determine if the provided `estimator_type` is supported for that particular model. Parameters ---------- response_col : str Name of response variable column, default 'y' date_col : str Name of date variable column, default 'ds' estimator_type : orbit.BaseEstimator Any subclass of `orbit.BaseEstimator` """ # data labels for sampler API (stan, pyro, numpyro etc.) _data_input_mapper = None # model name (e.g. name of `*.stan` and `*.pyro` file in package) _model_name = None # supported estimators in ..estimators # concrete classes should overwrite this _supported_estimator_types = None # set for each model def __init__(self, response_col='y', date_col='ds', estimator_type=StanEstimatorMCMC, **kwargs): self.response_col = response_col self.date_col = date_col self.estimator_type = estimator_type # create concrete estimator object self.estimator = self.estimator_type(**kwargs) self._model_param_names = list() # init posterior samples # `_posterior_samples` is set by `fit()` self._posterior_samples = dict() self._aggregate_posteriors = dict() # validator model / estimator compatibility self._validate_supported_estimator_type() def _validate_supported_estimator_type(self): if self.estimator_type not in self._supported_estimator_types: msg_template = "Model class: {} is incompatible with Estimator: {}. Estimator Support: {}" model_class = type(self) estimator_type = self.estimator_type raise IllegalArgument( msg_template.format(model_class, estimator_type, str(self._supported_estimator_types))) def is_fitted(self): # if empty dict false, else true return bool(self._posterior_samples) def fit(self, **kwargs): raise AbstractMethodException( "Abstract method. Model should implement concrete .fit().") def predict(self, **kwargs): raise AbstractMethodException( "Abstract method. Model should implement concrete .predict().")
from __future__ import absolute_import, division, print_function import abc import logging import os import custom_inherit import six import buildcat.node log = logging.getLogger(__name__) @six.add_metaclass( custom_inherit.DocInheritMeta(abstract_base_class=True, style="numpy_napoleon")) class Target(buildcat.node.Node): """Abstract base class for :ref:`targets` - artifacts that are used, created, or updated by the build process. Most build systems assume that a target is a file or directory located on a filesystem, and Buildcat provides :class:`buildcat.target.Directory` and :class:`buildcat.target.File` for this purpose; however, you are free to derive from :class:`buildcat.target.Target` to define your own target types - for example, you might define a target based on a record in a database, a resource accessed on a web server, or any other entity that could be used, created, or updated during the build. """ def __init__(self): super(Target, self).__init__() def __repr__(self): return "buildcat.target.Target()"
class BaseTemplate(object, metaclass=ci.DocInheritMeta(style="numpy_with_merge_dedup") ): """ Base abstract class for univariate time-series model creation `BaseTemplate` will instantiate an estimator class of `estimator_type`. Each model defines its own `_supported_estimator_types` to determine if the provided `estimator_type` is supported for that particular model. Parameters ---------- response_col : str Name of response variable column, default 'y' date_col : str Name of date variable column, default 'ds' estimator_type : orbit.BaseEstimator Any subclass of `orbit.BaseEstimator` Notes ----- For attributes which are input by users and needed to mutate further downstream, we will introduce a new internal attribute with identical name except a prefix "_". e.g. If x appear in the arg default as `None` and we need to impute by 0. we will have self._x = 0 downstream. """ # TODO: for now we assume has to be ENUM, maybe we can allow list as well? such that left and right are always # using same name # data labels for sampler _data_input_mapper = None # used to match name of `*.stan` or `*.pyro` file to look for the model _model_name = None # TODO: right now we assume _fitter is only for one specific estimator type # TODO: in the future, we should make it for example like a dict {PyroEstimator: pyro_model} etc. in case we want # TODO: to support multiple estimators and use for validation # EXPERIMENTAL: _fitter is used for quick supply of pyro / stan object instead of supplying a file _fitter = None # supported estimators in ..estimators # concrete classes should overwrite this _supported_estimator_types = None # set for each model def __init__(self, response_col='y', date_col='ds', estimator_type=StanEstimatorMCMC, **kwargs): # general fields passed into Base Template self.response_col = response_col self.date_col = date_col # basic response fields # mainly set by ._set_training_df_meta() and ._set_dynamic_attributes() self.response = None self.date_array = None self.num_of_observations = None self.training_start = None self.training_end = None self._model_data_input = None # basic estimator fields self.estimator_type = estimator_type self.estimator = self.estimator_type(**kwargs) self.with_mcmc = None # set by ._set_init_values # this is ONLY used by stan which by default used 'random' self._init_values = None self._validate_supported_estimator_type() self._set_with_mcmc() # set by _set_model_param_names() self._model_param_names = list() # set by `fit()` self._posterior_samples = dict() # init aggregate posteriors self._aggregate_posteriors = {} # initialization related modules def _validate_supported_estimator_type(self): if self.estimator_type not in self._supported_estimator_types: msg_template = "Model class: {} is incompatible with Estimator: {}. Estimator Support: {}" model_class = type(self) estimator_type = self.estimator_type raise IllegalArgument( msg_template.format(model_class, estimator_type, str(self._supported_estimator_types))) def _set_with_mcmc(self): """Include extra indicator to indicate whether the object is using mcmc type of estimator """ estimator_type = self.estimator_type # set `with_mcmc` attribute based on estimator type # if no attribute for _is_mcmc_estimator, default to False if getattr(estimator_type, '_is_mcmc_estimator', False): self.with_mcmc = 1 else: self.with_mcmc = 0 def _set_model_param_names(self, **kwargs): """Set label for model parameters. This function can be dependent on static attributes. """ raise AbstractMethodException( "Abstract method. Model should implement concrete ._set_model_param_names()." ) def get_model_param_names(self): return self._model_param_names def _set_static_attributes(self, **kwargs): """Set static attributes which are independent from data matrix. These methods are supposed to be over-ride by child (model) template. For attributes dependent on data matrix, use _set_dynamic_attributes """ pass # fit and predict related modules def _validate_training_df(self, df): df_columns = df.columns # validate date_col if self.date_col not in df_columns: raise ModelException( "DataFrame does not contain `date_col`: {}".format( self.date_col)) # validate ordering of time series date_array = pd.to_datetime(df[self.date_col]).reset_index(drop=True) if not is_ordered_datetime(date_array): raise ModelException( 'Datetime index must be ordered and not repeat') # validate response variable is in df if self.response_col not in df_columns: raise ModelException( "DataFrame does not contain `response_col`: {}".format( self.response_col)) def _set_training_df_meta(self, df): self.response = df[self.response_col].values self.date_array = pd.to_datetime( df[self.date_col]).reset_index(drop=True) self.num_of_observations = len(self.response) self.response_sd = np.nanstd(self.response) self.training_start = df[self.date_col].iloc[0] self.training_end = df[self.date_col].iloc[-1] def _set_model_data_input(self): """Collects data attributes into a dict for sampling/optimization api""" # refresh a clean dict data_inputs = dict() if not self._data_input_mapper: raise ModelException('Empty or invalid data_input_mapper') for key in self._data_input_mapper: # mapper keys in upper case; inputs in lower case key_lower = key.name.lower() input_value = getattr(self, key_lower, None) if input_value is None: raise ModelException( '{} is missing from data input'.format(key_lower)) if isinstance(input_value, bool): # stan accepts bool as int only input_value = int(input_value) data_inputs[key.value] = input_value self._model_data_input = data_inputs def get_model_data_input(self): return self._model_data_input def _set_init_values(self): """Set init as a callable (for Stan ONLY) See: https://pystan.readthedocs.io/en/latest/api.htm """ pass def get_init_values(self): return self._init_values def is_fitted(self): # if empty dict false, else true return bool(self._posterior_samples) def _set_dynamic_attributes(self, df): """Set required input based on input DataFrame, rather than at object instantiation""" pass def fit(self, df): """Fit model to data and set extracted posterior samples""" estimator = self.estimator model_name = self._model_name df = df.copy() # default set and validation of input data frame self._validate_training_df(df) self._set_training_df_meta(df) # customize module self._set_dynamic_attributes(df) # default process post attributes setting # _set_model_data_input() behavior depends on _set_training_df_meta() self._set_model_data_input() # set initial values for randomization; right now only used by pystan; default as 'random' self._set_init_values() # estimator inputs data_input = self.get_model_data_input() init_values = self.get_init_values() model_param_names = self.get_model_param_names() # note that estimator will search for the .stan, .pyro model file based on the # estimator type and model_name provided model_extract = estimator.fit(model_name=model_name, model_param_names=model_param_names, data_input=data_input, fitter=self._fitter, init_values=init_values) self._posterior_samples = model_extract def predict(self, df, decompose=False, **kwargs): raise AbstractMethodException( "Abstract method. Model should implement concrete .predict().") def _predict(self, posterior_estimates, df, include_error=False, decompose=False, **kwargs): raise AbstractMethodException( "Abstract method. Model should implement concrete ._predict().")