Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/window/ewm.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

240 statements  

1from __future__ import annotations 

2 

3import datetime 

4from functools import partial 

5from typing import ( 

6 TYPE_CHECKING, 

7 cast, 

8) 

9 

10import numpy as np 

11 

12from pandas._libs.tslibs import Timedelta 

13import pandas._libs.window.aggregations as window_aggregations 

14from pandas.util._decorators import set_module 

15 

16from pandas.core.dtypes.common import ( 

17 is_datetime64_dtype, 

18 is_numeric_dtype, 

19) 

20from pandas.core.dtypes.dtypes import DatetimeTZDtype 

21from pandas.core.dtypes.generic import ABCSeries 

22from pandas.core.dtypes.missing import isna 

23 

24from pandas.core import common 

25from pandas.core.arrays.datetimelike import dtype_to_unit 

26from pandas.core.indexers.objects import ( 

27 BaseIndexer, 

28 ExponentialMovingWindowIndexer, 

29 GroupByIndexer, 

30) 

31from pandas.core.util.numba_ import ( 

32 get_jit_arguments, 

33 maybe_use_numba, 

34) 

35from pandas.core.window.common import zsqrt 

36from pandas.core.window.numba_ import ( 

37 generate_numba_ewm_func, 

38 generate_numba_ewm_table_func, 

39) 

40from pandas.core.window.online import ( 

41 EWMMeanState, 

42 generate_online_numba_ewma_func, 

43) 

44from pandas.core.window.rolling import ( 

45 BaseWindow, 

46 BaseWindowGroupBy, 

47) 

48 

49if TYPE_CHECKING: 

50 from pandas._typing import ( 

51 TimedeltaConvertibleTypes, 

52 TimeUnit, 

53 npt, 

54 ) 

55 

56 from pandas import ( 

57 DataFrame, 

58 Series, 

59 ) 

60 from pandas.core.generic import NDFrame 

61 

62 

63def get_center_of_mass( 

64 comass: float | None, 

65 span: float | None, 

66 halflife: float | None, 

67 alpha: float | None, 

68) -> float: 

69 valid_count = common.count_not_none(comass, span, halflife, alpha) 

70 if valid_count > 1: 

71 raise ValueError("comass, span, halflife, and alpha are mutually exclusive") 

72 

73 # Convert to center of mass; domain checks ensure 0 < alpha <= 1 

74 if comass is not None: 

75 if comass < 0: 

76 raise ValueError("comass must satisfy: comass >= 0") 

77 elif span is not None: 

78 if span < 1: 

79 raise ValueError("span must satisfy: span >= 1") 

80 comass = (span - 1) / 2 

81 elif halflife is not None: 

82 if halflife <= 0: 

83 raise ValueError("halflife must satisfy: halflife > 0") 

84 decay = 1 - np.exp(np.log(0.5) / halflife) 

85 comass = 1 / decay - 1 

86 elif alpha is not None: 

87 if alpha <= 0 or alpha > 1: 

88 raise ValueError("alpha must satisfy: 0 < alpha <= 1") 

89 comass = (1 - alpha) / alpha 

90 else: 

91 raise ValueError("Must pass one of comass, span, halflife, or alpha") 

92 

93 return float(comass) 

94 

95 

96def _calculate_deltas( 

97 times: np.ndarray | NDFrame, 

98 halflife: float | TimedeltaConvertibleTypes | None, 

99) -> npt.NDArray[np.float64]: 

100 """ 

101 Return the diff of the times divided by the half-life. These values are used in 

102 the calculation of the ewm mean. 

103 

104 Parameters 

105 ---------- 

106 times : np.ndarray, Series 

107 Times corresponding to the observations. Must be monotonically increasing 

108 and ``datetime64[ns]`` dtype. 

109 halflife : float, str, timedelta, optional 

110 Half-life specifying the decay 

111 

112 Returns 

113 ------- 

114 np.ndarray 

115 Diff of the times divided by the half-life 

116 """ 

117 unit = dtype_to_unit(times.dtype) 

118 unit = cast("TimeUnit", unit) 

119 if isinstance(times, ABCSeries): 

120 times = times._values 

121 _times = np.asarray(times.view(np.int64), dtype=np.float64) 

122 _halflife = float(Timedelta(halflife).as_unit(unit)._value) 

123 return np.diff(_times) / _halflife 

124 

125 

126@set_module("pandas.api.typing") 

127class ExponentialMovingWindow(BaseWindow): 

