8th day of python challenges 111-117
This commit is contained in:
8
venv/lib/python3.6/site-packages/pandas/tseries/api.py
Normal file
8
venv/lib/python3.6/site-packages/pandas/tseries/api.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Timeseries API
|
||||
"""
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
from pandas.tseries.frequencies import infer_freq
|
||||
import pandas.tseries.offsets as offsets
|
32
venv/lib/python3.6/site-packages/pandas/tseries/converter.py
Normal file
32
venv/lib/python3.6/site-packages/pandas/tseries/converter.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# flake8: noqa
|
||||
import warnings
|
||||
|
||||
# TODO `_matplotlib` module should be private, so the plotting backend
|
||||
# can be change. Decide whether all these should be public and exponsed
|
||||
# in `pandas.plotting`, or remove from here (I guess they are here for
|
||||
# legacy reasons
|
||||
from pandas.plotting._matplotlib.converter import (
|
||||
DatetimeConverter,
|
||||
MilliSecondLocator,
|
||||
PandasAutoDateFormatter,
|
||||
PandasAutoDateLocator,
|
||||
PeriodConverter,
|
||||
TimeConverter,
|
||||
TimeFormatter,
|
||||
TimeSeries_DateFormatter,
|
||||
TimeSeries_DateLocator,
|
||||
get_datevalue,
|
||||
get_finder,
|
||||
time2num,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
from pandas.plotting import register_matplotlib_converters
|
||||
|
||||
msg = (
|
||||
"'pandas.tseries.converter.register' has been moved and renamed to "
|
||||
"'pandas.plotting.register_matplotlib_converters'. "
|
||||
)
|
||||
warnings.warn(msg, FutureWarning, stacklevel=2)
|
||||
register_matplotlib_converters()
|
514
venv/lib/python3.6/site-packages/pandas/tseries/frequencies.py
Normal file
514
venv/lib/python3.6/site-packages/pandas/tseries/frequencies.py
Normal file
@@ -0,0 +1,514 @@
|
||||
from datetime import timedelta
|
||||
import re
|
||||
from typing import Dict
|
||||
|
||||
import numpy as np
|
||||
from pytz import AmbiguousTimeError
|
||||
|
||||
from pandas._libs.algos import unique_deltas
|
||||
from pandas._libs.tslibs import Timedelta, Timestamp
|
||||
from pandas._libs.tslibs.ccalendar import MONTH_ALIASES, int_to_weekday
|
||||
from pandas._libs.tslibs.fields import build_field_sarray
|
||||
import pandas._libs.tslibs.frequencies as libfreqs
|
||||
from pandas._libs.tslibs.offsets import _offset_to_period_map
|
||||
import pandas._libs.tslibs.resolution as libresolution
|
||||
from pandas._libs.tslibs.resolution import Resolution
|
||||
from pandas._libs.tslibs.timezones import UTC
|
||||
from pandas._libs.tslibs.tzconversion import tz_convert
|
||||
from pandas.util._decorators import cache_readonly
|
||||
|
||||
from pandas.core.dtypes.common import (
|
||||
is_datetime64_dtype,
|
||||
is_period_arraylike,
|
||||
is_timedelta64_dtype,
|
||||
)
|
||||
from pandas.core.dtypes.generic import ABCSeries
|
||||
|
||||
from pandas.core.algorithms import unique
|
||||
|
||||
from pandas.tseries.offsets import (
|
||||
DateOffset,
|
||||
Day,
|
||||
Hour,
|
||||
Micro,
|
||||
Milli,
|
||||
Minute,
|
||||
Nano,
|
||||
Second,
|
||||
prefix_mapping,
|
||||
)
|
||||
|
||||
_ONE_MICRO = 1000
|
||||
_ONE_MILLI = _ONE_MICRO * 1000
|
||||
_ONE_SECOND = _ONE_MILLI * 1000
|
||||
_ONE_MINUTE = 60 * _ONE_SECOND
|
||||
_ONE_HOUR = 60 * _ONE_MINUTE
|
||||
_ONE_DAY = 24 * _ONE_HOUR
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Offset names ("time rules") and related functions
|
||||
|
||||
#: cache of previously seen offsets
|
||||
_offset_map = {} # type: Dict[str, DateOffset]
|
||||
|
||||
|
||||
def get_period_alias(offset_str):
|
||||
""" alias to closest period strings BQ->Q etc"""
|
||||
return _offset_to_period_map.get(offset_str, None)
|
||||
|
||||
|
||||
_name_to_offset_map = {
|
||||
"days": Day(1),
|
||||
"hours": Hour(1),
|
||||
"minutes": Minute(1),
|
||||
"seconds": Second(1),
|
||||
"milliseconds": Milli(1),
|
||||
"microseconds": Micro(1),
|
||||
"nanoseconds": Nano(1),
|
||||
}
|
||||
|
||||
|
||||
def to_offset(freq):
|
||||
"""
|
||||
Return DateOffset object from string or tuple representation
|
||||
or datetime.timedelta object
|
||||
|
||||
Parameters
|
||||
----------
|
||||
freq : str, tuple, datetime.timedelta, DateOffset or None
|
||||
|
||||
Returns
|
||||
-------
|
||||
DateOffset
|
||||
None if freq is None.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If freq is an invalid frequency
|
||||
|
||||
See Also
|
||||
--------
|
||||
DateOffset
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> to_offset('5min')
|
||||
<5 * Minutes>
|
||||
|
||||
>>> to_offset('1D1H')
|
||||
<25 * Hours>
|
||||
|
||||
>>> to_offset(('W', 2))
|
||||
<2 * Weeks: weekday=6>
|
||||
|
||||
>>> to_offset((2, 'B'))
|
||||
<2 * BusinessDays>
|
||||
|
||||
>>> to_offset(datetime.timedelta(days=1))
|
||||
<Day>
|
||||
|
||||
>>> to_offset(Hour())
|
||||
<Hour>
|
||||
"""
|
||||
if freq is None:
|
||||
return None
|
||||
|
||||
if isinstance(freq, DateOffset):
|
||||
return freq
|
||||
|
||||
if isinstance(freq, tuple):
|
||||
name = freq[0]
|
||||
stride = freq[1]
|
||||
if isinstance(stride, str):
|
||||
name, stride = stride, name
|
||||
name, _ = libfreqs._base_and_stride(name)
|
||||
delta = get_offset(name) * stride
|
||||
|
||||
elif isinstance(freq, timedelta):
|
||||
delta = None
|
||||
freq = Timedelta(freq)
|
||||
try:
|
||||
for name in freq.components._fields:
|
||||
offset = _name_to_offset_map[name]
|
||||
stride = getattr(freq.components, name)
|
||||
if stride != 0:
|
||||
offset = stride * offset
|
||||
if delta is None:
|
||||
delta = offset
|
||||
else:
|
||||
delta = delta + offset
|
||||
except Exception:
|
||||
raise ValueError(libfreqs.INVALID_FREQ_ERR_MSG.format(freq))
|
||||
|
||||
else:
|
||||
delta = None
|
||||
stride_sign = None
|
||||
try:
|
||||
splitted = re.split(libfreqs.opattern, freq)
|
||||
if splitted[-1] != "" and not splitted[-1].isspace():
|
||||
# the last element must be blank
|
||||
raise ValueError("last element must be blank")
|
||||
for sep, stride, name in zip(
|
||||
splitted[0::4], splitted[1::4], splitted[2::4]
|
||||
):
|
||||
if sep != "" and not sep.isspace():
|
||||
raise ValueError("separator must be spaces")
|
||||
prefix = libfreqs._lite_rule_alias.get(name) or name
|
||||
if stride_sign is None:
|
||||
stride_sign = -1 if stride.startswith("-") else 1
|
||||
if not stride:
|
||||
stride = 1
|
||||
if prefix in Resolution._reso_str_bump_map.keys():
|
||||
stride, name = Resolution.get_stride_from_decimal(
|
||||
float(stride), prefix
|
||||
)
|
||||
stride = int(stride)
|
||||
offset = get_offset(name)
|
||||
offset = offset * int(np.fabs(stride) * stride_sign)
|
||||
if delta is None:
|
||||
delta = offset
|
||||
else:
|
||||
delta = delta + offset
|
||||
except Exception:
|
||||
raise ValueError(libfreqs.INVALID_FREQ_ERR_MSG.format(freq))
|
||||
|
||||
if delta is None:
|
||||
raise ValueError(libfreqs.INVALID_FREQ_ERR_MSG.format(freq))
|
||||
|
||||
return delta
|
||||
|
||||
|
||||
def get_offset(name):
|
||||
"""
|
||||
Return DateOffset object associated with rule name
|
||||
|
||||
Examples
|
||||
--------
|
||||
get_offset('EOM') --> BMonthEnd(1)
|
||||
"""
|
||||
if name not in libfreqs._dont_uppercase:
|
||||
name = name.upper()
|
||||
name = libfreqs._lite_rule_alias.get(name, name)
|
||||
name = libfreqs._lite_rule_alias.get(name.lower(), name)
|
||||
else:
|
||||
name = libfreqs._lite_rule_alias.get(name, name)
|
||||
|
||||
if name not in _offset_map:
|
||||
try:
|
||||
split = name.split("-")
|
||||
klass = prefix_mapping[split[0]]
|
||||
# handles case where there's no suffix (and will TypeError if too
|
||||
# many '-')
|
||||
offset = klass._from_name(*split[1:])
|
||||
except (ValueError, TypeError, KeyError):
|
||||
# bad prefix or suffix
|
||||
raise ValueError(libfreqs.INVALID_FREQ_ERR_MSG.format(name))
|
||||
# cache
|
||||
_offset_map[name] = offset
|
||||
|
||||
return _offset_map[name]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Period codes
|
||||
|
||||
|
||||
def infer_freq(index, warn=True):
|
||||
"""
|
||||
Infer the most likely frequency given the input index. If the frequency is
|
||||
uncertain, a warning will be printed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : DatetimeIndex or TimedeltaIndex
|
||||
if passed a Series will use the values of the series (NOT THE INDEX)
|
||||
warn : boolean, default True
|
||||
|
||||
Returns
|
||||
-------
|
||||
str or None
|
||||
None if no discernible frequency
|
||||
TypeError if the index is not datetime-like
|
||||
ValueError if there are less than three values.
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
if isinstance(index, ABCSeries):
|
||||
values = index._values
|
||||
if not (
|
||||
is_datetime64_dtype(values)
|
||||
or is_timedelta64_dtype(values)
|
||||
or values.dtype == object
|
||||
):
|
||||
raise TypeError(
|
||||
"cannot infer freq from a non-convertible dtype "
|
||||
"on a Series of {dtype}".format(dtype=index.dtype)
|
||||
)
|
||||
index = values
|
||||
|
||||
if is_period_arraylike(index):
|
||||
raise TypeError(
|
||||
"PeriodIndex given. Check the `freq` attribute "
|
||||
"instead of using infer_freq."
|
||||
)
|
||||
elif is_timedelta64_dtype(index):
|
||||
# Allow TimedeltaIndex and TimedeltaArray
|
||||
inferer = _TimedeltaFrequencyInferer(index, warn=warn)
|
||||
return inferer.get_freq()
|
||||
|
||||
if isinstance(index, pd.Index) and not isinstance(index, pd.DatetimeIndex):
|
||||
if isinstance(index, (pd.Int64Index, pd.Float64Index)):
|
||||
raise TypeError(
|
||||
"cannot infer freq from a non-convertible index "
|
||||
"type {type}".format(type=type(index))
|
||||
)
|
||||
index = index.values
|
||||
|
||||
if not isinstance(index, pd.DatetimeIndex):
|
||||
try:
|
||||
index = pd.DatetimeIndex(index)
|
||||
except AmbiguousTimeError:
|
||||
index = pd.DatetimeIndex(index.asi8)
|
||||
|
||||
inferer = _FrequencyInferer(index, warn=warn)
|
||||
return inferer.get_freq()
|
||||
|
||||
|
||||
class _FrequencyInferer:
|
||||
"""
|
||||
Not sure if I can avoid the state machine here
|
||||
"""
|
||||
|
||||
def __init__(self, index, warn=True):
|
||||
self.index = index
|
||||
self.values = index.asi8
|
||||
|
||||
# This moves the values, which are implicitly in UTC, to the
|
||||
# the timezone so they are in local time
|
||||
if hasattr(index, "tz"):
|
||||
if index.tz is not None:
|
||||
self.values = tz_convert(self.values, UTC, index.tz)
|
||||
|
||||
self.warn = warn
|
||||
|
||||
if len(index) < 3:
|
||||
raise ValueError("Need at least 3 dates to infer frequency")
|
||||
|
||||
self.is_monotonic = (
|
||||
self.index._is_monotonic_increasing or self.index._is_monotonic_decreasing
|
||||
)
|
||||
|
||||
@cache_readonly
|
||||
def deltas(self):
|
||||
return unique_deltas(self.values)
|
||||
|
||||
@cache_readonly
|
||||
def deltas_asi8(self):
|
||||
return unique_deltas(self.index.asi8)
|
||||
|
||||
@cache_readonly
|
||||
def is_unique(self):
|
||||
return len(self.deltas) == 1
|
||||
|
||||
@cache_readonly
|
||||
def is_unique_asi8(self):
|
||||
return len(self.deltas_asi8) == 1
|
||||
|
||||
def get_freq(self):
|
||||
"""
|
||||
Find the appropriate frequency string to describe the inferred
|
||||
frequency of self.values
|
||||
|
||||
Returns
|
||||
-------
|
||||
str or None
|
||||
"""
|
||||
if not self.is_monotonic or not self.index._is_unique:
|
||||
return None
|
||||
|
||||
delta = self.deltas[0]
|
||||
if _is_multiple(delta, _ONE_DAY):
|
||||
return self._infer_daily_rule()
|
||||
|
||||
# Business hourly, maybe. 17: one day / 65: one weekend
|
||||
if self.hour_deltas in ([1, 17], [1, 65], [1, 17, 65]):
|
||||
return "BH"
|
||||
# Possibly intraday frequency. Here we use the
|
||||
# original .asi8 values as the modified values
|
||||
# will not work around DST transitions. See #8772
|
||||
elif not self.is_unique_asi8:
|
||||
return None
|
||||
|
||||
delta = self.deltas_asi8[0]
|
||||
if _is_multiple(delta, _ONE_HOUR):
|
||||
# Hours
|
||||
return _maybe_add_count("H", delta / _ONE_HOUR)
|
||||
elif _is_multiple(delta, _ONE_MINUTE):
|
||||
# Minutes
|
||||
return _maybe_add_count("T", delta / _ONE_MINUTE)
|
||||
elif _is_multiple(delta, _ONE_SECOND):
|
||||
# Seconds
|
||||
return _maybe_add_count("S", delta / _ONE_SECOND)
|
||||
elif _is_multiple(delta, _ONE_MILLI):
|
||||
# Milliseconds
|
||||
return _maybe_add_count("L", delta / _ONE_MILLI)
|
||||
elif _is_multiple(delta, _ONE_MICRO):
|
||||
# Microseconds
|
||||
return _maybe_add_count("U", delta / _ONE_MICRO)
|
||||
else:
|
||||
# Nanoseconds
|
||||
return _maybe_add_count("N", delta)
|
||||
|
||||
@cache_readonly
|
||||
def day_deltas(self):
|
||||
return [x / _ONE_DAY for x in self.deltas]
|
||||
|
||||
@cache_readonly
|
||||
def hour_deltas(self):
|
||||
return [x / _ONE_HOUR for x in self.deltas]
|
||||
|
||||
@cache_readonly
|
||||
def fields(self):
|
||||
return build_field_sarray(self.values)
|
||||
|
||||
@cache_readonly
|
||||
def rep_stamp(self):
|
||||
return Timestamp(self.values[0])
|
||||
|
||||
def month_position_check(self):
|
||||
return libresolution.month_position_check(self.fields, self.index.dayofweek)
|
||||
|
||||
@cache_readonly
|
||||
def mdiffs(self):
|
||||
nmonths = self.fields["Y"] * 12 + self.fields["M"]
|
||||
return unique_deltas(nmonths.astype("i8"))
|
||||
|
||||
@cache_readonly
|
||||
def ydiffs(self):
|
||||
return unique_deltas(self.fields["Y"].astype("i8"))
|
||||
|
||||
def _infer_daily_rule(self):
|
||||
annual_rule = self._get_annual_rule()
|
||||
if annual_rule:
|
||||
nyears = self.ydiffs[0]
|
||||
month = MONTH_ALIASES[self.rep_stamp.month]
|
||||
alias = "{prefix}-{month}".format(prefix=annual_rule, month=month)
|
||||
return _maybe_add_count(alias, nyears)
|
||||
|
||||
quarterly_rule = self._get_quarterly_rule()
|
||||
if quarterly_rule:
|
||||
nquarters = self.mdiffs[0] / 3
|
||||
mod_dict = {0: 12, 2: 11, 1: 10}
|
||||
month = MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]]
|
||||
alias = "{prefix}-{month}".format(prefix=quarterly_rule, month=month)
|
||||
return _maybe_add_count(alias, nquarters)
|
||||
|
||||
monthly_rule = self._get_monthly_rule()
|
||||
if monthly_rule:
|
||||
return _maybe_add_count(monthly_rule, self.mdiffs[0])
|
||||
|
||||
if self.is_unique:
|
||||
days = self.deltas[0] / _ONE_DAY
|
||||
if days % 7 == 0:
|
||||
# Weekly
|
||||
day = int_to_weekday[self.rep_stamp.weekday()]
|
||||
return _maybe_add_count("W-{day}".format(day=day), days / 7)
|
||||
else:
|
||||
return _maybe_add_count("D", days)
|
||||
|
||||
if self._is_business_daily():
|
||||
return "B"
|
||||
|
||||
wom_rule = self._get_wom_rule()
|
||||
if wom_rule:
|
||||
return wom_rule
|
||||
|
||||
def _get_annual_rule(self):
|
||||
if len(self.ydiffs) > 1:
|
||||
return None
|
||||
|
||||
if len(unique(self.fields["M"])) > 1:
|
||||
return None
|
||||
|
||||
pos_check = self.month_position_check()
|
||||
return {"cs": "AS", "bs": "BAS", "ce": "A", "be": "BA"}.get(pos_check)
|
||||
|
||||
def _get_quarterly_rule(self):
|
||||
if len(self.mdiffs) > 1:
|
||||
return None
|
||||
|
||||
if not self.mdiffs[0] % 3 == 0:
|
||||
return None
|
||||
|
||||
pos_check = self.month_position_check()
|
||||
return {"cs": "QS", "bs": "BQS", "ce": "Q", "be": "BQ"}.get(pos_check)
|
||||
|
||||
def _get_monthly_rule(self):
|
||||
if len(self.mdiffs) > 1:
|
||||
return None
|
||||
pos_check = self.month_position_check()
|
||||
return {"cs": "MS", "bs": "BMS", "ce": "M", "be": "BM"}.get(pos_check)
|
||||
|
||||
def _is_business_daily(self):
|
||||
# quick check: cannot be business daily
|
||||
if self.day_deltas != [1, 3]:
|
||||
return False
|
||||
|
||||
# probably business daily, but need to confirm
|
||||
first_weekday = self.index[0].weekday()
|
||||
shifts = np.diff(self.index.asi8)
|
||||
shifts = np.floor_divide(shifts, _ONE_DAY)
|
||||
weekdays = np.mod(first_weekday + np.cumsum(shifts), 7)
|
||||
return np.all(
|
||||
((weekdays == 0) & (shifts == 3))
|
||||
| ((weekdays > 0) & (weekdays <= 4) & (shifts == 1))
|
||||
)
|
||||
|
||||
def _get_wom_rule(self):
|
||||
# wdiffs = unique(np.diff(self.index.week))
|
||||
# We also need -47, -49, -48 to catch index spanning year boundary
|
||||
# if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all():
|
||||
# return None
|
||||
|
||||
weekdays = unique(self.index.weekday)
|
||||
if len(weekdays) > 1:
|
||||
return None
|
||||
|
||||
week_of_months = unique((self.index.day - 1) // 7)
|
||||
# Only attempt to infer up to WOM-4. See #9425
|
||||
week_of_months = week_of_months[week_of_months < 4]
|
||||
if len(week_of_months) == 0 or len(week_of_months) > 1:
|
||||
return None
|
||||
|
||||
# get which week
|
||||
week = week_of_months[0] + 1
|
||||
wd = int_to_weekday[weekdays[0]]
|
||||
|
||||
return "WOM-{week}{weekday}".format(week=week, weekday=wd)
|
||||
|
||||
|
||||
class _TimedeltaFrequencyInferer(_FrequencyInferer):
|
||||
def _infer_daily_rule(self):
|
||||
if self.is_unique:
|
||||
days = self.deltas[0] / _ONE_DAY
|
||||
if days % 7 == 0:
|
||||
# Weekly
|
||||
wd = int_to_weekday[self.rep_stamp.weekday()]
|
||||
alias = "W-{weekday}".format(weekday=wd)
|
||||
return _maybe_add_count(alias, days / 7)
|
||||
else:
|
||||
return _maybe_add_count("D", days)
|
||||
|
||||
|
||||
def _is_multiple(us, mult):
|
||||
return us % mult == 0
|
||||
|
||||
|
||||
def _maybe_add_count(base, count):
|
||||
if count != 1:
|
||||
assert count == int(count)
|
||||
count = int(count)
|
||||
return "{count}{base}".format(count=count, base=base)
|
||||
else:
|
||||
return base
|
535
venv/lib/python3.6/site-packages/pandas/tseries/holiday.py
Normal file
535
venv/lib/python3.6/site-packages/pandas/tseries/holiday.py
Normal file
@@ -0,0 +1,535 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
import warnings
|
||||
|
||||
from dateutil.relativedelta import FR, MO, SA, SU, TH, TU, WE # noqa
|
||||
import numpy as np
|
||||
|
||||
from pandas.errors import PerformanceWarning
|
||||
|
||||
from pandas import DateOffset, Series, Timestamp, date_range
|
||||
|
||||
from pandas.tseries.offsets import Day, Easter
|
||||
|
||||
|
||||
def next_monday(dt):
|
||||
"""
|
||||
If holiday falls on Saturday, use following Monday instead;
|
||||
if holiday falls on Sunday, use Monday instead
|
||||
"""
|
||||
if dt.weekday() == 5:
|
||||
return dt + timedelta(2)
|
||||
elif dt.weekday() == 6:
|
||||
return dt + timedelta(1)
|
||||
return dt
|
||||
|
||||
|
||||
def next_monday_or_tuesday(dt):
|
||||
"""
|
||||
For second holiday of two adjacent ones!
|
||||
If holiday falls on Saturday, use following Monday instead;
|
||||
if holiday falls on Sunday or Monday, use following Tuesday instead
|
||||
(because Monday is already taken by adjacent holiday on the day before)
|
||||
"""
|
||||
dow = dt.weekday()
|
||||
if dow == 5 or dow == 6:
|
||||
return dt + timedelta(2)
|
||||
elif dow == 0:
|
||||
return dt + timedelta(1)
|
||||
return dt
|
||||
|
||||
|
||||
def previous_friday(dt):
|
||||
"""
|
||||
If holiday falls on Saturday or Sunday, use previous Friday instead.
|
||||
"""
|
||||
if dt.weekday() == 5:
|
||||
return dt - timedelta(1)
|
||||
elif dt.weekday() == 6:
|
||||
return dt - timedelta(2)
|
||||
return dt
|
||||
|
||||
|
||||
def sunday_to_monday(dt):
|
||||
"""
|
||||
If holiday falls on Sunday, use day thereafter (Monday) instead.
|
||||
"""
|
||||
if dt.weekday() == 6:
|
||||
return dt + timedelta(1)
|
||||
return dt
|
||||
|
||||
|
||||
def weekend_to_monday(dt):
|
||||
"""
|
||||
If holiday falls on Sunday or Saturday,
|
||||
use day thereafter (Monday) instead.
|
||||
Needed for holidays such as Christmas observation in Europe
|
||||
"""
|
||||
if dt.weekday() == 6:
|
||||
return dt + timedelta(1)
|
||||
elif dt.weekday() == 5:
|
||||
return dt + timedelta(2)
|
||||
return dt
|
||||
|
||||
|
||||
def nearest_workday(dt):
|
||||
"""
|
||||
If holiday falls on Saturday, use day before (Friday) instead;
|
||||
if holiday falls on Sunday, use day thereafter (Monday) instead.
|
||||
"""
|
||||
if dt.weekday() == 5:
|
||||
return dt - timedelta(1)
|
||||
elif dt.weekday() == 6:
|
||||
return dt + timedelta(1)
|
||||
return dt
|
||||
|
||||
|
||||
def next_workday(dt):
|
||||
"""
|
||||
returns next weekday used for observances
|
||||
"""
|
||||
dt += timedelta(days=1)
|
||||
while dt.weekday() > 4:
|
||||
# Mon-Fri are 0-4
|
||||
dt += timedelta(days=1)
|
||||
return dt
|
||||
|
||||
|
||||
def previous_workday(dt):
|
||||
"""
|
||||
returns previous weekday used for observances
|
||||
"""
|
||||
dt -= timedelta(days=1)
|
||||
while dt.weekday() > 4:
|
||||
# Mon-Fri are 0-4
|
||||
dt -= timedelta(days=1)
|
||||
return dt
|
||||
|
||||
|
||||
def before_nearest_workday(dt):
|
||||
"""
|
||||
returns previous workday after nearest workday
|
||||
"""
|
||||
return previous_workday(nearest_workday(dt))
|
||||
|
||||
|
||||
def after_nearest_workday(dt):
|
||||
"""
|
||||
returns next workday after nearest workday
|
||||
needed for Boxing day or multiple holidays in a series
|
||||
"""
|
||||
return next_workday(nearest_workday(dt))
|
||||
|
||||
|
||||
class Holiday:
|
||||
"""
|
||||
Class that defines a holiday with start/end dates and rules
|
||||
for observance.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
year=None,
|
||||
month=None,
|
||||
day=None,
|
||||
offset=None,
|
||||
observance=None,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
days_of_week=None,
|
||||
):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the holiday , defaults to class name
|
||||
offset : array of pandas.tseries.offsets or
|
||||
class from pandas.tseries.offsets
|
||||
computes offset from date
|
||||
observance: function
|
||||
computes when holiday is given a pandas Timestamp
|
||||
days_of_week:
|
||||
provide a tuple of days e.g (0,1,2,3,) for Monday Through Thursday
|
||||
Monday=0,..,Sunday=6
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pandas.tseries.holiday import Holiday, nearest_workday
|
||||
>>> from dateutil.relativedelta import MO
|
||||
>>> USMemorialDay = Holiday('Memorial Day', month=5, day=31,
|
||||
offset=pd.DateOffset(weekday=MO(-1)))
|
||||
>>> USLaborDay = Holiday('Labor Day', month=9, day=1,
|
||||
offset=pd.DateOffset(weekday=MO(1)))
|
||||
>>> July3rd = Holiday('July 3rd', month=7, day=3,)
|
||||
>>> NewYears = Holiday('New Years Day', month=1, day=1,
|
||||
observance=nearest_workday),
|
||||
>>> July3rd = Holiday('July 3rd', month=7, day=3,
|
||||
days_of_week=(0, 1, 2, 3))
|
||||
"""
|
||||
if offset is not None and observance is not None:
|
||||
raise NotImplementedError("Cannot use both offset and observance.")
|
||||
|
||||
self.name = name
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.offset = offset
|
||||
self.start_date = (
|
||||
Timestamp(start_date) if start_date is not None else start_date
|
||||
)
|
||||
self.end_date = Timestamp(end_date) if end_date is not None else end_date
|
||||
self.observance = observance
|
||||
assert days_of_week is None or type(days_of_week) == tuple
|
||||
self.days_of_week = days_of_week
|
||||
|
||||
def __repr__(self):
|
||||
info = ""
|
||||
if self.year is not None:
|
||||
info += "year={year}, ".format(year=self.year)
|
||||
info += "month={mon}, day={day}, ".format(mon=self.month, day=self.day)
|
||||
|
||||
if self.offset is not None:
|
||||
info += "offset={offset}".format(offset=self.offset)
|
||||
|
||||
if self.observance is not None:
|
||||
info += "observance={obs}".format(obs=self.observance)
|
||||
|
||||
repr = "Holiday: {name} ({info})".format(name=self.name, info=info)
|
||||
return repr
|
||||
|
||||
def dates(self, start_date, end_date, return_name=False):
|
||||
"""
|
||||
Calculate holidays observed between start date and end date
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start_date : starting date, datetime-like, optional
|
||||
end_date : ending date, datetime-like, optional
|
||||
return_name : bool, optional, default=False
|
||||
If True, return a series that has dates and holiday names.
|
||||
False will only return dates.
|
||||
"""
|
||||
start_date = Timestamp(start_date)
|
||||
end_date = Timestamp(end_date)
|
||||
|
||||
filter_start_date = start_date
|
||||
filter_end_date = end_date
|
||||
|
||||
if self.year is not None:
|
||||
dt = Timestamp(datetime(self.year, self.month, self.day))
|
||||
if return_name:
|
||||
return Series(self.name, index=[dt])
|
||||
else:
|
||||
return [dt]
|
||||
|
||||
dates = self._reference_dates(start_date, end_date)
|
||||
holiday_dates = self._apply_rule(dates)
|
||||
if self.days_of_week is not None:
|
||||
holiday_dates = holiday_dates[
|
||||
np.in1d(holiday_dates.dayofweek, self.days_of_week)
|
||||
]
|
||||
|
||||
if self.start_date is not None:
|
||||
filter_start_date = max(
|
||||
self.start_date.tz_localize(filter_start_date.tz), filter_start_date
|
||||
)
|
||||
if self.end_date is not None:
|
||||
filter_end_date = min(
|
||||
self.end_date.tz_localize(filter_end_date.tz), filter_end_date
|
||||
)
|
||||
holiday_dates = holiday_dates[
|
||||
(holiday_dates >= filter_start_date) & (holiday_dates <= filter_end_date)
|
||||
]
|
||||
if return_name:
|
||||
return Series(self.name, index=holiday_dates)
|
||||
return holiday_dates
|
||||
|
||||
def _reference_dates(self, start_date, end_date):
|
||||
"""
|
||||
Get reference dates for the holiday.
|
||||
|
||||
Return reference dates for the holiday also returning the year
|
||||
prior to the start_date and year following the end_date. This ensures
|
||||
that any offsets to be applied will yield the holidays within
|
||||
the passed in dates.
|
||||
"""
|
||||
if self.start_date is not None:
|
||||
start_date = self.start_date.tz_localize(start_date.tz)
|
||||
|
||||
if self.end_date is not None:
|
||||
end_date = self.end_date.tz_localize(start_date.tz)
|
||||
|
||||
year_offset = DateOffset(years=1)
|
||||
reference_start_date = Timestamp(
|
||||
datetime(start_date.year - 1, self.month, self.day)
|
||||
)
|
||||
|
||||
reference_end_date = Timestamp(
|
||||
datetime(end_date.year + 1, self.month, self.day)
|
||||
)
|
||||
# Don't process unnecessary holidays
|
||||
dates = date_range(
|
||||
start=reference_start_date,
|
||||
end=reference_end_date,
|
||||
freq=year_offset,
|
||||
tz=start_date.tz,
|
||||
)
|
||||
|
||||
return dates
|
||||
|
||||
def _apply_rule(self, dates):
|
||||
"""
|
||||
Apply the given offset/observance to a DatetimeIndex of dates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dates : DatetimeIndex
|
||||
Dates to apply the given offset/observance rule
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dates with rules applied
|
||||
"""
|
||||
if self.observance is not None:
|
||||
return dates.map(lambda d: self.observance(d))
|
||||
|
||||
if self.offset is not None:
|
||||
if not isinstance(self.offset, list):
|
||||
offsets = [self.offset]
|
||||
else:
|
||||
offsets = self.offset
|
||||
for offset in offsets:
|
||||
|
||||
# if we are adding a non-vectorized value
|
||||
# ignore the PerformanceWarnings:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", PerformanceWarning)
|
||||
dates += offset
|
||||
return dates
|
||||
|
||||
|
||||
holiday_calendars = {}
|
||||
|
||||
|
||||
def register(cls):
|
||||
try:
|
||||
name = cls.name
|
||||
except AttributeError:
|
||||
name = cls.__name__
|
||||
holiday_calendars[name] = cls
|
||||
|
||||
|
||||
def get_calendar(name):
|
||||
"""
|
||||
Return an instance of a calendar based on its name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Calendar name to return an instance of
|
||||
"""
|
||||
return holiday_calendars[name]()
|
||||
|
||||
|
||||
class HolidayCalendarMetaClass(type):
|
||||
def __new__(cls, clsname, bases, attrs):
|
||||
calendar_class = super().__new__(cls, clsname, bases, attrs)
|
||||
register(calendar_class)
|
||||
return calendar_class
|
||||
|
||||
|
||||
class AbstractHolidayCalendar(metaclass=HolidayCalendarMetaClass):
|
||||
"""
|
||||
Abstract interface to create holidays following certain rules.
|
||||
"""
|
||||
|
||||
rules = [] # type: List[Holiday]
|
||||
start_date = Timestamp(datetime(1970, 1, 1))
|
||||
end_date = Timestamp(datetime(2030, 12, 31))
|
||||
_cache = None
|
||||
|
||||
def __init__(self, name=None, rules=None):
|
||||
"""
|
||||
Initializes holiday object with a given set a rules. Normally
|
||||
classes just have the rules defined within them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the holiday calendar, defaults to class name
|
||||
rules : array of Holiday objects
|
||||
A set of rules used to create the holidays.
|
||||
"""
|
||||
super().__init__()
|
||||
if name is None:
|
||||
name = self.__class__.__name__
|
||||
self.name = name
|
||||
|
||||
if rules is not None:
|
||||
self.rules = rules
|
||||
|
||||
def rule_from_name(self, name):
|
||||
for rule in self.rules:
|
||||
if rule.name == name:
|
||||
return rule
|
||||
|
||||
return None
|
||||
|
||||
def holidays(self, start=None, end=None, return_name=False):
|
||||
"""
|
||||
Returns a curve with holidays between start_date and end_date
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : starting date, datetime-like, optional
|
||||
end : ending date, datetime-like, optional
|
||||
return_name : bool, optional
|
||||
If True, return a series that has dates and holiday names.
|
||||
False will only return a DatetimeIndex of dates.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex of holidays
|
||||
"""
|
||||
if self.rules is None:
|
||||
raise Exception(
|
||||
"Holiday Calendar {name} does not have any "
|
||||
"rules specified".format(name=self.name)
|
||||
)
|
||||
|
||||
if start is None:
|
||||
start = AbstractHolidayCalendar.start_date
|
||||
|
||||
if end is None:
|
||||
end = AbstractHolidayCalendar.end_date
|
||||
|
||||
start = Timestamp(start)
|
||||
end = Timestamp(end)
|
||||
|
||||
holidays = None
|
||||
# If we don't have a cache or the dates are outside the prior cache, we
|
||||
# get them again
|
||||
if self._cache is None or start < self._cache[0] or end > self._cache[1]:
|
||||
for rule in self.rules:
|
||||
rule_holidays = rule.dates(start, end, return_name=True)
|
||||
|
||||
if holidays is None:
|
||||
holidays = rule_holidays
|
||||
else:
|
||||
holidays = holidays.append(rule_holidays)
|
||||
|
||||
self._cache = (start, end, holidays.sort_index())
|
||||
|
||||
holidays = self._cache[2]
|
||||
holidays = holidays[start:end]
|
||||
|
||||
if return_name:
|
||||
return holidays
|
||||
else:
|
||||
return holidays.index
|
||||
|
||||
@staticmethod
|
||||
def merge_class(base, other):
|
||||
"""
|
||||
Merge holiday calendars together. The base calendar
|
||||
will take precedence to other. The merge will be done
|
||||
based on each holiday's name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base : AbstractHolidayCalendar
|
||||
instance/subclass or array of Holiday objects
|
||||
other : AbstractHolidayCalendar
|
||||
instance/subclass or array of Holiday objects
|
||||
"""
|
||||
try:
|
||||
other = other.rules
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not isinstance(other, list):
|
||||
other = [other]
|
||||
other_holidays = {holiday.name: holiday for holiday in other}
|
||||
|
||||
try:
|
||||
base = base.rules
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not isinstance(base, list):
|
||||
base = [base]
|
||||
base_holidays = {holiday.name: holiday for holiday in base}
|
||||
|
||||
other_holidays.update(base_holidays)
|
||||
return list(other_holidays.values())
|
||||
|
||||
def merge(self, other, inplace=False):
|
||||
"""
|
||||
Merge holiday calendars together. The caller's class
|
||||
rules take precedence. The merge will be done
|
||||
based on each holiday's name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : holiday calendar
|
||||
inplace : bool (default=False)
|
||||
If True set rule_table to holidays, else return array of Holidays
|
||||
"""
|
||||
holidays = self.merge_class(self, other)
|
||||
if inplace:
|
||||
self.rules = holidays
|
||||
else:
|
||||
return holidays
|
||||
|
||||
|
||||
USMemorialDay = Holiday(
|
||||
"Memorial Day", month=5, day=31, offset=DateOffset(weekday=MO(-1))
|
||||
)
|
||||
USLaborDay = Holiday("Labor Day", month=9, day=1, offset=DateOffset(weekday=MO(1)))
|
||||
USColumbusDay = Holiday(
|
||||
"Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2))
|
||||
)
|
||||
USThanksgivingDay = Holiday(
|
||||
"Thanksgiving", month=11, day=1, offset=DateOffset(weekday=TH(4))
|
||||
)
|
||||
USMartinLutherKingJr = Holiday(
|
||||
"Martin Luther King Jr. Day",
|
||||
start_date=datetime(1986, 1, 1),
|
||||
month=1,
|
||||
day=1,
|
||||
offset=DateOffset(weekday=MO(3)),
|
||||
)
|
||||
USPresidentsDay = Holiday(
|
||||
"Presidents Day", month=2, day=1, offset=DateOffset(weekday=MO(3))
|
||||
)
|
||||
GoodFriday = Holiday("Good Friday", month=1, day=1, offset=[Easter(), Day(-2)])
|
||||
|
||||
EasterMonday = Holiday("Easter Monday", month=1, day=1, offset=[Easter(), Day(1)])
|
||||
|
||||
|
||||
class USFederalHolidayCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
US Federal Government Holiday Calendar based on rules specified by:
|
||||
https://www.opm.gov/policy-data-oversight/
|
||||
snow-dismissal-procedures/federal-holidays/
|
||||
"""
|
||||
|
||||
rules = [
|
||||
Holiday("New Years Day", month=1, day=1, observance=nearest_workday),
|
||||
USMartinLutherKingJr,
|
||||
USPresidentsDay,
|
||||
USMemorialDay,
|
||||
Holiday("July 4th", month=7, day=4, observance=nearest_workday),
|
||||
USLaborDay,
|
||||
USColumbusDay,
|
||||
Holiday("Veterans Day", month=11, day=11, observance=nearest_workday),
|
||||
USThanksgivingDay,
|
||||
Holiday("Christmas", month=12, day=25, observance=nearest_workday),
|
||||
]
|
||||
|
||||
|
||||
def HolidayCalendarFactory(name, base, other, base_class=AbstractHolidayCalendar):
|
||||
rules = AbstractHolidayCalendar.merge_class(base, other)
|
||||
calendar_class = type(name, (base_class,), {"rules": rules, "name": name})
|
||||
return calendar_class
|
2816
venv/lib/python3.6/site-packages/pandas/tseries/offsets.py
Normal file
2816
venv/lib/python3.6/site-packages/pandas/tseries/offsets.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
# flake8: noqa
|
||||
|
||||
from pandas.plotting._matplotlib.timeseries import tsplot
|
Reference in New Issue
Block a user