--- title: "Introduction to epkde" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to epkde} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(epkde) set.seed(42) ``` ## Overview **epkde** implements the approximate Bayesian method for bandwidth selection in multivariate kernel density estimation (KDE) from Filippone & Sanguinetti (2011). The key idea is to place a prior on the kernel *precision* matrix $\Lambda$ (the inverse bandwidth), and use the **Expectation Propagation (EP)** algorithm to compute an approximate posterior, exploiting a leave-one-out cross-validated likelihood. Three kernel structures are supported: | Structure | Prior on $\Lambda$ | Free parameters | |-------------|---------------------|-----------------| | Isotropic | Gamma on scalar $\lambda$ | 1 | | Diagonal | Independent Gammas on $\lambda_k$ | $d$ | | Full | Wishart on matrix $\Lambda$ | $d(d+1)/2$ | The **model evidence** returned by each fit makes it possible to select the appropriate structure using Bayes factors, without a held-out test set. --- ## Quick start: bivariate Gaussian mixture ```{r data} # Generate data from a bivariate Gaussian mixture n <- 150; d <- 2 x <- rbind(matrix(rnorm(n/2 * d, mean = c(-2, 0)), ncol = d), matrix(rnorm(n/2 * d, mean = c( 2, 0)), ncol = d)) ``` ### Isotropic fit ```{r iso} fit_iso <- ep_kde_isotropic(x, prior_shape = 1, prior_rate = 1) cat("Posterior mean precision (lambda):", fit_iso$post_shape / fit_iso$post_rate, "\n") cat("Log model evidence:", fit_iso$log_evidence, "\n") ``` ### Diagonal fit ```{r diag} fit_diag <- ep_kde_diagonal(x, prior_shape = rep(1, d), prior_rate = rep(1, d)) cat("Posterior mean precision per dimension:", fit_diag$post_shape / fit_diag$post_rate, "\n") cat("Log model evidence:", fit_diag$log_evidence, "\n") ``` ### Full precision matrix fit ```{r full} fit_full <- ep_kde_full(x, prior_nu = 0) cat("Posterior nu:", fit_full$post_nu, "\n") cat("Posterior mean precision matrix:\n") print(fit_full$post_mean) cat("Log model evidence:", fit_full$log_evidence, "\n") ``` ### Model comparison via Bayes factors ```{r bf} ev <- c(isotropic = model_evidence(fit_iso), diagonal = model_evidence(fit_diag), full = model_evidence(fit_full)) cat("Log evidences:\n"); print(ev) cat("\nLog Bayes factor (diagonal vs. isotropic):", ev["diagonal"] - ev["isotropic"], "\n") ``` ### Evaluating the fitted density ```{r predict} # Grid for evaluation gr <- seq(-6, 6, length.out = 50) grid <- as.matrix(expand.grid(gr, gr)) # Use the diagonal fit: Lambda = diag(post_shape / post_rate) Lambda_diag <- diag(fit_diag$post_shape / fit_diag$post_rate) p_hat <- kde_predict(grid, x, Lambda_diag) # Contour plot contour(gr, gr, matrix(p_hat, 50), main = "Bayesian KDE (diagonal bandwidth)", xlab = expression(x[1]), ylab = expression(x[2])) points(x, pch = 20, cex = 0.4) ``` --- ## Online learning The isotropic model supports **sequential updates**: after fitting on an initial batch, you can incorporate new observations cheaply without reprocessing the original data. ```{r online} n_init <- 100 x_init <- x[1:n_init, ] x_more <- x[(n_init + 1):n, ] fit_init <- ep_kde_isotropic(x_init, prior_shape = 1, prior_rate = 1) fit_update <- ep_kde_online(x_init, x_more, prior_shape = 1, prior_rate = 1, fit_old = fit_init) fit_all <- ep_kde_isotropic(x, prior_shape = 1, prior_rate = 1) cat("Online posterior mean precision :", fit_update$post_shape / fit_update$post_rate, "\n") cat("Offline (all-at-once) mean prec :", fit_all$post_shape / fit_all$post_rate, "\n") ``` --- ## References Filippone, M. & Sanguinetti, G. (2011). Approximate inference of the bandwidth in multivariate kernel density estimation. *Computational Statistics & Data Analysis*, **55**(12), 3104–3122.