mirror of
https://github.com/saymrwulf/prophet.git
synced 2026-07-30 20:18:11 +00:00
Update python-holidays integration (#2379)
This commit is contained in:
parent
49e455a86f
commit
8cf3ba9e46
5 changed files with 51901 additions and 34548 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -6,13 +6,32 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import prophet.hdays as hdays_part2
|
||||
import holidays as hdays_part1
|
||||
import holidays
|
||||
|
||||
|
||||
def get_country_holidays_class(country):
|
||||
"""Get class for a supported country.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: country code
|
||||
|
||||
Returns
|
||||
-------
|
||||
A valid country holidays class
|
||||
"""
|
||||
substitutions = {
|
||||
"TU": "TR", # For compatibility with Turkey as 'TU' cases.
|
||||
}
|
||||
|
||||
country = substitutions.get(country, country)
|
||||
if not hasattr(holidays, country):
|
||||
raise AttributeError(f"Holidays in {country} are not currently supported!")
|
||||
|
||||
return getattr(holidays, country)
|
||||
|
||||
|
||||
def get_holiday_names(country):
|
||||
|
|
@ -26,18 +45,8 @@ def get_holiday_names(country):
|
|||
-------
|
||||
A set of all possible holiday names of given country
|
||||
"""
|
||||
years = np.arange(1995, 2045)
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
holiday_names = getattr(hdays_part2, country)(years=years).values()
|
||||
except AttributeError:
|
||||
try:
|
||||
holiday_names = getattr(hdays_part1, country)(years=years).values()
|
||||
except AttributeError as e:
|
||||
raise AttributeError(f"Holidays in {country} are not currently supported!") from e
|
||||
|
||||
return set(holiday_names)
|
||||
country_holidays = get_country_holidays_class(country)
|
||||
return set(country_holidays(language="en_US", years=np.arange(1995, 2045)).values())
|
||||
|
||||
|
||||
def make_holidays_df(year_list, country, province=None, state=None):
|
||||
|
|
@ -53,16 +62,15 @@ def make_holidays_df(year_list, country, province=None, state=None):
|
|||
Dataframe with 'ds' and 'holiday', which can directly feed
|
||||
to 'holidays' params in Prophet
|
||||
"""
|
||||
try:
|
||||
holidays = getattr(hdays_part2, country)(years=year_list, expand=False)
|
||||
except AttributeError:
|
||||
try:
|
||||
holidays = getattr(hdays_part1, country)(prov=province, state=state, years=year_list, expand=False)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(f"Holidays in {country} are not currently supported!") from e
|
||||
country_holidays = get_country_holidays_class(country)
|
||||
holidays = country_holidays(expand=False, language="en_US", subdiv=province, years=year_list)
|
||||
|
||||
holidays_df = pd.DataFrame([(date, holidays.get_list(date)) for date in holidays], columns=['ds', 'holiday'])
|
||||
holidays_df = holidays_df.explode('holiday')
|
||||
holidays_df = pd.DataFrame(
|
||||
[(date, holidays.get_list(date)) for date in holidays],
|
||||
columns=["ds", "holiday"],
|
||||
)
|
||||
holidays_df = holidays_df.explode("holiday")
|
||||
holidays_df.reset_index(inplace=True, drop=True)
|
||||
holidays_df['ds'] = pd.to_datetime(holidays_df['ds'])
|
||||
return (holidays_df)
|
||||
holidays_df["ds"] = pd.to_datetime(holidays_df["ds"])
|
||||
|
||||
return holidays_df
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ dependencies = [
|
|||
"pandas>=1.0.4",
|
||||
"LunarCalendar>=0.0.9",
|
||||
"convertdate>=2.1.2",
|
||||
"holidays>=0.14.2",
|
||||
"holidays>=0.25",
|
||||
"python-dateutil>=2.8.0",
|
||||
"tqdm>=4.36.1",
|
||||
"importlib_resources",
|
||||
|
|
|
|||
|
|
@ -8,72 +8,72 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import inspect
|
||||
import re
|
||||
import unicodedata
|
||||
import warnings
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
import holidays as hdays_part1
|
||||
import prophet.hdays as hdays_part2
|
||||
from holidays import list_supported_countries
|
||||
from prophet.make_holidays import make_holidays_df
|
||||
|
||||
|
||||
def utf8_to_ascii(text):
|
||||
"""Holidays often have utf-8 characters. These are not allowed in R
|
||||
package data (they generate a NOTE).
|
||||
def utf8_to_ascii(text: str) -> str:
|
||||
"""Holidays often have utf-8 characters. These are not allowed in R package data (they generate a NOTE).
|
||||
TODO: revisit whether we want to do this lossy conversion.
|
||||
"""
|
||||
ascii_text = (
|
||||
unicodedata.normalize('NFD', text)
|
||||
.encode('ascii', 'ignore')
|
||||
.decode('ascii')
|
||||
.strip()
|
||||
)
|
||||
ascii_text = unicodedata.normalize("NFD", text).encode("ascii", "ignore").decode("ascii")
|
||||
# Remove trailing empty brackets and spaces.
|
||||
ascii_text = re.sub(r"\(\)$", "", ascii_text).strip()
|
||||
|
||||
# Check if anything converted
|
||||
if sum(1 for x in ascii_text if x not in [' ', '(', ')', ',']) == 0:
|
||||
return 'FAILED_TO_PARSE'
|
||||
if sum(1 for x in ascii_text if x not in [" ", "(", ")", ","]) == 0:
|
||||
return "FAILED_TO_PARSE"
|
||||
else:
|
||||
return ascii_text
|
||||
|
||||
|
||||
def generate_holidays_file():
|
||||
"""Generate csv file of all possible holiday names, ds,
|
||||
and countries, year combination
|
||||
"""
|
||||
year_list = np.arange(1995, 2045, 1).tolist()
|
||||
def generate_holidays_df() -> pd.DataFrame:
|
||||
"""Generate csv file of all possible holiday names, ds, and countries, year combination."""
|
||||
country_codes = set(list_supported_countries().keys())
|
||||
|
||||
# For compatibility with Turkey as 'TU' cases.
|
||||
country_codes.add("TU")
|
||||
|
||||
all_holidays = []
|
||||
# class names in holiday packages which are not countries
|
||||
# Also cut out countries with utf-8 holidays that don't parse to ascii
|
||||
class_to_exclude = {'rd', 'BY', 'BG', 'JP', 'RS', 'UA', 'KR'}
|
||||
|
||||
class_list2 = inspect.getmembers(hdays_part2, inspect.isclass)
|
||||
country_set = {name for name in list(zip(*class_list2))[0] if len(name) == 2}
|
||||
class_list1 = inspect.getmembers(hdays_part1, inspect.isclass)
|
||||
country_set1 = {name for name in list(zip(*class_list1))[0] if len(name) == 2}
|
||||
country_set.update(country_set1)
|
||||
country_set -= class_to_exclude
|
||||
|
||||
for country in country_set:
|
||||
df = make_holidays_df(year_list=year_list, country=country)
|
||||
df['country'] = country
|
||||
for country_code in country_codes:
|
||||
df = make_holidays_df(
|
||||
year_list=np.arange(1995, 2045, 1).tolist(),
|
||||
country=country_code,
|
||||
)
|
||||
df["country"] = country_code
|
||||
all_holidays.append(df)
|
||||
|
||||
generated_holidays = pd.concat(all_holidays, axis=0, ignore_index=True)
|
||||
generated_holidays['year'] = generated_holidays.ds.dt.year
|
||||
generated_holidays.sort_values(['country', 'ds', 'holiday'], inplace=True)
|
||||
generated_holidays["year"] = generated_holidays.ds.dt.year
|
||||
generated_holidays.sort_values(["country", "ds", "holiday"], inplace=True)
|
||||
|
||||
# Convert to ASCII, and drop holidays that fail to convert
|
||||
generated_holidays['holiday'] = generated_holidays['holiday'].apply(utf8_to_ascii)
|
||||
failed_countries = generated_holidays.loc[generated_holidays['holiday'] == 'FAILED_TO_PARSE', 'country'].unique()
|
||||
generated_holidays["holiday"] = generated_holidays["holiday"].apply(utf8_to_ascii)
|
||||
failed_countries = generated_holidays.loc[
|
||||
generated_holidays["holiday"] == "FAILED_TO_PARSE", "country"
|
||||
].unique()
|
||||
if len(failed_countries) > 0:
|
||||
print("Failed to convert UTF-8 holidays for:")
|
||||
print('\n'.join(failed_countries))
|
||||
assert 'FAILED_TO_PARSE' not in generated_holidays['holiday'].unique()
|
||||
generated_holidays.to_csv("../R/data-raw/generated_holidays.csv", index=False)
|
||||
print("\n".join(failed_countries))
|
||||
assert "FAILED_TO_PARSE" not in generated_holidays["holiday"].unique()
|
||||
return generated_holidays
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# execute only if run as a script
|
||||
generate_holidays_file()
|
||||
import argparse
|
||||
import pathlib
|
||||
|
||||
if not pathlib.Path.cwd().stem == "python":
|
||||
raise RuntimeError("Run script from prophet/python directory")
|
||||
OUT_CSV_PATH = pathlib.Path(".") / ".." / "R/data-raw/generated_holidays.csv"
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-o", "--outfile", default=OUT_CSV_PATH)
|
||||
args = parser.parse_args()
|
||||
df = generate_holidays_df()
|
||||
df.to_csv(args.outfile, index=False)
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from prophet.hdays import *
|
||||
Loading…
Reference in a new issue