Skip to content

Commit aee218f

Browse files
authored
release: v0.0.12 (#141)
2 parents 96c188d + adc2f2e commit aee218f

10 files changed

Lines changed: 220 additions & 36 deletions

File tree

docs/changelogs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
Welcome to the TimeCopilot Changelog. Here, you will find a comprehensive list of all the changes, updates, and improvements made to the TimeCopilot project. This section is designed to keep you informed about the latest features, bug fixes, and enhancements as we continue to develop and refine the TimeCopilot experience. Stay tuned for regular updates and feel free to explore the details of each release below.
44

55

6+
- [v0.0.12](v0.0.12.md)
7+
- [v0.0.11](v0.0.11.md)
68
- [v0.0.10](v0.0.10.md)

docs/changelogs/v0.0.12.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
### Features
2+
3+
* **Query Method**: Added a `query` method to the forecaster for flexible, programmatic access to model capabilities. See [#134](https://github.com/AzulGarza/timecopilot/pull/134).
4+
```python
5+
from timecopilot import TimeCopilot
6+
7+
tc = TimeCopilot(llm="openai:gpt-4o")
8+
tc.forecast(
9+
df="https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv",
10+
h=12,
11+
)
12+
result = tc.query("What is the best model for monthly data?")
13+
print(result.output)
14+
```
15+
16+
* **Async TimeCopilot Agent**: Introduced the `AsyncTimeCopilot` class for asynchronous forecasting and querying. See [#135](https://github.com/AzulGarza/timecopilot/pull/135) and [#138](https://github.com/AzulGarza/timecopilot/pull/138).
17+
```python
18+
import asyncio
19+
from timecopilot import AsyncTimeCopilot
20+
21+
async def main():
22+
tc = AsyncTimeCopilot(llm="openai:gpt-4o")
23+
await tc.forecast(
24+
df="https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv",
25+
h=12
26+
)
27+
answer = await tc.query("Which model performed best?")
28+
print(answer.output)
29+
30+
asyncio.run(main())
31+
```
32+
33+
* **Fallback Model Support**: The `TimeCopilotForecaster` now supports a fallback model, which is used if the primary model fails. See [#123](https://github.com/AzulGarza/timecopilot/pull/123).
34+
```python
35+
from timecopilot.forecaster import TimeCopilotForecaster
36+
from timecopilot.models.foundational.timesfm import TimesFM
37+
from timecopilot.models.benchmarks.stats import SeasonalNaive
38+
39+
forecaster = TimeCopilotForecaster(
40+
models=[TimesFM()],
41+
fallback_model=SeasonalNaive()
42+
)
43+
```
44+
45+
* **TimesFM 2.0 Support**: Added support for TimesFM 2.0, enabling the use of the latest version of Google's TimesFM model. See [#128](https://github.com/AzulGarza/timecopilot/pull/128).
46+
```python
47+
from timecopilot.models.foundational.timesfm import TimesFM
48+
49+
model = TimesFM(
50+
# default value
51+
repo_id="google/timesfm-2.0-500m-pytorch",
52+
)
53+
```
54+
55+
* **TabPFN Foundation Model**: Added the [TabPFN](https://github.com/PriorLabs/TabPFN) time series foundation model. See [#113](https://github.com/AzulGarza/timecopilot/pull/113).
56+
```python
57+
import pandas as pd
58+
from timecopilot.models.foundational.tabpfn import TabPFN
59+
60+
df = pd.read_csv("https://timecopilot.s3.amazonaws.com/public/data/algeria_exports.csv", parse_dates=["ds"])
61+
model = TabPFN()
62+
fcst = model.forecast(df, h=12)
63+
print(fcst)
64+
```
65+
66+
* **Median Ensemble**: Introduced a new Median Ensemble model that combines predictions from multiple models to improve forecast accuracy. See [#144](https://github.com/AzulGarza/timecopilot/pull/144).
67+
```python
68+
import pandas as pd
69+
from timecopilot.models.benchmarks import SeasonalNaive
70+
from timecopilot.models.ensembles.median import MedianEnsemble
71+
from timecopilot.models.foundational.chronos import Chronos
72+
73+
74+
df = pd.read_csv(
75+
"https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv",
76+
parse_dates=["ds"],
77+
)
78+
79+
models = [
80+
Chronos(
81+
repo_id="amazon/chronos-t5-tiny",
82+
alias="Chronos-T5",
83+
),
84+
Chronos(
85+
repo_id="amazon/chronos-bolt-tiny",
86+
alias="Chronos-Bolt",
87+
),
88+
SeasonalNaive(),
89+
]
90+
median_ensemble = MedianEnsemble(models=models)
91+
fcst_df = median_ensemble.forecast(
92+
df=df,
93+
h=12,
94+
)
95+
print(fcst_df)
96+
```
97+
98+
* **GIFTEval Module**: Added the [GIFTEval](https://github.com/SalesforceAIResearch/gift-eval/) module for advanced evaluation of forecasting models. See [#140](https://github.com/AzulGarza/timecopilot/pull/140).
99+
```python
100+
import pandas as pd
101+
from timecopilot.gift_eval.eval import GIFTEval, QUANTILE_LEVELS
102+
from timecopilot.gift_eval.gluonts_predictor import GluonTSPredictor
103+
from timecopilot.models.benchmarks import SeasonalNaive
104+
105+
storage_path = ".pytest_cache/gift_eval"
106+
GIFTEval.download_data(storage_path)
107+
108+
gifteval = GIFTEval(
109+
dataset_name="m4_weekly",
110+
term="short",
111+
output_path="./seasonal_naive",
112+
storage_path=storage_path,
113+
)
114+
predictor = GluonTSPredictor(
115+
forecaster=SeasonalNaive(),
116+
h=gifteval.dataset.prediction_length,
117+
freq=gifteval.dataset.freq,
118+
quantiles=QUANTILE_LEVELS,
119+
batch_size=512,
120+
)
121+
gifteval.evaluate_predictor(
122+
predictor,
123+
batch_size=512,
124+
)
125+
eval_df = pd.read_csv("./seasonal_naive/all_results.csv")
126+
print(eval_df)
127+
```
128+
129+
### Fixes
130+
131+
* **Model Compatibility**: Added support for the Moirai and TimeGPT models. See [#115](https://github.com/AzulGarza/timecopilot/pull/115), [#117](https://github.com/AzulGarza/timecopilot/pull/117).
132+
* **GluonTS Forecaster**: Improved frequency handling and now uses the median for forecasts. See [#124](https://github.com/AzulGarza/timecopilot/pull/124), [#127](https://github.com/AzulGarza/timecopilot/pull/127).
133+
* **TimesFM Quantile Names**: TimesFM now returns correct quantile names. See [#131](https://github.com/AzulGarza/timecopilot/pull/131).
134+
* **Removed Lag Llama**: The Lag Llama model has been removed. See [#116](https://github.com/AzulGarza/timecopilot/pull/116).
135+
* **DataFrame Handling**: Fixed DataFrame copying to avoid index side effects. See [#120](https://github.com/AzulGarza/timecopilot/pull/120).
136+
137+
### Docs
138+
139+
* **Foundation Model Documentation**: Added comprehensive documentation for foundation models, including paper citations and repository links. See [#118](https://github.com/AzulGarza/timecopilot/pull/118).
140+
* **Unique Alias Validation**: Added validation to prevent column conflicts in `TimeCopilotForecaster`. See [#122](https://github.com/AzulGarza/timecopilot/pull/122).
141+
142+
---
143+
144+
**Full Changelog**: https://github.com/AzulGarza/timecopilot/compare/v0.0.11...v0.0.12

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ nav:
3232
- api/gift_eval/gift_eval.md
3333
- Changelogs:
3434
- changelogs/index.md
35+
- changelogs/v0.0.12.md
3536
- changelogs/v0.0.11.md
3637
- changelogs/v0.0.10.md
3738

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ docs = [
6161
"mkdocs>=1.6.1",
6262
"mkdocs-material>=9.6.14",
6363
"mkdocstrings[python]>=0.29.1",
64+
"mktestdocs>=0.2.5",
6465
"modal>=1.0.4",
6566
"ruff>=0.12.1",
6667
]

tests/gift_eval/conftest.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
import pandas as pd
44
import pytest
5-
from huggingface_hub import snapshot_download
5+
6+
from timecopilot.gift_eval.eval import GIFTEval
67

78

89
@pytest.fixture(scope="session")
9-
def cache_dir() -> Path:
10-
cache_dir = Path(".pytest_cache") / "gift_eval"
11-
cache_dir.mkdir(parents=True, exist_ok=True)
12-
return cache_dir
10+
def cache_path() -> Path:
11+
cache_path = Path(".pytest_cache") / "gift_eval"
12+
cache_path.mkdir(parents=True, exist_ok=True)
13+
return cache_path
1314

1415

1516
@pytest.fixture(scope="session")
16-
def all_results_df(cache_dir: Path) -> pd.DataFrame:
17-
all_results_file = cache_dir / "seasonal_naive_all_results.csv"
17+
def all_results_df(cache_path: Path) -> pd.DataFrame:
18+
all_results_file = cache_path / "seasonal_naive_all_results.csv"
1819
if not all_results_file.exists():
1920
all_results_df = pd.read_csv(
2021
"https://huggingface.co/spaces/Salesforce/GIFT-Eval/raw/main/results/seasonal_naive/all_results.csv"
@@ -24,15 +25,6 @@ def all_results_df(cache_dir: Path) -> pd.DataFrame:
2425

2526

2627
@pytest.fixture(scope="session")
27-
def gift_eval_dir(cache_dir: Path) -> Path:
28-
snapshot_download(
29-
repo_id="Salesforce/GiftEval",
30-
repo_type="dataset",
31-
local_dir=cache_dir,
32-
)
33-
return cache_dir
34-
35-
36-
@pytest.fixture(autouse=True)
37-
def gift_eval_env(monkeypatch, gift_eval_dir):
38-
monkeypatch.setenv("GIFT_EVAL", gift_eval_dir)
28+
def storage_path(cache_path: Path) -> Path:
29+
GIFTEval.download_data(cache_path)
30+
return cache_path

tests/gift_eval/test_evaluation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ def test_evaluation(
4747
dataset_name: str,
4848
term: str,
4949
all_results_df: pd.DataFrame,
50+
storage_path: Path,
5051
):
5152
with tempfile.TemporaryDirectory() as temp_dir:
5253
gifteval = GIFTEval(
5354
dataset_name=dataset_name,
5455
term=term,
55-
output_dir=temp_dir,
56+
output_path=temp_dir,
57+
storage_path=storage_path,
5658
)
5759
predictor = GluonTSPredictor(
5860
forecaster=SeasonalNaive(

timecopilot/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .agent import TimeCopilot
1+
from .agent import AsyncTimeCopilot, TimeCopilot
22
from .forecaster import TimeCopilotForecaster
33

4-
__all__ = ["TimeCopilot", "TimeCopilotForecaster"]
4+
__all__ = ["AsyncTimeCopilot", "TimeCopilot", "TimeCopilotForecaster"]

timecopilot/gift_eval/data.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,25 @@ def __call__(
107107

108108

109109
class Dataset:
110+
def _storage_path_from_env_var(self, env_var: str) -> Path:
111+
load_dotenv()
112+
env_var_value = os.getenv(env_var)
113+
if env_var_value is None:
114+
raise ValueError(f"Environment variable {env_var} is not set")
115+
return Path(env_var_value)
116+
110117
def __init__(
111118
self,
112119
name: str,
113120
term: Term | str = Term.SHORT,
114121
to_univariate: bool = False,
122+
storage_path: Path | str | None = None,
115123
storage_env_var: str = "GIFT_EVAL",
116124
):
117-
load_dotenv()
118-
env_var = os.getenv(storage_env_var)
119-
if env_var is None:
120-
raise ValueError(f"Environment variable {storage_env_var} is not set")
121-
storage_path = Path(env_var)
125+
if storage_path is None:
126+
storage_path = self._storage_path_from_env_var(storage_env_var)
127+
else:
128+
storage_path = Path(storage_path)
122129
self.hf_dataset = datasets.load_from_disk(str(storage_path / name)).with_format(
123130
"numpy"
124131
)

timecopilot/gift_eval/eval.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from gluonts.model import evaluate_model
2020
from gluonts.model.predictor import RepresentablePredictor
2121
from gluonts.time_feature import get_seasonality
22+
from huggingface_hub import snapshot_download
2223

2324
from .data import Dataset
2425
from .gluonts_predictor import GluonTSPredictor
@@ -54,11 +55,26 @@ class GIFTEval:
5455
desired.
5556
"""
5657

58+
@staticmethod
59+
def download_data(storage_path: Path | str | None = None):
60+
"""
61+
Download the GIFTEval dataset from Hugging Face.
62+
63+
Args:
64+
storage_path (Path | str | None): Path to store the dataset.
65+
"""
66+
snapshot_download(
67+
repo_id="Salesforce/GiftEval",
68+
repo_type="dataset",
69+
local_dir=storage_path,
70+
)
71+
5772
def __init__(
5873
self,
5974
dataset_name: str,
6075
term: str,
61-
output_dir: str | Path | None,
76+
output_path: Path | str | None = None,
77+
storage_path: Path | str | None = None,
6278
):
6379
# fmt: off
6480
"""
@@ -67,19 +83,25 @@ def __init__(
6783
Args:
6884
dataset_name (str): Name of the dataset to evaluate on.
6985
term (str): Evaluation term (e.g., 'medium', 'long').
70-
output_dir (str | Path | None): Directory to save results CSV, or
86+
output_path (str | Path | None): Directory to save results CSV, or
7187
None to skip saving.
88+
storage_path (Path | str | None): Path where the dataset is stored.
7289
7390
Example:
7491
```python
92+
import pandas as pd
7593
from timecopilot.gift_eval.eval import GIFTEval, QUANTILE_LEVELS
7694
from timecopilot.gift_eval.gluonts_predictor import GluonTSPredictor
7795
from timecopilot.models.benchmarks import SeasonalNaive
7896
97+
storage_path = "./gift_eval_data"
98+
GIFTEval.download_data(storage_path)
99+
79100
gifteval = GIFTEval(
80-
dataset_name=dataset_name,
81-
term=term,
82-
output_dir="./my-dir",
101+
dataset_name="m4_weekly",
102+
term="short",
103+
output_path="./seasonal_naive",
104+
storage_path=storage_path,
83105
)
84106
predictor = GluonTSPredictor(
85107
forecaster=SeasonalNaive(),
@@ -92,7 +114,7 @@ def __init__(
92114
predictor,
93115
batch_size=512,
94116
)
95-
eval_df = pd.read_csv("./my-dir/all_results.csv")
117+
eval_df = pd.read_csv("./seasonal_naive/all_results.csv")
96118
```
97119
98120
Raises:
@@ -131,17 +153,19 @@ def __init__(
131153
name=dataset_name,
132154
term=term,
133155
to_univariate=False,
156+
storage_path=storage_path,
134157
).target_dim
135158
!= 1
136159
)
137160
self.dataset = Dataset(
138161
name=dataset_name,
139162
term=term,
140163
to_univariate=to_univariate,
164+
storage_path=storage_path,
141165
)
142166
self.dataset_name = dataset_name
143167
self.seasonality = get_seasonality(self.dataset.freq)
144-
self.output_dir = output_dir
168+
self.output_path = output_path
145169

146170
def evaluate_predictor(
147171
self,
@@ -220,8 +244,8 @@ def evaluate_predictor(
220244
"num_variates",
221245
],
222246
)
223-
if self.output_dir is not None:
224-
csv_file_path = Path(self.output_dir) / "all_results.csv"
247+
if self.output_path is not None:
248+
csv_file_path = Path(self.output_path) / "all_results.csv"
225249
csv_file_path.parent.mkdir(parents=True, exist_ok=True)
226250
results_df.to_csv(csv_file_path, index=False)
227251

0 commit comments

Comments
 (0)