128 r""" 

129 Provide exponentially weighted (EW) calculations. 

130 

131 Exactly one of ``com``, ``span``, ``halflife``, or ``alpha`` must be 

132 provided if ``times`` is not provided. If ``times`` is provided and ``adjust=True``, 

133 ``halflife`` and one of ``com``, ``span`` or ``alpha`` may be provided. 

134 If ``times`` is provided and ``adjust=False``, ``halflife`` must be the only 

135 provided decay-specification parameter. 

136 

137 Parameters 

138 ---------- 

139 com : float, optional 

140 Specify decay in terms of center of mass 

141 

142 :math:`\alpha = 1 / (1 + com)`, for :math:`com \geq 0`. 

143 

144 span : float, optional 

145 Specify decay in terms of span 

146 

147 :math:`\alpha = 2 / (span + 1)`, for :math:`span \geq 1`. 

148 

149 halflife : float, str, timedelta, optional 

150 Specify decay in terms of half-life 

151 

152 :math:`\alpha = 1 - \exp\left(-\ln(2) / halflife\right)`, for 

153 :math:`halflife > 0`. 

154 

155 If ``times`` is specified, a timedelta convertible unit over which an 

156 observation decays to half its value. Only applicable to ``mean()``, 

157 and halflife value will not apply to the other functions. 

158 

159 alpha : float, optional 

160 Specify smoothing factor :math:`\alpha` directly 

161 

162 :math:`0 < \alpha \leq 1`. 

163 

164 min_periods : int, default 0 

165 Minimum number of observations in window required to have a value; 

166 otherwise, result is ``np.nan``. 

167 

168 adjust : bool, default True 

169 Divide by decaying adjustment factor in beginning periods to account 

170 for imbalance in relative weightings (viewing EWMA as a moving average). 

171 

172 - When ``adjust=True`` (default), the EW function is calculated using weights 

173 :math:`w_i = (1 - \alpha)^i`. For example, the EW moving average of the series 

174 [:math:`x_0, x_1, ..., x_t`] would be: 

175 

176 .. math:: 

177 y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ... + (1 - 

178 \alpha)^t x_0}{1 + (1 - \alpha) + (1 - \alpha)^2 + ... + (1 - \alpha)^t} 

179 

180 - When ``adjust=False``, the exponentially weighted function is calculated 

181 recursively: 

182 

183 .. math:: 

184 \begin{split} 

185 y_0 &= x_0\\ 

186 y_t &= (1 - \alpha) y_{t-1} + \alpha x_t, 

187 \end{split} 

188 ignore_na : bool, default False 

189 Ignore missing values when calculating weights. 

190 

191 - When ``ignore_na=False`` (default), weights are based on absolute positions. 

192 For example, the weights of :math:`x_0` and :math:`x_2` used in calculating 

193 the final weighted average of [:math:`x_0`, None, :math:`x_2`] are 

194 :math:`(1-\alpha)^2` and :math:`1` if ``adjust=True``, and 

195 :math:`(1-\alpha)^2` and :math:`\alpha` if ``adjust=False``. 

196 

197 - When ``ignore_na=True``, weights are based 

198 on relative positions. For example, the weights of :math:`x_0` and :math:`x_2` 

199 used in calculating the final weighted average of 

200 [:math:`x_0`, None, :math:`x_2`] are :math:`1-\alpha` and :math:`1` if 

201 ``adjust=True``, and :math:`1-\alpha` and :math:`\alpha` if ``adjust=False``. 

202 

203 times : np.ndarray, Series, default None 

204 

205 Only applicable to ``mean()``. 

206 

207 Times corresponding to the observations. Must be monotonically increasing and 

208 ``datetime64[ns]`` dtype. 

209 

210 If 1-D array like, a sequence with the same shape as the observations. 

211 

212 method : str {'single', 'table'}, default 'single' 

213 Execute the rolling operation per single column or row (``'single'``) 

214 or over the entire object (``'table'``). 

215 

216 This argument is only implemented when specifying ``engine='numba'`` 

217 in the method call. 

218 

219 Only applicable to ``mean()`` 

220 

221 Returns 

222 ------- 

223 pandas.api.typing.ExponentialMovingWindow 

224 An instance of ExponentialMovingWindow for further exponentially weighted (EW) 

225 calculations, e.g. using the ``mean`` method. 

226 

227 See Also 

228 -------- 

229 rolling : Provides rolling window calculations. 

230 expanding : Provides expanding transformations. 

231 

232 Notes 

233 ----- 

234 See :ref:`Windowing Operations <window.exponentially_weighted>` 

235 for further usage details and examples. 

236 

237 Examples 

238 -------- 

239 >>> df = pd.DataFrame({'B': [0, 1, 2, np.nan, 4]}) 

240 >>> df 

241 B 

242 0 0.0 

243 1 1.0 

244 2 2.0 

245 3 NaN 

246 4 4.0 

247 

248 >>> df.ewm(com=0.5).mean() 

249 B 

250 0 0.000000 

251 1 0.750000 

252 2 1.615385 

253 3 1.615385 

254 4 3.670213 

255 >>> df.ewm(alpha=2 / 3).mean() 

256 B 

257 0 0.000000 

258 1 0.750000 

259 2 1.615385 

260 3 1.615385 

261 4 3.670213 

262 

263 **adjust** 

264 

265 >>> df.ewm(com=0.5, adjust=True).mean() 

266 B 

267 0 0.000000 

268 1 0.750000 

269 2 1.615385 

270 3 1.615385 

271 4 3.670213 

272 >>> df.ewm(com=0.5, adjust=False).mean() 

273 B 

274 0 0.000000 

275 1 0.666667 

276 2 1.555556 

277 3 1.555556 

278 4 3.650794 

279 

280 **ignore_na** 

281 

282 >>> df.ewm(com=0.5, ignore_na=True).mean() 

283 B 

284 0 0.000000 

285 1 0.750000 

286 2 1.615385 

287 3 1.615385 

288 4 3.225000 

289 >>> df.ewm(com=0.5, ignore_na=False).mean() 

290 B 

291 0 0.000000 

292 1 0.750000 

293 2 1.615385 

294 3 1.615385 

295 4 3.670213 

296 

297 **times** 

298 

299 Exponentially weighted mean with weights calculated with a timedelta ``halflife`` 

300 relative to ``times``. 

301 

302 >>> times = ['2020-01-01', '2020-01-03', '2020-01-10', '2020-01-15', '2020-01-17'] 

303 >>> df.ewm(halflife='4 days', times=pd.DatetimeIndex(times)).mean() 

304 B 

305 0 0.000000 

306 1 0.585786 

307 2 1.523889 

308 3 1.523889 

309 4 3.233686 

310 """ 

