Download code/calendar_features.py from Elpriser/eu-price-forecast: direct link, hf CLI and curl.
- Browser
- Download file 5.67 kB
-
https://huggingface.co/Elpriser/eu-price-forecast/resolve/main/code/calendar_features.py
- Command line
-
hf download hf://Elpriser/eu-price-forecast/code/calendar_features.py
-
curl -L -o calendar_features.py https://huggingface.co/Elpriser/eu-price-forecast/resolve/main/code/calendar_features.py
5.67 kB
| """Public holidays per country, for the one calendar feature the Nordic model does not have. | |
| A holiday flattens demand for the whole day, and in the EU zones that moves the day's price level and its shape more | |
| than the weekday does (Christmas week, Easter, 1 May). The Nordic model gets by with weekday alone; across 25 countries | |
| that would be the first thing a quick look at the residuals finds. | |
| No `holidays` package (it is not on the Spark and the model should not need pip): the dates are computed here, from | |
| Easter (the anonymous Gregorian algorithm) and a short table of the days that are a holiday in (almost) every country plus | |
| the national ones that move a price. A missing local holiday costs a little accuracy on one day a year, a wrong flag is not | |
| possible to avoid completely either - this is a model feature, not a statement of law. | |
| """ | |
| from datetime import date, timedelta | |
| def easter(year): | |
| """Easter Sunday of the Gregorian calendar (Meeus/Jones/Butcher).""" | |
| a, b, c = year % 19, year // 100, year % 100 | |
| d, e = b // 4, b % 4 | |
| f = (b + 8) // 25 | |
| g = (b - f + 1) // 3 | |
| h = (19 * a + b - d - g + 15) % 30 | |
| i, k = c // 4, c % 4 | |
| l = (32 + 2 * e + 2 * i - h - k) % 7 | |
| m = (a + 11 * h + 22 * l) // 451 | |
| month, day = divmod(h + l - 7 * m + 114, 31) | |
| return date(year, month, day + 1) | |
| # fixed-date holidays: (month, day) per country | |
| FIXED = { | |
| "DE": [(1, 1), (5, 1), (10, 3), (12, 25), (12, 26)], | |
| "FR": [(1, 1), (5, 1), (5, 8), (7, 14), (8, 15), (11, 1), (11, 11), (12, 25)], | |
| "PL": [(1, 1), (1, 6), (5, 1), (5, 3), (8, 15), (11, 1), (11, 11), (12, 25), (12, 26)], | |
| "IT": [(1, 1), (1, 6), (4, 25), (5, 1), (6, 2), (8, 15), (11, 1), (12, 8), (12, 25), (12, 26)], | |
| "AT": [(1, 1), (1, 6), (5, 1), (8, 15), (10, 26), (11, 1), (12, 8), (12, 25), (12, 26)], | |
| "BE": [(1, 1), (5, 1), (7, 21), (8, 15), (11, 1), (11, 11), (12, 25)], | |
| "CH": [(1, 1), (8, 1), (12, 25), (12, 26)], | |
| "CZ": [(1, 1), (5, 1), (5, 8), (7, 5), (7, 6), (9, 28), (10, 28), (11, 17), (12, 24), (12, 25), (12, 26)], | |
| "HU": [(1, 1), (3, 15), (5, 1), (8, 20), (10, 23), (11, 1), (12, 25), (12, 26)], | |
| "SI": [(1, 1), (1, 2), (2, 8), (4, 27), (5, 1), (5, 2), (6, 25), (8, 15), (10, 31), (11, 1), (12, 25), (12, 26)], | |
| "ES": [(1, 1), (1, 6), (5, 1), (8, 15), (10, 12), (11, 1), (12, 6), (12, 8), (12, 25)], | |
| "PT": [(1, 1), (4, 25), (5, 1), (6, 10), (8, 15), (10, 5), (11, 1), (12, 1), (12, 8), (12, 25)], | |
| "SK": [(1, 1), (1, 6), (5, 1), (5, 8), (7, 5), (8, 29), (9, 1), (9, 15), (11, 1), (11, 17), (12, 24), (12, 25), (12, 26)], | |
| "GR": [(1, 1), (1, 6), (3, 25), (5, 1), (8, 15), (10, 28), (12, 25), (12, 26)], | |
| "RO": [(1, 1), (1, 2), (1, 24), (5, 1), (6, 1), (8, 15), (11, 30), (12, 1), (12, 25), (12, 26)], | |
| "BG": [(1, 1), (3, 3), (5, 1), (5, 6), (5, 24), (9, 6), (9, 22), (12, 24), (12, 25), (12, 26)], | |
| "HR": [(1, 1), (1, 6), (5, 1), (5, 30), (6, 22), (6, 25), (8, 5), (8, 15), (11, 1), (11, 18), (12, 25), (12, 26)], | |
| "RS": [(1, 1), (1, 2), (1, 7), (2, 15), (2, 16), (5, 1), (5, 2), (11, 11)], | |
| "EE": [(1, 1), (2, 24), (5, 1), (6, 23), (6, 24), (8, 20), (12, 24), (12, 25), (12, 26)], | |
| "LV": [(1, 1), (5, 1), (5, 4), (6, 23), (6, 24), (11, 18), (12, 24), (12, 25), (12, 26), (12, 31)], | |
| "LT": [(1, 1), (2, 16), (3, 11), (5, 1), (6, 24), (7, 6), (8, 15), (11, 1), (11, 2), (12, 24), (12, 25), (12, 26)], | |
| "IE": [(1, 1), (3, 17), (12, 25), (12, 26)], | |
| "ME": [(1, 1), (1, 2), (5, 1), (5, 2), (5, 21), (5, 22), (7, 13), (7, 14)], | |
| "MK": [(1, 1), (1, 7), (5, 1), (5, 24), (8, 2), (9, 8), (10, 11), (10, 23), (12, 8)], | |
| "AL": [(1, 1), (1, 2), (3, 14), (3, 22), (5, 1), (10, 19), (11, 28), (11, 29), (12, 8), (12, 25)], | |
| } | |
| # Easter-relative days (offset from Easter Sunday) per country, Western Easter | |
| EASTER = { | |
| "DE": [-2, 1, 39, 50], "FR": [1, 39, 50], "PL": [1, 60], "IT": [1], "AT": [1, 39, 50, 60], | |
| "BE": [1, 39, 50], "CH": [-2, 1, 39, 50], "CZ": [-2, 1], "HU": [-2, 1, 50], "SI": [1, 50], | |
| "ES": [-2], "PT": [-2, 60], "SK": [-2, 1], "GR": [], "RO": [], "BG": [], "HR": [1, 60], "RS": [], | |
| "EE": [-2], "LV": [-2, 1], "LT": [1], "IE": [1], "ME": [], "MK": [], "AL": [], | |
| } | |
| # Orthodox Easter countries keep the Orthodox feasts; the dates are tabled below (computed by the Julian algorithm) | |
| ORTHODOX = {"GR": [-2, 1, 50], "RO": [-2, 1, 50], "BG": [-2, 1], "RS": [-2, 1], "ME": [1], "MK": [1], "AL": []} | |
| def orthodox_easter(year): | |
| """Orthodox Easter Sunday as a Gregorian date (Julian algorithm, +13 days in 1900-2099).""" | |
| a, b, c = year % 4, year % 7, year % 19 | |
| d = (19 * c + 15) % 30 | |
| e = (2 * a + 4 * b - d + 34) % 7 | |
| month, day = divmod(d + e + 114, 31) | |
| return date(year, month, day + 1) + timedelta(days=13) | |
| _cache = {} | |
| def holidays_of(country, year): | |
| key = (country, year) | |
| if key not in _cache: | |
| days = {date(year, m, d) for m, d in FIXED.get(country, [])} | |
| e = easter(year) | |
| days |= {e + timedelta(days=o) for o in EASTER.get(country, [])} | |
| oe = orthodox_easter(year) | |
| days |= {oe + timedelta(days=o) for o in ORTHODOX.get(country, [])} | |
| _cache[key] = days | |
| return _cache[key] | |
| def is_holiday(d, country): | |
| return d in holidays_of(country, d.year) | |
| def xmas_week(d): | |
| """24 December to 1 January: demand is low in every country, holiday or not.""" | |
| return (d.month == 12 and d.day >= 24) or (d.month == 1 and d.day == 1) | |
| def flags(dates, country): | |
| """(is_holiday, xmas_week) as two lists of 0/1 for an iterable of dates.""" | |
| h, x = [], [] | |
| for d in dates: | |
| h.append(1 if is_holiday(d, country) else 0) | |
| x.append(1 if xmas_week(d) else 0) | |
| return h, x | |