Andes Retail — units sold, next 28 days
Six stores, ten SKUs, one line per store/SKU/day. Planning wants a unit forecast
for every row of data/holdout.csv, which is the 28 days that follow the last
day in data/sales.csv.
Time: 90 minutes. Write everything in solution.py.
Files
| File | What it is |
|---|---|
data/sales.csv |
The history. Has units_sold. |
data/holdout.csv |
The next 28 days: same stores, same SKUs, prices and promotions already scheduled. No units_sold. |
Columns
| Column | Notes |
|---|---|
sale_date |
Two formats. Stores S1–S3 export ISO (2026-03-04); S4–S6 export DD/MM/YYYY (04/03/2026). |
store_id |
S1 … S6. |
sku |
Ten SKUs. |
unit_price |
Text, as money: S/ 1,234.50. Thousands separator included. |
promo_flag |
1 during a promotion. Blank means no promotion was recorded, which is a 0. |
units_sold |
Target. Present only in sales.csv. |
row_id |
Export line number, sales.csv only. Not a feature. |
Known defects, stated as rules
- Re-synced lines. Some
row_idvalues appear twice, as identical copies. - Refunds. A refund is booked as a negative
units_soldagainst the same line. Refunds are not demand. - One store was closed. For a stretch of consecutive days one store has no rows at all — not rows with zero units. Nothing marks this; you have to find it.
unit_priceandpromo_flagneed coercing before anything numeric works.
Tasks
T1 — parse_dates(df) -> pd.DataFrame (10 pts)
Return the frame with sale_date as datetime64. Nothing may fail to parse, and the
earliest and latest dates must be the real ones — getting day and month the wrong way round
still produces valid dates, which is why this is graded on the range and not on the dtype
alone.
T2 — parse_prices(df) -> pd.DataFrame (10 pts)
Return the frame with unit_price numeric. One value per input row, nothing dropped.
T3 — sales_summary(df) -> dict (15 pts)
Given a date-parsed frame, return exactly these four keys:
| Key | Meaning |
|---|---|
refund_rows |
How many rows of the file have negative units_sold. |
gross_units |
Total units_sold over the rows where it is positive. |
closed_store |
The store_id from rule 3. |
closed_days |
How many calendar dates that store has no rows for. |
T4 — clean(df) -> pd.DataFrame (15 pts)
Return a frame with the duplicated lines removed, the refund rows removed, unit_price
numeric, and promo_flag filled with 0 where it was blank.
T5 — split_last_days(df, days) -> (train, valid) (15 pts)
Split a date-parsed frame in two by time: valid is the last days calendar days
inclusive, train is everything before. Every row goes to exactly one side, and the last
training date must be strictly earlier than the first validation date.
T6 — fit_predict(train, holdout) -> np.ndarray (35 pts)
Both frames arrive raw, exactly as the CSVs read. Return predicted units_sold for
every row of holdout, in file order.
Graded on RMSE: <= 14.0 beats predicting a single number, <= 9.00 beats a store×SKU
historical average, <= 6.50 means price and promotion are in the model too.
Allowed: pandas, numpy, scikit-learn.