311 

312 _attributes = [ 

313 "com", 

314 "span", 

315 "halflife", 

316 "alpha", 

317 "min_periods", 

318 "adjust", 

319 "ignore_na", 

320 "times", 

321 "method", 

322 ] 

323 

324 def __init__( 

325 self, 

326 obj: NDFrame, 

327 com: float | None = None, 

328 span: float | None = None, 

329 halflife: float | TimedeltaConvertibleTypes | None = None, 

330 alpha: float | None = None, 

331 min_periods: int | None = 0, 

332 adjust: bool = True, 

333 ignore_na: bool = False, 

334 times: np.ndarray | NDFrame | None = None, 

335 method: str = "single", 

336 *, 

337 selection=None, 

338 ) -> None: 

339 super().__init__( 

340 obj=obj, 

341 min_periods=1 if min_periods is None else max(int(min_periods), 1), 

342 on=None, 

343 center=False, 

344 closed=None, 

345 method=method, 

346 selection=selection, 

347 ) 

348 self.com = com 

349 self.span = span 

350 self.halflife = halflife 

351 self.alpha = alpha 

352 self.adjust = adjust 

353 self.ignore_na = ignore_na 

354 self.times = times 

355 if self.times is not None: 

356 times_dtype = getattr(self.times, "dtype", None) 

357 if not ( 

358 is_datetime64_dtype(times_dtype) 

359 or isinstance(times_dtype, DatetimeTZDtype) 

360 ): 

361 raise ValueError("times must be datetime64 dtype.") 

362 if len(self.times) != len(obj): 

363 raise ValueError("times must be the same length as the object.") 

364 if not isinstance(self.halflife, (str, datetime.timedelta, np.timedelta64)): 

365 raise ValueError("halflife must be a timedelta convertible object") 

366 if isna(self.times).any(): 

367 raise ValueError("Cannot convert NaT values to integer") 

368 self._deltas = _calculate_deltas(self.times, self.halflife) 

369 # Halflife is no longer applicable when calculating COM 

370 # But allow COM to still be calculated if the user passes other decay args 

371 if common.count_not_none(self.com, self.span, self.alpha) > 0: 

372 if not self.adjust: 

373 raise NotImplementedError( 

374 "None of com, span, or alpha can be specified if " 

375 "times is provided and adjust=False" 

376 ) 

377 self._com = get_center_of_mass(self.com, self.span, None, self.alpha) 

378 else: 

379 self._com = 1.0 

380 else: 

381 if self.halflife is not None and isinstance( 

382 self.halflife, (str, datetime.timedelta, np.timedelta64) 

383 ): 

384 raise ValueError( 

385 "halflife can only be a timedelta convertible argument if " 

386 "times is not None." 

387 ) 

388 # Without times, points are equally spaced 

389 self._deltas = np.ones(max(self.obj.shape[0] - 1, 0), dtype=np.float64) 

390 self._com = get_center_of_mass( 

391 # error: Argument 3 to "get_center_of_mass" has incompatible type 

392 # "Union[float, Any, None, timedelta64, signedinteger[_64Bit]]"; 

393 # expected "Optional[float]" 

394 self.com, 

395 self.span, 

396 self.halflife, # type: ignore[arg-type] 

397 self.alpha, 

398 ) 

399 

400 def _check_window_bounds( 

401 self, start: np.ndarray, end: np.ndarray, num_vals: int 

402 ) -> None: 

403 # emw algorithms are iterative with each point 

404 # ExponentialMovingWindowIndexer "bounds" are the entire window 

405 pass 

406 

407 def _get_window_indexer(self) -> BaseIndexer: 

408 """ 

409 Return an indexer class that will compute the window start and end bounds 

410 """ 

411 return ExponentialMovingWindowIndexer() 

412 

413 def online( 

414 self, engine: str = "numba", engine_kwargs=None 

415 ) -> OnlineExponentialMovingWindow: 

416 """ 

417 Return an ``OnlineExponentialMovingWindow`` object to calculate 

418 exponentially moving window aggregations in an online method. 

419 

420 Parameters 

421 ---------- 

422 engine: str, default ``'numba'`` 

423 Execution engine to calculate online aggregations. 

424 Applies to all supported aggregation methods. 

425 

426 engine_kwargs : dict, default None 

427 Applies to all supported aggregation methods. 

428 

429 * For ``'numba'`` engine, the engine can accept ``nogil`` 

430 and ``parallel`` dictionary keys. The values must either be ``True`` or 

431 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is 

432 ``{'nogil': False, 'parallel': False}`` and will be 

433 applied to the function 

434 

435 Returns 

436 ------- 

437 OnlineExponentialMovingWindow 

438 """ 

