def apply_and_concat(self, ntimes, *args, apply_func=None, to_2d=False, keys=None, wrap_kwargs=None, **kwargs): """Apply `apply_func` `ntimes` times and concatenate the results along columns. See `vectorbt.base.combine_fns.apply_and_concat_one`. Arguments `*args` and `**kwargs` will be directly passed to `apply_func`. If `to_2d` is True, 2-dimensional NumPy arrays will be passed, otherwise as is. Use `keys` as the outermost level. !!! note The resulted arrays to be concatenated must have the same shape as broadcast input arrays. ## Example ```python-repl >>> import vectorbt as vbt >>> import pandas as pd >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b']) >>> df.vbt.apply_and_concat(3, [1, 2, 3], ... apply_func=lambda i, a, b: a * b[i], keys=['c', 'd', 'e']) c d e a b a b a b x 3 4 6 8 9 12 y 5 6 10 12 15 18 ``` """ checks.assert_not_none(apply_func) # Optionally cast to 2d array if to_2d: obj_arr = reshape_fns.to_2d(self._obj, raw=True) else: obj_arr = np.asarray(self._obj) if checks.is_numba_func(apply_func): result = combine_fns.apply_and_concat_one_nb( ntimes, apply_func, obj_arr, *args, **kwargs) else: result = combine_fns.apply_and_concat_one(ntimes, apply_func, obj_arr, *args, **kwargs) # Build column hierarchy if keys is not None: new_columns = index_fns.combine_indexes(keys, self.wrapper.columns) else: top_columns = pd.Index(np.arange(ntimes), name='apply_idx') new_columns = index_fns.combine_indexes(top_columns, self.wrapper.columns) return self.wrapper.wrap(result, group_by=False, **merge_dicts(dict(columns=new_columns), wrap_kwargs))
def apply_and_concat(self, ntimes: int, *args, apply_func: tp.Optional[tp.Callable] = None, keep_pd: bool = False, to_2d: bool = False, numba_loop: bool = False, use_ray: bool = False, keys: tp.Optional[tp.IndexLike] = None, wrap_kwargs: tp.KwargsLike = None, **kwargs) -> tp.Frame: """Apply `apply_func` `ntimes` times and concatenate the results along columns. See `vectorbt.base.combine_fns.apply_and_concat_one`. Args: ntimes (int): Number of times to call `apply_func`. *args: Variable arguments passed to `apply_func`. apply_func (callable): Apply function. Can be Numba-compiled. keep_pd (bool): Whether to keep inputs as pandas objects, otherwise convert to NumPy arrays. to_2d (bool): Whether to reshape inputs to 2-dim arrays, otherwise keep as-is. numba_loop (bool): Whether to loop using Numba. Set to True when iterating large number of times over small input, but note that Numba doesn't support variable keyword arguments. use_ray (bool): Whether to use Ray to execute `combine_func` in parallel. Only works with `numba_loop` set to False and `concat` is set to True. See `vectorbt.base.combine_fns.ray_apply` for related keyword arguments. keys (index_like): Outermost column level. wrap_kwargs (dict): Keyword arguments passed to `vectorbt.base.array_wrapper.ArrayWrapper.wrap`. **kwargs: Keyword arguments passed to `combine_func`. !!! note The resulted arrays to be concatenated must have the same shape as broadcast input arrays. ## Example ```python-repl >>> import vectorbt as vbt >>> import pandas as pd >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b']) >>> df.vbt.apply_and_concat(3, [1, 2, 3], ... apply_func=lambda i, a, b: a * b[i], keys=['c', 'd', 'e']) c d e a b a b a b x 3 4 6 8 9 12 y 5 6 10 12 15 18 ``` Use Ray for small inputs and large processing times: ```python-repl >>> def apply_func(i, a): ... time.sleep(1) ... return a >>> sr = pd.Series([1, 2, 3]) >>> %timeit sr.vbt.apply_and_concat(3, apply_func=apply_func) 3.01 s ± 2.15 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) >>> %timeit sr.vbt.apply_and_concat(3, apply_func=apply_func, use_ray=True) 1.01 s ± 2.31 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) ``` """ checks.assert_not_none(apply_func) # Optionally cast to 2d array if to_2d: obj = reshape_fns.to_2d(self.obj, raw=not keep_pd) else: if not keep_pd: obj = np.asarray(self.obj) else: obj = self.obj if checks.is_numba_func(apply_func) and numba_loop: if use_ray: raise ValueError("Ray cannot be used within Numba") result = combine_fns.apply_and_concat_one_nb( ntimes, apply_func, obj, *args, **kwargs) else: if use_ray: result = combine_fns.apply_and_concat_one_ray( ntimes, apply_func, obj, *args, **kwargs) else: result = combine_fns.apply_and_concat_one( ntimes, apply_func, obj, *args, **kwargs) # Build column hierarchy if keys is not None: new_columns = index_fns.combine_indexes( [keys, self.wrapper.columns]) else: top_columns = pd.Index(np.arange(ntimes), name='apply_idx') new_columns = index_fns.combine_indexes( [top_columns, self.wrapper.columns]) return self.wrapper.wrap(result, group_by=False, **merge_dicts(dict(columns=new_columns), wrap_kwargs))