Beispiel #1
0
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
Beispiel #2
0
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')
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0

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
        -------
Beispiel #9
0
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])
Beispiel #10
0
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().")
Beispiel #11
0
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()"
Beispiel #12
0
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().")