439 return OnlineExponentialMovingWindow( 

440 obj=self.obj, 

441 com=self.com, 

442 span=self.span, 

443 halflife=self.halflife, 

444 alpha=self.alpha, 

445 min_periods=self.min_periods, 

446 adjust=self.adjust, 

447 ignore_na=self.ignore_na, 

448 times=self.times, 

449 engine=engine, 

450 engine_kwargs=engine_kwargs, 

451 selection=self._selection, 

452 ) 

453 

454 def aggregate(self, func=None, *args, **kwargs): 

455 """ 

456 Aggregate using one or more operations over the specified axis. 

457 

458 Parameters 

459 ---------- 

460 func : function, str, list or dict 

461 Function to use for aggregating the data. If a function, must either 

462 work when passed a Series/Dataframe or when passed to 

463 Series/Dataframe.apply. 

464 

465 Accepted combinations are: 

466 

467 - function 

468 - string function name 

469 - list of functions and/or function names, e.g. ``[np.sum, 'mean']`` 

470 - dict of axis labels -> functions, function names or list of such. 

471 *args 

472 Positional arguments to pass to `func`. 

473 **kwargs 

474 Keyword arguments to pass to `func`. 

475 

476 Returns 

477 ------- 

478 scalar, Series or DataFrame 

479 

480 The return can be: 

481 

482 * scalar : when Series.agg is called with single function 

483 * Series : when DataFrame.agg is called with a single function 

484 * DataFrame : when DataFrame.agg is called with several functions 

485 

486 See Also 

487 -------- 

488 pandas.DataFrame.rolling.aggregate 

489 

490 Notes 

491 ----- 

492 The aggregation operations are always performed over an axis, either the 

493 index (default) or the column axis. This behavior is different from 

494 `numpy` aggregation functions (`mean`, `median`, `prod`, `sum`, `std`, 

495 `var`), where the default is to compute the aggregation of the flattened 

496 array, e.g., ``numpy.mean(arr_2d)`` as opposed to 

497 ``numpy.mean(arr_2d, axis=0)``. 

498 

499 `agg` is an alias for `aggregate`. Use the alias. 

500 

501 Functions that mutate the passed object can produce unexpected 

502 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation` 

503 for more details. 

504 

505 A passed user-defined-function will be passed a Series for evaluation. 

506 

507 If ``func`` defines an index relabeling, ``axis`` must be ``0`` or ``index``. 

508 

509 Examples 

510 -------- 

511 >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]}) 

512 >>> df 

513 A B C 

514 0 1 4 7 

515 1 2 5 8 

516 2 3 6 9 

517 

518 >>> df.ewm(alpha=0.5).mean() 

519 A B C 

520 0 1.000000 4.000000 7.000000 

521 1 1.666667 4.666667 7.666667 

522 2 2.428571 5.428571 8.428571 

523 """ 

524 if callable(func): 

525 # GH#41700 BaseWindow.aggregate falls back to ``self.apply(...)``, 

526 # which ExponentialMovingWindow does not implement. 

527 raise NotImplementedError( 

528 f"{type(self).__name__}.aggregate does not support arbitrary " 

529 "callables; supported aggregations are mean, sum, std, var, " 

530 "cov, corr." 

531 ) 

532 return super().aggregate(func, *args, **kwargs) 

533 

534 agg = aggregate 

535 

536 def mean( 

537 self, 

538 numeric_only: bool = False, 

539 engine=None, 

540 engine_kwargs=None, 

541 ): 

542 """ 

543 Calculate the ewm (exponential weighted moment) mean. 

544 

545 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

546 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

547 :meth:`Series.ewm`. 

548 

549 Parameters 

550 ---------- 

551 numeric_only : bool, default False 

552 Include only float, int, boolean columns. 

553 

554 engine : str, default None 

555 * ``'cython'`` : Runs the operation through C-extensions from cython. 

556 * ``'numba'`` : Runs the operation through JIT compiled code from numba. 

557 * ``None`` : Defaults to ``'cython'`` or globally setting 

558 ``compute.use_numba`` 

559 

560 engine_kwargs : dict, default None 

561 * For ``'cython'`` engine, there are no accepted ``engine_kwargs`` 

562 * For ``'numba'`` engine, the engine can accept ``nogil`` 

563 and ``parallel`` dictionary keys. The values must either be ``True`` or 

564 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is 

565 ``{'nogil': False, 'parallel': False}`` 

566 

567 Returns 

568 ------- 

569 Series or DataFrame 

570 Return type is the same as the original object with ``np.float64`` dtype. 

571 

572 See Also 

573 -------- 

574 Series.ewm : Calling ewm with Series data. 

575 DataFrame.ewm : Calling ewm with DataFrames. 

576 Series.mean : Aggregating mean for Series. 

577 DataFrame.mean : Aggregating mean for DataFrame. 

578 

579 Notes 

580 ----- 

581 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba` for 

582 extended documentation and performance considerations for the Numba engine. 

583 

584 Examples 

585 -------- 

586 >>> ser = pd.Series([1, 2, 3, 4]) 

587 >>> ser.ewm(alpha=0.2).mean() 

588 0 1.000000 

589 1 1.555556 

590 2 2.147541 

591 3 2.775068 

592 dtype: float64 

593 """ 

594 if maybe_use_numba(engine): 

595 if self.method == "single": 

596 func = generate_numba_ewm_func 

597 else: 

598 func = generate_numba_ewm_table_func 

