Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/pandas/tseries/frequencies.py
7813 views
1
from __future__ import annotations
2
3
import warnings
4
5
import numpy as np
6
7
from pandas._libs.algos import unique_deltas
8
from pandas._libs.tslibs import (
9
Timestamp,
10
tzconversion,
11
)
12
from pandas._libs.tslibs.ccalendar import (
13
DAYS,
14
MONTH_ALIASES,
15
MONTH_NUMBERS,
16
MONTHS,
17
int_to_weekday,
18
)
19
from pandas._libs.tslibs.fields import (
20
build_field_sarray,
21
month_position_check,
22
)
23
from pandas._libs.tslibs.offsets import ( # noqa:F401
24
DateOffset,
25
Day,
26
_get_offset,
27
to_offset,
28
)
29
from pandas._libs.tslibs.parsing import get_rule_month
30
from pandas._typing import npt
31
from pandas.util._decorators import cache_readonly
32
from pandas.util._exceptions import find_stack_level
33
34
from pandas.core.dtypes.common import (
35
is_datetime64_dtype,
36
is_period_dtype,
37
is_timedelta64_dtype,
38
)
39
from pandas.core.dtypes.generic import ABCSeries
40
41
from pandas.core.algorithms import unique
42
43
_ONE_MICRO = 1000
44
_ONE_MILLI = _ONE_MICRO * 1000
45
_ONE_SECOND = _ONE_MILLI * 1000
46
_ONE_MINUTE = 60 * _ONE_SECOND
47
_ONE_HOUR = 60 * _ONE_MINUTE
48
_ONE_DAY = 24 * _ONE_HOUR
49
50
# ---------------------------------------------------------------------
51
# Offset names ("time rules") and related functions
52
53
_offset_to_period_map = {
54
"WEEKDAY": "D",
55
"EOM": "M",
56
"BM": "M",
57
"BQS": "Q",
58
"QS": "Q",
59
"BQ": "Q",
60
"BA": "A",
61
"AS": "A",
62
"BAS": "A",
63
"MS": "M",
64
"D": "D",
65
"C": "C",
66
"B": "B",
67
"T": "T",
68
"S": "S",
69
"L": "L",
70
"U": "U",
71
"N": "N",
72
"H": "H",
73
"Q": "Q",
74
"A": "A",
75
"W": "W",
76
"M": "M",
77
"Y": "A",
78
"BY": "A",
79
"YS": "A",
80
"BYS": "A",
81
}
82
83
_need_suffix = ["QS", "BQ", "BQS", "YS", "AS", "BY", "BA", "BYS", "BAS"]
84
85
for _prefix in _need_suffix:
86
for _m in MONTHS:
87
key = f"{_prefix}-{_m}"
88
_offset_to_period_map[key] = _offset_to_period_map[_prefix]
89
90
for _prefix in ["A", "Q"]:
91
for _m in MONTHS:
92
_alias = f"{_prefix}-{_m}"
93
_offset_to_period_map[_alias] = _alias
94
95
for _d in DAYS:
96
_offset_to_period_map[f"W-{_d}"] = f"W-{_d}"
97
98
99
def get_period_alias(offset_str: str) -> str | None:
100
"""
101
Alias to closest period strings BQ->Q etc.
102
"""
103
return _offset_to_period_map.get(offset_str, None)
104
105
106
def get_offset(name: str) -> DateOffset:
107
"""
108
Return DateOffset object associated with rule name.
109
110
.. deprecated:: 1.0.0
111
112
Examples
113
--------
114
get_offset('EOM') --> BMonthEnd(1)
115
"""
116
warnings.warn(
117
"get_offset is deprecated and will be removed in a future version, "
118
"use to_offset instead.",
119
FutureWarning,
120
stacklevel=find_stack_level(),
121
)
122
return _get_offset(name)
123
124
125
# ---------------------------------------------------------------------
126
# Period codes
127
128
129
def infer_freq(index, warn: bool = True) -> str | None:
130
"""
131
Infer the most likely frequency given the input index. If the frequency is
132
uncertain, a warning will be printed.
133
134
Parameters
135
----------
136
index : DatetimeIndex or TimedeltaIndex
137
If passed a Series will use the values of the series (NOT THE INDEX).
138
warn : bool, default True
139
140
Returns
141
-------
142
str or None
143
None if no discernible frequency.
144
145
Raises
146
------
147
TypeError
148
If the index is not datetime-like.
149
ValueError
150
If there are fewer than three values.
151
152
Examples
153
--------
154
>>> idx = pd.date_range(start='2020/12/01', end='2020/12/30', periods=30)
155
>>> pd.infer_freq(idx)
156
'D'
157
"""
158
from pandas.core.api import (
159
DatetimeIndex,
160
Float64Index,
161
Index,
162
Int64Index,
163
)
164
165
if isinstance(index, ABCSeries):
166
values = index._values
167
if not (
168
is_datetime64_dtype(values)
169
or is_timedelta64_dtype(values)
170
or values.dtype == object
171
):
172
raise TypeError(
173
"cannot infer freq from a non-convertible dtype "
174
f"on a Series of {index.dtype}"
175
)
176
index = values
177
178
inferer: _FrequencyInferer
179
180
if not hasattr(index, "dtype"):
181
pass
182
elif is_period_dtype(index.dtype):
183
raise TypeError(
184
"PeriodIndex given. Check the `freq` attribute "
185
"instead of using infer_freq."
186
)
187
elif is_timedelta64_dtype(index.dtype):
188
# Allow TimedeltaIndex and TimedeltaArray
189
inferer = _TimedeltaFrequencyInferer(index, warn=warn)
190
return inferer.get_freq()
191
192
if isinstance(index, Index) and not isinstance(index, DatetimeIndex):
193
if isinstance(index, (Int64Index, Float64Index)):
194
raise TypeError(
195
f"cannot infer freq from a non-convertible index type {type(index)}"
196
)
197
index = index._values
198
199
if not isinstance(index, DatetimeIndex):
200
index = DatetimeIndex(index)
201
202
inferer = _FrequencyInferer(index, warn=warn)
203
return inferer.get_freq()
204
205
206
class _FrequencyInferer:
207
"""
208
Not sure if I can avoid the state machine here
209
"""
210
211
def __init__(self, index, warn: bool = True):
212
self.index = index
213
self.i8values = index.asi8
214
215
# This moves the values, which are implicitly in UTC, to the
216
# the timezone so they are in local time
217
if hasattr(index, "tz"):
218
if index.tz is not None:
219
self.i8values = tzconversion.tz_convert_from_utc(
220
self.i8values, index.tz
221
)
222
223
self.warn = warn
224
225
if len(index) < 3:
226
raise ValueError("Need at least 3 dates to infer frequency")
227
228
self.is_monotonic = (
229
self.index._is_monotonic_increasing or self.index._is_monotonic_decreasing
230
)
231
232
@cache_readonly
233
def deltas(self) -> npt.NDArray[np.int64]:
234
return unique_deltas(self.i8values)
235
236
@cache_readonly
237
def deltas_asi8(self) -> npt.NDArray[np.int64]:
238
# NB: we cannot use self.i8values here because we may have converted
239
# the tz in __init__
240
return unique_deltas(self.index.asi8)
241
242
@cache_readonly
243
def is_unique(self) -> bool:
244
return len(self.deltas) == 1
245
246
@cache_readonly
247
def is_unique_asi8(self) -> bool:
248
return len(self.deltas_asi8) == 1
249
250
def get_freq(self) -> str | None:
251
"""
252
Find the appropriate frequency string to describe the inferred
253
frequency of self.i8values
254
255
Returns
256
-------
257
str or None
258
"""
259
if not self.is_monotonic or not self.index._is_unique:
260
return None
261
262
delta = self.deltas[0]
263
if delta and _is_multiple(delta, _ONE_DAY):
264
return self._infer_daily_rule()
265
266
# Business hourly, maybe. 17: one day / 65: one weekend
267
if self.hour_deltas in ([1, 17], [1, 65], [1, 17, 65]):
268
return "BH"
269
270
# Possibly intraday frequency. Here we use the
271
# original .asi8 values as the modified values
272
# will not work around DST transitions. See #8772
273
if not self.is_unique_asi8:
274
return None
275
276
delta = self.deltas_asi8[0]
277
if _is_multiple(delta, _ONE_HOUR):
278
# Hours
279
return _maybe_add_count("H", delta / _ONE_HOUR)
280
elif _is_multiple(delta, _ONE_MINUTE):
281
# Minutes
282
return _maybe_add_count("T", delta / _ONE_MINUTE)
283
elif _is_multiple(delta, _ONE_SECOND):
284
# Seconds
285
return _maybe_add_count("S", delta / _ONE_SECOND)
286
elif _is_multiple(delta, _ONE_MILLI):
287
# Milliseconds
288
return _maybe_add_count("L", delta / _ONE_MILLI)
289
elif _is_multiple(delta, _ONE_MICRO):
290
# Microseconds
291
return _maybe_add_count("U", delta / _ONE_MICRO)
292
else:
293
# Nanoseconds
294
return _maybe_add_count("N", delta)
295
296
@cache_readonly
297
def day_deltas(self):
298
return [x / _ONE_DAY for x in self.deltas]
299
300
@cache_readonly
301
def hour_deltas(self):
302
return [x / _ONE_HOUR for x in self.deltas]
303
304
@cache_readonly
305
def fields(self) -> np.ndarray: # structured array of fields
306
return build_field_sarray(self.i8values)
307
308
@cache_readonly
309
def rep_stamp(self):
310
return Timestamp(self.i8values[0])
311
312
def month_position_check(self):
313
return month_position_check(self.fields, self.index.dayofweek)
314
315
@cache_readonly
316
def mdiffs(self) -> npt.NDArray[np.int64]:
317
nmonths = self.fields["Y"] * 12 + self.fields["M"]
318
return unique_deltas(nmonths.astype("i8"))
319
320
@cache_readonly
321
def ydiffs(self) -> npt.NDArray[np.int64]:
322
return unique_deltas(self.fields["Y"].astype("i8"))
323
324
def _infer_daily_rule(self) -> str | None:
325
annual_rule = self._get_annual_rule()
326
if annual_rule:
327
nyears = self.ydiffs[0]
328
month = MONTH_ALIASES[self.rep_stamp.month]
329
alias = f"{annual_rule}-{month}"
330
return _maybe_add_count(alias, nyears)
331
332
quarterly_rule = self._get_quarterly_rule()
333
if quarterly_rule:
334
nquarters = self.mdiffs[0] / 3
335
mod_dict = {0: 12, 2: 11, 1: 10}
336
month = MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]]
337
alias = f"{quarterly_rule}-{month}"
338
return _maybe_add_count(alias, nquarters)
339
340
monthly_rule = self._get_monthly_rule()
341
if monthly_rule:
342
return _maybe_add_count(monthly_rule, self.mdiffs[0])
343
344
if self.is_unique:
345
return self._get_daily_rule()
346
347
if self._is_business_daily():
348
return "B"
349
350
wom_rule = self._get_wom_rule()
351
if wom_rule:
352
return wom_rule
353
354
return None
355
356
def _get_daily_rule(self) -> str | None:
357
days = self.deltas[0] / _ONE_DAY
358
if days % 7 == 0:
359
# Weekly
360
wd = int_to_weekday[self.rep_stamp.weekday()]
361
alias = f"W-{wd}"
362
return _maybe_add_count(alias, days / 7)
363
else:
364
return _maybe_add_count("D", days)
365
366
def _get_annual_rule(self) -> str | None:
367
if len(self.ydiffs) > 1:
368
return None
369
370
if len(unique(self.fields["M"])) > 1:
371
return None
372
373
pos_check = self.month_position_check()
374
return {"cs": "AS", "bs": "BAS", "ce": "A", "be": "BA"}.get(pos_check)
375
376
def _get_quarterly_rule(self) -> str | None:
377
if len(self.mdiffs) > 1:
378
return None
379
380
if not self.mdiffs[0] % 3 == 0:
381
return None
382
383
pos_check = self.month_position_check()
384
return {"cs": "QS", "bs": "BQS", "ce": "Q", "be": "BQ"}.get(pos_check)
385
386
def _get_monthly_rule(self) -> str | None:
387
if len(self.mdiffs) > 1:
388
return None
389
pos_check = self.month_position_check()
390
return {"cs": "MS", "bs": "BMS", "ce": "M", "be": "BM"}.get(pos_check)
391
392
def _is_business_daily(self) -> bool:
393
# quick check: cannot be business daily
394
if self.day_deltas != [1, 3]:
395
return False
396
397
# probably business daily, but need to confirm
398
first_weekday = self.index[0].weekday()
399
shifts = np.diff(self.index.asi8)
400
shifts = np.floor_divide(shifts, _ONE_DAY)
401
weekdays = np.mod(first_weekday + np.cumsum(shifts), 7)
402
403
return bool(
404
np.all(
405
((weekdays == 0) & (shifts == 3))
406
| ((weekdays > 0) & (weekdays <= 4) & (shifts == 1))
407
)
408
)
409
410
def _get_wom_rule(self) -> str | None:
411
# FIXME: dont leave commented-out
412
# wdiffs = unique(np.diff(self.index.week))
413
# We also need -47, -49, -48 to catch index spanning year boundary
414
# if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all():
415
# return None
416
417
weekdays = unique(self.index.weekday)
418
if len(weekdays) > 1:
419
return None
420
421
week_of_months = unique((self.index.day - 1) // 7)
422
# Only attempt to infer up to WOM-4. See #9425
423
week_of_months = week_of_months[week_of_months < 4]
424
if len(week_of_months) == 0 or len(week_of_months) > 1:
425
return None
426
427
# get which week
428
week = week_of_months[0] + 1
429
wd = int_to_weekday[weekdays[0]]
430
431
return f"WOM-{week}{wd}"
432
433
434
class _TimedeltaFrequencyInferer(_FrequencyInferer):
435
def _infer_daily_rule(self):
436
if self.is_unique:
437
return self._get_daily_rule()
438
439
440
def _is_multiple(us, mult: int) -> bool:
441
return us % mult == 0
442
443
444
def _maybe_add_count(base: str, count: float) -> str:
445
if count != 1:
446
assert count == int(count)
447
count = int(count)
448
return f"{count}{base}"
449
else:
450
return base
451
452
453
# ----------------------------------------------------------------------
454
# Frequency comparison
455
456
457
def is_subperiod(source, target) -> bool:
458
"""
459
Returns True if downsampling is possible between source and target
460
frequencies
461
462
Parameters
463
----------
464
source : str or DateOffset
465
Frequency converting from
466
target : str or DateOffset
467
Frequency converting to
468
469
Returns
470
-------
471
bool
472
"""
473
474
if target is None or source is None:
475
return False
476
source = _maybe_coerce_freq(source)
477
target = _maybe_coerce_freq(target)
478
479
if _is_annual(target):
480
if _is_quarterly(source):
481
return _quarter_months_conform(
482
get_rule_month(source), get_rule_month(target)
483
)
484
return source in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}
485
elif _is_quarterly(target):
486
return source in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}
487
elif _is_monthly(target):
488
return source in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}
489
elif _is_weekly(target):
490
return source in {target, "D", "C", "B", "H", "T", "S", "L", "U", "N"}
491
elif target == "B":
492
return source in {"B", "H", "T", "S", "L", "U", "N"}
493
elif target == "C":
494
return source in {"C", "H", "T", "S", "L", "U", "N"}
495
elif target == "D":
496
return source in {"D", "H", "T", "S", "L", "U", "N"}
497
elif target == "H":
498
return source in {"H", "T", "S", "L", "U", "N"}
499
elif target == "T":
500
return source in {"T", "S", "L", "U", "N"}
501
elif target == "S":
502
return source in {"S", "L", "U", "N"}
503
elif target == "L":
504
return source in {"L", "U", "N"}
505
elif target == "U":
506
return source in {"U", "N"}
507
elif target == "N":
508
return source in {"N"}
509
else:
510
return False
511
512
513
def is_superperiod(source, target) -> bool:
514
"""
515
Returns True if upsampling is possible between source and target
516
frequencies
517
518
Parameters
519
----------
520
source : str or DateOffset
521
Frequency converting from
522
target : str or DateOffset
523
Frequency converting to
524
525
Returns
526
-------
527
bool
528
"""
529
if target is None or source is None:
530
return False
531
source = _maybe_coerce_freq(source)
532
target = _maybe_coerce_freq(target)
533
534
if _is_annual(source):
535
if _is_annual(target):
536
return get_rule_month(source) == get_rule_month(target)
537
538
if _is_quarterly(target):
539
smonth = get_rule_month(source)
540
tmonth = get_rule_month(target)
541
return _quarter_months_conform(smonth, tmonth)
542
return target in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}
543
elif _is_quarterly(source):
544
return target in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}
545
elif _is_monthly(source):
546
return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}
547
elif _is_weekly(source):
548
return target in {source, "D", "C", "B", "H", "T", "S", "L", "U", "N"}
549
elif source == "B":
550
return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}
551
elif source == "C":
552
return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}
553
elif source == "D":
554
return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}
555
elif source == "H":
556
return target in {"H", "T", "S", "L", "U", "N"}
557
elif source == "T":
558
return target in {"T", "S", "L", "U", "N"}
559
elif source == "S":
560
return target in {"S", "L", "U", "N"}
561
elif source == "L":
562
return target in {"L", "U", "N"}
563
elif source == "U":
564
return target in {"U", "N"}
565
elif source == "N":
566
return target in {"N"}
567
else:
568
return False
569
570
571
def _maybe_coerce_freq(code) -> str:
572
"""we might need to coerce a code to a rule_code
573
and uppercase it
574
575
Parameters
576
----------
577
source : str or DateOffset
578
Frequency converting from
579
580
Returns
581
-------
582
str
583
"""
584
assert code is not None
585
if isinstance(code, DateOffset):
586
code = code.rule_code
587
return code.upper()
588
589
590
def _quarter_months_conform(source: str, target: str) -> bool:
591
snum = MONTH_NUMBERS[source]
592
tnum = MONTH_NUMBERS[target]
593
return snum % 3 == tnum % 3
594
595
596
def _is_annual(rule: str) -> bool:
597
rule = rule.upper()
598
return rule == "A" or rule.startswith("A-")
599
600
601
def _is_quarterly(rule: str) -> bool:
602
rule = rule.upper()
603
return rule == "Q" or rule.startswith("Q-") or rule.startswith("BQ")
604
605
606
def _is_monthly(rule: str) -> bool:
607
rule = rule.upper()
608
return rule == "M" or rule == "BM"
609
610
611
def _is_weekly(rule: str) -> bool:
612
rule = rule.upper()
613
return rule == "W" or rule.startswith("W-")
614
615