Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/pandas/tseries/frequencies.py
7813 views
from __future__ import annotations12import warnings34import numpy as np56from pandas._libs.algos import unique_deltas7from pandas._libs.tslibs import (8Timestamp,9tzconversion,10)11from pandas._libs.tslibs.ccalendar import (12DAYS,13MONTH_ALIASES,14MONTH_NUMBERS,15MONTHS,16int_to_weekday,17)18from pandas._libs.tslibs.fields import (19build_field_sarray,20month_position_check,21)22from pandas._libs.tslibs.offsets import ( # noqa:F40123DateOffset,24Day,25_get_offset,26to_offset,27)28from pandas._libs.tslibs.parsing import get_rule_month29from pandas._typing import npt30from pandas.util._decorators import cache_readonly31from pandas.util._exceptions import find_stack_level3233from pandas.core.dtypes.common import (34is_datetime64_dtype,35is_period_dtype,36is_timedelta64_dtype,37)38from pandas.core.dtypes.generic import ABCSeries3940from pandas.core.algorithms import unique4142_ONE_MICRO = 100043_ONE_MILLI = _ONE_MICRO * 100044_ONE_SECOND = _ONE_MILLI * 100045_ONE_MINUTE = 60 * _ONE_SECOND46_ONE_HOUR = 60 * _ONE_MINUTE47_ONE_DAY = 24 * _ONE_HOUR4849# ---------------------------------------------------------------------50# Offset names ("time rules") and related functions5152_offset_to_period_map = {53"WEEKDAY": "D",54"EOM": "M",55"BM": "M",56"BQS": "Q",57"QS": "Q",58"BQ": "Q",59"BA": "A",60"AS": "A",61"BAS": "A",62"MS": "M",63"D": "D",64"C": "C",65"B": "B",66"T": "T",67"S": "S",68"L": "L",69"U": "U",70"N": "N",71"H": "H",72"Q": "Q",73"A": "A",74"W": "W",75"M": "M",76"Y": "A",77"BY": "A",78"YS": "A",79"BYS": "A",80}8182_need_suffix = ["QS", "BQ", "BQS", "YS", "AS", "BY", "BA", "BYS", "BAS"]8384for _prefix in _need_suffix:85for _m in MONTHS:86key = f"{_prefix}-{_m}"87_offset_to_period_map[key] = _offset_to_period_map[_prefix]8889for _prefix in ["A", "Q"]:90for _m in MONTHS:91_alias = f"{_prefix}-{_m}"92_offset_to_period_map[_alias] = _alias9394for _d in DAYS:95_offset_to_period_map[f"W-{_d}"] = f"W-{_d}"969798def get_period_alias(offset_str: str) -> str | None:99"""100Alias to closest period strings BQ->Q etc.101"""102return _offset_to_period_map.get(offset_str, None)103104105def get_offset(name: str) -> DateOffset:106"""107Return DateOffset object associated with rule name.108109.. deprecated:: 1.0.0110111Examples112--------113get_offset('EOM') --> BMonthEnd(1)114"""115warnings.warn(116"get_offset is deprecated and will be removed in a future version, "117"use to_offset instead.",118FutureWarning,119stacklevel=find_stack_level(),120)121return _get_offset(name)122123124# ---------------------------------------------------------------------125# Period codes126127128def infer_freq(index, warn: bool = True) -> str | None:129"""130Infer the most likely frequency given the input index. If the frequency is131uncertain, a warning will be printed.132133Parameters134----------135index : DatetimeIndex or TimedeltaIndex136If passed a Series will use the values of the series (NOT THE INDEX).137warn : bool, default True138139Returns140-------141str or None142None if no discernible frequency.143144Raises145------146TypeError147If the index is not datetime-like.148ValueError149If there are fewer than three values.150151Examples152--------153>>> idx = pd.date_range(start='2020/12/01', end='2020/12/30', periods=30)154>>> pd.infer_freq(idx)155'D'156"""157from pandas.core.api import (158DatetimeIndex,159Float64Index,160Index,161Int64Index,162)163164if isinstance(index, ABCSeries):165values = index._values166if not (167is_datetime64_dtype(values)168or is_timedelta64_dtype(values)169or values.dtype == object170):171raise TypeError(172"cannot infer freq from a non-convertible dtype "173f"on a Series of {index.dtype}"174)175index = values176177inferer: _FrequencyInferer178179if not hasattr(index, "dtype"):180pass181elif is_period_dtype(index.dtype):182raise TypeError(183"PeriodIndex given. Check the `freq` attribute "184"instead of using infer_freq."185)186elif is_timedelta64_dtype(index.dtype):187# Allow TimedeltaIndex and TimedeltaArray188inferer = _TimedeltaFrequencyInferer(index, warn=warn)189return inferer.get_freq()190191if isinstance(index, Index) and not isinstance(index, DatetimeIndex):192if isinstance(index, (Int64Index, Float64Index)):193raise TypeError(194f"cannot infer freq from a non-convertible index type {type(index)}"195)196index = index._values197198if not isinstance(index, DatetimeIndex):199index = DatetimeIndex(index)200201inferer = _FrequencyInferer(index, warn=warn)202return inferer.get_freq()203204205class _FrequencyInferer:206"""207Not sure if I can avoid the state machine here208"""209210def __init__(self, index, warn: bool = True):211self.index = index212self.i8values = index.asi8213214# This moves the values, which are implicitly in UTC, to the215# the timezone so they are in local time216if hasattr(index, "tz"):217if index.tz is not None:218self.i8values = tzconversion.tz_convert_from_utc(219self.i8values, index.tz220)221222self.warn = warn223224if len(index) < 3:225raise ValueError("Need at least 3 dates to infer frequency")226227self.is_monotonic = (228self.index._is_monotonic_increasing or self.index._is_monotonic_decreasing229)230231@cache_readonly232def deltas(self) -> npt.NDArray[np.int64]:233return unique_deltas(self.i8values)234235@cache_readonly236def deltas_asi8(self) -> npt.NDArray[np.int64]:237# NB: we cannot use self.i8values here because we may have converted238# the tz in __init__239return unique_deltas(self.index.asi8)240241@cache_readonly242def is_unique(self) -> bool:243return len(self.deltas) == 1244245@cache_readonly246def is_unique_asi8(self) -> bool:247return len(self.deltas_asi8) == 1248249def get_freq(self) -> str | None:250"""251Find the appropriate frequency string to describe the inferred252frequency of self.i8values253254Returns255-------256str or None257"""258if not self.is_monotonic or not self.index._is_unique:259return None260261delta = self.deltas[0]262if delta and _is_multiple(delta, _ONE_DAY):263return self._infer_daily_rule()264265# Business hourly, maybe. 17: one day / 65: one weekend266if self.hour_deltas in ([1, 17], [1, 65], [1, 17, 65]):267return "BH"268269# Possibly intraday frequency. Here we use the270# original .asi8 values as the modified values271# will not work around DST transitions. See #8772272if not self.is_unique_asi8:273return None274275delta = self.deltas_asi8[0]276if _is_multiple(delta, _ONE_HOUR):277# Hours278return _maybe_add_count("H", delta / _ONE_HOUR)279elif _is_multiple(delta, _ONE_MINUTE):280# Minutes281return _maybe_add_count("T", delta / _ONE_MINUTE)282elif _is_multiple(delta, _ONE_SECOND):283# Seconds284return _maybe_add_count("S", delta / _ONE_SECOND)285elif _is_multiple(delta, _ONE_MILLI):286# Milliseconds287return _maybe_add_count("L", delta / _ONE_MILLI)288elif _is_multiple(delta, _ONE_MICRO):289# Microseconds290return _maybe_add_count("U", delta / _ONE_MICRO)291else:292# Nanoseconds293return _maybe_add_count("N", delta)294295@cache_readonly296def day_deltas(self):297return [x / _ONE_DAY for x in self.deltas]298299@cache_readonly300def hour_deltas(self):301return [x / _ONE_HOUR for x in self.deltas]302303@cache_readonly304def fields(self) -> np.ndarray: # structured array of fields305return build_field_sarray(self.i8values)306307@cache_readonly308def rep_stamp(self):309return Timestamp(self.i8values[0])310311def month_position_check(self):312return month_position_check(self.fields, self.index.dayofweek)313314@cache_readonly315def mdiffs(self) -> npt.NDArray[np.int64]:316nmonths = self.fields["Y"] * 12 + self.fields["M"]317return unique_deltas(nmonths.astype("i8"))318319@cache_readonly320def ydiffs(self) -> npt.NDArray[np.int64]:321return unique_deltas(self.fields["Y"].astype("i8"))322323def _infer_daily_rule(self) -> str | None:324annual_rule = self._get_annual_rule()325if annual_rule:326nyears = self.ydiffs[0]327month = MONTH_ALIASES[self.rep_stamp.month]328alias = f"{annual_rule}-{month}"329return _maybe_add_count(alias, nyears)330331quarterly_rule = self._get_quarterly_rule()332if quarterly_rule:333nquarters = self.mdiffs[0] / 3334mod_dict = {0: 12, 2: 11, 1: 10}335month = MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]]336alias = f"{quarterly_rule}-{month}"337return _maybe_add_count(alias, nquarters)338339monthly_rule = self._get_monthly_rule()340if monthly_rule:341return _maybe_add_count(monthly_rule, self.mdiffs[0])342343if self.is_unique:344return self._get_daily_rule()345346if self._is_business_daily():347return "B"348349wom_rule = self._get_wom_rule()350if wom_rule:351return wom_rule352353return None354355def _get_daily_rule(self) -> str | None:356days = self.deltas[0] / _ONE_DAY357if days % 7 == 0:358# Weekly359wd = int_to_weekday[self.rep_stamp.weekday()]360alias = f"W-{wd}"361return _maybe_add_count(alias, days / 7)362else:363return _maybe_add_count("D", days)364365def _get_annual_rule(self) -> str | None:366if len(self.ydiffs) > 1:367return None368369if len(unique(self.fields["M"])) > 1:370return None371372pos_check = self.month_position_check()373return {"cs": "AS", "bs": "BAS", "ce": "A", "be": "BA"}.get(pos_check)374375def _get_quarterly_rule(self) -> str | None:376if len(self.mdiffs) > 1:377return None378379if not self.mdiffs[0] % 3 == 0:380return None381382pos_check = self.month_position_check()383return {"cs": "QS", "bs": "BQS", "ce": "Q", "be": "BQ"}.get(pos_check)384385def _get_monthly_rule(self) -> str | None:386if len(self.mdiffs) > 1:387return None388pos_check = self.month_position_check()389return {"cs": "MS", "bs": "BMS", "ce": "M", "be": "BM"}.get(pos_check)390391def _is_business_daily(self) -> bool:392# quick check: cannot be business daily393if self.day_deltas != [1, 3]:394return False395396# probably business daily, but need to confirm397first_weekday = self.index[0].weekday()398shifts = np.diff(self.index.asi8)399shifts = np.floor_divide(shifts, _ONE_DAY)400weekdays = np.mod(first_weekday + np.cumsum(shifts), 7)401402return bool(403np.all(404((weekdays == 0) & (shifts == 3))405| ((weekdays > 0) & (weekdays <= 4) & (shifts == 1))406)407)408409def _get_wom_rule(self) -> str | None:410# FIXME: dont leave commented-out411# wdiffs = unique(np.diff(self.index.week))412# We also need -47, -49, -48 to catch index spanning year boundary413# if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all():414# return None415416weekdays = unique(self.index.weekday)417if len(weekdays) > 1:418return None419420week_of_months = unique((self.index.day - 1) // 7)421# Only attempt to infer up to WOM-4. See #9425422week_of_months = week_of_months[week_of_months < 4]423if len(week_of_months) == 0 or len(week_of_months) > 1:424return None425426# get which week427week = week_of_months[0] + 1428wd = int_to_weekday[weekdays[0]]429430return f"WOM-{week}{wd}"431432433class _TimedeltaFrequencyInferer(_FrequencyInferer):434def _infer_daily_rule(self):435if self.is_unique:436return self._get_daily_rule()437438439def _is_multiple(us, mult: int) -> bool:440return us % mult == 0441442443def _maybe_add_count(base: str, count: float) -> str:444if count != 1:445assert count == int(count)446count = int(count)447return f"{count}{base}"448else:449return base450451452# ----------------------------------------------------------------------453# Frequency comparison454455456def is_subperiod(source, target) -> bool:457"""458Returns True if downsampling is possible between source and target459frequencies460461Parameters462----------463source : str or DateOffset464Frequency converting from465target : str or DateOffset466Frequency converting to467468Returns469-------470bool471"""472473if target is None or source is None:474return False475source = _maybe_coerce_freq(source)476target = _maybe_coerce_freq(target)477478if _is_annual(target):479if _is_quarterly(source):480return _quarter_months_conform(481get_rule_month(source), get_rule_month(target)482)483return source in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}484elif _is_quarterly(target):485return source in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}486elif _is_monthly(target):487return source in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}488elif _is_weekly(target):489return source in {target, "D", "C", "B", "H", "T", "S", "L", "U", "N"}490elif target == "B":491return source in {"B", "H", "T", "S", "L", "U", "N"}492elif target == "C":493return source in {"C", "H", "T", "S", "L", "U", "N"}494elif target == "D":495return source in {"D", "H", "T", "S", "L", "U", "N"}496elif target == "H":497return source in {"H", "T", "S", "L", "U", "N"}498elif target == "T":499return source in {"T", "S", "L", "U", "N"}500elif target == "S":501return source in {"S", "L", "U", "N"}502elif target == "L":503return source in {"L", "U", "N"}504elif target == "U":505return source in {"U", "N"}506elif target == "N":507return source in {"N"}508else:509return False510511512def is_superperiod(source, target) -> bool:513"""514Returns True if upsampling is possible between source and target515frequencies516517Parameters518----------519source : str or DateOffset520Frequency converting from521target : str or DateOffset522Frequency converting to523524Returns525-------526bool527"""528if target is None or source is None:529return False530source = _maybe_coerce_freq(source)531target = _maybe_coerce_freq(target)532533if _is_annual(source):534if _is_annual(target):535return get_rule_month(source) == get_rule_month(target)536537if _is_quarterly(target):538smonth = get_rule_month(source)539tmonth = get_rule_month(target)540return _quarter_months_conform(smonth, tmonth)541return target in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}542elif _is_quarterly(source):543return target in {"D", "C", "B", "M", "H", "T", "S", "L", "U", "N"}544elif _is_monthly(source):545return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}546elif _is_weekly(source):547return target in {source, "D", "C", "B", "H", "T", "S", "L", "U", "N"}548elif source == "B":549return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}550elif source == "C":551return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}552elif source == "D":553return target in {"D", "C", "B", "H", "T", "S", "L", "U", "N"}554elif source == "H":555return target in {"H", "T", "S", "L", "U", "N"}556elif source == "T":557return target in {"T", "S", "L", "U", "N"}558elif source == "S":559return target in {"S", "L", "U", "N"}560elif source == "L":561return target in {"L", "U", "N"}562elif source == "U":563return target in {"U", "N"}564elif source == "N":565return target in {"N"}566else:567return False568569570def _maybe_coerce_freq(code) -> str:571"""we might need to coerce a code to a rule_code572and uppercase it573574Parameters575----------576source : str or DateOffset577Frequency converting from578579Returns580-------581str582"""583assert code is not None584if isinstance(code, DateOffset):585code = code.rule_code586return code.upper()587588589def _quarter_months_conform(source: str, target: str) -> bool:590snum = MONTH_NUMBERS[source]591tnum = MONTH_NUMBERS[target]592return snum % 3 == tnum % 3593594595def _is_annual(rule: str) -> bool:596rule = rule.upper()597return rule == "A" or rule.startswith("A-")598599600def _is_quarterly(rule: str) -> bool:601rule = rule.upper()602return rule == "Q" or rule.startswith("Q-") or rule.startswith("BQ")603604605def _is_monthly(rule: str) -> bool:606rule = rule.upper()607return rule == "M" or rule == "BM"608609610def _is_weekly(rule: str) -> bool:611rule = rule.upper()612return rule == "W" or rule.startswith("W-")613614615