599 ewm_func = func( 

600 **get_jit_arguments(engine_kwargs), 

601 com=self._com, 

602 adjust=self.adjust, 

603 ignore_na=self.ignore_na, 

604 deltas=tuple(self._deltas), 

605 normalize=True, 

606 ) 

607 return self._apply(ewm_func, name="mean") 

608 elif engine in ("cython", None): 

609 if engine_kwargs is not None: 

610 raise ValueError("cython engine does not accept engine_kwargs") 

611 

612 deltas = None if self.times is None else self._deltas 

613 window_func = partial( 

614 window_aggregations.ewm, 

615 com=self._com, 

616 adjust=self.adjust, 

617 ignore_na=self.ignore_na, 

618 deltas=deltas, 

619 normalize=True, 

620 ) 

621 return self._apply(window_func, name="mean", numeric_only=numeric_only) 

622 else: 

623 raise ValueError("engine must be either 'numba' or 'cython'") 

624 

625 def sum( 

626 self, 

627 numeric_only: bool = False, 

628 engine=None, 

629 engine_kwargs=None, 

630 ): 

631 """ 

632 Calculate the ewm (exponential weighted moment) sum. 

633 

634 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

635 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

636 :meth:`Series.ewm`. 

637 

638 Parameters 

639 ---------- 

640 numeric_only : bool, default False 

641 Include only float, int, boolean columns. 

642 engine : str, default None 

643 * ``'cython'`` : Runs the operation through C-extensions from cython. 

644 * ``'numba'`` : Runs the operation through JIT compiled code from numba. 

645 * ``None`` : Defaults to ``'cython'`` or globally setting 

646 ``compute.use_numba`` 

647 engine_kwargs : dict, default None 

648 * For ``'cython'`` engine, there are no accepted ``engine_kwargs`` 

649 * For ``'numba'`` engine, the engine can accept ``nogil`` 

650 and ``parallel`` dictionary keys. The values must either be ``True`` or 

651 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is 

652 ``{'nogil': False, 'parallel': False}`` 

653 

654 Returns 

655 ------- 

656 Series or DataFrame 

657 Return type is the same as the original object with ``np.float64`` dtype. 

658 

659 See Also 

660 -------- 

661 Series.ewm : Calling ewm with Series data. 

662 DataFrame.ewm : Calling ewm with DataFrames. 

663 Series.sum : Aggregating sum for Series. 

664 DataFrame.sum : Aggregating sum for DataFrame. 

665 

666 Notes 

667 ----- 

668 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba` for extended 

669 documentation and performance considerations for the Numba engine. 

670 

671 Examples 

672 -------- 

673 >>> ser = pd.Series([1, 2, 3, 4]) 

674 >>> ser.ewm(alpha=0.2).sum() 

675 0 1.000 

676 1 2.800 

677 2 5.240 

678 3 8.192 

679 dtype: float64 

680 """ 

681 if not self.adjust: 

682 raise NotImplementedError("sum is not implemented with adjust=False") 

683 if self.times is not None: 

684 raise NotImplementedError("sum is not implemented with times") 

685 if maybe_use_numba(engine): 

686 if self.method == "single": 

687 func = generate_numba_ewm_func 

688 else: 

689 func = generate_numba_ewm_table_func 

690 ewm_func = func( 

691 **get_jit_arguments(engine_kwargs), 

692 com=self._com, 

693 adjust=self.adjust, 

694 ignore_na=self.ignore_na, 

695 deltas=tuple(self._deltas), 

696 normalize=False, 

697 ) 

698 return self._apply(ewm_func, name="sum") 

699 elif engine in ("cython", None): 

700 if engine_kwargs is not None: 

701 raise ValueError("cython engine does not accept engine_kwargs") 

702 

703 deltas = None if self.times is None else self._deltas 

704 window_func = partial( 

705 window_aggregations.ewm, 

706 com=self._com, 

707 adjust=self.adjust, 

708 ignore_na=self.ignore_na, 

709 deltas=deltas, 

710 normalize=False, 

711 ) 

712 return self._apply(window_func, name="sum", numeric_only=numeric_only) 

713 else: 

714 raise ValueError("engine must be either 'numba' or 'cython'") 

715 

716 def std(self, bias: bool = False, numeric_only: bool = False): 

717 """ 

718 Calculate the ewm (exponential weighted moment) standard deviation. 

719 

720 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

721 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

722 :meth:`Series.ewm`. 

723 

724 Parameters 

725 ---------- 

726 bias : bool, default False 

727 Use a standard estimation bias correction. 

728 numeric_only : bool, default False 

729 Include only float, int, boolean columns. 

730 

731 Returns 

732 ------- 

733 Series or DataFrame 

734 Return type is the same as the original object with ``np.float64`` dtype. 

735 

736 See Also 

737 -------- 

738 Series.ewm : Calling ewm with Series data. 

739 DataFrame.ewm : Calling ewm with DataFrames. 

740 Series.std : Aggregating std for Series. 

741 DataFrame.std : Aggregating std for DataFrame. 

742 

743 Examples 

744 -------- 

745 >>> ser = pd.Series([1, 2, 3, 4]) 

746 >>> ser.ewm(alpha=0.2).std() 

747 0 NaN 

748 1 0.707107 

749 2 0.995893 

750 3 1.277320 

751 dtype: float64 

752 """ 

753 if ( 

754 numeric_only 

755 and self._selected_obj.ndim == 1 

756 and not is_numeric_dtype(self._selected_obj.dtype) 

757 ): 

