import numpy as np
import pandas as pd
from pathlib import Path
from datetime import datetime, timedelta
SEED = 42
N_OBSERVATIONS = 100
SEASONAL_PERIOD = 12
DATA_DIR = Path(__file__).parent / "data"
def generate_timestamps(n: int, start: str = "2020-01-01") -> list[datetime]:
start_date = datetime.fromisoformat(start)
return [start_date + timedelta(days=30 * i) for i in range(n)]
def generate_stationary(n: int, rng: np.random.Generator) -> np.ndarray:
mean = 50.0
std = 5.0
return mean + rng.normal(0, std, n)
def generate_trend(n: int, rng: np.random.Generator) -> np.ndarray:
intercept = 10.0
slope = 0.5
noise_std = 3.0
t = np.arange(n)
return intercept + slope * t + rng.normal(0, noise_std, n)
def generate_seasonal(n: int, rng: np.random.Generator) -> np.ndarray:
mean = 50.0
amplitude = 10.0
noise_std = 2.0
t = np.arange(n)
seasonal = amplitude * np.sin(2 * np.pi * t / SEASONAL_PERIOD)
return mean + seasonal + rng.normal(0, noise_std, n)
def generate_trend_seasonal(n: int, rng: np.random.Generator) -> np.ndarray:
intercept = 20.0
slope = 0.3
amplitude = 8.0
noise_std = 2.0
t = np.arange(n)
trend = slope * t
seasonal = amplitude * np.sin(2 * np.pi * t / SEASONAL_PERIOD)
return intercept + trend + seasonal + rng.normal(0, noise_std, n)
def generate_seasonal_negative(n: int, rng: np.random.Generator) -> np.ndarray:
mean = 5.0 amplitude = 10.0 noise_std = 1.0
t = np.arange(n)
seasonal = amplitude * np.sin(2 * np.pi * t / SEASONAL_PERIOD)
return mean + seasonal + rng.normal(0, noise_std, n)
def generate_multiplicative_seasonal(n: int, rng: np.random.Generator) -> np.ndarray:
intercept = 50.0
slope = 0.5
t = np.arange(n)
level = intercept + slope * t
seasonal_factor = 1.0 + 0.3 * np.sin(2 * np.pi * t / SEASONAL_PERIOD)
noise_factor = 1.0 + rng.normal(0, 0.02, n)
return level * seasonal_factor * noise_factor
def generate_intermittent(n: int, rng: np.random.Generator) -> np.ndarray:
demand_prob = 0.3
has_demand = rng.random(n) < demand_prob
mean_demand = 5.0
demand_sizes = rng.poisson(mean_demand, n) + 1
series = np.where(has_demand, demand_sizes, 0).astype(float)
return series
def generate_high_frequency(n: int, rng: np.random.Generator) -> np.ndarray:
daily_period = 24
weekly_period = 168
t = np.arange(n)
daily = 5.0 * np.sin(2 * np.pi * t / daily_period)
weekly = 3.0 * np.sin(2 * np.pi * t / weekly_period)
trend = 0.01 * t
noise = rng.normal(0, 1.5, n)
return 50.0 + trend + daily + weekly + noise
def generate_structural_break(n: int, rng: np.random.Generator) -> np.ndarray:
mean_before = 50.0
mean_after = 70.0 noise_std = 3.0
break_point = n // 2
values = np.zeros(n)
values[:break_point] = mean_before + rng.normal(0, noise_std, break_point)
values[break_point:] = mean_after + rng.normal(0, noise_std, n - break_point)
return values
def generate_long_memory(n: int, rng: np.random.Generator) -> np.ndarray:
d = 0.3
K = min(100, n) psi = np.zeros(K)
psi[0] = 1.0
for k in range(1, K):
psi[k] = psi[k-1] * (k - 1 + d) / k
epsilon = rng.normal(0, 1, n + K)
values = np.zeros(n)
for t in range(n):
values[t] = np.sum(psi * epsilon[t:t+K][::-1])
values = 50.0 + 5.0 * values
return values
def generate_noisy_seasonal(n: int, rng: np.random.Generator) -> np.ndarray:
mean = 50.0
amplitude = 5.0 noise_std = 8.0
t = np.arange(n)
seasonal = amplitude * np.sin(2 * np.pi * t / SEASONAL_PERIOD)
return mean + seasonal + rng.normal(0, noise_std, n)
def save_series(name: str, timestamps: list[datetime], values: np.ndarray) -> Path:
df = pd.DataFrame({
"timestamp": timestamps,
"value": values
})
filepath = DATA_DIR / f"{name}.csv"
df.to_csv(filepath, index=False)
print(f" Saved {name}.csv ({len(values)} observations)")
return filepath
def main():
print("Generating synthetic time series data...")
print(f" Seed: {SEED}")
print(f" Observations: {N_OBSERVATIONS}")
print(f" Seasonal period: {SEASONAL_PERIOD}")
print()
DATA_DIR.mkdir(parents=True, exist_ok=True)
rng = np.random.default_rng(SEED)
timestamps = generate_timestamps(N_OBSERVATIONS)
series_generators = [
("stationary", generate_stationary),
("trend", generate_trend),
("seasonal", generate_seasonal),
("trend_seasonal", generate_trend_seasonal),
("seasonal_negative", generate_seasonal_negative), ("multiplicative_seasonal", generate_multiplicative_seasonal), ("intermittent", generate_intermittent), ("high_frequency", generate_high_frequency), ("structural_break", generate_structural_break), ("long_memory", generate_long_memory), ("noisy_seasonal", generate_noisy_seasonal), ]
for name, generator in series_generators:
values = generator(N_OBSERVATIONS, rng)
save_series(name, timestamps, values)
print()
print(f"Data saved to: {DATA_DIR.absolute()}")
if __name__ == "__main__":
main()