Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/resample.py: 26%

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

720 statements  

1from __future__ import annotations 

2 

3import copy 

4from typing import ( 

5 TYPE_CHECKING, 

6 Concatenate, 

7 Literal, 

8 Self, 

9 cast, 

10 final, 

11 no_type_check, 

12 overload, 

13) 

14import warnings 

15 

16import numpy as np 

17 

18from pandas._libs import lib 

19from pandas._libs.tslibs import ( 

20 BaseOffset, 

21 IncompatibleFrequency, 

22 NaT, 

23 Period, 

24 Timedelta, 

25 Timestamp, 

26 to_offset, 

27) 

28from pandas._typing import NDFrameT 

29from pandas.errors import ( 

30 AbstractMethodError, 

31 Pandas4Warning, 

32) 

33from pandas.util._decorators import set_module 

34from pandas.util._exceptions import find_stack_level 

35 

36from pandas.core.dtypes.dtypes import ( 

37 ArrowDtype, 

38 PeriodDtype, 

39) 

40from pandas.core.dtypes.generic import ( 

41 ABCDataFrame, 

42 ABCSeries, 

43) 

44 

45import pandas.core.algorithms as algos 

46from pandas.core.apply import ResamplerWindowApply 

47from pandas.core.arrays import ArrowExtensionArray 

48from pandas.core.base import ( 

49 PandasObject, 

50 SelectionMixin, 

51) 

52from pandas.core.generic import ( 

53 NDFrame, 

54) 

55from pandas.core.groupby.groupby import ( 

56 BaseGroupBy, 

57 GroupBy, 

58 get_groupby, 

59) 

60from pandas.core.groupby.grouper import Grouper 

61from pandas.core.groupby.ops import BinGrouper 

62from pandas.core.indexes.api import MultiIndex 

63from pandas.core.indexes.base import Index 

64from pandas.core.indexes.datetimes import ( 

65 DatetimeIndex, 

66 date_range, 

67) 

68from pandas.core.indexes.period import ( 

69 PeriodIndex, 

70 period_range, 

71) 

72from pandas.core.indexes.timedeltas import ( 

73 TimedeltaIndex, 

74 timedelta_range, 

75) 

76from pandas.core.reshape.concat import concat 

77 

78from pandas.tseries.frequencies import ( 

79 is_subperiod, 

80 is_superperiod, 

81) 

82from pandas.tseries.offsets import ( 

83 Day, 

84 Tick, 

85) 

86 

87if TYPE_CHECKING: 

88 from collections.abc import ( 

89 Callable, 

90 Hashable, 

91 ) 

92 

93 from pandas._typing import ( 

94 Any, 

95 AnyArrayLike, 

96 Axis, 

97 FreqIndexT, 

98 Frequency, 

99 IndexLabel, 

100 InterpolateOptions, 

101 P, 

102 T, 

103 TimedeltaConvertibleTypes, 

104 TimeGrouperOrigin, 

105 TimestampConvertibleTypes, 

106 TimeUnit, 

107 npt, 

108 ) 

109 

110 from pandas import ( 

111 DataFrame, 

112 Series, 

113 ) 

114 from pandas.core.generic import NDFrame 

115 

116_shared_docs_kwargs: dict[str, str] = {} 

117 

118 

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

120class Resampler(BaseGroupBy, PandasObject): 

121 """ 

122 Class for resampling datetimelike data, a groupby-like operation. 

123 See aggregate, transform, and apply functions on this object. 

124 

125 It's easiest to use obj.resample(...) to use Resampler. 

126 

127 Parameters 

128 ---------- 

129 obj : Series or DataFrame 

130 groupby : TimeGrouper 

131 

132 Returns 

133 ------- 

134 a Resampler of the appropriate type 

135 

136 Notes 

137 ----- 

138 After resampling, see aggregate, apply, and transform functions. 

139 """ 

140 

141 _grouper: BinGrouper 

142 _timegrouper: TimeGrouper 

143 binner: DatetimeIndex | TimedeltaIndex | PeriodIndex # depends on subclass 

144 exclusions: frozenset[Hashable] = frozenset() # for SelectionMixin compat 

145 _internal_names_set = set({"obj", "ax", "_indexer"}) 

146 

147 # to the groupby descriptor 

148 _attributes = [ 

149 "freq", 

150 "closed", 

151 "label", 

152 "convention", 

153 "origin", 

154 "offset", 

155 ] 

156 

157 def __init__( 

158 self, 

159 obj: NDFrame, 

160 timegrouper: TimeGrouper, 

161 *, 

162 gpr_index: Index, 

163 group_keys: bool = False, 

164 selection=None, 

165 include_groups: bool = False, 

166 ) -> None: 

167 if include_groups: 

168 raise ValueError("include_groups=True is no longer allowed.") 

169 self._timegrouper = timegrouper 

170 self.keys = None 

171 self.sort = True 

172 self.group_keys = group_keys 

173 self.as_index = True 

174 

175 self.obj, self.ax, self._indexer = self._timegrouper._set_grouper( 

176 self._convert_obj(obj), sort=True, gpr_index=gpr_index 

177 ) 

178 self.binner, self._grouper = self._get_binner() 

179 self._selection = selection 

180 if self._timegrouper.key is not None: 

181 self.exclusions = frozenset([self._timegrouper.key]) 

182 else: 

183 self.exclusions = frozenset() 

184 

185 @final 

186 def __str__(self) -> str: 

187 """ 

188 Provide a nice str repr of our rolling object. 

189 """ 

190 attrs = ( 

191 f"{k}={getattr(self._timegrouper, k)}" 

192 for k in self._attributes 

193 if getattr(self._timegrouper, k, None) is not None 

194 ) 

195 return f"{type(self).__name__} [{', '.join(attrs)}]" 

196 

197 @final 

198 def __getattr__(self, attr: str): 

199 if attr in self._internal_names_set: 

200 return object.__getattribute__(self, attr) 

201 if attr in self._attributes: 

202 return getattr(self._timegrouper, attr) 

203 if attr in self.obj: 

204 return self[attr] 

205 

206 return object.__getattribute__(self, attr) 

207 

208 @final 

209 @property 

210 def _from_selection(self) -> bool: 

211 """ 

212 Is the resampling from a DataFrame column or MultiIndex level. 

213 """ 

214 # upsampling and PeriodIndex resampling do not work 

215 # with selection, this state used to catch and raise an error 

216 return self._timegrouper is not None and ( 

217 self._timegrouper.key is not None or self._timegrouper.level is not None 

218 ) 

219 

220 def _convert_obj(self, obj: NDFrameT) -> NDFrameT: 

221 """ 

222 Provide any conversions for the object in order to correctly handle. 

223 

224 Parameters 

225 ---------- 

226 obj : Series or DataFrame 

227 

228 Returns 

229 ------- 

230 Series or DataFrame 

231 """ 

232 return obj._consolidate() 

233 

234 def _get_binner_for_time(self): 

235 raise AbstractMethodError(self) 

236 

237 @final 

238 def _get_binner(self): 

239 """ 

240 Create the BinGrouper, assume that self.set_grouper(obj) 

241 has already been called. 

242 """ 

243 binner, bins, binlabels = self._get_binner_for_time() 

244 assert len(bins) == len(binlabels) 

245 if self._timegrouper._arrow_dtype is not None: 

246 binlabels = binlabels.astype(self._timegrouper._arrow_dtype) 

247 bin_grouper = BinGrouper(bins, binlabels, indexer=self._indexer) 

248 return binner, bin_grouper 

249 

250 @overload 

251 def pipe( 

252 self, 

253 func: Callable[Concatenate[Self, P], T], 

254 *args: P.args, 

255 **kwargs: P.kwargs, 

256 ) -> T: ... 

257 

258 @overload 

259 def pipe( 

260 self, 

261 func: tuple[Callable[..., T], str], 

262 *args: Any, 

263 **kwargs: Any, 

264 ) -> T: ... 

265 

266 @final 

267 def pipe( 

268 self, 

269 func: Callable[Concatenate[Self, P], T] | tuple[Callable[..., T], str], 

270 *args: Any, 

271 **kwargs: Any, 

272 ) -> T: 

273 """ 

274 Apply a ``func`` with arguments to this Resampler object and return its result. 

275 

276 Use `.pipe` when you want to improve readability by chaining together 

277 functions that expect Series, DataFrames, GroupBy or Resampler objects. 

278 Instead of writing 

279 

280 >>> h = lambda x, arg2, arg3: x + 1 - arg2 * arg3 

281 >>> g = lambda x, arg1: x * 5 / arg1 

282 >>> f = lambda x: x**4 

283 >>> df = pd.DataFrame([["a", 4], ["b", 5]], columns=["group", "value"]) 

284 >>> h(g(f(df.groupby("group")), arg1=1), arg2=2, arg3=3) # doctest: +SKIP 

285 

286 You can write 

287 

288 >>> ( 

289 ... df.groupby("group").pipe(f).pipe(g, arg1=1).pipe(h, arg2=2, arg3=3) 

290 ... ) # doctest: +SKIP 

291 

292 which is much more readable. 

293 

294 Parameters 

295 ---------- 

296 func : callable or tuple of (callable, str) 

297 Function to apply to this Resampler object or, alternatively, 

298 a `(callable, data_keyword)` tuple where `data_keyword` is a 

299 string indicating the keyword of `callable` that expects the 

300 Resampler object. 

301 *args : iterable, optional 

302 Positional arguments passed into `func`. 

303 **kwargs : dict, optional 

304 A dictionary of keyword arguments passed into `func`. 

305 

306 Returns 

307 ------- 

308 any 

309 The result of applying ``func`` to the Resampler object. 

310 

311 See Also 

312 -------- 

313 Series.pipe : Apply a function with arguments to a series. 

314 DataFrame.pipe: Apply a function with arguments to a dataframe. 

315 apply : Apply function to each group instead of to the 

316 full Resampler object. 

317 

318 Notes 

319 ----- 

320 See more `here 

321 <https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#piping-function-calls>`_ 

322 

323 Examples 

324 -------- 

325 >>> df = pd.DataFrame( 

326 ... {"A": [1, 2, 3, 4]}, index=pd.date_range("2012-08-02", periods=4) 

327 ... ) 

328 >>> df 

329 A 

330 2012-08-02 1 

331 2012-08-03 2 

332 2012-08-04 3 

333 2012-08-05 4 

334 

335 To get the difference between each 2-day period's maximum and minimum 

336 value in one pass, you can do 

337 

338 >>> df.resample("2D").pipe(lambda x: x.max() - x.min()) 

339 A 

340 2012-08-02 1 

341 2012-08-04 1 

342 """ 

343 return super().pipe(func, *args, **kwargs) 

344 

345 @final 

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

347 """ 

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

349 

350 Parameters 

351 ---------- 

352 func : function, str, list or dict 

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

354 work when passed a DataFrame or when passed to DataFrame.apply. 

355 

356 Accepted combinations are: 

357 

358 - function 

359 - string function name 

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

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

362 *args 

363 Positional arguments to pass to `func`. 

364 **kwargs 

365 Keyword arguments to pass to `func`. 

366 

367 Returns 

368 ------- 

369 scalar, Series or DataFrame 

370 

371 The return can be: 

372 

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

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

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

376 

377 See Also 

378 -------- 

379 DataFrame.groupby.aggregate : Aggregate using callable, string, dict, 

380 or list of string/callables. 

381 DataFrame.resample.transform : Transforms the Series on each group 

382 based on the given function. 

383 DataFrame.aggregate: Aggregate using one or more 

384 operations over the specified axis. 

385 

386 Notes 

387 ----- 

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

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

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

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

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

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

394 

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

396 

397 Functions that mutate the passed object can produce unexpected 

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

399 for more details. 

400 

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

402 

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

404 

405 Examples 

406 -------- 

407 >>> s = pd.Series( 

408 ... [1, 2, 3, 4, 5], index=pd.date_range("20130101", periods=5, freq="s") 

409 ... ) 

410 >>> s 

411 2013-01-01 00:00:00 1 

412 2013-01-01 00:00:01 2 

413 2013-01-01 00:00:02 3 

414 2013-01-01 00:00:03 4 

415 2013-01-01 00:00:04 5 

416 Freq: s, dtype: int64 

417 

418 >>> r = s.resample("2s") 

419 

420 >>> r.agg("sum") 

421 2013-01-01 00:00:00 3 

422 2013-01-01 00:00:02 7 

423 2013-01-01 00:00:04 5 

424 Freq: 2s, dtype: int64 

425 

426 >>> r.agg(["sum", "mean", "max"]) 

427 sum mean max 

428 2013-01-01 00:00:00 3 1.5 2 

429 2013-01-01 00:00:02 7 3.5 4 

430 2013-01-01 00:00:04 5 5.0 5 

431 

432 >>> r.agg({"result": lambda x: x.mean() / x.std(), "total": "sum"}) 

433 result total 

434 2013-01-01 00:00:00 2.121320 3 

435 2013-01-01 00:00:02 4.949747 7 

436 2013-01-01 00:00:04 NaN 5 

437 

438 >>> r.agg(average="mean", total="sum") 

439 average total 

440 2013-01-01 00:00:00 1.5 3 

441 2013-01-01 00:00:02 3.5 7 

442 2013-01-01 00:00:04 5.0 5 

443 """ 

444 result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg() 

445 if result is None: 

446 how = func 

447 result = self._groupby_and_aggregate(how, *args, **kwargs) 

448 

449 return result 

450 

451 agg = aggregate 

452 apply = aggregate 

453 

454 @final 

455 def transform(self, arg, *args, **kwargs): 

