01-explore
notebooks/01-explore.py
Explore — daily time series
A self-contained tour of:
- Generating a synthetic daily series with trend + weekly seasonality + noise.
- Running summary statistics.
- Plotting raw values vs. a 7-day rolling mean.
- Persisting cleaned data as a Parquet artifact for downstream notebooks.
import numpy as np import pandas as pd rng = np.random.default_rng(seed=42) dates = pd.date_range("2025-01-01", periods=365, freq="D") trend = np.linspace(100, 120, len(dates)) # slow upward drift weekly = np.where( dates.dayofweek >= 5, # Sat, Sun 5.0, np.where(dates.dayofweek == 0, -2.0, 0.0), # Mon dips ) noise = rng.normal(0.0, 3.0, len(dates)) value = trend + weekly + noise df = pd.DataFrame({"date": dates, "value": value}) print(f"rows: {len(df)} range: {df['value'].min():.2f} \u2013 {df['value'].max():.2f}") df.head()
rows: 365 range: 94.37 – 127.66
date value 0 2025-01-01 100.914151 1 2025-01-02 96.934993 2 2025-01-03 102.361244 3 2025-01-04 107.986529 4 2025-01-05 99.366675
| date | value | |
|---|---|---|
| 0 | 2025-01-01 | 100.914151 |
| 1 | 2025-01-02 | 96.934993 |
| 2 | 2025-01-03 | 102.361244 |
| 3 | 2025-01-04 | 107.986529 |
| 4 | 2025-01-05 | 99.366675 |
raw
ok
32 ms
import jellycell.api as jc summary = { "rows": len(df), "mean": round(float(df["value"].mean()), 3), "std": round(float(df["value"].std()), 3), "min": round(float(df["value"].min()), 3), "max": round(float(df["value"].max()), 3), "start": df["date"].min().date().isoformat(), "end": df["date"].max().date().isoformat(), } jc.save(summary, "artifacts/summary.json") jc.save(df, "artifacts/daily.parquet") print(summary)
{'rows': 365, 'mean': 111.113, 'std': 6.953, 'min': 94.368, 'max': 127.656, 'start': '2025-01-01', 'end': '2025-12-31'}
raw
ok
226 ms
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(df["date"], df["value"], linewidth=0.9, color="#6b7280", label="daily") ax.plot( df["date"], df["value"].rolling(7, center=True).mean(), linewidth=2.0, color="#4f46e5", label="7-day rolling mean", ) ax.set_title("Daily series: raw vs. 7-day moving average") ax.set_xlabel("Date") ax.set_ylabel("Value") ax.legend(loc="lower right", frameon=False) ax.grid(alpha=0.3) fig.autofmt_xdate() jc.figure(path="artifacts/raw_plot.png", fig=fig)
PosixPath('/Users/blaise/Desktop/blaise-oss/jellycell/examples/timeseries/artifacts/raw_plot.png')
<Figure size 1000x400 with 1 Axes>
artifacts
raw_plot.png
78.9 KB
raw
ok
70 ms
# Weekday-of-week profile: box-plot residuals after de-trending. detrended = df["value"] - df["value"].rolling(7, center=True).mean() df_plot = pd.DataFrame({"dow": df["date"].dt.day_name(), "residual": detrended}) order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] fig, ax = plt.subplots(figsize=(8, 4)) box = [df_plot.loc[df_plot["dow"] == d, "residual"].dropna().values for d in order] ax.boxplot( box, labels=[d[:3] for d in order], patch_artist=True, boxprops={"facecolor": "#eef2ff", "edgecolor": "#4f46e5"}, medianprops={"color": "#4f46e5", "linewidth": 1.5}, ) ax.axhline(0, color="#6b7280", linewidth=0.8, linestyle="--") ax.set_title("Weekly seasonality (detrended residuals by weekday)") ax.set_ylabel("Residual") ax.grid(alpha=0.3, axis="y") jc.figure(path="artifacts/weekday_profile.png", fig=fig)
/var/folders/_s/ntz4jgdd0_n0tlk0n5v9zzh40000gn/T/ipykernel_33975/2828954734.py:8: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11. ax.boxplot(
PosixPath('/Users/blaise/Desktop/blaise-oss/jellycell/examples/timeseries/artifacts/weekday_profile.png')
<Figure size 800x400 with 1 Axes>
artifacts
weekday_profile.png
18.5 KB