Store Sales Time Series Forecasting
Download public Store Sales CSVs, build time-based features, create validation by date, train a baseline model, and produce a forecast submission.
Final deliverable: A time-series forecasting notebook with a date-based validation split and submission.csv file.
Dataset: Store Sales public CSV mirror
The notebook downloads a public mirror of the Ecuador store sales files. The task is to forecast unit sales for store/family/date combinations.
- train.csv: date, store_nbr, family, sales, onpromotion
- test.csv: future date/store/family rows requiring predictions
- stores.csv, oil.csv, holidays_events.csv, transactions.csv: optional external/context tables
Download Store Sales Data
Download the public Store Sales CSV files.
Run the public download cell and inspect the CSV files.
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.
from pathlib import Path
from urllib.request import urlretrieve
import zipfile
DATA_DIR = Path("data/store-sales-time-series-forecasting")
DATA_DIR.mkdir(parents=True, exist_ok=True)
def find_file(name):
possible_roots = [DATA_DIR, Path("/kaggle/input")]
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")
files = {
"train.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/train.csv",
"test.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/test.csv",
"sample_submission.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/sample_submission.csv",
"stores.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/stores.csv",
"oil.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/oil.csv",
"holidays_events.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/holidays_events.csv",
"transactions.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/transactions.csv"
}
for filename, url in files.items():
target = DATA_DIR / filename
if target.exists() and target.stat().st_size > 0:
print(f"already exists: {target}")
continue
print(f"downloading {filename} ...")
urlretrieve(url, target)
print(f"saved: {target}")
for zip_path in DATA_DIR.glob("*.zip"):
extract_dir = DATA_DIR / zip_path.stem
extract_dir.mkdir(exist_ok=True)
if any(extract_dir.iterdir()):
print(f"already extracted: {extract_dir}")
continue
with zipfile.ZipFile(zip_path, "r") as zf:
zf.extractall(extract_dir)
print(f"extracted: {zip_path} -> {extract_dir}")
print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
print(path)
if len(all_files) > 40:
print(f"... {len(all_files) - 40} more files not shown")Show reference solution
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.
from pathlib import Path
from urllib.request import urlretrieve
import zipfile
DATA_DIR = Path("data/store-sales-time-series-forecasting")
DATA_DIR.mkdir(parents=True, exist_ok=True)
def find_file(name):
possible_roots = [DATA_DIR, Path("/kaggle/input")]
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")
files = {
"train.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/train.csv",
"test.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/test.csv",
"sample_submission.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/sample_submission.csv",
"stores.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/stores.csv",
"oil.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/oil.csv",
"holidays_events.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/holidays_events.csv",
"transactions.csv": "https://huggingface.co/datasets/t4tiana/store-sales-time-series-forecasting/resolve/main/transactions.csv"
}
for filename, url in files.items():
target = DATA_DIR / filename
if target.exists() and target.stat().st_size > 0:
print(f"already exists: {target}")
continue
print(f"downloading {filename} ...")
urlretrieve(url, target)
print(f"saved: {target}")
for zip_path in DATA_DIR.glob("*.zip"):
extract_dir = DATA_DIR / zip_path.stem
extract_dir.mkdir(exist_ok=True)
if any(extract_dir.iterdir()):
print(f"already extracted: {extract_dir}")
continue
with zipfile.ZipFile(zip_path, "r") as zf:
zf.extractall(extract_dir)
print(f"extracted: {zip_path} -> {extract_dir}")
print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
print(path)
if len(all_files) > 40:
print(f"... {len(all_files) - 40} more files not shown")Time-series projects often have multiple related tables. Listing files first helps learners see what is available before modeling.
Load and Inspect the Time Series
Read train.csv with parsed dates and inspect date range, stores, families, and sales distribution.
Load train.csv, parse date, and print basic dataset shape and ranges.
from pathlib import Path import numpy as np import pandas as pd import matplotlib.pyplot as plt # Find train.csv and load it with parsed dates # Inspect shape, date range, stores, families
Show reference solution
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
train_path = find_file("train.csv")
df = pd.read_csv(train_path, parse_dates=["date"])
print(df.shape)
print(df["date"].min(), df["date"].max())
print("stores:", df["store_nbr"].nunique())
print("families:", df["family"].nunique())
display(df.head())
display(df["sales"].describe())Forecasting starts with understanding the time axis. Random train/test splits are usually wrong for time series.
Plot Aggregate Sales Over Time
See trend, seasonality, and unusual spikes at the daily aggregate level.
Group sales by date and plot total sales.
# Aggregate sales by date # Plot daily total sales
Show reference solution
daily_sales = df.groupby("date", as_index=False)["sales"].sum()
plt.figure(figsize=(12, 4))
plt.plot(daily_sales["date"], daily_sales["sales"])
plt.title("Total Daily Sales")
plt.xlabel("Date")
plt.ylabel("Sales")
plt.show()A simple aggregate plot reveals seasonality, holidays, missing periods, and trend changes before feature engineering.
Create Date Features
Add calendar features that a tree model can use.
Create day_of_week, month, year, day_of_month, weekofyear, and weekend features for train and test.
# Define a function add_date_features(data) # Apply it to train data
Show reference solution
def add_date_features(data):
data = data.copy()
data["day_of_week"] = data["date"].dt.dayofweek
data["month"] = data["date"].dt.month
data["year"] = data["date"].dt.year
data["day_of_month"] = data["date"].dt.day
data["weekofyear"] = data["date"].dt.isocalendar().week.astype(int)
data["is_weekend"] = data["day_of_week"].isin([5, 6]).astype(int)
return data
df_features = add_date_features(df)
display(df_features[["date", "day_of_week", "month", "weekofyear", "is_weekend"]].head())Tree models do not understand dates directly. Calendar features expose weekly and yearly patterns.
Create a Date-Based Validation Split
Train on earlier dates and validate on the most recent period.
Use the last 30 training days as validation and all earlier rows as training.
# Find cutoff date # Split train_part and val_part by date
Show reference solution
cutoff = df_features["date"].max() - pd.Timedelta(days=30) train_part = df_features[df_features["date"] <= cutoff].copy() val_part = df_features[df_features["date"] > cutoff].copy() print(train_part["date"].min(), train_part["date"].max()) print(val_part["date"].min(), val_part["date"].max()) print(train_part.shape, val_part.shape)
Time-series validation must mimic the future. The model should not train on rows after the validation period.
Train a Baseline Forecasting Model
Train a model using store, family, promotion, and date features.
Use ordinal encoding, store/family aggregate features, log-target training, and a fast gradient-boosting baseline. Evaluate RMSLE on a recent validation window.
from sklearn.compose import ColumnTransformer from sklearn.ensemble import HistGradientBoostingRegressor from sklearn.metrics import mean_squared_log_error from sklearn.pipeline import Pipeline from sklearn.preprocessing import OrdinalEncoder # Build features # Add store/family historical aggregate features without using validation rows # Train on log1p(sales) and evaluate RMSLE
Show reference solution
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_squared_log_error
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OrdinalEncoder
def add_group_features(base, source):
base = base.copy()
keys = ["store_nbr", "family"]
source = source.copy()
sf_mean = source.groupby(keys)["sales"].mean().rename("sf_mean").reset_index()
sf_dow_mean = source.groupby(keys + ["day_of_week"])["sales"].mean().rename("sf_dow_mean").reset_index()
sf_month_mean = source.groupby(keys + ["month"])["sales"].mean().rename("sf_month_mean").reset_index()
base = base.merge(sf_mean, on=keys, how="left")
base = base.merge(sf_dow_mean, on=keys + ["day_of_week"], how="left")
base = base.merge(sf_month_mean, on=keys + ["month"], how="left")
global_mean = float(source["sales"].mean())
for col in ["sf_mean", "sf_dow_mean", "sf_month_mean"]:
base[col] = base[col].fillna(global_mean)
return base
features = [
"store_nbr", "family", "onpromotion", "day_of_week", "month", "year",
"day_of_month", "weekofyear", "is_weekend", "sf_mean", "sf_dow_mean", "sf_month_mean",
]
categorical = ["family"]
numeric = [col for col in features if col not in categorical]
preprocess = ColumnTransformer([
("cat", OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1), categorical),
("num", "passthrough", numeric),
])
recent_cutoff = train_part["date"].max() - pd.Timedelta(days=365)
recent_train = train_part[train_part["date"] >= recent_cutoff].copy()
recent_train_model = add_group_features(recent_train, recent_train)
val_model = add_group_features(val_part, recent_train)
print("training rows used:", recent_train.shape[0])
model = Pipeline([
("preprocess", preprocess),
("regressor", HistGradientBoostingRegressor(
max_iter=250,
learning_rate=0.06,
l2_regularization=0.05,
random_state=42,
)),
])
model.fit(recent_train_model[features], np.log1p(recent_train_model["sales"]))
val_preds = np.expm1(model.predict(val_model[features])).clip(0)
rmsle = np.sqrt(mean_squared_log_error(val_part["sales"], val_preds))
print("RMSLE:", rmsle)The aggregate features tell the model what each store/family usually sells overall, on that weekday, and in that month. Training on log sales matches RMSLE better than raw sales.
Create Forecast Submission
Train on all training data, predict test rows, and save id/sales submission.
Load test.csv, add date features, predict sales, clip negatives, and write submission.csv.
# Load test.csv # Add date features # Fit on full train and predict test # Save submission.csv
Show reference solution
test_df = pd.read_csv(find_file("test.csv"), parse_dates=["date"])
test_features = add_date_features(test_df)
recent_full_cutoff = df_features["date"].max() - pd.Timedelta(days=365)
recent_full = df_features[df_features["date"] >= recent_full_cutoff].copy()
recent_full_model = add_group_features(recent_full, recent_full)
test_model = add_group_features(test_features, recent_full)
model.fit(recent_full_model[features], np.log1p(recent_full_model["sales"]))
test_preds = np.expm1(model.predict(test_model[features])).clip(0)
submission = pd.DataFrame({
"id": test_df["id"],
"sales": test_preds,
})
submission.to_csv("submission.csv", index=False)
display(submission.head())The final project artifact is a reproducible forecast file. The same feature pipeline must work on train, validation, and test.