16
16
# ' This parameter only matters if you are displaying multiple densities in
17
17
# ' one plot or if you are manually adjusting the scale limits.
18
18
# ' @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.
20
22
# ' @section Computed variables:
21
23
# ' \describe{
22
24
# ' \item{density}{density estimate}
@@ -129,14 +131,15 @@ compute_density <- function(x, w, from, to, bw = "nrd0", adjust = 1,
129
131
), n = 1 ))
130
132
}
131
133
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 ))) {
136
136
dens <- stats :: density(x , weights = w , bw = bw , adjust = adjust ,
137
137
kernel = kernel , n = n )
138
138
139
139
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 )
140
143
}
141
144
142
145
new_data_frame(list (
@@ -149,25 +152,33 @@ compute_density <- function(x, w, from, to, bw = "nrd0", adjust = 1,
149
152
), n = length(dens $ x ))
150
153
}
151
154
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.
152
162
reflect_density <- function (dens , bounds , from , to ) {
163
+ # No adjustment is needed if no finite bounds are supplied
153
164
if (all(is.infinite(bounds ))) {
154
165
return (dens )
155
166
}
156
167
168
+ # Estimate linearly with zero tails (crucial to account for infinite bound)
157
169
f_dens <- stats :: approxfun(
158
170
x = dens $ x , y = dens $ y , method = " linear" , yleft = 0 , yright = 0
159
171
)
160
172
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`
169
174
left <- max(from , bounds [1 ])
170
175
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
171
182
172
- seq( from = left , to = right , length.out = length( grid ) )
183
+ list ( x = out_x , y = out_y )
173
184
}
0 commit comments