758 # Raise directly so error message says std instead of var 

759 raise NotImplementedError( 

760 f"{type(self).__name__}.std does not implement numeric_only" 

761 ) 

762 if self.times is not None: 

763 raise NotImplementedError("std is not implemented with times") 

764 return zsqrt(self.var(bias=bias, numeric_only=numeric_only)) 

765 

766 def var(self, bias: bool = False, numeric_only: bool = False): 

767 """ 

768 Calculate the ewm (exponential weighted moment) variance. 

769 

770 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

771 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

772 :meth:`Series.ewm`. 

773 

774 Parameters 

775 ---------- 

776 bias : bool, default False 

777 Use a standard estimation bias correction. 

778 numeric_only : bool, default False 

779 Include only float, int, boolean columns. 

780 

781 Returns 

782 ------- 

783 Series or DataFrame 

784 Return type is the same as the original object with ``np.float64`` dtype. 

785 

786 See Also 

787 -------- 

788 Series.ewm : Calling ewm with Series data. 

789 DataFrame.ewm : Calling ewm with DataFrames. 

790 Series.var : Aggregating var for Series. 

791 DataFrame.var : Aggregating var for DataFrame. 

792 

793 Examples 

794 -------- 

795 >>> ser = pd.Series([1, 2, 3, 4]) 

796 >>> ser.ewm(alpha=0.2).var() 

797 0 NaN 

798 1 0.500000 

799 2 0.991803 

800 3 1.631547 

801 dtype: float64 

802 """ 

803 if self.times is not None: 

804 raise NotImplementedError("var is not implemented with times") 

805 window_func = window_aggregations.ewmcov 

806 wfunc = partial( 

807 window_func, 

808 com=self._com, 

809 adjust=self.adjust, 

810 ignore_na=self.ignore_na, 

811 bias=bias, 

812 ) 

813 

814 def var_func(values, begin, end, min_periods): 

815 return wfunc(values, begin, end, min_periods, values) 

816 

817 return self._apply(var_func, name="var", numeric_only=numeric_only) 

818 

819 def cov( 

820 self, 

821 other: DataFrame | Series | None = None, 

822 pairwise: bool | None = None, 

823 bias: bool = False, 

824 numeric_only: bool = False, 

825 ): 

826 """ 

827 Calculate the ewm (exponential weighted moment) sample covariance. 

828 

829 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

830 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

831 :meth:`Series.ewm`. 

832 

833 Parameters 

834 ---------- 

835 other : Series or DataFrame , optional 

836 If not supplied then will default to self and produce pairwise 

837 output. 

838 pairwise : bool, default None 

839 If False then only matching columns between self and other will be 

840 used and the output will be a DataFrame. 

841 If True then all pairwise combinations will be calculated and the 

842 output will be a MultiIndex DataFrame in the case of DataFrame 

843 inputs. In the case of missing elements, only complete pairwise 

844 observations will be used. 

845 bias : bool, default False 

846 Use a standard estimation bias correction. 

847 numeric_only : bool, default False 

848 Include only float, int, boolean columns. 

849 

850 Returns 

851 ------- 

852 Series or DataFrame 

853 Return type is the same as the original object with ``np.float64`` dtype. 

854 

855 See Also 

856 -------- 

857 Series.ewm : Calling ewm with Series data. 

858 DataFrame.ewm : Calling ewm with DataFrames. 

859 Series.cov : Aggregating cov for Series. 

860 DataFrame.cov : Aggregating cov for DataFrame. 

861 

862 Examples 

863 -------- 

864 >>> ser1 = pd.Series([1, 2, 3, 4]) 

865 >>> ser2 = pd.Series([10, 11, 13, 16]) 

866 >>> ser1.ewm(alpha=0.2).cov(ser2) 

867 0 NaN 

868 1 0.500000 

869 2 1.524590 

870 3 3.408836 

871 dtype: float64 

872 """ 

873 if self.times is not None: 

874 raise NotImplementedError("cov is not implemented with times") 

875 

876 from pandas import Series 

877 

878 self._validate_numeric_only("cov", numeric_only) 

879 

880 def cov_func(x, y): 

881 x_array = self._prep_values(x) 

882 y_array = self._prep_values(y) 

883 window_indexer = self._get_window_indexer() 

884 min_periods = ( 

885 self.min_periods 

886 if self.min_periods is not None 

887 else window_indexer.window_size 

888 ) 

889 start, end = window_indexer.get_window_bounds( 

890 num_values=len(x_array), 

891 min_periods=min_periods, 

892 center=self.center, 

893 closed=self.closed, 

894 step=self.step, 

895 ) 

896 result = window_aggregations.ewmcov( 

897 x_array, 

898 start, 

899 end, 

900 # error: Argument 4 to "ewmcov" has incompatible type 

901 # "Optional[int]"; expected "int" 

902 self.min_periods, # type: ignore[arg-type] 

903 y_array, 

904 self._com, 

905 self.adjust, 

906 self.ignore_na, 

907 bias, 

908 ) 

909 return Series(result, index=x.index, name=x.name, copy=False) 

910 

911 return self._apply_pairwise( 

912 self._selected_obj, other, pairwise, cov_func, numeric_only 

913 ) 

914 

915 def corr( 

916 self, 

917 other: DataFrame | Series | None = None, 

918 pairwise: bool | None = None, 

919 numeric_only: bool = False, 

920 ): 

