Exemple #1
0
def tile(arg: tp.ArrayLike,
         n: int,
         axis: int = 1,
         raw: bool = False) -> tp.AnyArray:
    """Repeat the whole `arg` `n` times along the specified axis."""
    arg = to_any_array(arg, raw=raw)
    if axis == 0:
        if arg.ndim == 2:
            if checks.is_pandas(arg):
                return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                    np.tile(arg.values, (n, 1)),
                    index=index_fns.tile_index(arg.index, n))
            return np.tile(arg, (n, 1))
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.tile(arg.values, n),
                index=index_fns.tile_index(arg.index, n))
        return np.tile(arg, n)
    elif axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.tile(arg.values, (1, n)),
                columns=index_fns.tile_index(arg.columns, n))
        return np.tile(arg, (1, n))
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #2
0
def tile(arg, n, axis=1):
    """Repeat the whole `arg` `n` times along the specified axis."""
    if not checks.is_array(arg):
        arg = np.asarray(arg)
    if axis == 0:
        if arg.ndim == 2:
            if checks.is_pandas(arg):
                return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                    np.tile(arg.values, (n, 1)),
                    index=index_fns.tile_index(arg.index, n))
            return np.tile(arg, (n, 1))
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.tile(arg.values, n),
                index=index_fns.tile_index(arg.index, n))
        return np.tile(arg, n)
    elif axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.tile(arg.values, (1, n)),
                columns=index_fns.tile_index(arg.columns, n))
        return np.tile(arg, (1, n))
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #3
0
def broadcast_to(arg1,
                 arg2,
                 index_from=1,
                 columns_from=1,
                 writeable=False,
                 copy_kwargs={},
                 raw=False,
                 **kwargs):
    """Bring first argument to the shape of second argument. 

    Closely resembles the other broadcast function."""
    if not checks.is_array_like(arg1):
        arg1 = np.asarray(arg1)
    if not checks.is_array_like(arg2):
        arg2 = np.asarray(arg2)

    is_2d = arg1.ndim > 1 or arg2.ndim > 1
    is_pd = checks.is_pandas(arg1) or checks.is_pandas(arg2)

    if is_pd:
        if is_2d:
            if checks.is_series(arg1):
                arg1 = arg1.to_frame()
            if checks.is_series(arg2):
                arg2 = arg2.to_frame()

        new_index = broadcast_index(arg1,
                                    arg2,
                                    index_from=index_from,
                                    axis=0,
                                    is_2d=is_2d,
                                    **kwargs)
        new_columns = broadcast_index(arg1,
                                      arg2,
                                      index_from=columns_from,
                                      axis=1,
                                      is_2d=is_2d,
                                      **kwargs)
    else:
        new_index, new_columns = None, None

    if is_broadcasting_needed(arg1, arg2):
        arg1_new = np.broadcast_to(arg1, arg2.shape, subok=True)
        arg1_new = np.array(arg1_new, copy=writeable, **copy_kwargs)
    else:
        arg1_new = np.array(arg1, copy=False, **copy_kwargs)
    return wrap_broadcasted(arg1,
                            arg1_new,
                            is_pd=is_pd,
                            new_index=new_index,
                            new_columns=new_columns)
Exemple #4
0
def wrap_broadcasted(old_arg,
                     new_arg,
                     is_pd=False,
                     new_index=None,
                     new_columns=None):
    """Transform newly broadcasted array to match the type of the original object."""
    if is_pd:
        if checks.is_pandas(old_arg):
            if new_index is None:
                # Take index from original pandas object
                if old_arg.shape[0] == new_arg.shape[0]:
                    new_index = old_arg.index
                else:
                    new_index = index_fns.repeat(old_arg.index,
                                                 new_arg.shape[0])
            if new_columns is None:
                # Take columns from original pandas object
                if new_arg.ndim == 2:
                    if checks.is_series(old_arg):
                        old_arg = old_arg.to_frame()
                    if old_arg.shape[1] == new_arg.shape[1]:
                        new_columns = old_arg.columns
                    else:
                        new_columns = index_fns.repeat(old_arg.columns,
                                                       new_arg.shape[1])
        else:
            if new_index is None and new_columns is None:
                # Return plain numpy array if not pandas and no rules set
                return new_arg
        return wrap_array(new_arg, index=new_index, columns=new_columns)
    return new_arg
Exemple #5
0
    def __init__(self, obj: tp.SeriesFrame, **kwargs) -> None:
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        checks.assert_dtype(obj, np.bool_)

        GenericAccessor.__init__(self, obj, **kwargs)
