import numpy as np import pandas as pd from typing import List, Dict, Optional import gluonts.time_feature from tabpfn_time_series.features.feature_generator_base import ( FeatureGenerator, ) class RunningIndexFeature(FeatureGenerator): def generate(self, df: pd.DataFrame) -> pd.DataFrame: df = df.copy() df["running_index"] = range(len(df)) return df class CalendarFeature(FeatureGenerator): def __init__( self, components: Optional[List[str]] = None, seasonal_features: Optional[Dict[str, List[float]]] = None, ): self.components = components or ["year"] self.seasonal_features = seasonal_features or { # (feature, natural seasonality) "second_of_minute": [60], "minute_of_hour": [60], "hour_of_day": [24], "day_of_week": [7], "day_of_month": [30.5], "day_of_year": [365], "week_of_year": [52], "month_of_year": [12], } def generate(self, df: pd.DataFrame) -> pd.DataFrame: df = df.copy() timestamps = df.index.get_level_values("timestamp") # Add basic calendar components for component in self.components: df[component] = getattr(timestamps, component) # Add seasonal features for feature_name, periods in self.seasonal_features.items(): feature_func = getattr(gluonts.time_feature, f"{feature_name}_index") feature = feature_func(timestamps).astype(np.int32) if periods is not None: for period in periods: period = period - 1 # Adjust for 0-based indexing df[f"{feature_name}_sin"] = np.sin(2 * np.pi * feature / period) df[f"{feature_name}_cos"] = np.cos(2 * np.pi * feature / period) else: df[feature_name] = feature return df class AdditionalCalendarFeature(CalendarFeature): def __init__( self, components: Optional[List[str]] = None, additional_seasonal_features: Optional[Dict[str, List[float]]] = None, ): super().__init__(components=components) self.seasonal_features = { **additional_seasonal_features, **self.seasonal_features, } class PeriodicSinCosineFeature(FeatureGenerator): def __init__(self, periods: List[float], name_suffix: str = None): self.periods = periods self.name_suffix = name_suffix def generate(self, df: pd.DataFrame) -> pd.DataFrame: df = df.copy() for i, period in enumerate(self.periods): name_suffix = f"{self.name_suffix}_{i}" if self.name_suffix else f"{period}" df[f"sin_{name_suffix}"] = np.sin(2 * np.pi * np.arange(len(df)) / period) df[f"cos_{name_suffix}"] = np.cos(2 * np.pi * np.arange(len(df)) / period) return df