Challenges · C03 · ★★★☆☆

Bike sharing: demand forecasts that admit what they do not know

Difficulty
★★★☆☆
Time
3-4 hours
Data
Capital Bikeshare (Washington DC) daily rental counts 2011-2012 with weather and calendar covariates
Skills
Count likelihoods and the log link · diagnosing a predictive distribution that is far too confident · non-linear effects with splines · index variables and ZeroSumNormal · LOO model comparison · pointwise LOO and Pareto-$k$ as an anomaly detector · out-of-sample forecasting with pm.Data · interval coverage · turning a predictive distribution into a capacity decision

The brief

The operations team of a bike-sharing scheme plans two things a day ahead: how many vans and staff to put on rebalancing (moving bikes from full docks to empty ones), and how many bikes to have in service. Both scale with the number of rentals they must be able to serve tomorrow. Plan too low and customers find empty docks; plan too high and vans and people stand idle.

They have a weather forecast for tomorrow and two years of history. What they ask for:

"Give us tomorrow's demand with a range we can trust. If you say 90%, we want to be inside it nine days out of ten. And tell us how much capacity to plan so that we can cover demand on 95% of days."

From conversations with the team you know that a typical day sees a few thousand rentals, that a busy summer day can reach eight thousand, and that the system is growing fast.

A point forecast is the easy half of this. The brief is about the spread: a predictive distribution whose stated probabilities can be taken at face value. Sampler diagnostics will not tell you whether you have one.

How this notebook works

  • Each task states what to deliver, not how. Write your code in the YOUR CODE HERE cells.
  • Stuck? h.hint("task2") reveals hints one level at a time: nudge → approach → code skeleton. Try to get by on nudges.
  • h.check("task2", coverage90=...) compares your numbers with the reference solution.
  • A full worked solution lives in notebooks/solutions/. Open it only when you are done (or truly stuck).
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm

from pymc_challenges import Hints, data

RANDOM_SEED = 2012
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-variat")

h = Hints("C03")
h.tasks()

C03 - Bike sharing: demand forecasts that admit what they do not know

  • task0 - Decode, split, look (2 hints, 2 check(s))
  • task1 - The textbook model for counts (3 hints, 1 check(s))
  • task2 - Fix what Task 1 revealed (3 hints, 3 check(s))
  • task3 - Too hot to cycle (3 hints, 2 check(s))
  • task4 - Calendar, weather situation, growth - and a scoreboard (3 hints, 2 check(s))
  • task5 - The days the model cannot believe (3 hints, 2 check(s))
  • task6 - Open the hold-out (3 hints, 3 check(s))
  • task7 - How much capacity? (3 hints, 3 check(s))

The data

One row per day, 1 January 2011 to 31 December 2012. cnt is the total number of rentals (casual + registered). Calendar columns: yr (0 = 2011, 1 = 2012), mnth, weekday (an integer - find out which day is 0), holiday, workingday (neither weekend nor holiday). weathersit is the day's weather situation: 1 = clear or partly cloudy, 2 = mist or overcast, 3 = light rain or snow, thunderstorm.

The UCI file stores the continuous weather variables normalised - each was divided by a constant so that it lies between 0 and 1:

column meaning divided by
temp mean temperature, °C 41
atemp "feels-like" temperature, °C 50
hum relative humidity, % 100
windspeed wind speed, km/h 67

Nobody on the operations team thinks in units of "0.34 of 41 °C".

data.describe("bike_day")
raw = data.load("bike_day")
raw.head()
bike_day: Daily rental counts 2011-2012 with weather and calendar covariates.
  source: Fanaee-T & Gama (2013), Capital Bikeshare (Washington DC), UCI ML Repository.
  url:    https://archive.ics.uci.edu/static/public/275/bike+sharing+dataset.zip