456 """ 

457 Call function producing a like-indexed Series on each group. 

458 

459 Return a Series with the transformed values. 

460 

461 Parameters 

462 ---------- 

463 arg : function 

464 To apply to each group. Should return a Series with the same index. 

465 *args, **kwargs 

466 Additional arguments and keywords. 

467 

468 Returns 

469 ------- 

470 Series 

471 A Series with the transformed values, maintaining the same index as 

472 the original object. 

473 

474 See Also 

475 -------- 

476 core.resample.Resampler.apply : Apply a function along each group. 

477 core.resample.Resampler.aggregate : Aggregate using one or more operations 

478 over the specified axis. 

479 

480 Examples 

481 -------- 

482 >>> s = pd.Series([1, 2], index=pd.date_range("20180101", periods=2, freq="1h")) 

483 >>> s 

484 2018-01-01 00:00:00 1 

485 2018-01-01 01:00:00 2 

486 Freq: h, dtype: int64 

487 

488 >>> resampled = s.resample("15min") 

489 >>> resampled.transform(lambda x: (x - x.mean()) / x.std()) 

490 2018-01-01 00:00:00 NaN 

491 2018-01-01 01:00:00 NaN 

492 Freq: h, dtype: float64 

493 """ 

494 return self._selected_obj.groupby(self._timegrouper).transform( 

495 arg, *args, **kwargs 

496 ) 

497 

498 def _downsample(self, how, **kwargs): 

499 raise AbstractMethodError(self) 

500 

501 def _upsample(self, f, limit: int | None = None, fill_value=None): 

502 raise AbstractMethodError(self) 

503 

504 def _gotitem(self, key, ndim: int, subset=None): 

505 """ 

506 Sub-classes to define. Return a sliced object. 

507 

508 Parameters 

509 ---------- 

510 key : string / list of selections 

511 ndim : {1, 2} 

512 requested ndim of result 

513 subset : object, default None 

514 subset to act on 

515 """ 

516 grouper = self._grouper 

517 if subset is None: 

518 subset = self.obj 

519 if key is not None: 

520 subset = subset[key] 

521 else: 

522 # reached via Apply.agg_dict_like with selection=None and ndim=1 

523 assert subset.ndim == 1 

524 if ndim == 1: 

525 assert subset.ndim == 1 

526 

527 grouped = get_groupby( 

528 subset, by=None, grouper=grouper, group_keys=self.group_keys 

529 ) 

530 return grouped 

531 

532 def _groupby_and_aggregate(self, how, *args, **kwargs): 

533 """ 

534 Re-evaluate the obj with a groupby aggregation. 

535 """ 

536 grouper = self._grouper 

537 

538 # Excludes `on` column when provided 

539 obj = self._obj_with_exclusions 

540 

541 grouped = get_groupby(obj, by=None, grouper=grouper, group_keys=self.group_keys) 

542 

543 try: 

544 if callable(how): 

545 # TODO: test_resample_apply_with_additional_args fails if we go 

546 # through the non-lambda path, not clear that it should. 

547 func = lambda x: how(x, *args, **kwargs) 

548 result = grouped.aggregate(func) 

549 else: 

550 result = grouped.aggregate(how, *args, **kwargs) 

551 except (AttributeError, KeyError): 

552 # we have a non-reducing function; try to evaluate 

553 # alternatively we want to evaluate only a column of the input 

554 

555 # test_apply_to_one_column_of_df the function being applied references 

556 # a DataFrame column, but aggregate_item_by_item operates column-wise 

557 # on Series, raising AttributeError or KeyError 

558 # (depending on whether the column lookup uses getattr/__getitem__) 

559 result = grouped.apply(how, *args, **kwargs) 

560 

561 except ValueError as err: 

562 if "Must produce aggregated value" in str(err): 

563 # raised in _aggregate_named 

564 # see test_apply_without_aggregation, test_apply_with_mutated_index 

565 pass 

566 else: 

567 raise 

568 

569 # we have a non-reducing function 

570 # try to evaluate 

571 result = grouped.apply(how, *args, **kwargs) 

572 

573 return self._wrap_result(result) 

574 

575 @final 

576 def _get_resampler_for_grouping( 

577 self, 

578 groupby: GroupBy, 

579 key, 

580 ): 

581 """ 

582 Return the correct class for resampling with groupby. 

583 """ 

584 return self._resampler_for_grouping( 

585 groupby=groupby, 

586 key=key, 

587 parent=self, 

588 ) 

589 

590 def _wrap_result(self, result): 

591 """ 

592 Potentially wrap any results. 

593 """ 

594 if isinstance(result, ABCSeries) and self._selection is not None: 

595 result.name = self._selection 

596 

597 if isinstance(result, ABCSeries) and result.empty: 

598 # When index is all NaT, result is empty but index is not 

599 obj = self.obj 

600 result.index = _asfreq_compat(obj.index[:0], freq=self.freq) 

601 result.name = getattr(obj, "name", None) 

602 

603 if self._timegrouper._arrow_dtype is not None: 

604 result.index = result.index.astype(self._timegrouper._arrow_dtype) 

605 result.index.name = self.obj.index.name 

606 

607 return result 

608 

609 @final 

610 def ffill(self, limit: int | None = None): 

611 """ 

612 Forward fill the values. 

613 

614 This method fills missing values by propagating the last valid 

615 observation forward, up to the next valid observation. It is commonly 

616 used in time series analysis when resampling data to a higher frequency 

617 (upsampling) and filling gaps in the resampled output. 

618 

619 Parameters 

620 ---------- 

621 limit : int, optional 

622 Limit of how many values to fill. 

623 

624 Returns 

625 ------- 

626 Series 

627 The resampled data with missing values filled forward. 

628 

629 See Also 

630 -------- 

631 Series.fillna: Fill NA/NaN values using the specified method. 

632 DataFrame.fillna: Fill NA/NaN values using the specified method. 

633 

634 Examples 

635 -------- 

636 Here we only create a ``Series``. 

637 

638 >>> ser = pd.Series( 

639 ... [1, 2, 3, 4], 

640 ... index=pd.DatetimeIndex( 

641 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

642 ... ), 

643 ... ) 

644 >>> ser 

645 2023-01-01 1 

646 2023-01-15 2 

647 2023-02-01 3 

648 2023-02-15 4 

649 dtype: int64 

650 

651 Example for ``ffill`` with downsampling (we have fewer dates after resampling): 

652 

653 >>> ser.resample("MS").ffill() 

654 2023-01-01 1 

655 2023-02-01 3 

656 Freq: MS, dtype: int64 

657 

658 Example for ``ffill`` with upsampling (fill the new dates with 

659 the previous value): 

660 

661 >>> ser.resample("W").ffill() 

662 2023-01-01 1 

663 2023-01-08 1 

664 2023-01-15 2 

665 2023-01-22 2 

666 2023-01-29 2 

667 2023-02-05 3 

668 2023-02-12 3 

669 2023-02-19 4 

670 Freq: W-SUN, dtype: int64 

671 

672 With upsampling and limiting (only fill the first new date with the 

673 previous value): 

674 

675 >>> ser.resample("W").ffill(limit=1) 

676 2023-01-01 1.0 

677 2023-01-08 1.0 

678 2023-01-15 2.0 

679 2023-01-22 2.0 

680 2023-01-29 NaN 

681 2023-02-05 3.0 

682 2023-02-12 NaN 

683 2023-02-19 4.0 

684 Freq: W-SUN, dtype: float64 

685 """ 

686 return self._upsample("ffill", limit=limit) 

687 

688 @final 

689 def nearest(self, limit: int | None = None): 

690 """ 

691 Resample by using the nearest value. 

692 

693 When resampling data, missing values may appear (e.g., when the 

694 resampling frequency is higher than the original frequency). 

695 The `nearest` method will replace ``NaN`` values that appeared in 

696 the resampled data with the value from the nearest member of the 

697 sequence, based on the index value. 

698 Missing values that existed in the original data will not be modified. 

699 If `limit` is given, fill only this many values in each direction for 

700 each of the original values. 

701 

702 Parameters 

703 ---------- 

704 limit : int, optional 

705 Limit of how many values to fill. 

706 

707 Returns 

708 ------- 

709 Series or DataFrame 

710 An upsampled Series or DataFrame with ``NaN`` values filled with 

711 their nearest value. 

712 

713 See Also 

714 -------- 

715 bfill : Backward fill the new missing values in the resampled data. 

716 ffill : Forward fill ``NaN`` values. 

717 

718 Examples 

719 -------- 

720 >>> s = pd.Series([1, 2], index=pd.date_range("20180101", periods=2, freq="1h")) 

721 >>> s 

722 2018-01-01 00:00:00 1 

723 2018-01-01 01:00:00 2 

724 Freq: h, dtype: int64 

725 

726 >>> s.resample("15min").nearest() 

727 2018-01-01 00:00:00 1 

728 2018-01-01 00:15:00 1 

729 2018-01-01 00:30:00 2 

730 2018-01-01 00:45:00 2 

731 2018-01-01 01:00:00 2 

732 Freq: 15min, dtype: int64 

733 

734 Limit the number of upsampled values imputed by the nearest: 

735 

736 >>> s.resample("15min").nearest(limit=1) 

737 2018-01-01 00:00:00 1.0 

738 2018-01-01 00:15:00 1.0 

739 2018-01-01 00:30:00 NaN 

740 2018-01-01 00:45:00 2.0 

741 2018-01-01 01:00:00 2.0 

742 Freq: 15min, dtype: float64 

743 """ 

744 return self._upsample("nearest", limit=limit) 

745 

746 @final 

747 def bfill(self, limit: int | None = None): 

748 """ 

749 Backward fill the new missing values in the resampled data. 

750 

751 In statistics, imputation is the process of replacing missing data with 

752 substituted values [1]_. When resampling data, missing values may 

753 appear (e.g., when the resampling frequency is higher than the original 

754 frequency). The backward fill will replace NaN values that appeared in 

755 the resampled data with the next value in the original sequence. 

756 Missing values that existed in the original data will not be modified. 

757 

758 Parameters 

759 ---------- 

760 limit : int, optional 

761 Limit of how many values to fill. 

762 

763 Returns 

764 ------- 

765 Series, DataFrame 

766 An upsampled Series or DataFrame with backward filled NaN values. 

767 

768 See Also 

769 -------- 

770 nearest : Fill NaN values with nearest neighbor starting from center. 

771 ffill : Forward fill NaN values. 

772 Series.fillna : Fill NaN values in the Series using the 

773 specified method, which can be 'backfill'. 

774 DataFrame.fillna : Fill NaN values in the DataFrame using the 

775 specified method, which can be 'backfill'. 

776 

777 References 

778 ---------- 

779 .. [1] https://en.wikipedia.org/wiki/Imputation_%28statistics%29 

780 

781 Examples 

782 -------- 

783 Resampling a Series: 

784 

785 >>> s = pd.Series( 

786 ... [1, 2, 3], index=pd.date_range("20180101", periods=3, freq="h") 

787 ... ) 

788 >>> s 

789 2018-01-01 00:00:00 1 

790 2018-01-01 01:00:00 2 

791 2018-01-01 02:00:00 3 

792 Freq: h, dtype: int64 

793 

794 >>> s.resample("30min").bfill() 

795 2018-01-01 00:00:00 1 

796 2018-01-01 00:30:00 2 

797 2018-01-01 01:00:00 2 

798 2018-01-01 01:30:00 3 

799 2018-01-01 02:00:00 3 

800 Freq: 30min, dtype: int64 

801 

802 >>> s.resample("15min").bfill(limit=2) 

803 2018-01-01 00:00:00 1.0 

804 2018-01-01 00:15:00 NaN 

805 2018-01-01 00:30:00 2.0 

806 2018-01-01 00:45:00 2.0 

807 2018-01-01 01:00:00 2.0 

808 2018-01-01 01:15:00 NaN 

809 2018-01-01 01:30:00 3.0 

810 2018-01-01 01:45:00 3.0 

811 2018-01-01 02:00:00 3.0 

812 Freq: 15min, dtype: float64 

813 

814 Resampling a DataFrame that has missing values: 

815 

816 >>> df = pd.DataFrame( 

817 ... {"a": [2, np.nan, 6], "b": [1, 3, 5]}, 

818 ... index=pd.date_range("20180101", periods=3, freq="h"), 

819 ... ) 

820 >>> df 

821 a b 

822 2018-01-01 00:00:00 2.0 1 

823 2018-01-01 01:00:00 NaN 3 

824 2018-01-01 02:00:00 6.0 5 

825 

826 >>> df.resample("30min").bfill() 

827 a b 

828 2018-01-01 00:00:00 2.0 1 

829 2018-01-01 00:30:00 NaN 3 

830 2018-01-01 01:00:00 NaN 3 

831 2018-01-01 01:30:00 6.0 5 

832 2018-01-01 02:00:00 6.0 5 

833 

834 >>> df.resample("15min").bfill(limit=2) 

835 a b 

836 2018-01-01 00:00:00 2.0 1.0 

837 2018-01-01 00:15:00 NaN NaN 

838 2018-01-01 00:30:00 NaN 3.0 

839 2018-01-01 00:45:00 NaN 3.0 

840 2018-01-01 01:00:00 NaN 3.0 

841 2018-01-01 01:15:00 NaN NaN 

842 2018-01-01 01:30:00 6.0 5.0 

843 2018-01-01 01:45:00 6.0 5.0 

844 2018-01-01 02:00:00 6.0 5.0 

845 """ 

846 return self._upsample("bfill", limit=limit) 

847 

848 @final 

849 def interpolate( 

850 self, 

851 method: InterpolateOptions = "linear", 

852 *, 

853 axis: Axis = 0, 

854 limit: int | None = None, 

855 limit_direction: Literal["forward", "backward", "both"] = "forward", 

856 limit_area=None, 

857 **kwargs, 

858 ): 