Exemple #6
0
def wrap_broadcasted(old_arg,
                     new_arg,
                     is_pd=False,
                     new_index=None,
                     new_columns=None):
    """If the newly brodcasted array was originally a pandas object, make it pandas object again 
    and assign it the newly broadcasted index/columns."""
    if is_pd:
        if checks.is_pandas(old_arg):
            if new_index is None:
                # Take index from original pandas object
                old_index = index_fns.get_index(old_arg, 0)
                if old_arg.shape[0] == new_arg.shape[0]:
                    new_index = old_index
                else:
                    new_index = index_fns.repeat_index(old_index,
                                                       new_arg.shape[0])
            if new_columns is None:
                # Take columns from original pandas object
                old_columns = index_fns.get_index(old_arg, 1)
                new_ncols = new_arg.shape[1] if new_arg.ndim == 2 else 1
                if len(old_columns) == new_ncols:
                    new_columns = old_columns
                else:
                    new_columns = index_fns.repeat_index(
                        old_columns, new_ncols)
        return array_wrapper.ArrayWrapper(index=new_index,
                                          columns=new_columns,
                                          ndim=new_arg.ndim).wrap(new_arg)
    return new_arg
Exemple #7
0
    def __init__(self, obj, freq=None):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        checks.assert_dtype(obj, np.bool)

        Generic_Accessor.__init__(self, obj, freq=freq)
Exemple #8
0
    def concat(self_or_cls, *others, as_columns=None, broadcast_kwargs={}):
        """Concatenate with `others` along columns.

        All arguments will be broadcasted using `vectorbt.utils.reshape_fns.broadcast`
        with `broadcast_kwargs`. Use `as_columns` as a top-level column level.

        Example:
            ```python-repl
            >>> import pandas as pd
            >>> sr = pd.Series([1, 2], index=['x', 'y'])
            >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b'])

            >>> print(sr.vbt.concat(df, as_columns=['c', 'd']))
                  c     d
               a  b  a  b
            x  1  1  3  4
            y  2  2  5  6
            ```"""
        others = tuple(
            map(lambda x: x._obj
                if isinstance(x, Base_Accessor) else x, others))
        if isinstance(self_or_cls, type):
            objs = others
        else:
            objs = (self_or_cls._obj, ) + others
        broadcasted = reshape_fns.broadcast(*objs, **broadcast_kwargs)
        broadcasted = tuple(map(reshape_fns.to_2d, broadcasted))
        if checks.is_pandas(broadcasted[0]):
            concated = pd.concat(broadcasted, axis=1)
            if as_columns is not None:
                concated.columns = index_fns.combine_indexes(
                    as_columns, broadcasted[0].columns)
        else:
            concated = np.hstack(broadcasted)
        return concated
    def __init__(self, obj, **kwargs):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        checks.assert_dtype(obj, np.bool)

        GenericAccessor.__init__(self, obj, **kwargs)
Exemple #10
0
def wrap_broadcasted(old_arg, new_arg, is_pd=False, new_index=None, new_columns=None):
    """If the newly brodcasted array was originally a pandas object, make it pandas object again 
    and assign it the newly broadcast index/columns."""
    if is_pd:
        if checks.is_pandas(old_arg):
            if new_index is None:
                # Take index from original pandas object
                old_index = index_fns.get_index(old_arg, 0)
                if old_arg.shape[0] == new_arg.shape[0]:
                    new_index = old_index
                else:
                    new_index = index_fns.repeat_index(old_index, new_arg.shape[0])
            if new_columns is None:
                # Take columns from original pandas object
                old_columns = index_fns.get_index(old_arg, 1)
                new_ncols = new_arg.shape[1] if new_arg.ndim == 2 else 1
                if len(old_columns) == new_ncols:
                    new_columns = old_columns
                else:
                    new_columns = index_fns.repeat_index(old_columns, new_ncols)
        if new_arg.ndim == 2:
            return pd.DataFrame(new_arg, index=new_index, columns=new_columns)
        if new_columns is not None and len(new_columns) == 1:
            name = new_columns[0]
            if name == 0:
                name = None
        else:
            name = None
        return pd.Series(new_arg, index=new_index, name=name)
    return new_arg
Exemple #11
0
def wrap_broadcasted(old_arg,
                     new_arg,
                     is_pd=False,
                     new_index=None,
                     new_columns=None):
    """If the newly brodcasted array was originally a pandas object, make it pandas object again 
    and assign it the newly broadcasted index/columns."""
    if is_pd:
        if checks.is_pandas(old_arg):
            if new_index is None:
                # Take index from original pandas object
                if old_arg.shape[0] == new_arg.shape[0]:
                    new_index = old_arg.index
                else:
                    new_index = index_fns.repeat_index(old_arg.index,
                                                       new_arg.shape[0])
            if new_columns is None:
                # Take columns from original pandas object
                if new_arg.ndim == 2:
                    if checks.is_series(old_arg):
                        old_arg = old_arg.to_frame()
                    if old_arg.shape[1] == new_arg.shape[1]:
                        new_columns = old_arg.columns
                    else:
                        new_columns = index_fns.repeat_index(
                            old_arg.columns, new_arg.shape[1])
        else:
            if new_index is None and new_columns is None:
                # Return plain numpy array if not pandas and no rules set
                return new_arg
        return ArrayWrapper(index=new_index, columns=new_columns).wrap(new_arg)
    return new_arg