921 """ 

922 Calculate the ewm (exponential weighted moment) sample correlation. 

923 

924 The weighting is controlled by the ``com``, ``span``, ``halflife``, or 

925 ``alpha`` parameter specified when calling :meth:`DataFrame.ewm` or 

926 :meth:`Series.ewm`. 

927 

928 Parameters 

929 ---------- 

930 other : Series or DataFrame, optional 

931 If not supplied then will default to self and produce pairwise 

932 output. 

933 pairwise : bool, default None 

934 If False then only matching columns between self and other will be 

935 used and the output will be a DataFrame. 

936 If True then all pairwise combinations will be calculated and the 

937 output will be a MultiIndex DataFrame in the case of DataFrame 

938 inputs. In the case of missing elements, only complete pairwise 

939 observations will be used. 

940 numeric_only : bool, default False 

941 Include only float, int, boolean columns. 

942 

943 Returns 

944 ------- 

945 Series or DataFrame 

946 Return type is the same as the original object with ``np.float64`` dtype. 

947 

948 See Also 

949 -------- 

950 Series.ewm : Calling ewm with Series data. 

951 DataFrame.ewm : Calling ewm with DataFrames. 

952 Series.corr : Aggregating corr for Series. 

953 DataFrame.corr : Aggregating corr for DataFrame. 

954 

955 Examples 

956 -------- 

957 >>> ser1 = pd.Series([1, 2, 3, 4]) 

958 >>> ser2 = pd.Series([10, 11, 13, 16]) 

959 >>> ser1.ewm(alpha=0.2).corr(ser2) 

960 0 NaN 

961 1 1.000000 

962 2 0.982821 

963 3 0.977802 

964 dtype: float64 

965 """ 

966 if self.times is not None: 

967 raise NotImplementedError("corr is not implemented with times") 

968 

969 from pandas import Series 

970 

971 self._validate_numeric_only("corr", numeric_only) 

972 

973 def cov_func(x, y): 

974 x_array = self._prep_values(x) 

975 y_array = self._prep_values(y) 

976 window_indexer = self._get_window_indexer() 

977 min_periods = ( 

978 self.min_periods 

979 if self.min_periods is not None 

980 else window_indexer.window_size 

981 ) 

982 start, end = window_indexer.get_window_bounds( 

983 num_values=len(x_array), 

984 min_periods=min_periods, 

985 center=self.center, 

986 closed=self.closed, 

987 step=self.step, 

988 ) 

989 

990 def _cov(X, Y): 

991 return window_aggregations.ewmcov( 

992 X, 

993 start, 

994 end, 

995 min_periods, 

996 Y, 

997 self._com, 

998 self.adjust, 

999 self.ignore_na, 

1000 True, 

1001 ) 

1002 

1003 with np.errstate(all="ignore"): 

1004 cov = _cov(x_array, y_array) 

1005 x_var = _cov(x_array, x_array) 

1006 y_var = _cov(y_array, y_array) 

1007 result = cov / zsqrt(x_var * y_var) 

1008 return Series(result, index=x.index, name=x.name, copy=False) 

1009 

1010 return self._apply_pairwise( 

1011 self._selected_obj, other, pairwise, cov_func, numeric_only 

1012 ) 

1013 

1014 

1015@set_module("pandas.api.typing") 

1016class ExponentialMovingWindowGroupBy(BaseWindowGroupBy, ExponentialMovingWindow): 

1017 """ 

1018 Provide an exponential moving window groupby implementation. 

1019 """ 

1020 

1021 _attributes = ExponentialMovingWindow._attributes + BaseWindowGroupBy._attributes 

1022 

1023 def __init__(self, obj, *args, _grouper=None, **kwargs) -> None: 

1024 super().__init__(obj, *args, _grouper=_grouper, **kwargs) 

1025 

1026 if not obj.empty and self.times is not None: 

1027 # sort the times and recalculate the deltas according to the groups 

1028 groupby_order = np.concatenate(list(self._grouper.indices.values())) 

1029 self._deltas = _calculate_deltas( 

1030 self.times.take(groupby_order), 

1031 self.halflife, 

1032 ) 

1033 

1034 def _get_window_indexer(self) -> GroupByIndexer: 

1035 """ 

1036 Return an indexer class that will compute the window start and end bounds 

1037 

1038 Returns 

1039 ------- 

1040 GroupByIndexer 

1041 """ 

1042 window_indexer = GroupByIndexer( 

1043 groupby_indices=self._grouper.indices, 

1044 window_indexer=ExponentialMovingWindowIndexer, 

1045 ) 

1046 return window_indexer 

1047 

1048 

1049class OnlineExponentialMovingWindow(ExponentialMovingWindow): 

1050 def __init__( 

1051 self, 

1052 obj: NDFrame, 

1053 com: float | None = None, 

1054 span: float | None = None, 

1055 halflife: float | TimedeltaConvertibleTypes | None = None, 

1056 alpha: float | None = None, 

1057 min_periods: int | None = 0, 

1058 adjust: bool = True, 

1059 ignore_na: bool = False, 

1060 times: np.ndarray | NDFrame | None = None, 

1061 engine: str = "numba", 

1062 engine_kwargs: dict[str, bool] | None = None, 

1063 *, 

1064 selection=None, 

1065 ) -> None: 