859 """ 

860 Interpolate values between target timestamps according to different methods. 

861 

862 The original index is first reindexed to target timestamps 

863 (see :meth:`core.resample.Resampler.asfreq`), 

864 then the interpolation of ``NaN`` values via :meth:`DataFrame.interpolate` 

865 happens. 

866 

867 Parameters 

868 ---------- 

869 method : str, default 'linear' 

870 Interpolation technique to use. One of: 

871 

872 * 'linear': Ignore the index and treat the values as equally 

873 spaced. This is the only method supported on MultiIndexes. 

874 * 'time': Works on daily and higher resolution data to interpolate 

875 given length of interval. 

876 * 'index', 'values': use the actual numerical values of the index. 

877 * 'pad': Fill in NaNs using existing values. 

878 * 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 

879 'barycentric', 'polynomial': Passed to 

880 `scipy.interpolate.interp1d`, whereas 'spline' is passed to 

881 `scipy.interpolate.UnivariateSpline`. These methods use the numerical 

882 values of the index. Both 'polynomial' and 'spline' require that 

883 you also specify an `order` (int), e.g. 

884 ``df.interpolate(method='polynomial', order=5)``. Note that, 

885 `slinear` method in Pandas refers to the Scipy first order `spline` 

886 instead of Pandas first order `spline`. 

887 * 'krogh', 'piecewise_polynomial', 'spline', 'pchip', 'akima', 

888 'cubicspline': Wrappers around the SciPy interpolation methods of 

889 similar names. See `Notes`. 

890 * 'from_derivatives': Refers to 

891 `scipy.interpolate.BPoly.from_derivatives`. 

892 

893 axis : {0 or 'index', 1 or 'columns', None}, default None 

894 Axis to interpolate along. For `Series` this parameter is unused 

895 and defaults to 0. 

896 limit : int, optional 

897 Maximum number of consecutive NaNs to fill. Must be greater than 

898 0. 

899 limit_direction : {'forward', 'backward', 'both'}, Optional 

900 Consecutive NaNs will be filled in this direction. 

901 

902 limit_area : {`None`, 'inside', 'outside'}, default None 

903 If limit is specified, consecutive NaNs will be filled with this 

904 restriction. 

905 

906 * ``None``: No fill restriction. 

907 * 'inside': Only fill NaNs surrounded by valid values 

908 (interpolate). 

909 * 'outside': Only fill NaNs outside valid values (extrapolate). 

910 

911 **kwargs : optional 

912 Keyword arguments to pass on to the interpolating function. 

913 

914 Returns 

915 ------- 

916 DataFrame or Series 

917 Interpolated values at the specified freq. 

918 

919 See Also 

920 -------- 

921 core.resample.Resampler.asfreq: Return the values at the new freq, 

922 essentially a reindex. 

923 DataFrame.interpolate: Fill NaN values using an interpolation method. 

924 DataFrame.bfill : Backward fill NaN values in the resampled data. 

925 DataFrame.ffill : Forward fill NaN values. 

926 

927 Notes 

928 ----- 

929 For high-frequent or non-equidistant time-series with timestamps 

930 the reindexing followed by interpolation may lead to information loss 

931 as shown in the last example. 

932 

933 Examples 

934 -------- 

935 

936 >>> start = "2023-03-01T07:00:00" 

937 >>> timesteps = pd.date_range(start, periods=5, freq="s") 

938 >>> series = pd.Series(data=[1, -1, 2, 1, 3], index=timesteps) 

939 >>> series 

940 2023-03-01 07:00:00 1 

941 2023-03-01 07:00:01 -1 

942 2023-03-01 07:00:02 2 

943 2023-03-01 07:00:03 1 

944 2023-03-01 07:00:04 3 

945 Freq: s, dtype: int64 

946 

947 Downsample the dataframe to 0.5Hz by providing the period time of 2s. 

948 

949 >>> series.resample("2s").interpolate("linear") 

950 2023-03-01 07:00:00 1 

951 2023-03-01 07:00:02 2 

952 2023-03-01 07:00:04 3 

953 Freq: 2s, dtype: int64 

954 

955 Upsample the dataframe to 2Hz by providing the period time of 500ms. 

956 

957 >>> series.resample("500ms").interpolate("linear") 

958 2023-03-01 07:00:00.000 1.0 

959 2023-03-01 07:00:00.500 0.0 

960 2023-03-01 07:00:01.000 -1.0 

961 2023-03-01 07:00:01.500 0.5 

962 2023-03-01 07:00:02.000 2.0 

963 2023-03-01 07:00:02.500 1.5 

964 2023-03-01 07:00:03.000 1.0 

965 2023-03-01 07:00:03.500 2.0 

966 2023-03-01 07:00:04.000 3.0 

967 Freq: 500ms, dtype: float64 

968 

969 Internal reindexing with ``asfreq()`` prior to interpolation leads to 

970 an interpolated timeseries on the basis of the reindexed timestamps 

971 (anchors). It is assured that all available datapoints from original 

972 series become anchors, so it also works for resampling-cases that lead 

973 to non-aligned timestamps, as in the following example: 

974 

975 >>> series.resample("400ms").interpolate("linear") 

976 2023-03-01 07:00:00.000 1.000000 

977 2023-03-01 07:00:00.400 0.333333 

978 2023-03-01 07:00:00.800 -0.333333 

979 2023-03-01 07:00:01.200 0.000000 

980 2023-03-01 07:00:01.600 1.000000 

981 2023-03-01 07:00:02.000 2.000000 

982 2023-03-01 07:00:02.400 1.666667 

983 2023-03-01 07:00:02.800 1.333333 

984 2023-03-01 07:00:03.200 1.666667 

985 2023-03-01 07:00:03.600 2.333333 

986 2023-03-01 07:00:04.000 3.000000 

987 Freq: 400ms, dtype: float64 

988 

989 Note that the series correctly decreases between two anchors 

990 ``07:00:00`` and ``07:00:02``. 

991 """ 

992 if "inplace" in kwargs: 

993 # GH#58690 

994 warnings.warn( 

995 f"The 'inplace' keyword in {type(self).__name__}.interpolate " 

996 "is deprecated and will be removed in a future version. " 

997 "resample(...).interpolate is never inplace.", 

998 Pandas4Warning, 

999 stacklevel=find_stack_level(), 

1000 ) 

1001 inplace = kwargs.pop("inplace") 

1002 if inplace: 

1003 raise ValueError("Cannot interpolate inplace on a resampled object.") 

1004 

1005 result = self._upsample("asfreq") 

1006 

1007 # If the original data has timestamps which are not aligned with the 

1008 # target timestamps, we need to add those points back to the data frame 

1009 # that is supposed to be interpolated. This does not work with 

1010 # PeriodIndex, so we skip this case. GH#21351 

1011 obj = self._selected_obj 

1012 is_period_index = isinstance(obj.index, PeriodIndex) 

1013 

1014 # Skip this step for PeriodIndex 

1015 if not is_period_index: 

1016 final_index = result.index 

1017 if isinstance(final_index, MultiIndex): 

1018 raise NotImplementedError( 

1019 "Direct interpolation of MultiIndex data frames is not " 

1020 "supported. If you tried to resample and interpolate on a " 

1021 "grouped data frame, please use:\n" 

1022 "`df.groupby(...).apply(lambda x: x.resample(...)." 

1023 "interpolate(...))`" 

1024 "\ninstead, as resampling and interpolation has to be " 

1025 "performed for each group independently." 

1026 ) 

1027 

1028 missing_data_points_index = obj.index.difference(final_index) 

1029 if len(missing_data_points_index) > 0: 

1030 result = concat( 

1031 [result, obj.loc[missing_data_points_index]] 

1032 ).sort_index() 

1033 

1034 result_interpolated = result.interpolate( 

1035 method=method, 

1036 axis=axis, 

1037 limit=limit, 

1038 inplace=False, 

1039 limit_direction=limit_direction, 

1040 limit_area=limit_area, 

1041 **kwargs, 

1042 ) 

1043 

1044 # No further steps if the original data has a PeriodIndex 

1045 if is_period_index: 

1046 return result_interpolated 

1047 

1048 # Make sure that original data points which do not align with the 

1049 # resampled index are removed 

1050 result_interpolated = result_interpolated.loc[final_index] 

1051 

1052 # Make sure frequency indexes are preserved 

1053 result_interpolated.index = final_index 

1054 return result_interpolated 

1055 

1056 @final 

1057 def asfreq(self, fill_value=None): 

1058 """ 

1059 Return the values at the new freq, essentially a reindex. 

1060 

1061 Parameters 

1062 ---------- 

1063 fill_value : scalar, optional 

1064 Value to use for missing values, applied during upsampling (note 

1065 this does not fill NaNs that already were present). 

1066 

1067 Returns 

1068 ------- 

1069 DataFrame or Series 

1070 Values at the specified freq. 

1071 

1072 See Also 

1073 -------- 

1074 Series.asfreq: Convert TimeSeries to specified frequency. 

1075 DataFrame.asfreq: Convert TimeSeries to specified frequency. 

1076 

1077 Examples 

1078 -------- 

1079 

1080 >>> ser = pd.Series( 

1081 ... [1, 2, 3, 4], 

1082 ... index=pd.DatetimeIndex( 

1083 ... ["2023-01-01", "2023-01-31", "2023-02-01", "2023-02-28"] 

1084 ... ), 

1085 ... ) 

1086 >>> ser 

1087 2023-01-01 1 

1088 2023-01-31 2 

1089 2023-02-01 3 

1090 2023-02-28 4 

1091 dtype: int64 

1092 >>> ser.resample("MS").asfreq() 

1093 2023-01-01 1 

1094 2023-02-01 3 

1095 Freq: MS, dtype: int64 

1096 """ 

1097 return self._upsample("asfreq", fill_value=fill_value) 

1098 

1099 @final 

1100 def sum( 

1101 self, 

1102 numeric_only: bool = False, 

1103 min_count: int = 0, 

1104 ): 

1105 """ 

1106 Compute sum of group values. 

1107 

1108 This method provides a simple way to compute the sum of values within each 

1109 resampled group, particularly useful for aggregating time-based data into 

1110 daily, monthly, or yearly sums. 

1111 

1112 Parameters 

1113 ---------- 

1114 numeric_only : bool, default False 

1115 Include only float, int, boolean columns. 

1116 

1117 .. versionchanged:: 2.0.0 

1118 

1119 numeric_only no longer accepts ``None``. 

1120 

1121 min_count : int, default 0 

1122 The required number of valid values to perform the operation. If fewer 

1123 than ``min_count`` non-NA values are present the result will be NA. 

1124 

1125 Returns 

1126 ------- 

1127 Series or DataFrame 

1128 Computed sum of values within each group. 

1129 

1130 See Also 

1131 -------- 

1132 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1133 core.resample.Resampler.count : Compute count of group, excluding missing 

1134 values. 

1135 DataFrame.resample : Resample time-series data. 

1136 Series.sum : Return the sum of the values over the requested axis. 

1137 

1138 Examples 

1139 -------- 

1140 >>> ser = pd.Series( 

1141 ... [1, 2, 3, 4], 

1142 ... index=pd.DatetimeIndex( 

1143 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1144 ... ), 

1145 ... ) 

1146 >>> ser 

1147 2023-01-01 1 

1148 2023-01-15 2 

1149 2023-02-01 3 

1150 2023-02-15 4 

1151 dtype: int64 

1152 >>> ser.resample("MS").sum() 

1153 2023-01-01 3 

1154 2023-02-01 7 

1155 Freq: MS, dtype: int64 

1156 """ 

1157 return self._downsample("sum", numeric_only=numeric_only, min_count=min_count) 

1158 

1159 @final 

1160 def prod( 

1161 self, 

1162 numeric_only: bool = False, 

1163 min_count: int = 0, 

1164 ): 

1165 """ 

1166 Compute prod of group values. 

1167 

1168 Parameters 

1169 ---------- 

1170 numeric_only : bool, default False 

1171 Include only float, int, boolean columns. 

1172 

1173 .. versionchanged:: 2.0.0 

1174 

1175 numeric_only no longer accepts ``None``. 

1176 

1177 min_count : int, default 0 

1178 The required number of valid values to perform the operation. If fewer 

1179 than ``min_count`` non-NA values are present the result will be NA. 

1180 

1181 Returns 

1182 ------- 

1183 Series or DataFrame 

1184 Computed prod of values within each group. 

1185 

1186 See Also 

1187 -------- 

1188 core.resample.Resampler.sum : Compute sum of groups, excluding missing values. 

1189 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1190 core.resample.Resampler.median : Compute median of groups, excluding missing 

1191 values. 

1192 

1193 Examples 

1194 -------- 

1195 >>> ser = pd.Series( 

1196 ... [1, 2, 3, 4], 

1197 ... index=pd.DatetimeIndex( 

1198 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1199 ... ), 

1200 ... ) 

1201 >>> ser 

1202 2023-01-01 1 

1203 2023-01-15 2 

1204 2023-02-01 3 

1205 2023-02-15 4 

1206 dtype: int64 

1207 >>> ser.resample("MS").prod() 

1208 2023-01-01 2 

1209 2023-02-01 12 

1210 Freq: MS, dtype: int64 

1211 """ 

1212 return self._downsample("prod", numeric_only=numeric_only, min_count=min_count) 

1213 

1214 @final 

1215 def min( 

1216 self, 

1217 numeric_only: bool = False, 

1218 min_count: int = 0, 

1219 ): 

