# create a data set: x (latent variable) and y (observation)
set.seed(42) # to make it reproducible (lots of random numbers follow)
<- 50 # number of observations
T <- rep(NA, T) # latent variable
x <- rep(NA, T) # observed values
y <- 2.2 # standard deviation for x
sx <- 0.3 # standard deviation for y
sy 1] <- rnorm(1, 0, 1)
x[1] <- rnorm(1, x[1], sy)
y[
for (t in seq(2, T)) {
<- rnorm(1, x[t-1], sx)
x[t] <- rnorm(1, x[t], sy)
y[t]
}<- x
x_true <- y obs
Particle filter using R
particle filter
A simple particle filter in R
The following example was adapted from the post in RPubs.
Simulate the data
Generate \(y_{1:T}\) as a sequence of noisy observations of a latent variable \(x_{1:T}\).
Implement a particle filter (sequential Monte Carlo)
# particle filter -----------------------------------------------------------
<- length(y) # number of observations
T <- 100 # number of particles
N # to store prior distributions for variables correspond to latent variable x
<- matrix(nrow=N, ncol = T)
x_prior <- matrix(nrow=N, ncol = T) # posterior distributions
x_post <- matrix(nrow=N, ncol = T) # weights used to draw posterior sample
weights <- matrix(nrow = N, ncol = T) # normalized weights
W <- matrix(nrow = N, ncol = T) # indices based on the normalized weights
A 1] <- rnorm(N, 0, sx)# initial X from a normal distribution
x_prior[, # calculate weights, normal likelihood
1] <- dnorm(obs[1], x_prior[, 1], sy)
weights[, 1] <- weights[, 1]/sum(weights[, 1])# normalise weights
W[, # indices based on the weighted resampling with replacement
1] <- sample(1:N, prob = W[1:N, 1], replace = T)
A[, 1] <- x_prior[A[, 1], 1] # posterior distribution using the indices
x_post[,
for (t in seq(2, T)) {
<- rnorm(N, x_post[, t-1], sx) # prior x_{t} based on x_{t-1}
x_prior[, t] <- dnorm(obs[t], x_prior[, t], sy) # calculate weights
weights[, t] <- weights[, t]/sum(weights[, t]) # normalise weights
W[, t] <- sample(1:N, prob = W[1:N, t], replace = T) # indices
A[, t] <- x_prior[A[, t], t] # posterior samples
x_post[, t] }
Summarize results
Calculate the mean and 2.5\(^\textrm{th}\) and 97.\(^\textrm{th}\) percentile of the posterior sample as a means to get 95% credible interval.
<- apply(x_post, 2, mean) # posterior mean
x_means <- apply(x_post, 2, function(x) quantile(x, probs = c(0.025, 0.975))) # 95% credible interval
x_quantiles <- data.frame(t = seq(1, T),
df x_mean = x_means,
x_lb = x_quantiles[1, ],
x_ub = x_quantiles[2, ],
x_true = x_true, # latent variables
y = y) # observed values
Plot the results
library(ggplot2)
::loadfonts("win", quiet=TRUE)
extrafonttheme_set(hrbrthemes::theme_ipsum_rc(base_size=14, subtitle_size=16, axis_title_size=12))
ggplot(df, aes(x = t)) +
geom_ribbon(aes(ymin = x_lb, ymax = x_ub, fill="95% CrI"), alpha=0.5) +
geom_line(aes(y=x_mean, color="Posterior mean")) +
geom_line(aes(y=x_true, color="True")) +
geom_point(aes(y=x_mean, color="Posterior mean")) +
geom_point(aes(y=x_true, color="True")) +
labs(y="values", x="index") +
scale_colour_manual("", values=c("Posterior mean"="firebrick",
"True"="darkgrey")) +
scale_fill_manual("", values="firebrick")+
theme(legend.position = "bottom")
# ggsave("particle_filter.png", gg, units="in", width=3.4*2, height=2.7*2)