[docs]@dataclassclassForecast:""" The main class to represent a forecast of an individual item. """orig_series:pd.Seriesconfig:ForecastConfigitem_config:ForecastItemConfigbase_config:ItemConfigpct_of_series:Optional[pd.Series]=Nonepct_of_config:Optional[ItemConfig]=Nonedef__post_init__(self):self.model=get_model(self.config,self.item_config,self.base_config)
[docs]defpredict(self)->pd.Series:ifnotself.model.has_been_fit:raiseForecastNotFitException("call .fit before .predict")returnself.model.predict()
[docs]defplot(self,ax:Optional[plt.Axes]=None,figsize:Tuple[int,int]=(12,5))->plt.Figure:ifnotself.model.has_prediction:raiseForecastNotPredictedException("call .predict before .plot")returnself.model.plot(ax=ax,figsize=figsize,title=self.name)
[docs]defto_manual(self,use_levels:bool=False,adjustments:Optional[Union[Sequence[float],Dict[int,float]]]=None,replacements:Optional[Union[Sequence[float],Dict[int,float]]]=None,):ifnotself.model.has_prediction:raiseForecastNotPredictedException("call .fit then .predict before .to_manual")ifuse_levels:values=self.result.valuesconfig_key="levels"reset_key="growth"else:# Growthvalues=self.result.pct_change().values# Fill in first growthvalues[0]=(self.result.iloc[0]-self.series.iloc[-1])/self.series.iloc[-1]config_key="growth"reset_key="levels"self.item_config.method="manual"ifadjustmentsisnotNone:ifnotisinstance(adjustments,dict)andlen(adjustments)!=len(values):raiseValueError(f"must pass equal length adjustments as number of periods. "f"Got {len(adjustments)} adjustments for {len(values)} periods")adjustments=_adjust_to_dict(adjustments)fori,adjinadjustments.items():values[i]+=adjifreplacementsisnotNone:ifnotisinstance(replacements,dict)andlen(replacements)!=len(values):raiseValueError(f"must pass equal length adjustments as number of periods. "f"Got {len(replacements)} adjustments for {len(values)} periods")replacements=_replace_to_dict(replacements)fori,replaceinreplacements.items():values[i]=replaceself.item_config.manual_forecasts[config_key]=list(values)self.item_config.manual_forecasts[reset_key]=[]self.model=ManualForecastModel(self.config,self.item_config,self.base_config)self.model.fit(self.series)self.model.predict()
@propertydefseries(self)->pd.Series:ifself.pct_of_seriesisNone:returnself.orig_serieselse:returnself.orig_series/self.pct_of_series@propertydefresult(self)->pd.Series:ifnotself.model.has_prediction:raiseForecastNotPredictedException("call .fit then .predict before .result")returnself.model.result@propertydefname(self)->str:ifself.pct_of_configisNone:returnself.base_config.display_name# Percentage of seriesreturnf"{self.base_config.display_name} % {self.pct_of_config.display_name}"
def_apply_operation_to_forecast(forecast:Forecast,other:T,func:Callable[[Any,T],Any],)->Forecast:updates:Dict[str,Any]={}updates["orig_series"]=func(forecast.orig_series,_get_attr_if_needed(other,"orig_series"))ifforecast.pct_of_seriesisnotNone:updates["pct_of_series"]=func(forecast.pct_of_series,_get_attr_if_needed(other,"pct_of_series"))ifforecast.pct_of_configisnotNone:updates["pct_of_config"]=func(forecast.pct_of_config,_get_attr_if_needed(other,"pct_of_config"))updates["item_config"]=func(forecast.item_config,_get_attr_if_needed(other,"item_config"))updates["base_config"]=func(forecast.base_config,_get_attr_if_needed(other,"base_config"))returnforecast.copy(**updates)def_get_attr_if_needed(other:Any,attr:str)->Any:ifisinstance(other,Forecast):returngetattr(other,attr)else:returnotherdef_adjust_to_dict(seq_or_dict:Union[Sequence[float],Dict[int,float]])->Dict[int,float]:ifisinstance(seq_or_dict,dict):returnseq_or_dict# If adjustment is 0 or None, skip the entry, otherwise add to the dictadjust_dict={i:valfori,valinenumerate(seq_or_dict)ifval}returnadjust_dictdef_replace_to_dict(seq_or_dict:Union[Sequence[float],Dict[int,float]])->Dict[int,float]:ifisinstance(seq_or_dict,dict):returnseq_or_dictreplace_dict={i:valfori,valinenumerate(seq_or_dict)}returnreplace_dict