1220 """ 

1221 Compute min value of group. 

1222 

1223 Parameters 

1224 ---------- 

1225 numeric_only : bool, default False 

1226 Include only float, int, boolean columns. 

1227 

1228 .. versionchanged:: 2.0.0 

1229 

1230 numeric_only no longer accepts ``None``. 

1231 

1232 min_count : int, default 0 

1233 The required number of valid values to perform the operation. If fewer 

1234 than ``min_count`` non-NA values are present the result will be NA. 

1235 

1236 Returns 

1237 ------- 

1238 Series or DataFrame 

1239 Compute the minimum value in the given Series or DataFrame. 

1240 

1241 See Also 

1242 -------- 

1243 core.resample.Resampler.max : Compute max value of group. 

1244 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1245 core.resample.Resampler.median : Compute median of groups, excluding missing 

1246 values. 

1247 

1248 Examples 

1249 -------- 

1250 >>> ser = pd.Series( 

1251 ... [1, 2, 3, 4], 

1252 ... index=pd.DatetimeIndex( 

1253 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1254 ... ), 

1255 ... ) 

1256 >>> ser 

1257 2023-01-01 1 

1258 2023-01-15 2 

1259 2023-02-01 3 

1260 2023-02-15 4 

1261 dtype: int64 

1262 >>> ser.resample("MS").min() 

1263 2023-01-01 1 

1264 2023-02-01 3 

1265 Freq: MS, dtype: int64 

1266 """ 

1267 return self._downsample("min", numeric_only=numeric_only, min_count=min_count) 

1268 

1269 @final 

1270 def max( 

1271 self, 

1272 numeric_only: bool = False, 

1273 min_count: int = 0, 

1274 ): 

1275 """ 

1276 Compute max value of group. 

1277 

1278 Parameters 

1279 ---------- 

1280 numeric_only : bool, default False 

1281 Include only float, int, boolean columns. 

1282 

1283 .. versionchanged:: 2.0.0 

1284 

1285 numeric_only no longer accepts ``None``. 

1286 

1287 min_count : int, default 0 

1288 The required number of valid values to perform the operation. If fewer 

1289 than ``min_count`` non-NA values are present the result will be NA. 

1290 

1291 Returns 

1292 ------- 

1293 Series or DataFrame 

1294 Computes the maximum value in the given Series or Dataframe. 

1295 

1296 See Also 

1297 -------- 

1298 core.resample.Resampler.min : Compute min value of group. 

1299 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1300 core.resample.Resampler.median : Compute median of groups, excluding missing 

1301 values. 

1302 

1303 Examples 

1304 -------- 

1305 >>> ser = pd.Series( 

1306 ... [1, 2, 3, 4], 

1307 ... index=pd.DatetimeIndex( 

1308 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1309 ... ), 

1310 ... ) 

1311 >>> ser 

1312 2023-01-01 1 

1313 2023-01-15 2 

1314 2023-02-01 3 

1315 2023-02-15 4 

1316 dtype: int64 

1317 >>> ser.resample("MS").max() 

1318 2023-01-01 2 

1319 2023-02-01 4 

1320 Freq: MS, dtype: int64 

1321 """ 

1322 return self._downsample("max", numeric_only=numeric_only, min_count=min_count) 

1323 

1324 @final 

1325 def first( 

1326 self, 

1327 numeric_only: bool = False, 

1328 min_count: int = 0, 

1329 skipna: bool = True, 

1330 ): 

1331 """ 

1332 Compute the first non-null entry of each column. 

1333 

1334 Parameters 

1335 ---------- 

1336 numeric_only : bool, default False 

1337 Include only float, int, boolean columns. 

1338 min_count : int, default 0 

1339 The required number of valid values to perform the operation. If fewer 

1340 than ``min_count`` non-NA values are present the result will be NA. 

1341 skipna : bool, default True 

1342 Exclude NA/null values. If an entire group is NA, the result will be NA. 

1343 

1344 Returns 

1345 ------- 

1346 Series or DataFrame 

1347 First values within each group. 

1348 

1349 See Also 

1350 -------- 

1351 core.resample.Resampler.last : Compute the last non-null value in each group. 

1352 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1353 

1354 Examples 

1355 -------- 

1356 >>> s = pd.Series( 

1357 ... [1, 2, 3, 4], 

1358 ... index=pd.DatetimeIndex( 

1359 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1360 ... ), 

1361 ... ) 

1362 >>> s 

1363 2023-01-01 1 

1364 2023-01-15 2 

1365 2023-02-01 3 

1366 2023-02-15 4 

1367 dtype: int64 

1368 >>> s.resample("MS").first() 

1369 2023-01-01 1 

1370 2023-02-01 3 

1371 Freq: MS, dtype: int64 

1372 """ 

1373 return self._downsample( 

1374 "first", numeric_only=numeric_only, min_count=min_count, skipna=skipna 

1375 ) 

1376 

1377 @final 

1378 def last( 

1379 self, 

1380 numeric_only: bool = False, 

1381 min_count: int = 0, 

1382 skipna: bool = True, 

1383 ): 

1384 """ 

1385 Compute the last non-null entry of each column. 

1386 

1387 Parameters 

1388 ---------- 

1389 numeric_only : bool, default False 

1390 Include only float, int, boolean columns. 

1391 min_count : int, default 0 

1392 The required number of valid values to perform the operation. If fewer 

1393 than ``min_count`` non-NA values are present the result will be NA. 

1394 skipna : bool, default True 

1395 Exclude NA/null values. If an entire group is NA, the result will be NA. 

1396 

1397 Returns 

1398 ------- 

1399 Series or DataFrame 

1400 Last of values within each group. 

1401 

1402 See Also 

1403 -------- 

1404 core.resample.Resampler.first : Compute the first non-null value in each group. 

1405 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1406 

1407 Examples 

1408 -------- 

1409 >>> s = pd.Series( 

1410 ... [1, 2, 3, 4], 

1411 ... index=pd.DatetimeIndex( 

1412 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1413 ... ), 

1414 ... ) 

1415 >>> s 

1416 2023-01-01 1 

1417 2023-01-15 2 

1418 2023-02-01 3 

1419 2023-02-15 4 

1420 dtype: int64 

1421 >>> s.resample("MS").last() 

1422 2023-01-01 2 

1423 2023-02-01 4 

1424 Freq: MS, dtype: int64 

1425 """ 

1426 return self._downsample( 

1427 "last", numeric_only=numeric_only, min_count=min_count, skipna=skipna 

1428 ) 

1429 

1430 @final 

1431 def median(self, numeric_only: bool = False): 

1432 """ 

1433 Compute median of groups, excluding missing values. 

1434 

1435 For multiple groupings, the result index will be a MultiIndex 

1436 

1437 Parameters 

1438 ---------- 

1439 numeric_only : bool, default False 

1440 Include only float, int, boolean columns. 

1441 

1442 .. versionchanged:: 2.0.0 

1443 

1444 numeric_only no longer accepts ``None`` and defaults to False. 

1445 

1446 Returns 

1447 ------- 

1448 Series or DataFrame 

1449 Median of values within each group. 

1450 

1451 See Also 

1452 -------- 

1453 Series.groupby : Apply a function groupby to a Series. 

1454 DataFrame.groupby : Apply a function groupby to each row or column of a 

1455 DataFrame. 

1456 

1457 Examples 

1458 -------- 

1459 

1460 >>> ser = pd.Series( 

1461 ... [1, 2, 3, 3, 4, 5], 

1462 ... index=pd.DatetimeIndex( 

1463 ... [ 

1464 ... "2023-01-01", 

1465 ... "2023-01-10", 

1466 ... "2023-01-15", 

1467 ... "2023-02-01", 

1468 ... "2023-02-10", 

1469 ... "2023-02-15", 

1470 ... ] 

1471 ... ), 

1472 ... ) 

1473 >>> ser.resample("MS").median() 

1474 2023-01-01 2.0 

1475 2023-02-01 4.0 

1476 Freq: MS, dtype: float64 

1477 """ 

1478 return self._downsample("median", numeric_only=numeric_only) 

1479 

1480 @final 

1481 def mean( 

1482 self, 

1483 numeric_only: bool = False, 

1484 ): 

1485 """ 

1486 Compute mean of groups, excluding missing values. 

1487 

1488 Parameters 

1489 ---------- 

1490 numeric_only : bool, default False 

1491 Include only `float`, `int` or `boolean` data. 

1492 

1493 .. versionchanged:: 2.0.0 

1494 

1495 numeric_only now defaults to ``False``. 

1496 

1497 Returns 

1498 ------- 

1499 DataFrame or Series 

1500 Mean of values within each group. 

1501 

1502 See Also 

1503 -------- 

1504 core.resample.Resampler.median : Compute median of groups, excluding missing 

1505 values. 

1506 core.resample.Resampler.sum : Compute sum of groups, excluding missing values. 

1507 core.resample.Resampler.std : Compute standard deviation of groups, excluding 

1508 missing values. 

1509 core.resample.Resampler.var : Compute variance of groups, excluding missing 

1510 values. 

1511 

1512 Examples 

1513 -------- 

1514 

1515 >>> ser = pd.Series( 

1516 ... [1, 2, 3, 4], 

1517 ... index=pd.DatetimeIndex( 

1518 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1519 ... ), 

1520 ... ) 

1521 >>> ser 

1522 2023-01-01 1 

1523 2023-01-15 2 

1524 2023-02-01 3 

1525 2023-02-15 4 

1526 dtype: int64 

1527 >>> ser.resample("MS").mean() 

1528 2023-01-01 1.5 

1529 2023-02-01 3.5 

1530 Freq: MS, dtype: float64 

1531 """ 

1532 return self._downsample("mean", numeric_only=numeric_only) 

1533 

1534 @final 

1535 def std( 

1536 self, 

1537 ddof: int = 1, 

1538 numeric_only: bool = False, 

1539 ): 

1540 """ 

1541 Compute standard deviation of groups, excluding missing values. 

1542 

1543 Parameters 

1544 ---------- 

1545 ddof : int, default 1 

1546 Degrees of freedom. 

1547 numeric_only : bool, default False 

1548 Include only `float`, `int` or `boolean` data. 

1549 

1550 .. versionchanged:: 2.0.0 

1551 

1552 numeric_only now defaults to ``False``. 

1553 

1554 Returns 

1555 ------- 

1556 DataFrame or Series 

1557 Standard deviation of values within each group. 

1558 

1559 See Also 

1560 -------- 

1561 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1562 core.resample.Resampler.median : Compute median of groups, excluding missing 

1563 values. 

1564 core.resample.Resampler.var : Compute variance of groups, excluding missing 

1565 values. 

1566 

1567 Examples 

1568 -------- 

1569 

1570 >>> ser = pd.Series( 

1571 ... [1, 3, 2, 4, 3, 8], 

1572 ... index=pd.DatetimeIndex( 

1573 ... [ 

1574 ... "2023-01-01", 

1575 ... "2023-01-10", 

1576 ... "2023-01-15", 

1577 ... "2023-02-01", 

1578 ... "2023-02-10", 

1579 ... "2023-02-15", 

1580 ... ] 

1581 ... ), 

1582 ... ) 

1583 >>> ser.resample("MS").std() 

1584 2023-01-01 1.000000 

1585 2023-02-01 2.645751 

1586 Freq: MS, dtype: float64 

1587 """ 

1588 return self._downsample("std", ddof=ddof, numeric_only=numeric_only) 

1589 

1590 @final 

1591 def var( 

1592 self, 

1593 ddof: int = 1, 

1594 numeric_only: bool = False, 

1595 ): 

1596 """ 

1597 Compute variance of groups, excluding missing values. 

1598 

1599 Parameters 

1600 ---------- 

1601 ddof : int, default 1 

1602 Degrees of freedom. 

1603 

1604 numeric_only : bool, default False 

1605 Include only `float`, `int` or `boolean` data. 

1606 

1607 .. versionchanged:: 2.0.0 

1608 

1609 numeric_only now defaults to ``False``. 

1610 

1611 Returns 

1612 ------- 

1613 DataFrame or Series 

1614 Variance of values within each group. 

1615 

1616 See Also 

1617 -------- 

1618 core.resample.Resampler.std : Compute standard deviation of groups, excluding 

1619 missing values. 

1620 core.resample.Resampler.mean : Compute mean of groups, excluding missing values. 

1621 core.resample.Resampler.median : Compute median of groups, excluding missing 

1622 values. 

1623 

1624 Examples 

1625 -------- 

1626 

1627 >>> ser = pd.Series( 

1628 ... [1, 3, 2, 4, 3, 8], 

1629 ... index=pd.DatetimeIndex( 

1630 ... [ 

1631 ... "2023-01-01", 

1632 ... "2023-01-10", 

1633 ... "2023-01-15", 

1634 ... "2023-02-01", 

1635 ... "2023-02-10", 

1636 ... "2023-02-15", 

1637 ... ] 

1638 ... ), 

1639 ... ) 

1640 >>> ser.resample("MS").var() 

1641 2023-01-01 1.0 

1642 2023-02-01 7.0 

1643 Freq: MS, dtype: float64 

1644 

1645 >>> ser.resample("MS").var(ddof=0) 

1646 2023-01-01 0.666667 

1647 2023-02-01 4.666667 

1648 Freq: MS, dtype: float64 

1649 """ 

1650 return self._downsample("var", ddof=ddof, numeric_only=numeric_only) 

1651 

1652 @final 

1653 def sem( 

1654 self, 

1655 ddof: int = 1, 

1656 numeric_only: bool = False, 

1657 ): 

1658 """ 

1659 Compute standard error of the mean of groups, excluding missing values. 

1660 

1661 For multiple groupings, the result index will be a MultiIndex. 

1662 

1663 Parameters 

1664 ---------- 

1665 ddof : int, default 1 

1666 Degrees of freedom. 

1667 

1668 numeric_only : bool, default False 

1669 Include only `float`, `int` or `boolean` data. 

1670 

1671 .. versionchanged:: 2.0.0 

1672 

1673 numeric_only now defaults to ``False``. 

1674 

1675 Returns 

1676 ------- 

1677 Series or DataFrame 

1678 Standard error of the mean of values within each group. 

1679 

1680 See Also 

1681 -------- 

1682 DataFrame.sem : Return unbiased standard error of the mean over requested axis. 

1683 Series.sem : Return unbiased standard error of the mean over requested axis. 

1684 

1685 Examples 

1686 -------- 

1687 

1688 >>> ser = pd.Series( 

1689 ... [1, 3, 2, 4, 3, 8], 

1690 ... index=pd.DatetimeIndex( 

1691 ... [ 

1692 ... "2023-01-01", 

1693 ... "2023-01-10", 

1694 ... "2023-01-15", 

1695 ... "2023-02-01", 

1696 ... "2023-02-10", 

1697 ... "2023-02-15", 

1698 ... ] 

1699 ... ), 

1700 ... ) 

1701 >>> ser.resample("MS").sem() 

1702 2023-01-01 0.577350 

1703 2023-02-01 1.527525 

1704 Freq: MS, dtype: float64 

1705 """ 