Exemple #12
0
def soft_broadcast_to_ndim(arg, ndim):
    """Try to softly bring `arg` to the specified number of dimensions `ndim` (max 2)."""
    if not checks.is_array(arg):
        arg = np.asarray(arg)
    if ndim == 1:
        if arg.ndim == 2:
            if arg.shape[1] == 1:
                if checks.is_pandas(arg):
                    return arg.iloc[:, 0]
                return arg[:, 0]  # downgrade
    if ndim == 2:
        if arg.ndim == 1:
            if checks.is_pandas(arg):
                return arg.to_frame()
            return arg[:, None]  # upgrade
    return arg  # do nothing
Exemple #13
0
def repeat(arg, n, along_axis=1):
    """Repeat array n times along specified axis."""
    if not checks.is_array_like(arg):
        arg = np.asarray(arg)
    if along_axis == 0:
        if checks.is_pandas(arg):
            return arg.vbt.wrap_array(np.repeat(arg.values, n, axis=0),
                                      index=index_fns.repeat(arg.index, n))
        return np.repeat(arg, n, axis=0)
    elif along_axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return arg.vbt.wrap_array(np.repeat(arg.values, n, axis=1),
                                      columns=index_fns.repeat(arg.columns, n))
        return np.repeat(arg, n, axis=1)
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #14
0
    def __init__(self, obj, freq=None):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj
        self._obj = obj

        # Initialize array wrapper
        wrapper = ArrayWrapper.from_obj(obj)
        ArrayWrapper.__init__(self, index=wrapper.index, columns=wrapper.columns, ndim=wrapper.ndim, freq=freq)
Exemple #15
0
def repeat(arg, n, axis=1):
    """Repeat each element in `arg` `n` times along the specified axis."""
    if not checks.is_array(arg):
        arg = np.asarray(arg)
    if axis == 0:
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.repeat(arg.values, n, axis=0), index=index_fns.repeat_index(arg.index, n))
        return np.repeat(arg, n, axis=0)
    elif axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.repeat(arg.values, n, axis=1), columns=index_fns.repeat_index(arg.columns, n))
        return np.repeat(arg, n, axis=1)
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #16
0
    def __init__(self, obj, freq=None, year_freq=None):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        Generic_Accessor.__init__(self, obj, freq=freq)

        # Set year frequency
        self._year_freq = year_freq
Exemple #17
0
    def __init__(self,
                 obj: tp.Frame,
                 column_names: tp.Optional[tp.Dict[str, str]] = None,
                 **kwargs) -> None:
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj
        self._column_names = column_names

        GenericDFAccessor.__init__(self, obj, **kwargs)
Exemple #18
0
    def __init__(self,
                 obj: tp.Frame,
                 year_freq: tp.Optional[tp.FrequencyLike] = None,
                 **kwargs) -> None:
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        GenericDFAccessor.__init__(self, obj, **kwargs)
        ReturnsAccessor.__init__(self, obj, year_freq=year_freq, **kwargs)
Exemple #19
0
    def __init__(self,
                 obj: tp.SeriesFrame,
                 year_freq: tp.Optional[tp.FrequencyLike] = None,
                 **kwargs) -> None:
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        GenericAccessor.__init__(self, obj, **kwargs)

        # Set year frequency
        self._year_freq = year_freq
Exemple #20
0
def to_pd_array(arg: tp.ArrayLike) -> tp.SeriesFrame:
    """Convert any array-like object to a pandas object."""
    if checks.is_pandas(arg):
        return arg
    arg = np.asarray(arg)
    if arg.ndim == 1:
        return pd.Series(arg)
    if arg.ndim == 2:
        return pd.DataFrame(arg)
    raise ValueError(
        "Wrong number of dimensions: cannot convert to Series or DataFrame")
Exemple #21
0
def repeat(arg: tp.ArrayLike,
           n: int,
           axis: int = 1,
           raw: bool = False) -> tp.AnyArray:
    """Repeat each element in `arg` `n` times along the specified axis."""
    arg = to_any_array(arg, raw=raw)
    if axis == 0:
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.repeat(arg.values, n, axis=0),
                index=index_fns.repeat_index(arg.index, n))
        return np.repeat(arg, n, axis=0)
    elif axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return array_wrapper.ArrayWrapper.from_obj(arg).wrap(
                np.repeat(arg.values, n, axis=1),
                columns=index_fns.repeat_index(arg.columns, n))
        return np.repeat(arg, n, axis=1)
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #22
0
def tile(arg, n, along_axis=1):
    """Tile array n times along specified axis."""
    if not checks.is_array_like(arg):
        arg = np.asarray(arg)
    if along_axis == 0:
        if arg.ndim == 1:
            if checks.is_pandas(arg):
                return arg.vbt.wrap_array(np.tile(arg.values, n),
                                          index=index_fns.tile(arg.index, n))
            return np.tile(arg, n)
        if arg.ndim == 2:
            if checks.is_pandas(arg):
                return arg.vbt.wrap_array(np.tile(arg.values, (n, 1)),
                                          index=index_fns.tile(arg.index, n))
            return np.tile(arg, (n, 1))
    elif along_axis == 1:
        arg = to_2d(arg)
        if checks.is_pandas(arg):
            return arg.vbt.wrap_array(np.tile(arg.values, (1, n)),
                                      columns=index_fns.tile(arg.columns, n))
        return np.tile(arg, (1, n))
    else:
        raise ValueError("Only axis 0 and 1 are supported")