instant dteday season yr mnth holiday weekday workingday weathersit temp atemp hum windspeed casual registered cnt
0 1 2011-01-01 1 0 1 0 6 0 2 0.344167 0.363625 0.805833 0.160446 331 654 985
1 2 2011-01-02 1 0 1 0 0 0 2 0.363478 0.353739 0.696087 0.248539 131 670 801
2 3 2011-01-03 1 0 1 0 1 1 1 0.196364 0.189405 0.437273 0.248309 120 1229 1349
3 4 2011-01-04 1 0 1 0 2 1 1 0.200000 0.212122 0.590435 0.160296 108 1454 1562
4 5 2011-01-05 1 0 1 0 3 1 1 0.226957 0.229270 0.436957 0.186900 82 1518 1600

The final eight weeks (56 days, from 6 November 2012) are the hold-out. They stay locked away until Task 6: no plots, no fitting, no peeking.

HOLDOUT_START = pd.Timestamp("2012-11-06")

Task 0 · Decode, split, look

Deliver

  1. A data frame with the weather in physical units (°C, %, km/h) and readable labels for weekday and weather situation. Verify the weekday coding rather than guessing it.
  2. train (before HOLDOUT_START) and test (the rest).
  3. Plots of the training data that show what a demand model will have to reproduce: the series over time, demand against temperature, demand by weather situation and by weekday. Write down the three or four features you consider essential. Do you need both temp and atemp?
Your turn Work this task in the notebook.

Task 1 · The textbook model for counts

Rentals are counts, so start where every textbook starts: a Poisson regression with a log link. Use the weather only - temperature, humidity and wind speed, each entering linearly on the log scale.

Deliver

  1. Priors you can defend, shown with a prior predictive check on the scale the operations team thinks in: rentals per day.
  2. A fit with clean sampler diagnostics. Look at how precisely the model claims to know its coefficients.
  3. A verdict: is this model's predictive distribution fit for the brief? Back it with at least one graphical posterior predictive check that could embarrass the model, and one number: the share of training days that fall inside the model's central 90% predictive interval.
Your turn Work this task in the notebook.

Task 2 · Fix what Task 1 revealed

Your verdict should have been damning, and "the sampler is fine" is no defence.

Deliver

  1. The diagnosis in one sentence: which assumption of the Task 1 model do the data violate, and by roughly what factor? (A number, please.)
  2. A repaired model that changes only what the diagnosis demands - same predictors, same priors for the coefficients. Explain what its new parameter means in words the operations team would understand.
  3. The same checks as in Task 1, now passed (or not?).
  4. Put the posterior standard deviations of the coefficients from both models side by side. What happened, and what would it have cost someone who believed the first model?
Your turn Work this task in the notebook.

Task 3 · Too hot to cycle

A straight line on the log scale says every extra degree multiplies demand by the same factor, for ever. Washington summers disagree.

Deliver

  1. Evidence from the Task 2 model that the linear temperature effect is wrong (a residual plot will do).
  2. A model in which temperature enters through a smooth function instead. Everything else stays as in Task 2.
  3. A plot of the estimated temperature effect as a multiplier on expected demand, with a credible band, across the observed temperature range.
  4. Two numbers with uncertainty: the temperature at which demand peaks, and how much lower (in %) expected demand is at 35 °C than at the peak.
Your turn Work this task in the notebook.

Task 4 · Calendar, weather situation, growth - and a scoreboard

The operations team knows that demand depends on the day of the week, on public holidays, on the weather situation (rain is worse than humidity alone suggests), and that the system grew between 2011 and 2012. Add all four to the Task 3 model.

Deliver

  1. A model in which the categorical effects have no arbitrary reference level: every weekday and every weather situation is treated symmetrically by the prior, and the intercept keeps its meaning. Clean diagnostics. The file also offers workingday - explain why you should not simply add it as well.
  2. The effects in units the team can use: by what percentage does a wet day change demand relative to a clear one? How much did demand grow from 2011 to 2012? Which weekday is busiest? Do not forget the uncertainty.
  3. A PSIS-LOO comparison of all four models so far (az.compare, az.plot_compare) and your reading of it: which steps mattered, judged by elpd_diff against dse? Read the warnings - you will come back to them in Task 5.
Your turn Work this task in the notebook.

Task 5 · The days the model cannot believe

LOO is not only a scoreboard. It computes, for every single day, how well the model predicts that day when it has not seen it - and how much the posterior leans on it.