1706 return self._downsample("sem", ddof=ddof, numeric_only=numeric_only) 

1707 

1708 @final 

1709 def ohlc(self): 

1710 """ 

1711 Compute open, high, low and close values of a group, excluding missing values. 

1712 

1713 Returns 

1714 ------- 

1715 DataFrame 

1716 Open, high, low and close values within each group. 

1717 

1718 See Also 

1719 -------- 

1720 DataFrame.agg : Aggregate using one or more operations over the specified axis. 

1721 DataFrame.resample : Resample time-series data. 

1722 DataFrame.groupby : Group DataFrame using a mapper or by a Series of columns. 

1723 

1724 Examples 

1725 -------- 

1726 >>> ser = pd.Series( 

1727 ... [1, 3, 2, 4, 3, 5], 

1728 ... index=pd.DatetimeIndex( 

1729 ... [ 

1730 ... "2023-01-01", 

1731 ... "2023-01-10", 

1732 ... "2023-01-15", 

1733 ... "2023-02-01", 

1734 ... "2023-02-10", 

1735 ... "2023-02-15", 

1736 ... ] 

1737 ... ), 

1738 ... ) 

1739 >>> ser.resample("MS").ohlc() 

1740 open high low close 

1741 2023-01-01 1 3 1 2 

1742 2023-02-01 4 5 3 5 

1743 """ 

1744 ax = self.ax 

1745 obj = self._obj_with_exclusions 

1746 if len(ax) == 0: 

1747 # GH#42902 

1748 obj = obj.copy() 

1749 obj.index = _asfreq_compat(obj.index, self.freq) 

1750 if obj.ndim == 1: 

1751 obj = obj.to_frame() 

1752 obj = obj.reindex(["open", "high", "low", "close"], axis=1) 

1753 else: 

1754 mi = MultiIndex.from_product( 

1755 [obj.columns, ["open", "high", "low", "close"]] 

1756 ) 

1757 obj = obj.reindex(mi, axis=1) 

1758 return obj 

1759 

1760 return self._downsample("ohlc") 

1761 

1762 @final 

1763 def nunique(self): 

1764 """ 

1765 Return number of unique elements in the group. 

1766 

1767 Returns 

1768 ------- 

1769 Series 

1770 Number of unique values within each group. 

1771 

1772 See Also 

1773 -------- 

1774 core.groupby.SeriesGroupBy.nunique : Method nunique for SeriesGroupBy. 

1775 

1776 Examples 

1777 -------- 

1778 >>> ser = pd.Series( 

1779 ... [1, 2, 3, 3], 

1780 ... index=pd.DatetimeIndex( 

1781 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1782 ... ), 

1783 ... ) 

1784 >>> ser 

1785 2023-01-01 1 

1786 2023-01-15 2 

1787 2023-02-01 3 

1788 2023-02-15 3 

1789 dtype: int64 

1790 >>> ser.resample("MS").nunique() 

1791 2023-01-01 2 

1792 2023-02-01 1 

1793 Freq: MS, dtype: int64 

1794 """ 

1795 return self._downsample("nunique") 

1796 

1797 @final 

1798 def size(self): 

1799 """ 

1800 Compute group sizes. 

1801 

1802 Returns 

1803 ------- 

1804 Series 

1805 Number of rows in each group. 

1806 

1807 See Also 

1808 -------- 

1809 Series.groupby : Apply a function groupby to a Series. 

1810 DataFrame.groupby : Apply a function groupby to each row 

1811 or column of a DataFrame. 

1812 

1813 Examples 

1814 -------- 

1815 >>> ser = pd.Series( 

1816 ... [1, 2, 3], 

1817 ... index=pd.DatetimeIndex(["2023-01-01", "2023-01-15", "2023-02-01"]), 

1818 ... ) 

1819 >>> ser 

1820 2023-01-01 1 

1821 2023-01-15 2 

1822 2023-02-01 3 

1823 dtype: int64 

1824 >>> ser.resample("MS").size() 

1825 2023-01-01 2 

1826 2023-02-01 1 

1827 Freq: MS, dtype: int64 

1828 """ 

1829 result = self._downsample("size") 

1830 

1831 # If the result is a non-empty DataFrame we stack to get a Series 

1832 # GH 46826 

1833 if isinstance(result, ABCDataFrame) and not result.empty: 

1834 result = result.stack() 

1835 

1836 if not len(self.ax): 

1837 from pandas import Series 

1838 

1839 if self._selected_obj.ndim == 1: 

1840 name = self._selected_obj.name 

1841 else: 

1842 name = None 

1843 result = Series([], index=result.index, dtype="int64", name=name) 

1844 return result 

1845 

1846 @final 

1847 def count(self): 

1848 """ 

1849 Compute count of group, excluding missing values. 

1850 

1851 Returns 

1852 ------- 

1853 Series or DataFrame 

1854 Count of values within each group. 

1855 

1856 See Also 

1857 -------- 

1858 Series.groupby : Apply a function groupby to a Series. 

1859 DataFrame.groupby : Apply a function groupby to each row 

1860 or column of a DataFrame. 

1861 

1862 Examples 

1863 -------- 

1864 >>> ser = pd.Series( 

1865 ... [1, 2, 3, 4], 

1866 ... index=pd.DatetimeIndex( 

1867 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"] 

1868 ... ), 

1869 ... ) 

1870 >>> ser 

1871 2023-01-01 1 

1872 2023-01-15 2 

1873 2023-02-01 3 

1874 2023-02-15 4 

1875 dtype: int64 

1876 >>> ser.resample("MS").count() 

1877 2023-01-01 2 

1878 2023-02-01 2 

1879 Freq: MS, dtype: int64 

1880 """ 

1881 result = self._downsample("count") 

1882 if not len(self.ax): 

1883 if self._selected_obj.ndim == 1: 

1884 result = type(self._selected_obj)( 

1885 [], index=result.index, dtype="int64", name=self._selected_obj.name 

1886 ) 

1887 else: 

1888 from pandas import DataFrame 

1889 

1890 result = DataFrame( 

1891 [], index=result.index, columns=result.columns, dtype="int64" 

1892 ) 

1893 

1894 return result 

1895 

1896 @final 

1897 def quantile(self, q: float | list[float] | AnyArrayLike = 0.5, **kwargs): 

1898 """ 

1899 Return value at the given quantile. 

1900 

1901 Computes the quantile of values within each resampled group. 

1902 

1903 Parameters 

1904 ---------- 

1905 q : float or array-like, default 0.5 (50% quantile) 

1906 Value between 0 <= q <= 1, the quantile(s) to compute. 

1907 **kwargs 

1908 Additional keyword arguments to be passed to the function. 

1909 

1910 Returns 

1911 ------- 

1912 DataFrame or Series 

1913 Quantile of values within each group. 

1914 

1915 See Also 

1916 -------- 

1917 Series.quantile 

1918 Return a series, where the index is q and the values are the quantiles. 

1919 DataFrame.quantile 

1920 Return a DataFrame, where the columns are the columns of self, 

1921 and the values are the quantiles. 

1922 DataFrameGroupBy.quantile 

1923 Return a DataFrame, where the columns are groupby columns, 

1924 and the values are its quantiles. 

1925 

1926 Examples 

1927 -------- 

1928 

1929 >>> ser = pd.Series( 

1930 ... [1, 3, 2, 4, 3, 8], 

1931 ... index=pd.DatetimeIndex( 

1932 ... [ 

1933 ... "2023-01-01", 

1934 ... "2023-01-10", 

1935 ... "2023-01-15", 

1936 ... "2023-02-01", 

1937 ... "2023-02-10", 

1938 ... "2023-02-15", 

1939 ... ] 

1940 ... ), 

1941 ... ) 

1942 >>> ser.resample("MS").quantile() 

1943 2023-01-01 2.0 

1944 2023-02-01 4.0 

1945 Freq: MS, dtype: float64 

1946 

1947 >>> ser.resample("MS").quantile(0.25) 

1948 2023-01-01 1.5 

1949 2023-02-01 3.5 

1950 Freq: MS, dtype: float64 

1951 """ 

1952 return self._downsample("quantile", q=q, **kwargs) 

1953 

1954 

1955class _GroupByMixin(PandasObject, SelectionMixin): 

1956 """ 

1957 Provide the groupby facilities. 

1958 """ 

1959 

1960 _attributes: list[str] # in practice the same as Resampler._attributes 

1961 _selection: IndexLabel | None = None 

1962 _groupby: GroupBy 

1963 _timegrouper: TimeGrouper 

1964 

1965 def __init__( 

1966 self, 

1967 *, 

1968 parent: Resampler, 

1969 groupby: GroupBy, 

1970 key=None, 

1971 selection: IndexLabel | None = None, 

1972 ) -> None: 

1973 # reached via ._gotitem and _get_resampler_for_grouping 

1974 

1975 assert isinstance(groupby, GroupBy), type(groupby) 

1976 

1977 # parent is always a Resampler, sometimes a _GroupByMixin 

1978 assert isinstance(parent, Resampler), type(parent) 

1979 

1980 # initialize our GroupByMixin object with 

1981 # the resampler attributes 

1982 for attr in self._attributes: 

1983 setattr(self, attr, getattr(parent, attr)) 

1984 self._selection = selection 

1985 

1986 self.binner = parent.binner 

1987 self.key = key 

1988 

1989 self._groupby = groupby 

1990 self._timegrouper = copy.copy(parent._timegrouper) 

1991 

1992 self.ax = parent.ax 

1993 self.obj = parent.obj 

1994 

1995 @no_type_check 

1996 def _apply(self, f, *args, **kwargs): 

1997 """ 

1998 Dispatch to _upsample; we are stripping all of the _upsample kwargs and 

1999 performing the original function call on the grouped object. 

2000 """ 

2001 

2002 def func(x): 

2003 x = self._resampler_cls(x, timegrouper=self._timegrouper, gpr_index=self.ax) 

2004 

2005 if isinstance(f, str): 

2006 return getattr(x, f)(**kwargs) 

2007 

2008 return x.apply(f, *args, **kwargs) 

2009 

2010 result = self._groupby.apply(func) 

2011 

2012 # GH 47705 

2013 if ( 

2014 isinstance(result, ABCDataFrame) 

2015 and len(result) == 0 

2016 and not isinstance(result.index, PeriodIndex) 

2017 ): 

2018 result = result.set_index( 

2019 _asfreq_compat(self.obj.index[:0], freq=self.freq), append=True 

2020 ) 

2021 

2022 return self._wrap_result(result) 

2023 

2024 _upsample = _apply 

2025 _downsample = _apply 

2026 _groupby_and_aggregate = _apply 

2027 

2028 @final 

2029 def _gotitem(self, key, ndim, subset=None): 

2030 """ 

2031 Sub-classes to define. Return a sliced object. 

2032 

2033 Parameters 

2034 ---------- 

2035 key : string / list of selections 

2036 ndim : {1, 2} 

2037 requested ndim of result 

2038 subset : object, default None 

2039 subset to act on 

2040 """ 

2041 # create a new object to prevent aliasing 

2042 if subset is None: 

2043 subset = self.obj 

2044 if key is not None: 

2045 subset = subset[key] 

2046 else: 

2047 # reached via Apply.agg_dict_like with selection=None, ndim=1 

2048 assert subset.ndim == 1 

2049 

2050 # Try to select from a DataFrame, falling back to a Series 

2051 try: 

2052 if isinstance(key, list) and self.key not in key and self.key is not None: 

2053 key.append(self.key) 

2054 groupby = self._groupby[key] 

2055 except IndexError: 

2056 groupby = self._groupby 

2057 

2058 selection = self._infer_selection(key, subset) 

2059 

2060 new_rs = type(self)( 

2061 groupby=groupby, 

2062 parent=cast(Resampler, self), 

2063 selection=selection, 

2064 ) 

2065 return new_rs 

2066 

2067 

2068class DatetimeIndexResampler(Resampler): 

2069 ax: DatetimeIndex 

2070 

2071 @property 

2072 def _resampler_for_grouping(self) -> type[DatetimeIndexResamplerGroupby]: 

2073 return DatetimeIndexResamplerGroupby 

2074 

2075 def _get_binner_for_time(self): 

2076 # this is how we are actually creating the bins 

2077 return self._timegrouper._get_time_bins(self.ax) 

2078 

2079 def _downsample(self, how, **kwargs): 

2080 """ 

2081 Downsample the cython defined function. 

2082 

2083 Parameters 

2084 ---------- 

2085 how : string / cython mapped function 

2086 **kwargs : kw args passed to how function 

2087 """ 

2088 ax = self.ax 

2089 

2090 # Excludes `on` column when provided 

2091 obj = self._obj_with_exclusions 

2092 

2093 if not len(ax): 

2094 # reset to the new freq 

2095 obj = obj.copy() 

2096 obj.index = obj.index._with_freq(self.freq) 

2097 assert obj.index.freq == self.freq, (obj.index.freq, self.freq) 

2098 return obj 

2099 

2100 # we are downsampling 

2101 # we want to call the actual grouper method here 

2102 result = obj.groupby(self._grouper).aggregate(how, **kwargs) 

2103 return self._wrap_result(result) 

2104 

