Before today, we effectively were performing one-sample tests of means, proportions, and counts.
Now, we will focus on incorporating just a single predictor into our analysis.
We now switch to thinking about analysis in terms of regression.
y_i = \beta_0 + \beta_1 x_i + \varepsilon_i
\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i
Capital Bikeshare is a bike sharing service in the Washington, D.C. area. To best serve its registered members, the company must understand the demand for its service. We will analyze the number of rides taken on a random sample of n days, (Y_1, Y_2, ..., Y_n).
Because Y_i is a count variable, you might assume that ridership might need the Poisson distribution. However, past bike riding seasons have exhibited bell-shaped daily ridership with a variability in ridership that far exceeds the typical ridership.
We will instead assume that the number of rides varies normally around some typical ridership, \mu, with standard deviation, \sigma.
Y_i|\mu,\sigma \overset{\text{ind}}{\sim} N(\mu, \sigma^2)
In our example, Y is the number of rides and X is the temperature.
Our specific goal will be to model the relationship between ridership and temperature:
It is reasonable to assume a positive relationship between temperature and number of rides.
For learning purposes, let’s focus on the model with a single predictor (y ~ x in R).
\{ (Y_1, X_1), (Y_2, X_2), ..., (Y_n, X_n) \}
where Y_i is the number of rides and X_i is the high temperature (oF) on day i.
Assuming that the relationship is linear, we can model
\mu_i = \beta_0 + \beta_1 X_i,
\mu_i = \beta_0 + \beta_1 X_i,
\mu_i = \beta_0 + \beta_1 X_i,
Y_i|\beta_0,\beta_1,\sigma\overset{\text{ind}}{\sim} N(\mu_i, \sigma^2) \text{ with } \mu_i = \beta_0 + \beta_1 X_i
We know the assumptions for linear regression in the frequentist framework.
In Bayesian Normal regression,
First, we will assume that our prior models of \beta_0, \beta_1, and \sigma are independent.
It is common to use the a normal prior for \beta_0 and \beta_1.
\begin{align*} \beta_0 &\sim N(m_0, s_0^2) \\ \beta_1 &\sim N(m_1, s_1^2) \end{align*}
\sigma \sim \text{Exp}(l)
\begin{align*} &\text{data}: & Y_i|\beta_0, \beta_1, \sigma & \overset{\text{ind}}{\sim} N(\mu_i, \sigma^2) \text{ with } \mu_i = \beta_0 + \beta_1 X_i \\ &\text{priors:} &\beta_0 & \sim N(m_0, s_0^2) \\ & & \beta_1 &\sim N(m_1, s_1^2) \\ & & & \sigma \sim \text{Exp}(l) \end{align*}
On an average temperature day for DC (65oF-70oF), there are typically around 5000 riders, but this could vary between 3000 and 7000 riders.
For every one degree increase in temperature, ridership typically increases by 100 rides, but this could vary between 20 and 180 rides.
At any given temperature, the daily ridership will tend to vary with a moderate standard deviation of 1250 rides.
\beta_{0\text{c}} \sim N(5000, 1000^2)
\beta_{1\text{c}} \sim N(100, 40^2)
\sigma \sim \text{Exp}(0.0008)
\begin{align*} \beta_{0\text{c}} &\sim N(5000, 1000^2) \\ \beta_{1\text{c}} &\sim N(100, 40^2) \\ \sigma &\sim \text{Exp}(0.0008) \end{align*}
\beta_{0\text{c}} \sim N(5000, 1000^2) \ \ \ \beta_{1\text{c}} \sim N(100, 40^2) \ \ \ \sigma \sim \text{Exp}(0.0008)
\beta_{0\text{c}} \sim N(5000, 1000^2) \ \ \ \beta_{1\text{c}} \sim N(100, 40^2) \ \ \ \sigma \sim \text{Exp}(0.0008)
Let’s now update what we know using the bikes data.
Looking at the basic relationship between the number of rides vs. the temperature (as it feels outside),
Note: I am skipping the derivation / the true math behind how to find the posterior in this situation. (It involves multiple integrals!)
We will use the stan_glm() function from rstanarm – it contains pre-defined Bayesian regression models.
stan_glm() also applies to the wider family of GzLM (i.e., logistic & Poisson/negbin).bike_model <- stan_glm(rides ~ temp_feel, # data model
data = bikes, # dataset
family = gaussian, # distribution to apply
prior_intercept = normal(5000, 1000), # b0_c
prior = normal(100, 40), # b1_c
prior_aux = exponential(0.0008), # sigma
chains = 4, # 4 chains
iter = 5000*2, # 10000 iterations - throw out first 5000
seed = 84735) # starting place in RNGbike_model <- stan_glm(rides ~ temp_feel, # data model
data = bikes, # dataset
family = gaussian, # distribution to apply
prior_intercept = normal(5000, 1000), # b0_c
prior = normal(100, 40), # b1_c
prior_aux = exponential(0.0008), # sigma
chains = 4, # 4 chains
iter = 5000*2, # 10000 iterations - throw out first 5000
seed = 84735) # starting place in RNGrides ~ temp_feel),data = bikes), andfamily = gaussian).bike_model <- stan_glm(rides ~ temp_feel, # data model
data = bikes, # dataset
family = gaussian, # distribution to apply
prior_intercept = normal(5000, 1000), # b0_c
prior = normal(100, 40), # b1_c
prior_aux = exponential(0.0008), # sigma
chains = 4, # 4 chains
iter = 5000*2, # 10000 iterations - throw out first 5000
seed = 84735) # starting place in RNGprior_intercept \to \beta_{0\text{c}},prior \to \beta_{1\text{c}}, andprior_aux \to \sigma.bike_model <- stan_glm(rides ~ temp_feel, # data model
data = bikes, # dataset
family = gaussian, # distribution to apply
prior_intercept = normal(5000, 1000), # b0_c
prior = normal(100, 40), # b1_c
prior_aux = exponential(0.0008), # sigma
chains = 4, # 4 chains
iter = 5000*2, # 10000 iterations - throw out first 5000
seed = 84735) # starting place in RNGchains),iter) for each seed, andseed).Wait, how does this work when we are looking at three model parameters?
We will have three vectors: one for each model parameter.
\begin{align*} <\beta_0^{(1)}, & \ \beta_0^{(2)}, ..., \beta_0^{(5000)}> \\ <\beta_1^{(1)}, & \ \beta_1^{(2)}, ..., \beta_1^{(5000)} > \\ <\sigma^{(1)}, & \ \sigma^{(2)}, ..., \sigma^{(5000)} > \\ \end{align*}
Run the simulation code (from a previous slide).
Then, run diagnostics:
(Intercept) temp_feel sigma
0.98500 0.98525 0.96260
(Intercept) temp_feel sigma
1.0001302 1.0001308 0.9998656
Quick diagnostics indicate that the resulting chains are trustworthy.
The effective sample size ratios are slightly above 1 and the R-hat values are very close to 1.
| term | estimate | std.error | conf.low | conf.high |
|---|---|---|---|---|
| (Intercept) | -2189.94315 | 355.897730 | -2883.84137 | -1488.79560 |
| temp_feel | 82.15595 | 5.055149 | 72.00998 | 92.02413 |
y = -2189.94 + 82.16x
bike_model.
add_fitted_draws() function is from the tidybayes package.bike_model.weather_WU data in the bayesrules package.
| location | mintemp | maxtemp | rainfall | windgustdir | windgustspeed | winddir9am | winddir3pm | windspeed9am | windspeed3pm | humidity9am | humidity3pm | pressure9am | pressure3pm | temp9am | temp3pm | raintoday | risk_mm | raintomorrow | year | month | day_of_year |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Uluru | 12.3 | 30.1 | 0 | ENE | 39 | E | ENE | 20 | 13 | 23 | 10 | 1023.3 | 1018.5 | 20.9 | 29.7 | No | 0.0 | No | 2014 | 9 | 255 |
| Uluru | 20.5 | 35.9 | 5 | SSE | 52 | SE | SE | 9 | 20 | 71 | 29 | 1012.9 | 1009.1 | 23.4 | 33.9 | Yes | 0.2 | No | 2015 | 2 | 54 |
| Uluru | 15.8 | 41.4 | 0 | NNW | 50 | SSE | NW | 7 | 24 | 15 | 4 | 1012.3 | 1008.5 | 24.1 | 39.7 | No | 0.0 | No | 2013 | 10 | 282 |
| Uluru | 18.3 | 36.0 | 0 | SE | 57 | E | ESE | 28 | 17 | 29 | 11 | 1016.0 | 1011.7 | 26.4 | 34.2 | No | 0.0 | No | 2014 | 1 | 6 |
| Uluru | 28.9 | 44.8 | 0 | ENE | 44 | ESE | E | 24 | 26 | 10 | 8 | 1010.5 | 1006.4 | 36.7 | 43.3 | No | 0.0 | No | 2019 | 1 | 13 |
| Uluru | 18.1 | 35.9 | 0 | ESE | 35 | ESE | SE | 22 | 11 | 32 | 14 | 1012.2 | 1007.6 | 25.1 | 33.5 | No | 0.0 | No | 2019 | 2 | 41 |
temp3pm) and a subset of possible predictors that we’d have access to in the morning:weather_WU <- weather_WU %>%
select(location, windspeed9am, humidity9am, pressure9am, temp9am, temp3pm)
head(weather_WU)| location | windspeed9am | humidity9am | pressure9am | temp9am | temp3pm |
|---|---|---|---|---|---|
| Uluru | 20 | 23 | 1023.3 | 20.9 | 29.7 |
| Uluru | 9 | 71 | 1012.9 | 23.4 | 33.9 |
| Uluru | 7 | 15 | 1012.3 | 24.1 | 39.7 |
| Uluru | 28 | 29 | 1016.0 | 26.4 | 34.2 |
| Uluru | 24 | 10 | 1010.5 | 36.7 | 43.3 |
| Uluru | 22 | 32 | 1012.2 | 25.1 | 33.5 |
\begin{align*} Y_i | \beta_0, \beta_1, \sigma &\overset{\text{ind}}{\sim} N(\mu_i, \sigma^2), \text{ with } \mu_i = \beta_0 + \beta_1 X_{i1} \\ \beta_{0c} &\sim N(25, 5^2) \\ \beta_1 &\sim N(0,3.1^2) \\ \sigma & \sim \text{Exp}(0.13) \end{align*}
stan_glm() to autoscale our priors. What did it change them to?Priors for model 'weather_model_1'
------
Intercept (after predictors centered)
Specified prior:
~ normal(location = 25, scale = 5)
Adjusted prior:
~ normal(location = 25, scale = 38)
Coefficients
Specified prior:
~ normal(location = 0, scale = 2.5)
Adjusted prior:
~ normal(location = 0, scale = 3.1)
Auxiliary (sigma)
Specified prior:
~ exponential(rate = 1)
Adjusted prior:
~ exponential(rate = 0.13)
------
See help('prior_summary.stanreg') for more details
2.5% 97.5%
(Intercept) 2.2619956 6.149827
temp9am 0.9455295 1.137758
sigma 3.7552460 4.568298
95% credible interval for \beta_1: (0.95, 1.14)
95% credible interval for \sigma: (3.76, 4.57)
X_{i2} = \begin{cases} 1 & \text{Wollongong} \\ 0 & \text{otherwise (i.e., Uluru).} \end{cases}
\begin{array}{rl} \text{data:} & Y_i \mid \beta_0, \beta_1, \sigma \overset{\text{ind}}{\sim} N(\mu_i, \sigma^2) \quad \text{with} \quad \mu_i = \beta_0 + \beta_1 X_{i2} \\ \text{priors:} & \beta_{0c} \sim N(25, 5^2) \\ & \beta_1 \sim N(0, 38^2) \\ & \sigma \sim \text{Exp}(0.13). \end{array}
y = \beta_0 + \beta_1 x_2
y = \beta_0 + \beta_1 x_2
\beta_1 is the typical difference in 3 pm temperature in Wollongong (x_2=1) as compared to Uluru (x_2=0).
When we use a binary predictor, this results in two models, effectively.
\begin{align*} y = \beta_0 + \beta_1 x_2 \to \text{(U)} \ y &= \beta_0 \\ \text{(W)} \ y&= \beta_0+\beta_1 \end{align*}
| term | estimate | std.error | conf.low | conf.high |
|---|---|---|---|---|
| (Intercept) | 29.71672 | 0.5613064 | 28.64001 | 30.804213 |
| locationWollongong | -10.31494 | 0.7882179 | -11.86444 | -8.785879 |
\hat{y} = 29.72 - 10.31 x
temp9am and location).| term | estimate | std.error | conf.low | conf.high |
|---|---|---|---|---|
| (Intercept) | 11.3302605 | 0.6742922 | 9.9899497 | 12.6477844 |
| temp9am | 0.8575587 | 0.0292976 | 0.7997129 | 0.9154097 |
| locationWollongong | -7.0612664 | 0.3513952 | -7.7450413 | -6.3618179 |
\hat{y} = 11.33 + 0.86 x_1 - 7.06 x_2
As the 9 am temperature increases by 1 degree, the expected 3 pm temperature increases by 0.86 degrees (95% CI 0.80, 0.92).
The 3 pm temperature is about 7 degrees lower in Wollongong than in Uluru (95% CI -7.75, -6.36).
Take a break!
Back in 10 minutes.
We will now learn how to evaluate how good our regression model is.
How fair is the model?
We want to now learn how to evaluate how good our regression model is.
How wrong is the model?
How accurate are the posterior predictive models?
Consider our example before the break, the Capital Bikeshare data.
We simulated under the following model:
\begin{align*} Y_i | \beta_0, \beta_1, \sigma &\overset{\text{ind}}{\sim} N(\beta_0 + \beta_1X_i,\sigma^2) \\ \beta_{0\text{c}} &\sim N(5000, 1000^2) \\ \beta_{1\text{c}} &\sim N(100, 40^2) \\ \sigma &\sim \text{Exp}(0.0008) \end{align*}
How was the data collected?
By whom and for what purpose was the data collected?
How might the results of the analysis, or the data collection itself, impact individuals and society?
Note!
What biases might be baked into this analysis?
How do I approach thinking about bias in analysis?
Y_i | \beta_0, \beta_1, \sigma \overset{\text{ind}}{\sim} N(\beta_0 + \beta_1X_i,\sigma^2)
What are the assumptions on our model?
The observed data, Y_i, is independent of other observed data, Y_j for i \neq j.
The observed data, Y_i, is independent of other observed data, Y_j for i \neq j.
In this example, the data are daily counts of bike rides.
“Reasonable” does not mean “perfect,” but instead, “good enough to proceed.”
geom_smooth(method = "lm").geom_smooth().When does “visualization” suggest that a linear model is not appropriate?
Are there “issues” with this approach?
To examine linearity in multiple regression, we will perform a posterior predictive check.
If the combined model assumptions are reasonable, then our posterior model should be able to simulate ridership data that’s similar to the original 500 rides observations.
| term | estimate | std.error | conf.low | conf.high |
|---|---|---|---|---|
| (Intercept) | -2177.92731 | 357.204567 | -2771.30965 | -1593.26616 |
| temp_feel | 81.88632 | 5.071117 | 73.48454 | 90.30665 |
\hat{y} = -2177.9 + 81.9x \ \ \ \to \ \ \ \hat{\text{rides}} = -2177.9 + 81.9\text{temp}
How do we perform the posterior predictive check?
The pp_check() function plots a number of the corresponding simulated datasets.
pp_check() function plots a number of the corresponding simulated datasets.Although we are interested in predicting the future, we can begin by evaluating how well it predicts the data that was used to build the model.
We can use the posterior_predict() function to generate posterior predictive summaries to either our data or new data.
[1] 20000 500
ppc_intervals() function to visualize the posterior predictive intervals for each observation.ppc_intervals() function to visualize the posterior predictive intervals for each observation.Let Y_1, Y_2, ..., Y_n denote n observed outcomes. Each Y_i has a corresponding posterior predictive model with mean Y_i' and standard deviation \text{sd}_i
Median Absolute Error (MAE): the typical difference between the observed value and posterior predictive mean.
\text{MAE} = \text{median} | Y_i - Y_i'|
\text{MAE scaled} = \text{median} \frac{| Y_i - Y_i'|}{\text{sd}_i}
Let Y_1, Y_2, ..., Y_n denote n observed outcomes. Each Y_i has a corresponding posterior predictive model with mean Y_i' and standard deviation \text{sd}_i
Within 50: Proportion of observed values that fall within their 50% posterior prediction interval.
Within 95: Proportion of observed values that fall within their 95% posterior prediction interval.
We get these four values out of the prediction_summary() function,
| mae | mae_scaled | within_50 | within_95 |
|---|---|---|---|
| 993.3983 | 0.7720912 | 0.438 | 0.968 |
| mae | mae_scaled | within_50 | within_95 |
|---|---|---|---|
| 993.3983 | 0.7720912 | 0.438 | 0.968 |
What is our final conclusion? Does our Bayesian model produce accurate predictions?
Unfortunately, the answer is subjective and dependent on context.
Per usual, we are just the statisticians. The business must decide what is acceptable.
How can we improve the model’s posterior predictive accuracy?
STA6349 · Applied Bayesian Analysis · Summer 2026