Exemple #23
0
def broadcast_to(arg1: tp.ArrayLike,
                 arg2: tp.ArrayLike,
                 to_pd: tp.Optional[bool] = None,
                 index_from: tp.Optional[IndexFromLike] = None,
                 columns_from: tp.Optional[IndexFromLike] = None,
                 **kwargs) -> BCRT:
    """Broadcast `arg1` to `arg2`.

    Pass None to `index_from`/`columns_from` to use index/columns of the second argument.

    Keyword arguments `**kwargs` are passed to `broadcast`.

    ## Example

    ```python-repl
    >>> import numpy as np
    >>> import pandas as pd
    >>> from vectorbt.base.reshape_fns import broadcast_to

    >>> a = np.array([1, 2, 3])
    >>> sr = pd.Series([4, 5, 6], index=pd.Index(['x', 'y', 'z']), name='a')

    >>> broadcast_to(a, sr)
    x    1
    y    2
    z    3
    Name: a, dtype: int64

    >>> broadcast_to(sr, a)
    array([4, 5, 6])
    ```
    """
    arg1 = to_any_array(arg1)
    arg2 = to_any_array(arg2)
    if to_pd is None:
        to_pd = checks.is_pandas(arg2)
    if to_pd:
        # Take index and columns from arg2
        if index_from is None:
            index_from = index_fns.get_index(arg2, 0)
        if columns_from is None:
            columns_from = index_fns.get_index(arg2, 1)
    return broadcast(arg1,
                     to_shape=arg2.shape,
                     to_pd=to_pd,
                     index_from=index_from,
                     columns_from=columns_from,
                     **kwargs)
Exemple #24
0
def broadcast_to(arg1,
                 arg2,
                 to_pd=None,
                 index_from=None,
                 columns_from=None,
                 **kwargs):
    """Broadcast `arg1` to `arg2`.

    Keyword arguments `**kwargs` are passed to `broadcast`.

    ## Example

    ```python-repl
    >>> import numpy as np
    >>> import pandas as pd
    >>> from vectorbt.base.reshape_fns import broadcast_to

    >>> a = np.array([1, 2, 3])
    >>> sr = pd.Series([4, 5, 6], index=pd.Index(['x', 'y', 'z']), name='a')

    >>> broadcast_to(a, sr)
    x    1
    y    2
    z    3
    Name: a, dtype: int64

    >>> broadcast_to(sr, a)
    array([4, 5, 6])
    ```
    """
    if not checks.is_array(arg1):
        arg1 = np.asarray(arg1)
    if not checks.is_array(arg2):
        arg2 = np.asarray(arg2)
    if to_pd is None:
        to_pd = checks.is_pandas(arg2)
    if to_pd:
        # Take index and columns from arg2
        if index_from is None:
            index_from = index_fns.get_index(arg2, 0)
        if columns_from is None:
            columns_from = index_fns.get_index(arg2, 1)
    return broadcast(arg1,
                     to_shape=arg2.shape,
                     to_pd=to_pd,
                     index_from=index_from,
                     columns_from=columns_from,
                     **kwargs)
Exemple #25
0
 def concat(self_or_cls, *others, as_columns=None, broadcast_kwargs={}):
     others = tuple(
         map(lambda x: x._obj
             if isinstance(x, Base_Accessor) else x, others))
     if isinstance(self_or_cls, type):
         objs = others
     else:
         objs = (self_or_cls._obj, ) + others
     broadcasted = reshape_fns.broadcast(*objs, **broadcast_kwargs)
     broadcasted = tuple(map(reshape_fns.to_2d, broadcasted))
     if checks.is_pandas(broadcasted[0]):
         concated = pd.concat(broadcasted, axis=1)
         if as_columns is not None:
             concated.columns = index_fns.combine(as_columns,
                                                  broadcasted[0].columns)
     else:
         concated = np.hstack(broadcasted)
     return concated
    def __init__(self, obj, **kwargs):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        GenericSRAccessor.__init__(self, obj, **kwargs)
        SignalsAccessor.__init__(self, obj, **kwargs)
Exemple #27
0
    def __init__(self, obj, column_names=None, freq=None):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj
        self._column_names = column_names

        Generic_DFAccessor.__init__(self, obj, freq=freq)
