#' @title Weighted 2D kernel density estimation
#' @author Jose Alquicira-Hernandez
#' @param x Dimension 1
#' @param y Dimension 2
#' @param w Weight variable
#' @param h vector of bandwidths for x and y directions.
#' Defaults to normal reference bandwidth (ks::hpi).
#' A scalar value will be taken to apply to both directions.
#' @param adjust Bandwidth adjustment
#' @param n Number of grid points in each direction. Can be scalar or a
#' length-2 integer vector.
#' @param lims The limits of the rectangle covered by the grid as
#' c(xl, xu, yl, yu).
#' @return A list of three components.
#' \itemize{
#' \item \code{x, y} The x and y coordinates of the grid points, vectors of
#' length n.
#' \item \code{z} An n[1] by n[2] matrix of the weighted estimated density:
#' rows correspond to the value of x, columns to the value of y.
#' }
#' @importFrom Matrix Matrix
#' @importFrom stats dnorm
#' @importFrom methods is
#' @examples
#'
#' set.seed(1)
#' x <- rnorm(100)
#'
#' set.seed(2)
#' y <- rnorm(100)
#'
#' set.seed(3)
#' w <- sample(c(0, 1), 100, replace = TRUE)
#'
#' dens <- Nebulosa:::wkde2d(x, y, w)
wkde2d <- function(x, y, w, h, adjust = 1, n = 100,
lims = c(range(x), range(y))) {
# Validate values and dimensions
nx <- length(x)
if (!all(all(nx == length(y)), all(nx == length(w)))) {
stop("data vectors must be the same length")
}
if (any(!is.finite(x)) || any(!is.finite(y))) {
stop("missing or infinite values in the data are not allowed")
}
if (any(!is.finite(lims))) {
stop("only finite values are allowed in 'lims'")
}
h <- c(
ks::hpi(x),
ks::hpi(y)
)
h <- h * adjust
# Get grid
gx <- seq.int(lims[1L], lims[2L], length.out = n)
gy <- seq.int(lims[3L], lims[4L], length.out = n)
# weight
ax <- outer(gx, x, "-") / h[1L]
ay <- outer(gy, y, "-") / h[2L]
w <- Matrix::Matrix(rep(w, n), nrow = n, ncol = nx, byrow = TRUE)
z <- Matrix::tcrossprod(dnorm(ax) * w, dnorm(ay) * w) /
(sum(w) * h[1L] * h[2L])
dens <- list(x = gx, y = gy, z = z)
dens
}
get_dens <- function(data, dens, method) {
if (method == "ks") {
ix <- findInterval(data[, 1], dens$eval.points[[1]])
iy <- findInterval(data[, 2], dens$eval.points[[2]])
ii <- cbind(ix, iy)
z <- dens$estimate[ii]
} else if (method == "wkde") {
ix <- findInterval(data[, 1], dens$x)
iy <- findInterval(data[, 2], dens$y)
ii <- cbind(ix, iy)
z <- dens$z[ii]
}
z
}
#' @title Estimate weighted kernel density
#' @author Jose Alquicira-Hernandez
#' @param w Vector with weights for each observation
#' @param x Matrix with dimensions where to calculate the density from. Only
#' the first two dimensions will be used
#' @param method Kernel density estimation method:
#' \itemize{
#' \item \code{ks}: Computes density using the \code{kde} function from the
#' \code{ks} package.
#' \item \code{wkde}: Computes density using a modified version of the
#' \code{kde2d} function from the \code{MASS}
#' package to allow weights. Bandwidth selection from the \code{ks} package
#' is used instead.
#' }
#' @param adjust Numeric value to adjust to bandwidth. Default: 1. Not available
#' for \code{ks} method
#' @param map Whether to map densities to individual observations
#' @return If \code{map} is \code{TRUE}, a vector with corresponding densities
#' for each observation is returned. Otherwise,
#' a list with the density estimates from the selected method is returned.
#' @importFrom ks kde hpi
#' @examples
#'
#' dens <- Nebulosa:::calculate_density(iris[, 3], iris[, 1:2], method = "wkde")
calculate_density <- function(w, x, method, adjust = 1, map = TRUE) {
if (method == "ks") {
dens <- kde(x[, c(1, 2)],
w = w / sum(w) * length(w)
)
} else if (method == "wkde") {
dens <- wkde2d(
x = x[, 1],
y = x[, 2],
w = w / sum(w) * length(w),
adjust = adjust
)
}
if (map) {
get_dens(x, dens, method)
} else {
dens
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.