Skip to content

Commit 8b9734d

Browse files
committed
Refresh bounds usage in StatDensity.
1 parent c42ca9d commit 8b9734d

File tree

1 file changed

+25
-14
lines changed

1 file changed

+25
-14
lines changed

R/stat-density.r

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
#' This parameter only matters if you are displaying multiple densities in
1717
#' one plot or if you are manually adjusting the scale limits.
1818
#' @param bounds Known lower and upper bounds for estimated data. Default
19-
#' `c(-Inf, Inf)` means that there are no (finite) bounds.
19+
#' `c(-Inf, Inf)` means that there are no (finite) bounds. If any bound is
20+
#' finite, boundary effect of default density estimation will be corrected by
21+
#' reflecting tails outside `bounds` around their closest edge.
2022
#' @section Computed variables:
2123
#' \describe{
2224
#' \item{density}{density estimate}
@@ -129,14 +131,15 @@ compute_density <- function(x, w, from, to, bw = "nrd0", adjust = 1,
129131
), n = 1))
130132
}
131133

132-
if (all(is.infinite(bounds))) {
133-
dens <- stats::density(x, weights = w, bw = bw, adjust = adjust,
134-
kernel = kernel, n = n, from = from, to = to)
135-
} else {
134+
# Decide whether to use boundary correction
135+
if (any(is.finite(bounds))) {
136136
dens <- stats::density(x, weights = w, bw = bw, adjust = adjust,
137137
kernel = kernel, n = n)
138138

139139
dens <- reflect_density(dens = dens, bounds = bounds, from = from, to = to)
140+
} else {
141+
dens <- stats::density(x, weights = w, bw = bw, adjust = adjust,
142+
kernel = kernel, n = n, from = from, to = to)
140143
}
141144

142145
new_data_frame(list(
@@ -149,25 +152,33 @@ compute_density <- function(x, w, from, to, bw = "nrd0", adjust = 1,
149152
), n = length(dens$x))
150153
}
151154

155+
# Update density estimation to mitigate boundary effect at known `bounds`:
156+
# - All x values will lie inside `bounds`.
157+
# - All y-values will be updated to have total probability of `bounds` be
158+
# closer to 1. This is done by reflecting tails outside of `bounds` around
159+
# their closest edge. This leads to those tails lie inside of `bounds`
160+
# (completely, if they are not wider than `bounds` itself, which is a common
161+
# situation) and correct boundary effect of default density estimation.
152162
reflect_density <- function(dens, bounds, from, to) {
163+
# No adjustment is needed if no finite bounds are supplied
153164
if (all(is.infinite(bounds))) {
154165
return(dens)
155166
}
156167

168+
# Estimate linearly with zero tails (crucial to account for infinite bound)
157169
f_dens <- stats::approxfun(
158170
x = dens$x, y = dens$y, method = "linear", yleft = 0, yright = 0
159171
)
160172

161-
out_x <- intersection_grid(dens$x, bounds, from, to)
162-
out_y <- f_dens(out_x) + f_dens(bounds[1] + (bounds[1] - out_x)) +
163-
f_dens(bounds[2] + (bounds[2] - out_x))
164-
165-
list(x = out_x, y = out_y)
166-
}
167-
168-
intersection_grid <- function(grid, bounds, from, to) {
173+
# Create a uniform x-grid inside `bounds`
169174
left <- max(from, bounds[1])
170175
right <- min(to, bounds[2])
176+
out_x <- seq(from = left, to = right, length.out = length(dens$x))
177+
178+
# Update density estimation by adding reflected tails from outside `bounds`
179+
left_reflection <- f_dens(bounds[1] + (bounds[1] - out_x))
180+
right_reflection <- f_dens(bounds[2] + (bounds[2] - out_x))
181+
out_y <- f_dens(out_x) + left_reflection + right_reflection
171182

172-
seq(from = left, to = right, length.out = length(grid))
183+
list(x = out_x, y = out_y)
173184
}

0 commit comments

Comments
 (0)