Source code for colour.continuous.signal

# -*- coding: utf-8 -*-
"""
Signal
======

Defines the class implementing support for continuous signal:

-   :class:`colour.continuous.Signal`
"""

from __future__ import division, unicode_literals

import numpy as np
from operator import add, mul, pow, sub, iadd, imul, ipow, isub

# Python 3 compatibility.
try:
    from operator import div, idiv
except ImportError:
    from operator import truediv, itruediv

    div = truediv
    idiv = itruediv
from collections import OrderedDict
try:  # pragma: no cover
    from collections import Iterator, Mapping, Sequence
except ImportError:  # pragma: no cover
    from collections.abc import Iterator, Mapping, Sequence

from colour.algebra import Extrapolator, KernelInterpolator
from colour.constants import DEFAULT_FLOAT_DTYPE
from colour.continuous import AbstractContinuousFunction
from colour.utilities import (as_array, fill_nan, full, is_pandas_installed,
                              runtime_warning, tsplit, tstack, usage_warning)
from colour.utilities.deprecation import ObjectRenamed

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2020 - Colour Developers'
__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-developers@colour-science.org'
__status__ = 'Production'

__all__ = ['Signal']


[docs]class Signal(AbstractContinuousFunction): """ Defines the base class for continuous signal. The class implements the :meth:`Signal.function` method so that evaluating the function for any independent domain :math:`x \\in\\mathbb{R}` variable returns a corresponding range :math:`y \\in\\mathbb{R}` variable. It adopts an interpolating function encapsulated inside an extrapolating function. The resulting function independent domain, stored as discrete values in the :attr:`colour.continuous.Signal.domain` attribute corresponds with the function dependent and already known range stored in the :attr:`colour.continuous.Signal.range` attribute. Parameters ---------- data : Series or Signal or array_like or dict_like, optional Data to be stored in the continuous signal. domain : array_like, optional Values to initialise the :attr:`colour.continuous.Signal.domain` attribute with. If both ``data`` and ``domain`` arguments are defined, the latter with be used to initialise the :attr:`colour.continuous.Signal.domain` attribute. Other Parameters ---------------- name : unicode, optional Continuous signal name. dtype : type, optional **{np.float16, np.float32, np.float64, np.float128}**, Floating point data type. interpolator : object, optional Interpolator class type to use as interpolating function. interpolator_kwargs : dict_like, optional Arguments to use when instantiating the interpolating function. extrapolator : object, optional Extrapolator class type to use as extrapolating function. extrapolator_kwargs : dict_like, optional Arguments to use when instantiating the extrapolating function. Attributes ---------- dtype domain range interpolator interpolator_kwargs extrapolator extrapolator_kwargs function Methods ------- __str__ __repr__ __hash__ __getitem__ __setitem__ __contains__ __eq__ __ne__ arithmetical_operation signal_unpack_data fill_nan to_series Examples -------- Instantiation with implicit *domain*: >>> range_ = np.linspace(10, 100, 10) >>> print(Signal(range_)) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] Instantiation with explicit *domain*: >>> domain = np.arange(100, 1100, 100) >>> print(Signal(range_, domain)) [[ 100. 10.] [ 200. 20.] [ 300. 30.] [ 400. 40.] [ 500. 50.] [ 600. 60.] [ 700. 70.] [ 800. 80.] [ 900. 90.] [ 1000. 100.]] Instantiation with a *dict*: >>> print(Signal(dict(zip(domain, range_)))) [[ 100. 10.] [ 200. 20.] [ 300. 30.] [ 400. 40.] [ 500. 50.] [ 600. 60.] [ 700. 70.] [ 800. 80.] [ 900. 90.] [ 1000. 100.]] Instantiation with a *Pandas* `Series`: >>> if is_pandas_installed(): ... from pandas import Series ... print(Signal( # doctest: +SKIP ... Series(dict(zip(domain, range_))))) [[ 100. 10.] [ 200. 20.] [ 300. 30.] [ 400. 40.] [ 500. 50.] [ 600. 60.] [ 700. 70.] [ 800. 80.] [ 900. 90.] [ 1000. 100.]] Retrieving domain *y* variable for arbitrary range *x* variable: >>> x = 150 >>> range_ = np.sin(np.linspace(0, 1, 10)) >>> Signal(range_, domain)[x] # doctest: +ELLIPSIS 0.0359701... >>> x = np.linspace(100, 1000, 3) >>> Signal(range_, domain)[x] # doctest: +ELLIPSIS array([ ..., 4.7669395...e-01, 8.4147098...e-01]) Using an alternative interpolating function: >>> x = 150 >>> from colour.algebra import CubicSplineInterpolator >>> Signal( ... range_, ... domain, ... interpolator=CubicSplineInterpolator)[x] # doctest: +ELLIPSIS 0.0555274... >>> x = np.linspace(100, 1000, 3) >>> Signal( ... range_, ... domain, ... interpolator=CubicSplineInterpolator)[x] # doctest: +ELLIPSIS array([ 0. , 0.4794253..., 0.8414709...]) """ def __init__(self, data=None, domain=None, **kwargs): super(Signal, self).__init__(kwargs.get('name')) self._dtype = None self._domain = None self._range = None self._interpolator = KernelInterpolator self._interpolator_kwargs = {} self._extrapolator = Extrapolator self._extrapolator_kwargs = { 'method': 'Constant', 'left': np.nan, 'right': np.nan } self.domain, self.range = self.signal_unpack_data(data, domain) self.dtype = kwargs.get('dtype', DEFAULT_FLOAT_DTYPE) self.interpolator = kwargs.get('interpolator') self.interpolator_kwargs = kwargs.get('interpolator_kwargs') self.extrapolator = kwargs.get('extrapolator') self.extrapolator_kwargs = kwargs.get('extrapolator_kwargs') self._create_function() @property def dtype(self): """ Getter and setter property for the continuous signal dtype. Parameters ---------- value : type Value to set the continuous signal dtype with. Returns ------- type Continuous signal dtype. """ return self._dtype @dtype.setter def dtype(self, value): """ Setter for **self.dtype** property. """ if value is not None: assert value in np.sctypes['float'], ( '"dtype" must be one of the following types: {0}'.format( np.sctypes['float'])) self._dtype = value # The following self-assignments are written as intended and # triggers the rebuild of the underlying function. self.domain = self.domain self.range = self.range @property def domain(self): """ Getter and setter property for the continuous signal independent domain :math:`x` variable. Parameters ---------- value : array_like Value to set the continuous signal independent domain :math:`x` variable with. Returns ------- ndarray Continuous signal independent domain :math:`x` variable. """ return np.copy(self._domain) @domain.setter def domain(self, value): """ Setter for the **self.domain** property. """ if value is not None: if np.asarray(value).dtype != object: if not np.all(np.isfinite(value)): runtime_warning( '"{0}" new "domain" variable is not finite: {1}, ' 'unpredictable results may occur!'.format( self.name, value)) value = np.copy(value).astype(self.dtype) if self._range is not None: if value.size != self._range.size: runtime_warning( '"{0}" new "domain" and current "range" variables ' 'have different size, "range" variable will be ' 'resized to "domain" variable shape!'.format( self.name)) self._range = np.resize(self._range, value.shape) self._domain = value self._create_function() @property def range(self): """ Getter and setter property for the continuous signal corresponding range :math:`y` variable. Parameters ---------- value : array_like Value to set the continuous signal corresponding range :math:`y` variable with. Returns ------- ndarray Continuous signal corresponding range :math:`y` variable. """ return np.copy(self._range) @range.setter def range(self, value): """ Setter for the **self.range** property. """ if value is not None: if np.asarray(value).dtype != object: if not np.all(np.isfinite(value)): runtime_warning( '"{0}" new "range" variable is not finite: {1}, ' 'unpredictable results may occur!'.format( self.name, value)) value = np.copy(value).astype(self.dtype) if self._domain is not None: assert value.size == self._domain.size, ( '"domain" and "range" variables must have same size!') self._range = value self._create_function() @property def interpolator(self): """ Getter and setter property for the continuous signal interpolator type. Parameters ---------- value : type Value to set the continuous signal interpolator type with. Returns ------- type Continuous signal interpolator type. """ return self._interpolator @interpolator.setter def interpolator(self, value): """ Setter for the **self.interpolator** property. """ if value is not None: # TODO: Check for interpolator capabilities. self._interpolator = value self._create_function() @property def interpolator_kwargs(self): """ Getter and setter property for the continuous signal interpolator instantiation time arguments. Parameters ---------- value : dict Value to set the continuous signal interpolator instantiation time arguments to. Returns ------- dict Continuous signal interpolator instantiation time arguments. """ return self._interpolator_kwargs @interpolator_kwargs.setter def interpolator_kwargs(self, value): """ Setter for the **self.interpolator_kwargs** property. """ if value is not None: assert isinstance(value, (dict, OrderedDict)), ( '"{0}" attribute: "{1}" type is not "dict" or "OrderedDict"!' ).format('interpolator_kwargs', value) self._interpolator_kwargs = value self._create_function() @property def extrapolator(self): """ Getter and setter property for the continuous signal extrapolator type. Parameters ---------- value : type Value to set the continuous signal extrapolator type with. Returns ------- type Continuous signal extrapolator type. """ return self._extrapolator @extrapolator.setter def extrapolator(self, value): """ Setter for the **self.extrapolator** property. """ if value is not None: # TODO: Check for extrapolator capabilities. self._extrapolator = value self._create_function() @property def extrapolator_kwargs(self): """ Getter and setter property for the continuous signal extrapolator instantiation time arguments. Parameters ---------- value : dict Value to set the continuous signal extrapolator instantiation time arguments to. Returns ------- dict Continuous signal extrapolator instantiation time arguments. """ return self._extrapolator_kwargs @extrapolator_kwargs.setter def extrapolator_kwargs(self, value): """ Setter for the **self.extrapolator_kwargs** property. """ if value is not None: assert isinstance(value, (dict, OrderedDict)), ( '"{0}" attribute: "{1}" type is not "dict" or "OrderedDict"!'. format('extrapolator_kwargs', value)) self._extrapolator_kwargs = value self._create_function() @property def function(self): """ Getter and setter property for the continuous signal callable. Parameters ---------- value : object Attribute value. Returns ------- callable Continuous signal callable. Notes ----- - This property is read only. """ return self._function
[docs] def __str__(self): """ Returns a formatted string representation of the continuous signal. Returns ------- unicode Formatted string representation. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> print(Signal(range_)) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] """ try: return str(tstack([self.domain, self.range])) except TypeError: return super(Signal, self).__str__()
[docs] def __repr__(self): """ Returns an evaluable string representation of the continuous signal. Returns ------- unicode Evaluable string representation. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> Signal(range_) # doctest: +ELLIPSIS Signal([[ 0., 10.], [ 1., 20.], [ 2., 30.], [ 3., 40.], [ 4., 50.], [ 5., 60.], [ 6., 70.], [ 7., 80.], [ 8., 90.], [ 9., 100.]], interpolator=KernelInterpolator, interpolator_kwargs={}, extrapolator=Extrapolator, extrapolator_kwargs={...}) """ try: representation = repr(tstack([self.domain, self.range])) representation = representation.replace('array', self.__class__.__name__) representation = representation.replace( ' [', '{0}['.format(' ' * (len(self.__class__.__name__) + 2))) representation = ('{0},\n' '{1}interpolator={2},\n' '{1}interpolator_kwargs={3},\n' '{1}extrapolator={4},\n' '{1}extrapolator_kwargs={5})').format( representation[:-1], ' ' * (len(self.__class__.__name__) + 1), self.interpolator.__name__, repr(self.interpolator_kwargs), self.extrapolator.__name__, repr(self.extrapolator_kwargs)) return representation except TypeError: return super(Signal, self).__repr__()
[docs] def __hash__(self): """ Returns the abstract continuous function hash. Returns ------- int Object hash. """ return hash(repr(self))
[docs] def __getitem__(self, x): """ Returns the corresponding range :math:`y` variable for independent domain :math:`x` variable. Parameters ---------- x : numeric, array_like or slice Independent domain :math:`x` variable. Returns ------- numeric or ndarray math:`y` range value. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal = Signal(range_) >>> print(signal) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] >>> signal[0] 10.0 >>> signal[np.array([0, 1, 2])] array([ 10., 20., 30.]) >>> signal[0:3] array([ 10., 20., 30.]) >>> signal[np.linspace(0, 5, 5)] # doctest: +ELLIPSIS array([ 10. , 22.8348902..., 34.8004492..., \ 47.5535392..., 60. ]) """ if isinstance(x, slice): return self._range[x] else: return self._function(x)
[docs] def __setitem__(self, x, y): """ Sets the corresponding range :math:`y` variable for independent domain :math:`x` variable. Parameters ---------- x : numeric, array_like or slice Independent domain :math:`x` variable. y : numeric or ndarray Corresponding range :math:`y` variable. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal = Signal(range_) >>> print(signal) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] >>> signal[0] = 20 >>> signal[0] 20.0 >>> signal[np.array([0, 1, 2])] = 30 >>> signal[np.array([0, 1, 2])] array([ 30., 30., 30.]) >>> signal[0:3] = 40 >>> signal[0:3] array([ 40., 40., 40.]) >>> signal[np.linspace(0, 5, 5)] = 50 >>> print(signal) [[ 0. 50. ] [ 1. 40. ] [ 1.25 50. ] [ 2. 40. ] [ 2.5 50. ] [ 3. 40. ] [ 3.75 50. ] [ 4. 50. ] [ 5. 50. ] [ 6. 70. ] [ 7. 80. ] [ 8. 90. ] [ 9. 100. ]] >>> signal[np.array([0, 1, 2])] = np.array([10, 20, 30]) >>> print(signal) [[ 0. 10. ] [ 1. 20. ] [ 1.25 50. ] [ 2. 30. ] [ 2.5 50. ] [ 3. 40. ] [ 3.75 50. ] [ 4. 50. ] [ 5. 50. ] [ 6. 70. ] [ 7. 80. ] [ 8. 90. ] [ 9. 100. ]] """ if isinstance(x, slice): self._range[x] = y else: x = np.atleast_1d(x).astype(self.dtype) y = np.resize(y, x.shape) # Matching domain, updating existing `self._range` values. mask = np.in1d(x, self._domain) x_m = x[mask] indexes = np.searchsorted(self._domain, x_m) self._range[indexes] = y[mask] # Non matching domain, inserting into existing `self.domain` # and `self.range`. x_nm = x[~mask] indexes = np.searchsorted(self._domain, x_nm) if indexes.size != 0: self._domain = np.insert(self._domain, indexes, x_nm) self._range = np.insert(self._range, indexes, y[~mask]) self._create_function()
[docs] def __contains__(self, x): """ Returns whether the continuous signal contains given independent domain :math:`x` variable. Parameters ---------- x : numeric, array_like or slice Independent domain :math:`x` variable. Returns ------- bool Is :math:`x` domain value contained. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal = Signal(range_) >>> 0 in signal True >>> 0.5 in signal True >>> 1000 in signal False """ return np.all( np.where( np.logical_and(x >= np.min(self._domain), x <= np.max(self._domain)), True, False, ))
[docs] def __eq__(self, other): """ Returns whether the continuous signal is equal to given other object. Parameters ---------- other : object Object to test whether it is equal to the continuous signal. Returns ------- bool Is given object equal to the continuous signal. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal_1 = Signal(range_) >>> signal_2 = Signal(range_) >>> signal_1 == signal_2 True >>> signal_2[0] = 20 >>> signal_1 == signal_2 False >>> signal_2[0] = 10 >>> signal_1 == signal_2 True >>> from colour.algebra import CubicSplineInterpolator >>> signal_2.interpolator = CubicSplineInterpolator >>> signal_1 == signal_2 False """ if isinstance(other, Signal): if all([ np.array_equal(self._domain, other.domain), np.array_equal(self._range, other.range), self._interpolator is other.interpolator, self._interpolator_kwargs == other.interpolator_kwargs, self._extrapolator is other.extrapolator, self._extrapolator_kwargs == other.extrapolator_kwargs ]): return True return False
[docs] def __ne__(self, other): """ Returns whether the continuous signal is not equal to given other object. Parameters ---------- other : object Object to test whether it is not equal to the continuous signal. Returns ------- bool Is given object not equal to the continuous signal. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal_1 = Signal(range_) >>> signal_2 = Signal(range_) >>> signal_1 != signal_2 False >>> signal_2[0] = 20 >>> signal_1 != signal_2 True >>> signal_2[0] = 10 >>> signal_1 != signal_2 False >>> from colour.algebra import CubicSplineInterpolator >>> signal_2.interpolator = CubicSplineInterpolator >>> signal_1 != signal_2 True """ return not (self == other)
def _create_function(self): """ Creates the continuous signal underlying function. """ if self._domain is not None and self._range is not None: self._function = self._extrapolator( self._interpolator(self.domain, self.range, **self._interpolator_kwargs), **self._extrapolator_kwargs) else: def _undefined_function(*args, **kwargs): """ Raises a :class:`RuntimeError` exception. Other Parameters ---------------- \\*args : list, optional Arguments. \\**kwargs : dict, optional Keywords arguments. Raises ------ RuntimeError """ raise RuntimeError( 'Underlying signal interpolator function does not exists, ' 'please ensure you defined both ' '"domain" and "range" variables!') self._function = _undefined_function def _fill_domain_nan(self, method='Interpolation', default=0): """ Fill NaNs in independent domain :math:`x` variable using given method. Parameters ---------- method : unicode, optional **{'Interpolation', 'Constant'}**, *Interpolation* method linearly interpolates through the NaNs, *Constant* method replaces NaNs with ``default``. default : numeric, optional Value to use with the *Constant* method. Returns ------- Signal NaNs filled continuous signal independent domain :math:`x` variable. """ self._domain = fill_nan(self._domain, method, default) self._create_function() def _fill_range_nan(self, method='Interpolation', default=0): """ Fill NaNs in corresponding range :math:`y` variable using given method. Parameters ---------- method : unicode, optional **{'Interpolation', 'Constant'}**, *Interpolation* method linearly interpolates through the NaNs, *Constant* method replaces NaNs with ``default``. default : numeric, optional Value to use with the *Constant* method. Returns ------- Signal NaNs filled continuous signal i corresponding range :math:`y` variable. """ self._range = fill_nan(self._range, method, default) self._create_function()
[docs] def arithmetical_operation(self, a, operation, in_place=False): """ Performs given arithmetical operation with :math:`a` operand, the operation can be either performed on a copy or in-place. Parameters ---------- a : numeric or ndarray or Signal Operand. operation : object Operation to perform. in_place : bool, optional Operation happens in place. Returns ------- Signal Continuous signal. Examples -------- Adding a single *numeric* variable: >>> range_ = np.linspace(10, 100, 10) >>> signal_1 = Signal(range_) >>> print(signal_1) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] >>> print(signal_1.arithmetical_operation(10, '+', True)) [[ 0. 20.] [ 1. 30.] [ 2. 40.] [ 3. 50.] [ 4. 60.] [ 5. 70.] [ 6. 80.] [ 7. 90.] [ 8. 100.] [ 9. 110.]] Adding an *array_like* variable: >>> a = np.linspace(10, 100, 10) >>> print(signal_1.arithmetical_operation(a, '+', True)) [[ 0. 30.] [ 1. 50.] [ 2. 70.] [ 3. 90.] [ 4. 110.] [ 5. 130.] [ 6. 150.] [ 7. 170.] [ 8. 190.] [ 9. 210.]] Adding a :class:`colour.continuous.Signal` class: >>> signal_2 = Signal(range_) >>> print(signal_1.arithmetical_operation(signal_2, '+', True)) [[ 0. 40.] [ 1. 70.] [ 2. 100.] [ 3. 130.] [ 4. 160.] [ 5. 190.] [ 6. 220.] [ 7. 250.] [ 8. 280.] [ 9. 310.]] """ operation, ioperator = { '+': (add, iadd), '-': (sub, isub), '*': (mul, imul), '/': (div, idiv), '**': (pow, ipow) }[operation] if in_place: if isinstance(a, Signal): self[self._domain] = operation(self._range, a[self._domain]) exclusive_or = np.setxor1d(self._domain, a.domain) self[exclusive_or] = full(exclusive_or.shape, np.nan) else: self.range = ioperator(self.range, a) return self else: copy = ioperator(self.copy(), a) return copy
[docs] @staticmethod def signal_unpack_data(data=None, domain=None, dtype=None): """ Unpack given data for continuous signal instantiation. Parameters ---------- data : Series or Signal or array_like or dict_like, optional Data to unpack for continuous signal instantiation. domain : array_like, optional Values to initialise the :attr:`colour.continuous.Signal.domain` attribute with. If both ``data`` and ``domain`` arguments are defined, the latter will be used to initialise the :attr:`colour.continuous.Signal.domain` attribute. dtype : type, optional **{np.float16, np.float32, np.float64, np.float128}**, Floating point data type. Returns ------- tuple Independent domain :math:`x` variable and corresponding range :math:`y` variable unpacked for continuous signal instantiation. Examples -------- Unpacking using implicit *domain*: >>> range_ = np.linspace(10, 100, 10) >>> domain, range_ = Signal.signal_unpack_data(range_) >>> print(domain) [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.] >>> print(range_) [ 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.] Unpacking using explicit *domain*: >>> domain = np.arange(100, 1100, 100) >>> domain, range = Signal.signal_unpack_data(range_, domain) >>> print(domain) [ 100. 200. 300. 400. 500. 600. 700. 800. 900. 1000.] >>> print(range_) [ 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.] Unpacking using a *dict*: >>> domain, range_ = Signal.signal_unpack_data( ... dict(zip(domain, range_))) >>> print(domain) [ 100. 200. 300. 400. 500. 600. 700. 800. 900. 1000.] >>> print(range_) [ 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.] Unpacking using a *Pandas* `Series`: >>> if is_pandas_installed(): ... from pandas import Series ... domain, range = Signal.signal_unpack_data( ... Series(dict(zip(domain, range_)))) ... # doctest: +ELLIPSIS >>> print(domain) # doctest: +SKIP [ 100. 200. 300. 400. 500. 600. 700. 800. 900. 1000.] >>> print(range_) # doctest: +SKIP [ 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.] Unpacking using a :class:`colour.continuous.Signal` class: >>> domain, range_ = Signal.signal_unpack_data( ... Signal(range_, domain)) >>> print(domain) [ 100. 200. 300. 400. 500. 600. 700. 800. 900. 1000.] >>> print(range_) [ 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.] """ if dtype is None: dtype = DEFAULT_FLOAT_DTYPE domain_u, range_u = None, None if isinstance(data, Signal): domain_u = data.domain range_u = data.range elif (issubclass(type(data), Sequence) or isinstance(data, (tuple, list, np.ndarray, Iterator))): data = tsplit(list(data) if isinstance(data, Iterator) else data) assert data.ndim == 1, 'User "data" must be 1-dimensional!' domain_u, range_u = np.arange(0, data.size, dtype=dtype), data elif (issubclass(type(data), Mapping) or isinstance(data, (dict, OrderedDict))): domain_u, range_u = tsplit(sorted(data.items())) elif is_pandas_installed(): from pandas import Series if isinstance(data, Series): domain_u = data.index.values range_u = data.values if domain is not None and range_u is not None: assert len(domain) == len(range_u), ( 'User "domain" is not compatible with unpacked range!') domain_u = as_array(domain, dtype) if range_u is not None: range_u = as_array(range_u, dtype) return domain_u, range_u
[docs] def fill_nan(self, method='Interpolation', default=0): """ Fill NaNs in independent domain :math:`x` variable and corresponding range :math:`y` variable using given method. Parameters ---------- method : unicode, optional **{'Interpolation', 'Constant'}**, *Interpolation* method linearly interpolates through the NaNs, *Constant* method replaces NaNs with ``default``. default : numeric, optional Value to use with the *Constant* method. Returns ------- Signal NaNs filled continuous signal. Examples -------- >>> range_ = np.linspace(10, 100, 10) >>> signal = Signal(range_) >>> signal[3:7] = np.nan >>> print(signal) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. nan] [ 4. nan] [ 5. nan] [ 6. nan] [ 7. 80.] [ 8. 90.] [ 9. 100.]] >>> print(signal.fill_nan()) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 40.] [ 4. 50.] [ 5. 60.] [ 6. 70.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] >>> signal[3:7] = np.nan >>> print(signal.fill_nan(method='Constant')) [[ 0. 10.] [ 1. 20.] [ 2. 30.] [ 3. 0.] [ 4. 0.] [ 5. 0.] [ 6. 0.] [ 7. 80.] [ 8. 90.] [ 9. 100.]] """ self._fill_domain_nan(method, default) self._fill_range_nan(method, default) return self
[docs] def to_series(self): """ Converts the continuous signal to a *Pandas* :class:`Series` class instance. Returns ------- Series Continuous signal as a *Pandas* :class:`Series` class instance. Examples -------- >>> if is_pandas_installed(): ... range_ = np.linspace(10, 100, 10) ... signal = Signal(range_) ... print(signal.to_series()) # doctest: +SKIP 0.0 10.0 1.0 20.0 2.0 30.0 3.0 40.0 4.0 50.0 5.0 60.0 6.0 70.0 7.0 80.0 8.0 90.0 9.0 100.0 Name: Signal (...), dtype: float64 """ if is_pandas_installed(): from pandas import Series return Series(data=self._range, index=self._domain, name=self.name)
# ------------------------------------------------------------------------# # --- API Changes and Deprecation Management ---# # ------------------------------------------------------------------------# @property def interpolator_args(self): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.interpolator_args', 'Signal.interpolator_kwargs'))) return self.interpolator_kwargs @interpolator_args.setter def interpolator_args(self, value): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.interpolator_args', 'Signal.interpolator_kwargs'))) self.interpolator_kwargs = value @property def extrapolator_args(self): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.extrapolator_args', 'Signal.extrapolator_kwargs'))) return self.extrapolator_kwargs @extrapolator_args.setter def extrapolator_args(self, value): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.extrapolator_args', 'Signal.extrapolator_kwargs'))) self.extrapolator_kwargs = value