Deliver

  1. For your best model so far: the pointwise elpd and the Pareto-$k$ value of every training day, plotted against the date, and a table of the six most surprising days.
  2. For the worst of them: what happened? (You have the date and the internet.) Is it the kind of day the brief asks you to forecast? Look at the other five as well - is any of them a data problem rather than a demand problem?
  3. A decision on how to handle what you found, with a justification an auditor would accept, and a refit. What changed in the dispersion and in the width of the predictive intervals - and why does one day out of 675 have that much leverage?
Your turn Work this task in the notebook.

Task 6 · Open the hold-out

Time to find out whether "90%" means 90%. Forecast the 56 hold-out days with the model from Task 5, using the weather that actually occurred (a perfect weather forecast - the best case).

Deliver

  1. Forecasts produced without refitting: swap the inputs of the fitted model and sample the posterior predictive.
  2. A plot of the 50% and 90% predictive intervals against the actual counts, plus the empirical coverage of both intervals and the mean error (forecast median minus actual).
  3. If the forecasts are biased: find out why - not by tuning against the hold-out, but by asking what your growth term assumes about November 2012, and checking that assumption on the training data. Fix the model, compare old and new with LOO, and forecast again.
  4. A list of the hold-out days that still fall outside the 90% interval. What do they have in common? Could the training data have warned you? (Task 5 again.) Which direction is the error, and how much does that direction hurt the operations team?
Your turn Work this task in the notebook.

Task 7 · How much capacity?

The team plans three days in the coming weeks and hands you the weather forecast for each. (None is a public holiday.)

scenarios = pd.DataFrame(
    {
        "date": pd.to_datetime(["2012-11-16", "2012-11-18", "2012-11-20"]),
        "temp_c": [14.0, 8.0, 11.0],
        "hum_pct": [50.0, 70.0, 90.0],
        "wind_kmh": [10.0, 20.0, 15.0],
        "weather": ["clear", "mist", "wet"],
    },
    index=["mild clear Friday", "cold misty Sunday", "wet Tuesday"],
)
COST_LOST_RENTAL = 4.0  # $ per rental the system could not serve (lost fare and goodwill)
COST_IDLE = 0.5  # $ per unit of capacity planned but not used (vans, staff, bikes standing by)
scenarios
date temp_c hum_pct wind_kmh weather
mild clear Friday 2012-11-16 14.0 50.0 10.0 clear
cold misty Sunday 2012-11-18 8.0 70.0 20.0 mist
wet Tuesday 2012-11-20 11.0 90.0 15.0 wet

Deliver

  1. For each scenario the capacity (in rentals per day) that covers demand with probability 95%, from your final model - and, next to it, the number you would get if you (wrongly) used only the uncertainty about the expected demand. How big is the gap?
  2. Management's 95% is a rule of thumb. With the two costs above, find the capacity that minimises expected cost for each scenario, numerically from the predictive draws. Which quantile of the predictive distribution does it correspond to? What ratio of the two costs would make the 95% rule optimal?
  3. Three sentences for the operations team: what to plan, how far to trust it, and when not to.
Your turn Work this task in the notebook.

Going further

  • Holiday seasons. The final model over-forecasts Thanksgiving weekend and the week after Christmas. Give it a way to learn that from 2011 (one Christmas is not much - what prior keeps the estimate honest?) and check whether hold-out coverage improves.
  • Two populations. casual and registered riders have opposite weekday patterns and different sensitivity to weather. Model them separately (or jointly, with shared growth) and add the predictive draws. Does the total forecast get sharper?
  • A Gaussian process instead of a spline. Replace the temperature spline with pm.gp.HSGP, and the linear trend with a slowly-varying HSGP in time. Does the temperature curve change where data are sparse? What happens to forecasts when the trend is a GP that reverts to its mean?
  • The weather forecast is uncertain too. Tomorrow's temperature is known to within a couple of degrees and "wet" is a probability. Propagate that through the capacity decision by sampling scenarios. When does it matter?
h.progress()

C03: 0/23 hints used, 18/18 checks passed.

Reference solution

A complete, executed solution with commentary on every task — including the ways the obvious model fails. Try the hint ladder first.

Open the worked solution →