2105 def _adjust_binner_for_upsample(self, binner): 

2106 """ 

2107 Adjust our binner when upsampling. 

2108 

2109 The range of a new index should not be outside specified range 

2110 """ 

2111 if self.closed == "right": 

2112 binner = binner[1:] 

2113 else: 

2114 binner = binner[:-1] 

2115 return binner 

2116 

2117 def _upsample(self, method, limit: int | None = None, fill_value=None): 

2118 """ 

2119 Parameters 

2120 ---------- 

2121 method : string {'backfill', 'bfill', 'pad', 

2122 'ffill', 'asfreq'} method for upsampling 

2123 limit : int, default None 

2124 Maximum size gap to fill when reindexing 

2125 fill_value : scalar, default None 

2126 Value to use for missing values 

2127 """ 

2128 if self._from_selection: 

2129 raise ValueError( 

2130 "Upsampling from level= or on= selection " 

2131 "is not supported, use .set_index(...) " 

2132 "to explicitly set index to datetime-like" 

2133 ) 

2134 

2135 ax = self.ax 

2136 obj = self._selected_obj 

2137 binner = self.binner 

2138 res_index = self._adjust_binner_for_upsample(binner) 

2139 

2140 # if index exactly matches target grid (same freq & alignment), use fast path 

2141 if ( 

2142 limit is None 

2143 and to_offset(ax.inferred_freq) == self.freq 

2144 and len(obj) == len(res_index) 

2145 and obj.index.equals(res_index) 

2146 ): 

2147 result = obj.copy() 

2148 result.index = res_index 

2149 else: 

2150 if method == "asfreq": 

2151 method = None 

2152 result = obj.reindex( 

2153 res_index, method=method, limit=limit, fill_value=fill_value 

2154 ) 

2155 

2156 return self._wrap_result(result) 

2157 

2158 def _wrap_result(self, result): 

2159 result = super()._wrap_result(result) 

2160 

2161 # we may have a different kind that we were asked originally 

2162 # convert if needed 

2163 if isinstance(self.ax, PeriodIndex) and not isinstance( 

2164 result.index, PeriodIndex 

2165 ): 

2166 if isinstance(result.index, MultiIndex): 

2167 # GH 24103 - e.g. groupby resample 

2168 if not isinstance(result.index.levels[-1], PeriodIndex): 

2169 new_level = result.index.levels[-1].to_period(self.freq) 

2170 result.index = result.index.set_levels(new_level, level=-1) 

2171 else: 

2172 result.index = result.index.to_period(self.freq) 

2173 return result 

2174 

2175 

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

2177# error: Definition of "ax" in base class "_GroupByMixin" is incompatible 

2178# with definition in base class "DatetimeIndexResampler" 

2179class DatetimeIndexResamplerGroupby( # type: ignore[misc] 

2180 _GroupByMixin, DatetimeIndexResampler 

2181): 

2182 """ 

2183 Provides a resample of a groupby implementation 

2184 """ 

2185 

2186 @property 

2187 def _resampler_cls(self): 

2188 return DatetimeIndexResampler 

2189 

2190 

2191class PeriodIndexResampler(DatetimeIndexResampler): 

2192 # error: Incompatible types in assignment (expression has type "PeriodIndex", base 

2193 # class "DatetimeIndexResampler" defined the type as "DatetimeIndex") 

2194 ax: PeriodIndex # type: ignore[assignment] 

2195 

2196 @property 

2197 def _resampler_for_grouping(self): 

2198 return PeriodIndexResamplerGroupby 

2199 

2200 def _get_binner_for_time(self): 

2201 return self._timegrouper._get_period_bins(self.ax) 

2202 

2203 def _convert_obj(self, obj: NDFrameT) -> NDFrameT: 

2204 obj = super()._convert_obj(obj) 

2205 

2206 if self._from_selection: 

2207 # see GH 14008, GH 12871 

2208 msg = ( 

2209 "Resampling from level= or on= selection " 

2210 "with a PeriodIndex is not currently supported, " 

2211 "use .set_index(...) to explicitly set index" 

2212 ) 

2213 raise NotImplementedError(msg) 

2214 

2215 return obj 

2216 

2217 def _downsample(self, how, **kwargs): 

2218 """ 

2219 Downsample the cython defined function. 

2220 

2221 Parameters 

2222 ---------- 

2223 how : string / cython mapped function 

2224 **kwargs : kw args passed to how function 

2225 """ 

2226 ax = self.ax 

2227 

2228 if is_subperiod(ax.freq, self.freq): 

2229 # Downsampling 

2230 return self._groupby_and_aggregate(how, **kwargs) 

2231 elif is_superperiod(ax.freq, self.freq): 

2232 if how == "ohlc": 

2233 # GH #13083 

2234 # upsampling to subperiods is handled as an asfreq, which works 

2235 # for pure aggregating/reducing methods 

2236 # OHLC reduces along the time dimension, but creates multiple 

2237 # values for each period -> handle by _groupby_and_aggregate() 

2238 return self._groupby_and_aggregate(how) 

2239 return self.asfreq() 

2240 elif ax.freq == self.freq: 

2241 return self.asfreq() 

2242 

2243 raise IncompatibleFrequency( 

2244 f"Frequency {ax.freq} cannot be resampled to {self.freq}, " 

2245 "as they are not sub or super periods" 

2246 ) 

2247 

2248 def _upsample(self, method, limit: int | None = None, fill_value=None): 

2249 """ 

2250 Parameters 

2251 ---------- 

2252 method : {'backfill', 'bfill', 'pad', 'ffill'} 

2253 Method for upsampling. 

2254 limit : int, default None 

2255 Maximum size gap to fill when reindexing. 

2256 fill_value : scalar, default None 

2257 Value to use for missing values. 

2258 """ 

2259 ax = self.ax 

2260 obj = self.obj 

2261 new_index = self.binner 

2262 

2263 # Start vs. end of period 

2264 memb = ax.asfreq(self.freq, how=self.convention) 

2265 

2266 # Get the fill indexer 

2267 if method == "asfreq": 

2268 method = None 

2269 indexer = memb.get_indexer(new_index, method=method, limit=limit) 

2270 new_obj = _take_new_index( 

2271 obj, 

2272 indexer, 

2273 new_index, 

2274 ) 

2275 return self._wrap_result(new_obj) 

2276 

2277 

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

2279# error: Definition of "ax" in base class "_GroupByMixin" is incompatible with 

2280# definition in base class "PeriodIndexResampler" 

2281class PeriodIndexResamplerGroupby( # type: ignore[misc] 

2282 _GroupByMixin, PeriodIndexResampler 

2283): 

2284 """ 

2285 Provides a resample of a groupby implementation. 

2286 """ 

2287 

2288 @property 

2289 def _resampler_cls(self): 

2290 return PeriodIndexResampler 

2291 

2292 

2293class TimedeltaIndexResampler(DatetimeIndexResampler): 

2294 # error: Incompatible types in assignment (expression has type "TimedeltaIndex", 

2295 # base class "DatetimeIndexResampler" defined the type as "DatetimeIndex") 

2296 ax: TimedeltaIndex # type: ignore[assignment] 

2297 

2298 @property 

2299 def _resampler_for_grouping(self): 

2300 return TimedeltaIndexResamplerGroupby 

2301 

2302 def _get_binner_for_time(self): 

2303 return self._timegrouper._get_time_delta_bins(self.ax) 

2304 

2305 def _adjust_binner_for_upsample(self, binner): 

2306 """ 

2307 Adjust our binner when upsampling. 

2308 

2309 The range of a new index is allowed to be greater than original range 

2310 so we don't need to change the length of a binner, GH 13022 

2311 """ 

2312 return binner 

2313 

2314 

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

2316# error: Definition of "ax" in base class "_GroupByMixin" is incompatible with 

2317# definition in base class "DatetimeIndexResampler" 

2318class TimedeltaIndexResamplerGroupby( # type: ignore[misc] 

2319 _GroupByMixin, TimedeltaIndexResampler 

2320): 

2321 """ 

2322 Provides a resample of a groupby implementation. 

2323 """ 

2324 

2325 @property 

2326 def _resampler_cls(self): 

2327 return TimedeltaIndexResampler 

2328 

2329 

2330def get_resampler(obj: Series | DataFrame, **kwds) -> Resampler: 

2331 """ 

2332 Create a TimeGrouper and return our resampler. 

2333 """ 

2334 tg = TimeGrouper(obj, **kwds) # type: ignore[arg-type] 

2335 return tg._get_resampler(obj) 

2336 

2337 

2338get_resampler.__doc__ = Resampler.__doc__ 

2339 

2340 

2341def get_resampler_for_grouping( 

2342 groupby: GroupBy, 

2343 rule, 

2344 how=None, 

2345 fill_method=None, 

2346 limit: int | None = None, 

2347 on=None, 

2348 **kwargs, 

2349) -> Resampler: 

2350 """ 

2351 Return our appropriate resampler when grouping as well. 

2352 """ 

2353 # .resample uses 'on' similar to how .groupby uses 'key' 

2354 tg = TimeGrouper(freq=rule, key=on, **kwargs) 

2355 resampler = tg._get_resampler(groupby.obj) 

2356 return resampler._get_resampler_for_grouping(groupby=groupby, key=tg.key) 

2357 

2358 

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

2360class TimeGrouper(Grouper): 

2361 """ 

2362 Custom groupby class for time-interval grouping. 

2363 

2364 Parameters 

2365 ---------- 

2366 freq : pandas date offset or offset alias for identifying bin edges 

2367 closed : closed end of interval; 'left' or 'right' 

2368 label : interval boundary to use for labeling; 'left' or 'right' 

2369 convention : {'start', 'end', 'e', 's'} 

2370 If axis is PeriodIndex 

2371 """ 

2372 

2373 _attributes = ( 

2374 *Grouper._attributes, 

2375 "closed", 

2376 "label", 

2377 "how", 

2378 "convention", 

2379 "origin", 

2380 "offset", 

2381 ) 

2382 

2383 origin: TimeGrouperOrigin 

2384 

2385 def __init__( 

2386 self, 

2387 obj: Grouper | None = None, 

2388 freq: Frequency = "Min", 

2389 key: str | None = None, 

2390 closed: Literal["left", "right"] | None = None, 

2391 label: Literal["left", "right"] | None = None, 

2392 how: str = "mean", 

2393 fill_method=None, 

2394 limit: int | None = None, 

2395 convention: Literal["start", "end", "e", "s"] | None = None, 

2396 origin: ( 

2397 Literal["epoch", "start", "start_day", "end", "end_day"] 

2398 | TimestampConvertibleTypes 

2399 ) = "start_day", 

2400 offset: TimedeltaConvertibleTypes | None = None, 

2401 group_keys: bool = False, 

2402 **kwargs, 

2403 ) -> None: 

2404 # Check for correctness of the keyword arguments which would 

2405 # otherwise silently use the default if misspelled 

2406 if label not in {None, "left", "right"}: 

2407 raise ValueError(f"Unsupported value {label} for `label`") 

2408 if closed not in {None, "left", "right"}: 

2409 raise ValueError(f"Unsupported value {closed} for `closed`") 

2410 if convention not in {None, "start", "end", "e", "s"}: 

2411 raise ValueError(f"Unsupported value {convention} for `convention`") 

2412 

2413 if (key is None and obj is not None and isinstance(obj.index, PeriodIndex)) or ( # type: ignore[attr-defined] 

2414 key is not None 

2415 and obj is not None 

2416 and getattr(obj[key], "dtype", None) == "period" # type: ignore[index] 

2417 ): 

2418 freq = to_offset(freq, is_period=True) 

2419 else: 

2420 freq = to_offset(freq) 

2421 

2422 if not isinstance(freq, Tick): 

2423 if offset is not None: 

2424 warnings.warn( 

2425 "The 'offset' keyword does not take effect when resampling " 

2426 "with a 'freq' that is not Tick-like (h, m, s, ms, us, ns)", 

2427 RuntimeWarning, 

2428 stacklevel=find_stack_level(), 

2429 ) 

2430 if origin != "start_day": 

2431 warnings.warn( 

2432 "The 'origin' keyword does not take effect when resampling " 

2433 "with a 'freq' that is not Tick-like (h, m, s, ms, us, ns)", 

2434 RuntimeWarning, 

2435 stacklevel=find_stack_level(), 

2436 ) 

2437 

2438 end_types = {"ME", "YE", "QE", "BME", "BYE", "BQE", "W"} 

2439 rule = freq.rule_code 

2440 if rule in end_types or ("-" in rule and rule[: rule.find("-")] in end_types): 

2441 if closed is None: 

2442 closed = "right" 

2443 if label is None: 

2444 label = "right" 

2445 # The backward resample sets ``closed`` to ``'right'`` by default 

2446 # since the last value should be considered as the edge point for 

2447 # the last bin. When origin in "end" or "end_day", the value for a 

2448 # specific ``Timestamp`` index stands for the resample result from 

2449 # the current ``Timestamp`` minus ``freq`` to the current 

2450 # ``Timestamp`` with a right close. 

2451 elif origin in ["end", "end_day"]: 

2452 if closed is None: 

2453 closed = "right" 

2454 if label is None: 

2455 label = "right" 

2456 else: 

2457 if closed is None: 

2458 closed = "left" 

2459 if label is None: 

2460 label = "left" 

2461 

2462 self.closed = closed 

2463 self.label = label 

2464 self.convention = convention if convention is not None else "e" 

2465 self.how = how 

2466 self.fill_method = fill_method 

2467 self.limit = limit 

2468 self.group_keys = group_keys 

2469 self._arrow_dtype: ArrowDtype | None = None 

2470 

2471 if origin in ("epoch", "start", "start_day", "end", "end_day"): 

2472 # error: Incompatible types in assignment (expression has type "Union[Union[ 

2473 # Timestamp, datetime, datetime64, signedinteger[_64Bit], float, str], 