1066 if times is not None: 

1067 raise NotImplementedError( 

1068 "times is not implemented with online operations." 

1069 ) 

1070 super().__init__( 

1071 obj=obj, 

1072 com=com, 

1073 span=span, 

1074 halflife=halflife, 

1075 alpha=alpha, 

1076 min_periods=min_periods, 

1077 adjust=adjust, 

1078 ignore_na=ignore_na, 

1079 times=times, 

1080 selection=selection, 

1081 ) 

1082 self._mean = EWMMeanState(self._com, self.adjust, self.ignore_na, obj.shape) 

1083 if maybe_use_numba(engine): 

1084 self.engine = engine 

1085 self.engine_kwargs = engine_kwargs 

1086 else: 

1087 raise ValueError("'numba' is the only supported engine") 

1088 

1089 def reset(self) -> None: 

1090 """ 

1091 Reset the state captured by `update` calls. 

1092 """ 

1093 self._mean.reset() 

1094 

1095 def aggregate(self, func=None, *args, **kwargs): 

1096 raise NotImplementedError("aggregate is not implemented.") 

1097 

1098 def std(self, bias: bool = False, *args, **kwargs): 

1099 raise NotImplementedError("std is not implemented.") 

1100 

1101 def corr( 

1102 self, 

1103 other: DataFrame | Series | None = None, 

1104 pairwise: bool | None = None, 

1105 numeric_only: bool = False, 

1106 ): 

1107 raise NotImplementedError("corr is not implemented.") 

1108 

1109 def cov( 

1110 self, 

1111 other: DataFrame | Series | None = None, 

1112 pairwise: bool | None = None, 

1113 bias: bool = False, 

1114 numeric_only: bool = False, 

1115 ): 

1116 raise NotImplementedError("cov is not implemented.") 

1117 

1118 def var(self, bias: bool = False, numeric_only: bool = False): 

1119 raise NotImplementedError("var is not implemented.") 

1120 

1121 def mean(self, *args, update=None, update_times=None, **kwargs): 

1122 """ 

1123 Calculate an online exponentially weighted mean. 

1124 

1125 Parameters 

1126 ---------- 

1127 update: DataFrame or Series, default None 

1128 New values to continue calculating the 

1129 exponentially weighted mean from the last values and weights. 

1130 Values should be float64 dtype. 

1131 

1132 ``update`` needs to be ``None`` the first time the 

1133 exponentially weighted mean is calculated. 

1134 

1135 update_times: Series or 1-D np.ndarray, default None 

1136 New times to continue calculating the 

1137 exponentially weighted mean from the last values and weights. 

1138 If ``None``, values are assumed to be evenly spaced 

1139 in time. 

1140 This feature is currently unsupported. 

1141 

1142 Returns 

1143 ------- 

1144 DataFrame or Series 

1145 

1146 Examples 

1147 -------- 

1148 >>> df = pd.DataFrame({"a": range(5), "b": range(5, 10)}) 

1149 >>> online_ewm = df.head(2).ewm(0.5).online() 

1150 >>> online_ewm.mean() 

1151 a b 

1152 0 0.00 5.00 

1153 1 0.75 5.75 

1154 >>> online_ewm.mean(update=df.tail(3)) 

1155 a b 

1156 2 1.615385 6.615385 

1157 3 2.550000 7.550000 

1158 4 3.520661 8.520661 

1159 >>> online_ewm.reset() 

1160 >>> online_ewm.mean() 

1161 a b 

1162 0 0.00 5.00 

1163 1 0.75 5.75 

1164 """ 

1165 result_kwargs = {} 

1166 is_frame = self._selected_obj.ndim == 2 

1167 if update_times is not None: 

1168 raise NotImplementedError("update_times is not implemented.") 

1169 if update is not None: 

1170 if self._mean.last_ewm is None: 

1171 raise ValueError( 

1172 "Must call mean with update=None first before passing update" 

1173 ) 

1174 result_from = 1 

1175 result_kwargs["index"] = update.index 

1176 if is_frame: 

1177 last_value = self._mean.last_ewm[np.newaxis, :] 

1178 result_kwargs["columns"] = update.columns 

1179 else: 

1180 last_value = self._mean.last_ewm 

1181 result_kwargs["name"] = update.name 

1182 np_array = np.concatenate((last_value, update.to_numpy())) 

1183 else: 

1184 result_from = 0 

1185 result_kwargs["index"] = self._selected_obj.index 

1186 if is_frame: 

1187 result_kwargs["columns"] = self._selected_obj.columns 

1188 else: 

1189 result_kwargs["name"] = self._selected_obj.name 

1190 np_array = self._selected_obj.astype(np.float64).to_numpy() 

1191 update_deltas = np.ones(max(len(np_array) - 1, 0), dtype=np.float64) 

1192 ewma_func = generate_online_numba_ewma_func( 

1193 **get_jit_arguments(self.engine_kwargs) 

1194 ) 

1195 result = self._mean.run_ewm( 

1196 np_array if is_frame else np_array[:, np.newaxis], 

1197 update_deltas, 

1198 self.min_periods, # type: ignore[arg-type] 

1199 ewma_func, 

1200 ) 

1201 if not is_frame: 

1202 result = result.squeeze() 

1203 result = result[result_from:] 

1204 result = self._selected_obj._constructor(result, **result_kwargs) 

1205 return result