From 93d2c6941bb7b31eb079bbb0377dac3028ba0a6c Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Mon, 15 Dec 2025 15:16:55 -0500 Subject: [PATCH 01/14] feat: adapter for sktime forecasters --- timecopilot/models/sktime.py | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 timecopilot/models/sktime.py diff --git a/timecopilot/models/sktime.py b/timecopilot/models/sktime.py new file mode 100644 index 0000000..60c47c6 --- /dev/null +++ b/timecopilot/models/sktime.py @@ -0,0 +1,88 @@ +from typing import Any + +import numpy as np +import pandas as pd + +from .utils.forecaster import Forecaster + +# from sktime.forecasting.base import BaseForecaster, ForecastingHorizon + +# NOTE: SKTime notes +# https://www.sktime.net/en/stable/examples/01_forecasting.html# +# https://www.sktime.net/en/stable/api_reference/auto_generated/sktime.forecasting.base.BaseForecaster.html +# NOTE: forecaster setup args vary, that setup is currently being left to users +# TODO: some forecasters require horizon be provided in fit() call, account for that +# TODO: exogenous data support +# TODO: different alias for different sktime forecasters +# should this be required? + + +class SKTimeAdapter(Forecaster): + """ + Wrapper for SKTime Forecaster models for time series forecasting. + + + See the [official documentation](https://www.sktime.net/en/stable/api_reference/auto_generated/sktime.forecasting.base.BaseForecaster.html) + for more details. + """ + + def __init__( + self, + model, + # model: BaseForecaster, + alias: str = "SKTimeAdapter", + *args: Any, + **kwargs: Any, + ): + """ + Args: + alias (str, optional): Custom name for the model instance. + Default is "SKTimeAdapter". + *args: Additional positional arguments passed to SKTimeAdapter. + **kwargs: Additional keyword arguments passed to SKTimeAdapter. + """ + super().__init__(*args, **kwargs) + self.alias = alias + self.model = model + + def forecast( + self, + df: pd.DataFrame, + h: int, + freq: str | None = None, + level: list[int | float] | None = None, + quantiles: list[float] | None = None, + ) -> pd.DataFrame: + # TODO: support for exogenous data + # TODO: add support for level for sktime models that can support it + # TODO: add support for quantiles for sktime models that can support it + if level is not None: + raise ValueError( + "Level and quantiles are not supported for adapted sktime models yet." + ) + # NOTE: may not be needed + freq = self._maybe_infer_freq(df, freq) + forecast_horizon = np.arange(1, 1 + h) + id_col = "unique_id" + datetime_col = "ds" + y_col = "y" + df = df.copy() + df[datetime_col] = pd.to_datetime(df[datetime_col]) + df = df.set_index([id_col, datetime_col]) + + # some sktime models require fh be passed in fit() + model = self.model + model.fit(y=df, fh=forecast_horizon) + # fh doesn't need to be passed in predict because it is being passed in fit + # if quantiles is not None: + # print("sktime quantile pred") + # fcst_df = model.predict_quantiles( + # alpha=qc.quantiles + # ) + # fcst_df = fcst_df.reset_index() + # return fcst_df + fcst_df = model.predict() + fcst_df = fcst_df.reset_index() + # fcst_df = qc.maybe_convert_quantiles_to_level(fcst_df, models=[self.alias]) + fcst_df.rename(columns={y_col: self.alias}, inplace=True) + return fcst_df From f7663c4139b640386ceca7bb92e509779d8451c8 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Mon, 15 Dec 2025 15:18:06 -0500 Subject: [PATCH 02/14] docs: example notebook for sktime --- docs/examples/sktime.ipynb | 416 +++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 docs/examples/sktime.ipynb diff --git a/docs/examples/sktime.ipynb b/docs/examples/sktime.ipynb new file mode 100644 index 0000000..02f4f61 --- /dev/null +++ b/docs/examples/sktime.ipynb @@ -0,0 +1,416 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e890f3b3", + "metadata": {}, + "source": [ + "# Using sktime models\n", + "\n", + "This is an example for using sktime based models with the `timecopilot` library." + ] + }, + { + "cell_type": "markdown", + "id": "6140af37", + "metadata": {}, + "source": [ + "## imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f5c85b29", + "metadata": {}, + "outputs": [], + "source": [ + "import nest_asyncio\n", + "nest_asyncio.apply()\n", + "\n", + "import timecopilot\n", + "import sktime as skt\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "694b7854", + "metadata": {}, + "source": [ + "## Setup the sktime model and adapt it to TimeCopilot\n", + "\n", + "sktime models need to be adapted to work properly with TimeCopilot. This is done by creating your model with sktime and passing it through SKTimeAdapter. Some sktime models may require more configuration to function properly with the data you intend to use it on. For example, when using sktime's NaiveForecaster with yearly data you might want to initialize it with an `sp` argument of `12` like this `NaiveForecaster(sp=12)`.\n", + "\n", + "The `Alias` argument should also be provided, especially if you plan on adding multiple sktime forecasters. If you add multiple sktime models without specifying aliases, TimeCopilot will not be able to properly call all of them." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e1870fe0", + "metadata": {}, + "outputs": [], + "source": [ + "from sktime.forecasting.trend import TrendForecaster\n", + "from timecopilot.models.sktime import SKTimeAdapter\n", + "\n", + "trend_forecaster = TrendForecaster()\n", + "\n", + "adapted_model = SKTimeAdapter(\n", + " model=trend_forecaster,\n", + " alias=\"TrendForecaster\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "37419dd4", + "metadata": {}, + "source": [ + "## Create a TimeCopilot instance with your sktime model\n", + "\n", + "You will need to specify the forecasters you're using when using sktime models. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f793b038", + "metadata": {}, + "outputs": [], + "source": [ + "tc = timecopilot.TimeCopilot(\n", + " llm=\"openai:gpt-4o\",\n", + " forecasters=[\n", + " adapted_model,\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "401d5b6f", + "metadata": {}, + "source": [ + "### Extending default model list with an sktime adapted model\n", + "\n", + "if you want to use the default list with the addition of your sktime model you could make a copy of the default list and append your model to it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76505216", + "metadata": {}, + "outputs": [], + "source": [ + "model_list = timecopilot.agent.DEFAULT_MODELS.copy()\n", + "model_list.append(adapted_model)\n", + "\n", + "tc = timecopilot.TimeCopilot(\n", + " llm=\"openai:gpt-4o\",\n", + " forecasters=model_list\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2d6f09e4", + "metadata": {}, + "source": [ + "## Forecasting \n", + "Once that setup is complete, you can use TimeCopilot with your adapted sktime model the same way you'd normally use TimeCopilot" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e9122229", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv(\"https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f95e1578", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "1it [00:00, 4.70it/s]\n", + "1it [00:00, 223.32it/s]\n", + "11it [00:00, 77.11it/s]\n" + ] + } + ], + "source": [ + "result = tc.forecast(\n", + " df=df,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7355c143", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The 'AirPassengers' time series has a series length of 144 with a clear seasonal pattern identified using key features. The high 'seasonal_strength' of 0.981 suggests strong seasonality, evident from the 12-month seasonal period. The time series also exhibits trends, shown by a 'trend' score of 0.997, and moderate curvature at 1.069. The high autocorrelation 'x_acf1' at 0.948 indicates the persistence of patterns over time. The Holt-Winters parameters suggest a stable level (alpha ~1) with no trend component (beta ~0) and significant seasonal smoothing (gamma ~0.75). These features suggest that both trend and seasonality are prominent and need to be captured by the model.\n" + ] + } + ], + "source": [ + "print(result.output.tsfeatures_analysis)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "86acfa60", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
unique_iddsTrendForecaster
0AirPassengers1961-01-01473.023018
1AirPassengers1961-02-01475.729097
2AirPassengers1961-03-01478.173296
3AirPassengers1961-04-01480.879374
4AirPassengers1961-05-01483.498159
5AirPassengers1961-06-01486.204237
6AirPassengers1961-07-01488.823023
7AirPassengers1961-08-01491.529101
8AirPassengers1961-09-01494.235179
9AirPassengers1961-10-01496.853964
10AirPassengers1961-11-01499.560042
11AirPassengers1961-12-01502.178827
12AirPassengers1962-01-01504.884906
13AirPassengers1962-02-01507.590984
14AirPassengers1962-03-01510.035183
15AirPassengers1962-04-01512.741261
16AirPassengers1962-05-01515.360046
17AirPassengers1962-06-01518.066125
18AirPassengers1962-07-01520.684910
19AirPassengers1962-08-01523.390988
20AirPassengers1962-09-01526.097066
21AirPassengers1962-10-01528.715851
22AirPassengers1962-11-01531.421929
23AirPassengers1962-12-01534.040714
\n", + "
" + ], + "text/plain": [ + " unique_id ds TrendForecaster\n", + "0 AirPassengers 1961-01-01 473.023018\n", + "1 AirPassengers 1961-02-01 475.729097\n", + "2 AirPassengers 1961-03-01 478.173296\n", + "3 AirPassengers 1961-04-01 480.879374\n", + "4 AirPassengers 1961-05-01 483.498159\n", + "5 AirPassengers 1961-06-01 486.204237\n", + "6 AirPassengers 1961-07-01 488.823023\n", + "7 AirPassengers 1961-08-01 491.529101\n", + "8 AirPassengers 1961-09-01 494.235179\n", + "9 AirPassengers 1961-10-01 496.853964\n", + "10 AirPassengers 1961-11-01 499.560042\n", + "11 AirPassengers 1961-12-01 502.178827\n", + "12 AirPassengers 1962-01-01 504.884906\n", + "13 AirPassengers 1962-02-01 507.590984\n", + "14 AirPassengers 1962-03-01 510.035183\n", + "15 AirPassengers 1962-04-01 512.741261\n", + "16 AirPassengers 1962-05-01 515.360046\n", + "17 AirPassengers 1962-06-01 518.066125\n", + "18 AirPassengers 1962-07-01 520.684910\n", + "19 AirPassengers 1962-08-01 523.390988\n", + "20 AirPassengers 1962-09-01 526.097066\n", + "21 AirPassengers 1962-10-01 528.715851\n", + "22 AirPassengers 1962-11-01 531.421929\n", + "23 AirPassengers 1962-12-01 534.040714" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result.fcst_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "timecopilot", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3c33446a8a1005b95953f1330beab1cc646b5927 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:38:28 -0500 Subject: [PATCH 03/14] docs: sktime adapter docstrings --- timecopilot/models/sktime.py | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/timecopilot/models/sktime.py b/timecopilot/models/sktime.py index 60c47c6..9ad8f35 100644 --- a/timecopilot/models/sktime.py +++ b/timecopilot/models/sktime.py @@ -36,6 +36,7 @@ def __init__( ): """ Args: + model (sktime.forecasting.base.BaseForecaster): sktime forecasting model alias (str, optional): Custom name for the model instance. Default is "SKTimeAdapter". *args: Additional positional arguments passed to SKTimeAdapter. @@ -53,6 +54,56 @@ def forecast( level: list[int | float] | None = None, quantiles: list[float] | None = None, ) -> pd.DataFrame: + """ + Generate forecasts for time series data using an sktime model. + + This method produces point forecasts and, optionally, prediction + intervals or quantile forecasts. The input DataFrame can contain one + or multiple time series in stacked (long) format. + + Prediction intervals and quantile forecasts are not currently supported + with sktime based models + + Args: + df (pd.DataFrame): + DataFrame containing the time series to forecast. It must + include as columns: + + - "unique_id": an ID column to distinguish multiple series. + - "ds": a time column indicating timestamps or periods. + - "y": a target column with the observed values. + + h (int): + Forecast horizon specifying how many future steps to predict. + freq (str, optional): + Frequency of the time series (e.g. "D" for daily, "M" for + monthly). See [Pandas frequency aliases](https://pandas.pydata.org/ + pandas-docs/stable/user_guide/timeseries.html#offset-aliases) for + valid values. If not provided, the frequency will be inferred + from the data. + level (list[int | float], optional): + Confidence levels for prediction intervals, expressed as + percentages (e.g. [80, 95]). If provided, the returned + DataFrame will include lower and upper interval columns for + each specified level. + quantiles (list[float], optional): + List of quantiles to forecast, expressed as floats between 0 + and 1. Should not be used simultaneously with `level`. When + provided, the output DataFrame will contain additional columns + named in the format "model-q-{percentile}", where {percentile} + = 100 × quantile value. + + Returns: + pd.DataFrame: + DataFrame containing forecast results. Includes: + + - point forecasts for each timestamp and series. + - prediction intervals if `level` is specified. + - quantile forecasts if `quantiles` is specified. + + For multi-series data, the output retains the same unique + identifiers as the input DataFrame. + """ # TODO: support for exogenous data # TODO: add support for level for sktime models that can support it # TODO: add support for quantiles for sktime models that can support it From fd40715c3d36dbbad872cdb36b5d547f2cfcf795 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:49:52 -0500 Subject: [PATCH 04/14] feat: sktime adapter alias auto default by default retrieve alias in sktime adapter from model type name --- timecopilot/models/sktime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/timecopilot/models/sktime.py b/timecopilot/models/sktime.py index 9ad8f35..4e8a2f7 100644 --- a/timecopilot/models/sktime.py +++ b/timecopilot/models/sktime.py @@ -30,7 +30,7 @@ def __init__( self, model, # model: BaseForecaster, - alias: str = "SKTimeAdapter", + alias: str | None = None, *args: Any, **kwargs: Any, ): @@ -38,12 +38,12 @@ def __init__( Args: model (sktime.forecasting.base.BaseForecaster): sktime forecasting model alias (str, optional): Custom name for the model instance. - Default is "SKTimeAdapter". + By default alias is retrieved from the type name of model. *args: Additional positional arguments passed to SKTimeAdapter. **kwargs: Additional keyword arguments passed to SKTimeAdapter. """ super().__init__(*args, **kwargs) - self.alias = alias + self.alias = alias if alias is not None else type(model).__name__ self.model = model def forecast( From 3b1951389c16fa742d2fd19a4e026f6642512434 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:54:55 -0500 Subject: [PATCH 05/14] docs: sktime nb added in mkdocs --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index d76eeef..bc346d6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,6 +28,7 @@ nav: - examples/gift-eval.ipynb - examples/chronos-family.ipynb - examples/cryptocurrency-quickstart.ipynb + - examples/sktime.ipynb - Experiments: - experiments/gift-eval.md - experiments/fev.md From 09e9e570d0f88081457f754f93ed743eee04612a Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:08:25 -0500 Subject: [PATCH 06/14] refactor: move sktime adapter to adapters directory --- timecopilot/models/{ => adapters}/sktime.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename timecopilot/models/{ => adapters}/sktime.py (100%) diff --git a/timecopilot/models/sktime.py b/timecopilot/models/adapters/sktime.py similarity index 100% rename from timecopilot/models/sktime.py rename to timecopilot/models/adapters/sktime.py From 4c8124fece582099645cdfbbc405b543bd7a4a14 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:09:51 -0500 Subject: [PATCH 07/14] docs: update sktime example modify sktime adapter import in notebook example to reflect move to adapters directory --- docs/examples/sktime.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/sktime.ipynb b/docs/examples/sktime.ipynb index 02f4f61..276e40b 100644 --- a/docs/examples/sktime.ipynb +++ b/docs/examples/sktime.ipynb @@ -47,13 +47,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "e1870fe0", "metadata": {}, "outputs": [], "source": [ "from sktime.forecasting.trend import TrendForecaster\n", - "from timecopilot.models.sktime import SKTimeAdapter\n", + "from timecopilot.models.adapters.sktime import SKTimeAdapter\n", "\n", "trend_forecaster = TrendForecaster()\n", "\n", From 4e262ea2c32d9646ef7d58e7a1da98b0420ac4cf Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:51:35 -0500 Subject: [PATCH 08/14] docs: add adapters to api reference --- docs/api/models/adapters/adapters.md | 8 ++++++++ mkdocs.yml | 1 + timecopilot/models/adapters/__init__.py | 0 3 files changed, 9 insertions(+) create mode 100644 docs/api/models/adapters/adapters.md create mode 100644 timecopilot/models/adapters/__init__.py diff --git a/docs/api/models/adapters/adapters.md b/docs/api/models/adapters/adapters.md new file mode 100644 index 0000000..c56efa2 --- /dev/null +++ b/docs/api/models/adapters/adapters.md @@ -0,0 +1,8 @@ +# `timecopilot.models.adapters` + + +::: timecopilot.models.adapters.sktime + options: + members: + - SKTimeAdapter + diff --git a/mkdocs.yml b/mkdocs.yml index bc346d6..11da8ce 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - api/models/ml.md - api/models/neural.md - api/models/ensembles.md + - api/models/adapters/adapters.md - api/models/utils/forecaster.md - api/gift-eval/gift-eval.md - Changelogs: diff --git a/timecopilot/models/adapters/__init__.py b/timecopilot/models/adapters/__init__.py new file mode 100644 index 0000000..e69de29 From 7dcd7280657f494f7c97ab73010d25dc9eba6d5c Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:59:27 -0500 Subject: [PATCH 09/14] docs: add example to sktime forecast docstring --- timecopilot/models/adapters/sktime.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/timecopilot/models/adapters/sktime.py b/timecopilot/models/adapters/sktime.py index 4e8a2f7..4155eac 100644 --- a/timecopilot/models/adapters/sktime.py +++ b/timecopilot/models/adapters/sktime.py @@ -54,6 +54,7 @@ def forecast( level: list[int | float] | None = None, quantiles: list[float] | None = None, ) -> pd.DataFrame: + # fmt: off """ Generate forecasts for time series data using an sktime model. @@ -103,7 +104,22 @@ def forecast( For multi-series data, the output retains the same unique identifiers as the input DataFrame. + + Example: + ```python + import pandas as pd + from timecopilot import TimeCopilot + from timecopilot.models.adapters.sktime import SKTimeAdapter + from sktime.forecasting.trends import TrendForecaster + + df = pd.read_csv("https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv") + adapted_skt_model = SKTimeAdapter(TrendForecaster()) + tc = TimeCopilot(llm="openai:gpt-4o", models=[adapted_skt_model]) + result = tc.forecast(df, h=12, freq="MS") + print(result.output) + ``` """ + # fmt: on # TODO: support for exogenous data # TODO: add support for level for sktime models that can support it # TODO: add support for quantiles for sktime models that can support it From 4b8c00dcaf0a8bd42e635fa9bfc9ad66414f85cd Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:42:16 -0500 Subject: [PATCH 10/14] fix: fix import error in sktime adapter error was caused by moving sktime adapter into adapters directory without updating relative import --- timecopilot/models/adapters/sktime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timecopilot/models/adapters/sktime.py b/timecopilot/models/adapters/sktime.py index 4155eac..b5e4b62 100644 --- a/timecopilot/models/adapters/sktime.py +++ b/timecopilot/models/adapters/sktime.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd -from .utils.forecaster import Forecaster +from ..utils.forecaster import Forecaster # from sktime.forecasting.base import BaseForecaster, ForecastingHorizon From e2a641727d73795a1473e115f58d141843156dd4 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:03:07 -0500 Subject: [PATCH 11/14] ci: add sktime as dev dependency --- pyproject.toml | 1 + uv.lock | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 71a4d60..0e5769f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dev = [ "pytest-rerunfailures>=15.1", "pytest-xdist>=3.8.0", "s3fs>=2025.3.0", + "sktime>=0.40.1", ] docs = [ "mkdocs-include-markdown-plugin>=7.1.6", diff --git a/uv.lock b/uv.lock index f4b177c..39bbcae 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'linux'", @@ -6585,6 +6585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878, upload-time = "2025-02-26T09:15:14.99Z" }, ] +[[package]] +name = "scikit-base" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/4d/53b477e05ba7897026133543114c48c9e67646180b85a9e3bd6edb6e5223/scikit_base-0.13.0.tar.gz", hash = "sha256:436c26067173a7235c465c64ac105008f6f889f09308c5e4e4a16e21c38c3ddc", size = 127431, upload-time = "2025-10-08T20:39:35.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1c/7c00d3823bef4ef2e5a95507475bc5cf3bdabf00c8577f8bb5ff406e9f2e/scikit_base-0.13.0-py3-none-any.whl", hash = "sha256:efe7df0f09854b9f28e9532f6be87ef433dd8f1358cabd24ebf168aa4ccca1fc", size = 151529, upload-time = "2025-10-08T20:39:33.951Z" }, +] + [[package]] name = "scikit-learn" version = "1.6.1" @@ -6815,6 +6824,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "sktime" +version = "0.40.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "packaging" }, + { name = "pandas", version = "2.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "scikit-base" }, + { name = "scikit-learn", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/7a/22b99a00a7cabaa34c350f78f33ae89711cfc08deef06043daee3835dbb3/sktime-0.40.1.tar.gz", hash = "sha256:bbf3c2971c7ad388fbebc1d1fb573e20f730e6486c00935b336d8edf13bca04d", size = 35197759, upload-time = "2025-11-25T00:03:38.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/0d/3b2e5b51593009f72fb7fb6956e6c652da1450c8c5484c40a276113c2114/sktime-0.40.1-py3-none-any.whl", hash = "sha256:130bb0c39ca4377e491c4dfa64c8ac67c3de2660dd406dd7690db6a8ac349c4e", size = 36265753, upload-time = "2025-11-25T00:03:34.522Z" }, +] + [[package]] name = "smmap" version = "5.0.2" @@ -7304,6 +7334,7 @@ dev = [ { name = "pytest-rerunfailures" }, { name = "pytest-xdist" }, { name = "s3fs" }, + { name = "sktime" }, ] docs = [ { name = "mkdocs" }, @@ -7356,6 +7387,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=15.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "s3fs", specifier = ">=2025.3.0" }, + { name = "sktime", specifier = ">=0.40.1" }, ] docs = [ { name = "mkdocs", specifier = ">=1.6.1" }, From f3d5498925c9d344b6416eec8d2037e5153b5331 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:58:06 -0500 Subject: [PATCH 12/14] docs: contributing guide - new models note --- docs/contributing.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 7f7fc23..9099ba0 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -77,6 +77,14 @@ All documentation files should use **kebab-case** (e.g., `model-hub.md`, `foreca For further reference, see the [Google Developer Documentation Style Guide on file names](https://developers.google.com/style/filenames). +### Adding Models & Adapters + +In order for new models and adapters to show up in TimeCopilot's documentation properly there are a couple steps to take: + +- Add the model/adapter in its own file or the appropriate file in `docs/api/models` or the appropriate subdirectory. For adapters, it may also be good to create an example notebook in `docs/examples` and add it to the `Examples` section of [mkdocs.yml](https://github.com/TimeCopilot/timecopilot/blob/main/mkdocs.yml). + - when creating a new file in `docs/api/models` for this step, add that file in the API Reference section of [mkdocs.yml](https://github.com/TimeCopilot/timecopilot/blob/main/mkdocs.yml) +- A docstring should be present for the new model/adapter with an example in the `forecast()` method in a similar style to the [TimeCopilot agent query method](https://timecopilot.dev/api/agent/#timecopilot.agent.TimeCopilot.query). Note: when adding an adapter with its own dependencies you may need to add those dependencies as dev dependencies with `uv add new_dep --group dev`. + ## Adding New Datasets The datasets utilized in our documentation are hosted on AWS at `https://timecopilot.s3.amazonaws.com/public/data/`. If you wish to contribute additional datasets for your changes, please contact [@AzulGarza](http://github.com/AzulGarza) for guidance. @@ -115,4 +123,4 @@ TimeCopilot uses some forked Python packages, maintained under custom names on P - **uni2ts**: - Forked from: [SalesforceAIResearch/uni2ts](https://github.com/SalesforceAIResearch/uni2ts) - TimeCopilot fork: [AzulGarza/uni2ts](https://github.com/AzulGarza/uni2ts) - - Published on PyPI as: [`timecopilot-uni2ts`](https://pypi.org/project/timecopilot-uni2ts/) \ No newline at end of file + - Published on PyPI as: [`timecopilot-uni2ts`](https://pypi.org/project/timecopilot-uni2ts/) From 42ddc41337f74f3f4b087545330d2431d0da5f89 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:11:35 -0500 Subject: [PATCH 13/14] docs: sktime docstring example fix --- timecopilot/models/adapters/sktime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timecopilot/models/adapters/sktime.py b/timecopilot/models/adapters/sktime.py index b5e4b62..7b84d55 100644 --- a/timecopilot/models/adapters/sktime.py +++ b/timecopilot/models/adapters/sktime.py @@ -110,7 +110,7 @@ def forecast( import pandas as pd from timecopilot import TimeCopilot from timecopilot.models.adapters.sktime import SKTimeAdapter - from sktime.forecasting.trends import TrendForecaster + from sktime.forecasting.trend import TrendForecaster df = pd.read_csv("https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv") adapted_skt_model = SKTimeAdapter(TrendForecaster()) From 77a82a63cb1cf0e46f0d2699d77305e111820414 Mon Sep 17 00:00:00 2001 From: spolisar <22416070+spolisar@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:00:23 -0500 Subject: [PATCH 14/14] docs: fix sktime adapter example --- timecopilot/models/adapters/sktime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timecopilot/models/adapters/sktime.py b/timecopilot/models/adapters/sktime.py index 7b84d55..a258226 100644 --- a/timecopilot/models/adapters/sktime.py +++ b/timecopilot/models/adapters/sktime.py @@ -114,7 +114,7 @@ def forecast( df = pd.read_csv("https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv") adapted_skt_model = SKTimeAdapter(TrendForecaster()) - tc = TimeCopilot(llm="openai:gpt-4o", models=[adapted_skt_model]) + tc = TimeCopilot(llm="openai:gpt-4o", forecasters=[adapted_skt_model]) result = tc.forecast(df, h=12, freq="MS") print(result.output) ```