2474 # Literal['epoch', 'start', 'start_day', 'end', 'end_day']]", variable has 

2475 # type "Union[Timestamp, Literal['epoch', 'start', 'start_day', 'end', 

2476 # 'end_day']]") 

2477 self.origin = origin # type: ignore[assignment] 

2478 else: 

2479 try: 

2480 self.origin = Timestamp(origin) 

2481 except (ValueError, TypeError) as err: 

2482 raise ValueError( 

2483 "'origin' should be equal to 'epoch', 'start', 'start_day', " 

2484 "'end', 'end_day' or " 

2485 f"should be a Timestamp convertible type. Got '{origin}' instead." 

2486 ) from err 

2487 

2488 try: 

2489 self.offset = Timedelta(offset) if offset is not None else None 

2490 except (ValueError, TypeError) as err: 

2491 raise ValueError( 

2492 "'offset' should be a Timedelta convertible type. " 

2493 f"Got '{offset}' instead." 

2494 ) from err 

2495 

2496 # always sort time groupers 

2497 kwargs["sort"] = True 

2498 

2499 super().__init__(freq=freq, key=key, **kwargs) 

2500 

2501 def _get_resampler(self, obj: NDFrame) -> Resampler: 

2502 """ 

2503 Return my resampler or raise if we have an invalid axis. 

2504 

2505 Parameters 

2506 ---------- 

2507 obj : Series or DataFrame 

2508 

2509 Returns 

2510 ------- 

2511 Resampler 

2512 

2513 Raises 

2514 ------ 

2515 TypeError if incompatible axis 

2516 

2517 """ 

2518 _, ax, _ = self._set_grouper(obj, gpr_index=None) 

2519 if isinstance(ax, DatetimeIndex): 

2520 return DatetimeIndexResampler( 

2521 obj, 

2522 timegrouper=self, 

2523 group_keys=self.group_keys, 

2524 gpr_index=ax, 

2525 ) 

2526 elif isinstance(ax, PeriodIndex): 

2527 return PeriodIndexResampler( 

2528 obj, 

2529 timegrouper=self, 

2530 group_keys=self.group_keys, 

2531 gpr_index=ax, 

2532 ) 

2533 elif isinstance(ax, TimedeltaIndex): 

2534 return TimedeltaIndexResampler( 

2535 obj, 

2536 timegrouper=self, 

2537 group_keys=self.group_keys, 

2538 gpr_index=ax, 

2539 ) 

2540 

2541 raise TypeError( 

2542 "Only valid with DatetimeIndex, " 

2543 "TimedeltaIndex or PeriodIndex, " 

2544 f"but got an instance of '{type(ax).__name__}'" 

2545 ) 

2546 

2547 def _get_grouper( 

2548 self, obj: NDFrameT, validate: bool = True, observed: bool = True 

2549 ) -> tuple[BinGrouper, NDFrameT]: 

2550 """ 

2551 Parameters 

2552 ---------- 

2553 obj : Series or DataFrame 

2554 Object being grouped. 

2555 validate : bool, default True 

2556 Unused. Only for compatibility with ``Grouper._get_grouper``. 

2557 observed : bool, default True 

2558 Unused. Only for compatibility with ``Grouper._get_grouper``. 

2559 

2560 Returns 

2561 ------- 

2562 A tuple of grouper, obj (possibly sorted) 

2563 """ 

2564 # create the resampler and return our binner 

2565 r = self._get_resampler(obj) 

2566 return r._grouper, cast(NDFrameT, r.obj) 

2567 

2568 def _get_time_bins(self, ax: DatetimeIndex): 

2569 if not isinstance(ax, DatetimeIndex): 

2570 raise TypeError( 

2571 "axis must be a DatetimeIndex, but got " 

2572 f"an instance of {type(ax).__name__}" 

2573 ) 

2574 

2575 if len(ax) == 0: 

2576 binner = labels = DatetimeIndex( 

2577 data=[], freq=self.freq, name=ax.name, dtype=ax.dtype 

2578 ) 

2579 return binner, [], labels 

2580 

2581 first, last = _get_timestamp_range_edges( 

2582 ax.min(), 

2583 ax.max(), 

2584 self.freq, 

2585 unit=ax.unit, 

2586 closed=self.closed, 

2587 origin=self.origin, 

2588 offset=self.offset, 

2589 ) 

2590 # GH #12037 

2591 # use first/last directly instead of call replace() on them 

2592 # because replace() will swallow the nanosecond part 

2593 # thus last bin maybe slightly before the end if the end contains 

2594 # nanosecond part and lead to `Values falls after last bin` error 

2595 # GH 25758: If DST lands at midnight (e.g. 'America/Havana'), user feedback 

2596 # has noted that ambiguous=True provides the most sensible result 

2597 binner = labels = date_range( 

2598 freq=self.freq, 

2599 start=first, 

2600 end=last, 

2601 tz=ax.tz, 

2602 name=ax.name, 

2603 ambiguous=True, 

2604 nonexistent="shift_forward", 

2605 unit=ax.unit, 

2606 ) 

2607 

2608 ax_values = ax.asi8 

2609 binner, bin_edges = self._adjust_bin_edges(binner, ax_values) 

2610 

2611 # general version, knowing nothing about relative frequencies 

2612 bins = lib.generate_bins_dt64( 

2613 ax_values, bin_edges, self.closed, hasnans=ax.hasnans 

2614 ) 

2615 

2616 if self.closed == "right": 

2617 labels = binner 

2618 if self.label == "right": 

2619 labels = labels[1:] 

2620 elif self.label == "right": 

2621 labels = labels[1:] 

2622 

2623 if ax.hasnans: 

2624 binner = binner.insert(0, NaT) 

2625 labels = labels.insert(0, NaT) 

2626 

2627 # if we end up with more labels than bins 

2628 # adjust the labels 

2629 # GH4076 

2630 if len(bins) < len(labels): 

2631 labels = labels[: len(bins)] 

2632 

2633 return binner, bins, labels 

2634 

2635 def _adjust_bin_edges( 

2636 self, binner: DatetimeIndex, ax_values: npt.NDArray[np.int64] 

2637 ) -> tuple[DatetimeIndex, npt.NDArray[np.int64]]: 

2638 # Some hacks for > daily data, see #1471, #1458, #1483 

2639 

2640 if self.freq.name in ("BME", "ME", "W") or self.freq.name.split("-")[0] in ( 

2641 "BQE", 

2642 "BYE", 

2643 "QE", 

2644 "YE", 

2645 "W", 

2646 ): 

2647 # If the right end-point is on the last day of the month, roll forwards 

2648 # until the last moment of that day. Note that we only do this for offsets 

2649 # which correspond to the end of a super-daily period - "month start", for 

2650 # example, is excluded. 

2651 if self.closed == "right": 

2652 # GH 21459, GH 9119: Adjust the bins relative to the wall time 

2653 edges_dti = binner.tz_localize(None) 

2654 edges_dti = ( 

2655 edges_dti 

2656 + Timedelta(days=1).as_unit(edges_dti.unit) 

2657 - Timedelta(1, unit=edges_dti.unit).as_unit(edges_dti.unit) 

2658 ) 

2659 bin_edges = edges_dti.tz_localize(binner.tz).asi8 

2660 else: 

2661 bin_edges = binner.asi8 

2662 

2663 # intraday values on last day 

2664 if bin_edges[-2] > ax_values.max(): 

2665 bin_edges = bin_edges[:-1] 

2666 binner = binner[:-1] 

2667 else: 

2668 bin_edges = binner.asi8 

2669 return binner, bin_edges 

2670 

2671 def _get_time_delta_bins(self, ax: TimedeltaIndex): 

2672 if not isinstance(ax, TimedeltaIndex): 

2673 raise TypeError( 

2674 "axis must be a TimedeltaIndex, but got " 

2675 f"an instance of {type(ax).__name__}" 

2676 ) 

2677 

2678 if not isinstance(self.freq, (Tick, Day)): 

2679 # GH#51896 

2680 raise ValueError( 

2681 "Resampling on a TimedeltaIndex requires fixed-duration `freq`, " 

2682 f"e.g. '24h' or '3D', not {self.freq}" 

2683 ) 

2684 

2685 if not len(ax): 

2686 binner = labels = TimedeltaIndex(data=[], freq=self.freq, name=ax.name) 

2687 return binner, [], labels 

2688 

2689 start, end = ax.min(), ax.max() 

2690 

2691 if self.closed == "right": 

2692 end += self.freq 

2693 

2694 labels = binner = timedelta_range( 

2695 start=start, end=end, freq=self.freq, name=ax.name 

2696 ) 

2697 

2698 end_stamps = labels 

2699 if self.closed == "left": 

2700 end_stamps += self.freq 

2701 

2702 bins = ax.searchsorted(end_stamps, side=self.closed) 

2703 

2704 if self.offset: 

2705 # GH 10530 & 31809 

2706 labels += self.offset 

2707 

2708 return binner, bins, labels 

2709 

2710 def _get_time_period_bins(self, ax: DatetimeIndex): 

2711 if not isinstance(ax, DatetimeIndex): 

2712 raise TypeError( 

2713 "axis must be a DatetimeIndex, but got " 

2714 f"an instance of {type(ax).__name__}" 

2715 ) 

2716 

2717 freq = self.freq 

2718 

2719 if len(ax) == 0: 

2720 binner = labels = PeriodIndex( 

2721 data=[], freq=freq, name=ax.name, dtype=ax.dtype 

2722 ) 

2723 return binner, [], labels 

2724 

2725 labels = binner = period_range(start=ax[0], end=ax[-1], freq=freq, name=ax.name) 

2726 

2727 end_stamps = (labels + freq).asfreq(freq, "s").to_timestamp() 

2728 if ax.tz: 

2729 end_stamps = end_stamps.tz_localize(ax.tz) 

2730 bins = ax.searchsorted(end_stamps, side="left") 

2731 

2732 return binner, bins, labels 

2733 

2734 def _get_period_bins(self, ax: PeriodIndex): 

2735 if not isinstance(ax, PeriodIndex): 

2736 raise TypeError( 

2737 "axis must be a PeriodIndex, but got " 

2738 f"an instance of {type(ax).__name__}" 

2739 ) 

2740 

2741 memb = ax.asfreq(self.freq, how=self.convention) 

2742 

2743 # NaT handling as in pandas._lib.lib.generate_bins_dt64() 

2744 nat_count = 0 

2745 if memb.hasnans: 

2746 # error: Incompatible types in assignment (expression has type 

2747 # "bool_", variable has type "int") [assignment] 

2748 nat_count = np.sum(memb._isnan) # type: ignore[assignment] 

2749 memb = memb[~memb._isnan] 

2750 

2751 if not len(memb): 

2752 # index contains no valid (non-NaT) values 

2753 bins = np.array([], dtype=np.int64) 

2754 binner = labels = PeriodIndex(data=[], freq=self.freq, name=ax.name) 

2755 if len(ax) > 0: 

2756 # index is all NaT 

2757 binner, bins, labels = _insert_nat_bin(binner, bins, labels, len(ax)) 

2758 return binner, bins, labels 

2759 

2760 freq_mult = self.freq.n 

2761 

2762 start = ax.min().asfreq(self.freq, how=self.convention) 

2763 end = ax.max().asfreq(self.freq, how="end") 

2764 bin_shift = 0 

2765 

2766 if isinstance(self.freq, Tick): 

2767 # GH 23882 & 31809: get adjusted bin edge labels with 'origin' 

2768 # and 'origin' support. This call only makes sense if the freq is a 

2769 # Tick since offset and origin are only used in those cases. 

2770 # Not doing this check could create an extra empty bin. 

2771 p_start, end = _get_period_range_edges( 

2772 start, 

2773 end, 

2774 self.freq, 

2775 closed=self.closed, 

2776 origin=self.origin, 

2777 offset=self.offset, 

2778 ) 

2779 

2780 # Get offset for bin edge (not label edge) adjustment 

2781 start_offset = Period(start, self.freq) - Period(p_start, self.freq) 

2782 # error: Item "Period" of "Union[Period, Any]" has no attribute "n" 

2783 bin_shift = start_offset.n % freq_mult # type: ignore[union-attr] 

2784 start = p_start 

2785 

2786 labels = binner = period_range( 

2787 start=start, end=end, freq=self.freq, name=ax.name 

2788 ) 

2789 

2790 i8 = memb.asi8 

2791 

2792 # when upsampling to subperiods, we need to generate enough bins 

2793 expected_bins_count = len(binner) * freq_mult 

2794 i8_extend = expected_bins_count - (i8[-1] - i8[0]) 

2795 rng = np.arange(i8[0], i8[-1] + i8_extend, freq_mult) 

2796 rng += freq_mult 

2797 # adjust bin edge indexes to account for base 

2798 rng -= bin_shift 

2799 

2800 # Wrap in PeriodArray for PeriodArray.searchsorted 

2801 prng = type(memb._data)(rng, dtype=memb.dtype) 

2802 bins = memb.searchsorted(prng, side="left") 

2803 

2804 if nat_count > 0: 

2805 binner, bins, labels = _insert_nat_bin(binner, bins, labels, nat_count) 

2806 

2807 return binner, bins, labels 

2808 

2809 def _set_grouper( 

2810 self, obj: NDFrameT, sort: bool = False, *, gpr_index: Index | None = None 

2811 ) -> tuple[NDFrameT, Index, npt.NDArray[np.intp] | None]: 

2812 obj, ax, indexer = super()._set_grouper(obj, sort, gpr_index=gpr_index) 

2813 if isinstance(ax.dtype, ArrowDtype) and ax.dtype.kind in "Mm": 

2814 self._arrow_dtype = ax.dtype 

2815 ax = Index( 

2816 cast(ArrowExtensionArray, ax.array)._maybe_convert_datelike_array() 

2817 ) 