Exemple #28
0
def broadcast_index(args,
                    to_shape,
                    index_from=None,
                    axis=0,
                    ignore_sr_names=None,
                    **kwargs):
    """Produce a broadcast index/columns.

    Args:
        *args (array_like): Array-like objects.
        to_shape (tuple): Target shape.
        index_from (None, int, str or array_like): Broadcasting rule for this index/these columns.

            Accepts the following values:

            * 'default' - take the value from `vectorbt.settings.broadcasting`
            * 'strict' - ensure that all pandas objects have the same index/columns
            * 'stack' - stack different indexes/columns using `vectorbt.base.index_fns.stack_indexes`
            * 'ignore' - ignore any index/columns
            * integer - use the index/columns of the i-nth object in `args`
            * None - use the original index/columns of the objects in `args`
            * everything else will be converted to `pd.Index`

        axis (int): Set to 0 for index and 1 for columns.
        ignore_sr_names (bool): Whether to ignore Series names if they are in conflict.

            Conflicting Series names are those that are different but not None.
        **kwargs: Keyword arguments passed to `vectorbt.base.index_fns.stack_indexes`.

    For defaults, see `vectorbt.settings.broadcasting`.

    !!! note
        Series names are treated as columns with a single element but without a name.
        If a column level without a name loses its meaning, better to convert Series to DataFrames
        with one column prior to broadcasting. If the name of a Series is not that important,
        better to drop it altogether by setting it to None.
    """
    from vectorbt import settings

    if ignore_sr_names is None:
        ignore_sr_names = settings.broadcasting['ignore_sr_names']
    index_str = 'columns' if axis == 1 else 'index'
    to_shape_2d = (to_shape[0], 1) if len(to_shape) == 1 else to_shape
    # maxlen stores the length of the longest index
    maxlen = to_shape_2d[1] if axis == 1 else to_shape_2d[0]
    new_index = None

    if index_from is not None:
        if isinstance(index_from, int):
            # Take index/columns of the object indexed by index_from
            if not checks.is_pandas(args[index_from]):
                raise TypeError(
                    f"Argument under index {index_from} must be a pandas object"
                )
            new_index = index_fns.get_index(args[index_from], axis)
        elif isinstance(index_from, str):
            if index_from == 'ignore':
                # Ignore index/columns
                new_index = pd.RangeIndex(start=0, stop=maxlen, step=1)
            elif index_from in ('stack', 'strict'):
                # Check whether all indexes/columns are equal
                last_index = None  # of type pd.Index
                index_conflict = False
                for arg in args:
                    if checks.is_pandas(arg):
                        index = index_fns.get_index(arg, axis)
                        if last_index is not None:
                            if not pd.Index.equals(index, last_index):
                                index_conflict = True
                        last_index = index
                        continue
                if not index_conflict:
                    new_index = last_index
                else:
                    # If pandas objects have different index/columns, stack them together
                    for arg in args:
                        if checks.is_pandas(arg):
                            index = index_fns.get_index(arg, axis)
                            if axis == 1 and checks.is_series(
                                    arg) and ignore_sr_names:
                                # ignore Series name
                                continue
                            if checks.is_default_index(index):
                                # ignore simple ranges without name
                                continue
                            if new_index is None:
                                new_index = index
                            else:
                                if index_from == 'strict':
                                    # If pandas objects have different index/columns, raise an exception
                                    if not pd.Index.equals(index, new_index):
                                        raise ValueError(
                                            f"Broadcasting {index_str} is not allowed when {index_str}_from=strict"
                                        )
                                # Broadcasting index must follow the rules of a regular broadcasting operation
                                # https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html#general-broadcasting-rules
                                # 1. rule: if indexes are of the same length, they are simply stacked
                                # 2. rule: if index has one element, it gets repeated and then stacked

                                if pd.Index.equals(index, new_index):
                                    continue
                                if len(index) != len(new_index):
                                    if len(index) > 1 and len(new_index) > 1:
                                        raise ValueError(
                                            "Indexes could not be broadcast together"
                                        )
                                    if len(index) > len(new_index):
                                        new_index = index_fns.repeat_index(
                                            new_index, len(index))
                                    elif len(index) < len(new_index):
                                        index = index_fns.repeat_index(
                                            index, len(new_index))
                                new_index = index_fns.stack_indexes(
                                    new_index, index, **kwargs)
            else:
                raise ValueError(
                    f"Invalid value {index_from} for {'columns' if axis == 1 else 'index'}_from"
                )
        else:
            new_index = index_from
        if new_index is not None:
            if maxlen > len(new_index):
                if index_from == 'strict':
                    raise ValueError(
                        f"Broadcasting {index_str} is not allowed when {index_str}_from=strict"
                    )
                # This happens only when some numpy object is longer than the new pandas index
                # In this case, new pandas index (one element) should be repeated to match this length.
                if maxlen > 1 and len(new_index) > 1:
                    raise ValueError("Indexes could not be broadcast together")
                new_index = index_fns.repeat_index(new_index, maxlen)
        elif index_from is not None:
            # new_index=None can mean two things: 1) take original metadata or 2) reset index/columns
            # In case when index_from is not None, we choose 2)
            new_index = pd.RangeIndex(start=0, stop=maxlen, step=1)
    return new_index
    def generate_ohlc_stop_exits(self, open, high=None, low=None, close=None, is_open_safe=True,
                                out_dict=None, sl_stop=0., ts_stop=0., tp_stop=0., entry_wait=1,
                                exit_wait=1, first=True, iteratively=False, broadcast_kwargs=None, wrap_kwargs=None):
        """Generate exits based on when the price hits (trailing) stop loss or take profit.

        If any of `high`, `low` or `close` is None, it will be set to `open`.

        Use `out_dict` as a dict to pass `hit_price` and `stop_type` arrays. You can also
        set `out_dict` to {} to produce these arrays automatically and still have access to them.

        For arguments, see `vectorbt.signals.nb.ohlc_stop_choice_nb`.
        If `iteratively` is True, see `vectorbt.signals.nb.generate_ohlc_stop_ex_iter_nb`.
        Otherwise, see `vectorbt.signals.nb.generate_ohlc_stop_ex_nb`.

        All array-like arguments including stops and `out_dict` will broadcast using
        `vectorbt.base.reshape_fns.broadcast` with `broadcast_kwargs`.

        For arguments, see `vectorbt.signals.nb.ohlc_stop_choice_nb`.

        !!! note
            `open` isn't necessarily open price, but can be any entry price (even previous close).
            Stop price is calculated based solely upon `open`.

        ## Example

        ```python-repl
        >>> from vectorbt.signals.enums import StopType

        >>> price = pd.DataFrame({
        ...     'open': [10, 11, 12, 11, 10],
        ...     'high': [11, 12, 13, 12, 11],
        ...     'low': [9, 10, 11, 10, 9],
        ...     'close': [10, 11, 12, 11, 10]
        ... })
        >>> out_dict = {}
        >>> exits = sig.vbt.signals.generate_ohlc_stop_exits(
        ...     price['open'], price['high'], price['low'], price['close'],
        ...     out_dict=out_dict, sl_stop=0.2, ts_stop=0.2, tp_stop=0.2)
        >>> out_dict['hit_price'][~exits] = np.nan
        >>> out_dict['stop_type'][~exits] = -1

        >>> exits
                        a      b      c
        2020-01-01  False  False  False
        2020-01-02   True   True  False
        2020-01-03  False  False  False
        2020-01-04  False  False  False
        2020-01-05  False  False   True

        >>> out_dict['hit_price']
                       a     b    c
        2020-01-01   NaN   NaN  NaN
        2020-01-02  12.0  12.0  NaN
        2020-01-03   NaN   NaN  NaN
        2020-01-04   NaN   NaN  NaN
        2020-01-05   NaN   NaN  9.6

        >>> out_dict['stop_type'].applymap(
        ...     lambda x: StopType._fields[x] if x in StopType else '')
                             a           b         c
        2020-01-01
        2020-01-02  TakeProfit  TakeProfit
        2020-01-03
        2020-01-04
        2020-01-05                          StopLoss
        ```
        """
        if broadcast_kwargs is None:
            broadcast_kwargs = {}
        entries = self._obj
        if high is None:
            high = open
        if low is None:
            low = open
        if close is None:
            close = open
        if out_dict is None:
            out_dict = {}
        hit_price_out = out_dict.get('hit_price', None)
        stop_type_out = out_dict.get('stop_type', None)
        out_args = ()
        if hit_price_out is not None:
            out_args += (hit_price_out,)
        if stop_type_out is not None:
            out_args += (stop_type_out,)

        keep_raw = (False, True, True, True, True, True, True, True) + (False,) * len(out_args)
        broadcast_kwargs = merge_dicts(dict(require_kwargs=dict(requirements='W')), broadcast_kwargs)
        entries, open, high, low, close, sl_stop, ts_stop, tp_stop, *out_args = reshape_fns.broadcast(
            entries, open, high, low, close, sl_stop, ts_stop, tp_stop, *out_args,
            **broadcast_kwargs, keep_raw=keep_raw)
        if hit_price_out is None:
            hit_price_out = np.empty_like(entries, dtype=np.float_)
        else:
            hit_price_out = out_args[0]
            if checks.is_pandas(hit_price_out):
                hit_price_out = hit_price_out.vbt.to_2d_array()
            out_args = out_args[1:]
        if stop_type_out is None:
            stop_type_out = np.empty_like(entries, dtype=np.int_)
        else:
            stop_type_out = out_args[0]
            if checks.is_pandas(stop_type_out):
                stop_type_out = stop_type_out.vbt.to_2d_array()

        # Perform generation
        if iteratively:
            new_entries, exits = nb.generate_ohlc_stop_ex_iter_nb(
                entries.vbt.to_2d_array(), open, high, low, close, hit_price_out,
                stop_type_out, sl_stop, ts_stop, tp_stop, is_open_safe, entry_wait,
                exit_wait, first, entries.ndim == 2)
            out_dict['hit_price'] = entries.vbt.wrapper.wrap(hit_price_out, **merge_dicts({}, wrap_kwargs))
            out_dict['stop_type'] = entries.vbt.wrapper.wrap(stop_type_out, **merge_dicts({}, wrap_kwargs))
            return entries.vbt.wrapper.wrap(new_entries, **merge_dicts({}, wrap_kwargs)), \
                   entries.vbt.wrapper.wrap(exits, **merge_dicts({}, wrap_kwargs))
        else:
            exits = nb.generate_ohlc_stop_ex_nb(
                entries.vbt.to_2d_array(), open, high, low, close, hit_price_out,
                stop_type_out, sl_stop, ts_stop, tp_stop, is_open_safe, exit_wait,
                first, entries.ndim == 2)
            out_dict['hit_price'] = entries.vbt.wrapper.wrap(hit_price_out, **merge_dicts({}, wrap_kwargs))
            out_dict['stop_type'] = entries.vbt.wrapper.wrap(stop_type_out, **merge_dicts({}, wrap_kwargs))
            return entries.vbt.wrapper.wrap(exits, **merge_dicts({}, wrap_kwargs))
Exemple #30
0
def broadcast(*args,
              to_shape=None,
              to_pd=None,
              to_frame=None,
              align_index=None,
              align_columns=None,
              index_from='default',
              columns_from='default',
              require_kwargs=None,
              keep_raw=False,
              return_meta=False,
              **kwargs):
    """Bring any array-like object in `args` to the same shape by using NumPy broadcasting.

    See [Broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html).

    Can broadcast pandas objects by broadcasting their index/columns with `broadcast_index`.

    Args:
        *args (array_like): Array-like objects.
        to_shape (tuple): Target shape. If set, will broadcast every element in `args` to `to_shape`.
        to_pd (bool, tuple or list): Whether to convert all output arrays to pandas, otherwise returns
            raw NumPy arrays. If None, converts only if there is at least one pandas object among them.
        to_frame (bool): Whether to convert all Series to DataFrames.
        align_index (bool): Whether to align index of pandas objects using multi-index.
        align_columns (bool): Whether to align columns of pandas objects using multi-index.
        index_from (any): Broadcasting rule for index.
        columns_from (any): Broadcasting rule for columns.
        require_kwargs (dict or list of dict): Keyword arguments passed to `np.require`.
        keep_raw (bool, tuple or list): Whether to keep the unbroadcasted version of the array.

            Only makes sure that the array can be broadcast to the target shape.
        return_meta (bool): If True, will also return new shape, index and columns.
        **kwargs: Keyword arguments passed to `broadcast_index`.

    For defaults, see `vectorbt.settings.broadcasting`.

    ## Example

    Without broadcasting index and columns:
    ```python-repl
    >>> import numpy as np
    >>> import pandas as pd
    >>> from vectorbt.base.reshape_fns import broadcast

    >>> v = 0
    >>> a = np.array([1, 2, 3])
    >>> sr = pd.Series([1, 2, 3], index=pd.Index(['x', 'y', 'z']), name='a')
    >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    ...     index=pd.Index(['x2', 'y2', 'z2']),
    ...     columns=pd.Index(['a2', 'b2', 'c2']))

    >>> for i in broadcast(
    ...     v, a, sr, df,
    ...     index_from=None,
    ...     columns_from=None,
    ... ): print(i)
       0  1  2
    0  0  0  0
    1  0  0  0
    2  0  0  0
       0  1  2
    0  1  2  3
    1  1  2  3
    2  1  2  3
       a  a  a
    x  1  1  1
    y  2  2  2
    z  3  3  3
        a2  b2  c2
    x2   1   2   3
    y2   4   5   6
    z2   7   8   9
    ```

    Taking new index and columns from position:
    ```python-repl
    >>> for i in broadcast(
    ...     v, a, sr, df,
    ...     index_from=2,
    ...     columns_from=3
    ... ): print(i)
       a2  b2  c2
    x   0   0   0
    y   0   0   0
    z   0   0   0
       a2  b2  c2
    x   1   2   3
    y   1   2   3
    z   1   2   3
       a2  b2  c2
    x   1   1   1
    y   2   2   2
    z   3   3   3
       a2  b2  c2
    x   1   2   3
    y   4   5   6
    z   7   8   9
    ```

    Broadcasting index and columns through stacking:
    ```python-repl
    >>> for i in broadcast(
    ...     v, a, sr, df,
    ...     index_from='stack',
    ...     columns_from='stack'
    ... ): print(i)
          a2  b2  c2
    x x2   0   0   0
    y y2   0   0   0
    z z2   0   0   0
          a2  b2  c2
    x x2   1   2   3
    y y2   1   2   3
    z z2   1   2   3
          a2  b2  c2
    x x2   1   1   1
    y y2   2   2   2
    z z2   3   3   3
          a2  b2  c2
    x x2   1   2   3
    y y2   4   5   6
    z z2   7   8   9
    ```

    Setting index and columns manually:
    ```python-repl
    >>> for i in broadcast(
    ...     v, a, sr, df,
    ...     index_from=['a', 'b', 'c'],
    ...     columns_from=['d', 'e', 'f']
    ... ): print(i)
       d  e  f
    a  0  0  0
    b  0  0  0
    c  0  0  0
       d  e  f
    a  1  2  3
    b  1  2  3
    c  1  2  3
       d  e  f
    a  1  1  1
    b  2  2  2
    c  3  3  3
       d  e  f
    a  1  2  3
    b  4  5  6
    c  7  8  9
    ```
    """
    from vectorbt import settings

    is_pd = False
    is_2d = False
    args = list(args)
    if require_kwargs is None:
        require_kwargs = {}
    if align_index is None:
        align_index = settings.broadcasting['align_index']
    if align_columns is None:
        align_columns = settings.broadcasting['align_columns']
    if isinstance(index_from, str) and index_from == 'default':
        index_from = settings.broadcasting['index_from']
    if isinstance(columns_from, str) and columns_from == 'default':
        columns_from = settings.broadcasting['columns_from']

    # Convert to np.ndarray object if not numpy or pandas
    # Also check whether we broadcast to pandas and whether work on 2-dim data
    for i in range(len(args)):
        if not checks.is_array(args[i]):
            args[i] = np.asarray(args[i])
        if args[i].ndim > 1:
            is_2d = True
        if checks.is_pandas(args[i]):
            is_pd = True

    # If target shape specified, check again if we work on 2-dim data
    if to_shape is not None:
        if isinstance(to_shape, int):
            to_shape = (to_shape, )
        checks.assert_type(to_shape, tuple)
        if len(to_shape) > 1:
            is_2d = True

    if to_frame is not None:
        # force either keeping Series or converting them to DataFrames
        is_2d = to_frame

    if to_pd is not None:
        # force either raw or pandas
        if isinstance(to_pd, (tuple, list)):
            is_pd = any(to_pd)
        else:
            is_pd = to_pd

    # Align pandas objects
    if align_index:
        index_to_align = []
        for i in range(len(args)):
            if checks.is_pandas(args[i]) and len(args[i].index) > 1:
                index_to_align.append(i)
        if len(index_to_align) > 1:
            indexes = [args[i].index for i in index_to_align]
            if len(set(map(len, indexes))) > 1:
                index_indices = index_fns.align_indexes(*indexes)
                for i in range(len(args)):
                    if i in index_to_align:
                        args[i] = args[i].iloc[index_indices[
                            index_to_align.index(i)]]
    if align_columns:
        cols_to_align = []
        for i in range(len(args)):
            if checks.is_frame(args[i]) and len(args[i].columns) > 1:
                cols_to_align.append(i)
        if len(cols_to_align) > 1:
            indexes = [args[i].columns for i in cols_to_align]
            if len(set(map(len, indexes))) > 1:
                col_indices = index_fns.align_indexes(*indexes)
                for i in range(len(args)):
                    if i in cols_to_align:
                        args[i] = args[i].iloc[:, col_indices[cols_to_align.
                                                              index(i)]]

    # Convert all pd.Series objects to pd.DataFrame if we work on 2-dim data
    args_2d = [
        arg.to_frame() if is_2d and checks.is_series(arg) else arg
        for arg in args
    ]

    # Get final shape
    if to_shape is None:
        to_shape = np.lib.stride_tricks._broadcast_shape(*args_2d)

    # Perform broadcasting
    new_args = []
    for i, arg in enumerate(args_2d):
        if isinstance(keep_raw, (tuple, list)):
            _keep_raw = keep_raw[i]
        else:
            _keep_raw = keep_raw
        bc_arg = np.broadcast_to(arg, to_shape)
        if _keep_raw:
            new_args.append(arg)
            continue
        new_args.append(bc_arg)

    # Force to match requirements
    for i in range(len(new_args)):
        if isinstance(require_kwargs, (tuple, list)):
            _require_kwargs = require_kwargs[i]
        else:
            _require_kwargs = require_kwargs
        new_args[i] = np.require(new_args[i], **_require_kwargs)

    if is_pd:
        # Decide on index and columns
        # NOTE: Important to pass args, not args_2d, to preserve original shape info
        new_index = broadcast_index(args,
                                    to_shape,
                                    index_from=index_from,
                                    axis=0,
                                    **kwargs)
        new_columns = broadcast_index(args,
                                      to_shape,
                                      index_from=columns_from,
                                      axis=1,
                                      **kwargs)
    else:
        new_index, new_columns = None, None

    # Bring arrays to their old types (e.g. array -> pandas)
    for i in range(len(new_args)):
        if isinstance(keep_raw, (tuple, list)):
            _keep_raw = keep_raw[i]
        else:
            _keep_raw = keep_raw
        if _keep_raw:
            continue
        if isinstance(to_pd, (tuple, list)):
            _is_pd = to_pd[i]
        else:
            _is_pd = is_pd
        new_args[i] = wrap_broadcasted(args[i],
                                       new_args[i],
                                       is_pd=_is_pd,
                                       new_index=new_index,
                                       new_columns=new_columns)

    if len(new_args) > 1:
        if return_meta:
            return tuple(new_args), to_shape, new_index, new_columns
        return tuple(new_args)
    if return_meta:
        return new_args[0], to_shape, new_index, new_columns
    return new_args[0]