2818 return obj, ax, indexer 

2819 

2820 

2821@overload 

2822def _take_new_index( 

2823 obj: DataFrame, indexer: npt.NDArray[np.intp], new_index: Index 

2824) -> DataFrame: ... 

2825 

2826 

2827@overload 

2828def _take_new_index( 

2829 obj: Series, indexer: npt.NDArray[np.intp], new_index: Index 

2830) -> Series: ... 

2831 

2832 

2833def _take_new_index( 

2834 obj: DataFrame | Series, 

2835 indexer: npt.NDArray[np.intp], 

2836 new_index: Index, 

2837) -> DataFrame | Series: 

2838 if isinstance(obj, ABCSeries): 

2839 new_values = algos.take_nd(obj._values, indexer) 

2840 return obj._constructor(new_values, index=new_index, name=obj.name) 

2841 elif isinstance(obj, ABCDataFrame): 

2842 new_mgr = obj._mgr.reindex_indexer(new_axis=new_index, indexer=indexer, axis=1) 

2843 return obj._constructor_from_mgr(new_mgr, axes=new_mgr.axes) 

2844 else: 

2845 raise ValueError("'obj' should be either a Series or a DataFrame") 

2846 

2847 

2848def _get_timestamp_range_edges( 

2849 first: Timestamp, 

2850 last: Timestamp, 

2851 freq: BaseOffset, 

2852 unit: TimeUnit, 

2853 closed: Literal["right", "left"] = "left", 

2854 origin: TimeGrouperOrigin = "start_day", 

2855 offset: Timedelta | None = None, 

2856) -> tuple[Timestamp, Timestamp]: 

2857 """ 

2858 Adjust the `first` Timestamp to the preceding Timestamp that resides on 

2859 the provided offset. Adjust the `last` Timestamp to the following 

2860 Timestamp that resides on the provided offset. Input Timestamps that 

2861 already reside on the offset will be adjusted depending on the type of 

2862 offset and the `closed` parameter. 

2863 

2864 Parameters 

2865 ---------- 

2866 first : pd.Timestamp 

2867 The beginning Timestamp of the range to be adjusted. 

2868 last : pd.Timestamp 

2869 The ending Timestamp of the range to be adjusted. 

2870 freq : pd.DateOffset 

2871 The dateoffset to which the Timestamps will be adjusted. 

2872 closed : {'right', 'left'}, default "left" 

2873 Which side of bin interval is closed. 

2874 origin : {'epoch', 'start', 'start_day'} or Timestamp, default 'start_day' 

2875 The timestamp on which to adjust the grouping. The timezone of origin must 

2876 match the timezone of the index. 

2877 If a timestamp is not used, these values are also supported: 

2878 

2879 - 'epoch': `origin` is 1970-01-01 

2880 - 'start': `origin` is the first value of the timeseries 

2881 - 'start_day': `origin` is the first day at midnight of the timeseries 

2882 offset : pd.Timedelta, default is None 

2883 An offset timedelta added to the origin. 

2884 

2885 Returns 

2886 ------- 

2887 A tuple of length 2, containing the adjusted pd.Timestamp objects. 

2888 """ 

2889 if isinstance(freq, Tick): 

2890 index_tz = first.tz 

2891 if isinstance(origin, Timestamp) and (origin.tz is None) != (index_tz is None): 

2892 raise ValueError("The origin must have the same timezone as the index.") 

2893 if origin == "epoch": 

2894 # set the epoch based on the timezone to have similar bins results when 

2895 # resampling on the same kind of indexes on different timezones 

2896 origin = Timestamp("1970-01-01", tz=index_tz) 

2897 

2898 first, last = _adjust_dates_anchored( 

2899 first, 

2900 last, 

2901 freq, 

2902 closed=closed, 

2903 origin=origin, 

2904 offset=offset, 

2905 unit=unit, 

2906 ) 

2907 else: 

2908 first = first.normalize() 

2909 last = last.normalize() 

2910 

2911 if closed == "left": 

2912 first = Timestamp(freq.rollback(first)) 

2913 else: 

2914 first = Timestamp(first - freq) 

2915 

2916 last = Timestamp(last + freq) 

2917 

2918 return first, last 

2919 

2920 

2921def _get_period_range_edges( 

2922 first: Period, 

2923 last: Period, 

2924 freq: BaseOffset, 

2925 closed: Literal["right", "left"] = "left", 

2926 origin: TimeGrouperOrigin = "start_day", 

2927 offset: Timedelta | None = None, 

2928) -> tuple[Period, Period]: 

2929 """ 

2930 Adjust the provided `first` and `last` Periods to the respective Period of 

2931 the given offset that encompasses them. 

2932 

2933 Parameters 

2934 ---------- 

2935 first : pd.Period 

2936 The beginning Period of the range to be adjusted. 

2937 last : pd.Period 

2938 The ending Period of the range to be adjusted. 

2939 freq : pd.DateOffset 

2940 The freq to which the Periods will be adjusted. 

2941 closed : {'right', 'left'}, default "left" 

2942 Which side of bin interval is closed. 

2943 origin : {'epoch', 'start', 'start_day'}, Timestamp, default 'start_day' 

2944 The timestamp on which to adjust the grouping. The timezone of origin must 

2945 match the timezone of the index. 

2946 

2947 If a timestamp is not used, these values are also supported: 

2948 

2949 - 'epoch': `origin` is 1970-01-01 

2950 - 'start': `origin` is the first value of the timeseries 

2951 - 'start_day': `origin` is the first day at midnight of the timeseries 

2952 offset : pd.Timedelta, default is None 

2953 An offset timedelta added to the origin. 

2954 

2955 Returns 

2956 ------- 

2957 A tuple of length 2, containing the adjusted pd.Period objects. 

2958 """ 

2959 if not all(isinstance(obj, Period) for obj in [first, last]): 

2960 raise TypeError("'first' and 'last' must be instances of type Period") 

2961 

2962 # GH 23882 

2963 first_ts = first.to_timestamp() 

2964 last_ts = last.to_timestamp() 

2965 adjust_first = not freq.is_on_offset(first_ts) 

2966 adjust_last = freq.is_on_offset(last_ts) 

2967 

2968 first_ts, last_ts = _get_timestamp_range_edges( 

2969 first_ts, last_ts, freq, unit="ns", closed=closed, origin=origin, offset=offset 

2970 ) 

2971 

2972 first = (first_ts + int(adjust_first) * freq).to_period(freq) 

2973 last = (last_ts - int(adjust_last) * freq).to_period(freq) 

2974 return first, last 

2975 

2976 

2977def _insert_nat_bin( 

2978 binner: PeriodIndex, bins: np.ndarray, labels: PeriodIndex, nat_count: int 

2979) -> tuple[PeriodIndex, np.ndarray, PeriodIndex]: 

2980 # NaT handling as in pandas._lib.lib.generate_bins_dt64() 

2981 # shift bins by the number of NaT 

2982 assert nat_count > 0 

2983 bins += nat_count 

2984 bins = np.insert(bins, 0, nat_count) 

2985 

2986 # Incompatible types in assignment (expression has type "Index", variable 

2987 # has type "PeriodIndex") 

2988 binner = binner.insert(0, NaT) # type: ignore[assignment] 

2989 # Incompatible types in assignment (expression has type "Index", variable 

2990 # has type "PeriodIndex") 

2991 labels = labels.insert(0, NaT) # type: ignore[assignment] 

2992 return binner, bins, labels 

2993 

2994 

2995def _adjust_dates_anchored( 

2996 first: Timestamp, 

2997 last: Timestamp, 

2998 freq: Tick, 

2999 closed: Literal["right", "left"] = "right", 

3000 origin: TimeGrouperOrigin = "start_day", 

3001 offset: Timedelta | None = None, 

3002 unit: TimeUnit = "ns", 

3003) -> tuple[Timestamp, Timestamp]: 

3004 # First and last offsets should be calculated from the start day to fix an 

3005 # error cause by resampling across multiple days when a one day period is 

3006 # not a multiple of the frequency. See GH 8683 

3007 # To handle frequencies that are not multiple or divisible by a day we let 

3008 # the possibility to define a fixed origin timestamp. See GH 31809 

3009 first = first.as_unit(unit) 

3010 last = last.as_unit(unit) 

3011 if offset is not None: 

3012 offset = offset.as_unit(unit) 

3013 

3014 freq_value = Timedelta(freq).as_unit(unit)._value 

3015 

3016 origin_timestamp = 0 # origin == "epoch" 

3017 if origin == "start_day": 

3018 origin_timestamp = first.normalize()._value 

3019 elif origin == "start": 

3020 origin_timestamp = first._value 

3021 elif isinstance(origin, Timestamp): 

3022 origin_timestamp = origin.as_unit(unit)._value 

3023 elif origin in ["end", "end_day"]: 

3024 origin_last = last if origin == "end" else last.ceil("D") 

3025 sub_freq_times = (origin_last._value - first._value) // freq_value 

3026 if closed == "left": 

3027 sub_freq_times += 1 

3028 first = origin_last - sub_freq_times * freq 

3029 origin_timestamp = first._value 

3030 origin_timestamp += offset._value if offset else 0 

3031 

3032 # GH 10117 & GH 19375. If first and last contain timezone information, 

3033 # Perform the calculation in UTC in order to avoid localizing on an 

3034 # Ambiguous or Nonexistent time. 

3035 first_tzinfo = first.tzinfo 

3036 last_tzinfo = last.tzinfo 

3037 if first_tzinfo is not None: 

3038 first = first.tz_convert("UTC") 

3039 if last_tzinfo is not None: 

3040 last = last.tz_convert("UTC") 

3041 

3042 foffset = (first._value - origin_timestamp) % freq_value 

3043 loffset = (last._value - origin_timestamp) % freq_value 

3044 

3045 if closed == "right": 

3046 if foffset > 0: 

3047 # roll back 

3048 fresult_int = first._value - foffset 

3049 else: 

3050 fresult_int = first._value - freq_value 

3051 

3052 if loffset > 0: 

3053 # roll forward 

3054 lresult_int = last._value + (freq_value - loffset) 

3055 else: 

3056 # already the end of the road 

3057 lresult_int = last._value 

3058 else: # closed == 'left' 

3059 if foffset > 0: 

3060 fresult_int = first._value - foffset 

3061 else: 

3062 # start of the road 

3063 fresult_int = first._value 

3064 

3065 if loffset > 0: 

3066 # roll forward 

3067 lresult_int = last._value + (freq_value - loffset) 

3068 else: 

3069 lresult_int = last._value + freq_value 

3070 fresult = Timestamp(fresult_int, unit=unit) 

3071 lresult = Timestamp(lresult_int, unit=unit) 

3072 if first_tzinfo is not None: 

3073 fresult = fresult.tz_localize("UTC").tz_convert(first_tzinfo) 

3074 if last_tzinfo is not None: 

3075 lresult = lresult.tz_localize("UTC").tz_convert(last_tzinfo) 

3076 return fresult, lresult 

3077 

3078 

3079def asfreq( 

3080 obj: NDFrameT, 

3081 freq, 

3082 method=None, 

3083 how=None, 

3084 normalize: bool = False, 

3085 fill_value=None, 

3086) -> NDFrameT: 

3087 """ 

3088 Utility frequency conversion method for Series/DataFrame. 

3089 

3090 See :meth:`pandas.NDFrame.asfreq` for full documentation. 

3091 """ 

3092 if isinstance(obj.index, PeriodIndex): 

3093 if method is not None: 

3094 raise NotImplementedError("'method' argument is not supported") 

3095 

3096 if how is None: 

3097 how = "E" 

3098 

3099 if isinstance(freq, BaseOffset): 

3100 if hasattr(freq, "_period_dtype_code"): 

3101 freq = PeriodDtype(freq)._freqstr 

3102 

3103 new_obj = obj.copy() 

3104 new_obj.index = obj.index.asfreq(freq, how=how) 

3105 

3106 elif len(obj.index) == 0: 

3107 new_obj = obj.copy() 

3108 

3109 new_obj.index = _asfreq_compat(obj.index, freq) 

3110 else: 

3111 unit: TimeUnit = "ns" 

3112 if isinstance(obj.index, DatetimeIndex): 

3113 # TODO: should we disallow non-DatetimeIndex? 

3114 unit = obj.index.unit 

3115 dti = date_range(obj.index.min(), obj.index.max(), freq=freq, unit=unit) 

3116 dti.name = obj.index.name 

3117 new_obj = obj.reindex(dti, method=method, fill_value=fill_value) 

3118 if normalize: 

3119 new_obj.index = new_obj.index.normalize() 

3120 

3121 return new_obj 

3122 

3123 

3124def _asfreq_compat(index: FreqIndexT, freq) -> FreqIndexT: 

3125 """ 

3126 Helper to mimic asfreq on (empty) DatetimeIndex and TimedeltaIndex. 

3127 

3128 Parameters 

3129 ---------- 

3130 index : PeriodIndex, DatetimeIndex, or TimedeltaIndex 

3131 freq : DateOffset 

3132 

3133 Returns 

3134 ------- 

3135 same type as index 

3136 """ 

3137 if len(index) != 0: 

3138 # This should never be reached, always checked by the caller 

3139 raise ValueError( 

3140 "Can only set arbitrary freq for empty DatetimeIndex or TimedeltaIndex" 

3141 ) 

3142 if isinstance(index, PeriodIndex): 

3143 new_index = index.asfreq(freq=freq) 

3144 elif isinstance(index, DatetimeIndex): 

3145 new_index = DatetimeIndex([], dtype=index.dtype, freq=freq, name=index.name) 

3146 elif isinstance(index, TimedeltaIndex): 

3147 new_index = TimedeltaIndex([], dtype=index.dtype, freq=freq, name=index.name) 

3148 else: # pragma: no cover 

3149 raise TypeError(type(index)) 

3150 return new_index