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,39 @@ 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.
162
+ #
163
+ # `dens` - output of `stats::density`.
164
+ # `bounds` - two-element vector with left and right known (user supplied)
165
+ # bounds of x values.
166
+ # `from`, `to` - numbers used as corresponding arguments of `stats::density()`
167
+ # in case of no boundary correction.
152
168
reflect_density <- function (dens , bounds , from , to ) {
169
+ # No adjustment is needed if no finite bounds are supplied
153
170
if (all(is.infinite(bounds ))) {
154
171
return (dens )
155
172
}
156
173
174
+ # Estimate linearly with zero tails (crucial to account for infinite bound)
157
175
f_dens <- stats :: approxfun(
158
176
x = dens $ x , y = dens $ y , method = " linear" , yleft = 0 , yright = 0
159
177
)
160
178
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 ) {
179
+ # Create a uniform x-grid inside `bounds`
169
180
left <- max(from , bounds [1 ])
170
181
right <- min(to , bounds [2 ])
182
+ out_x <- seq(from = left , to = right , length.out = length(dens $ x ))
183
+
184
+ # Update density estimation by adding reflected tails from outside `bounds`
185
+ left_reflection <- f_dens(bounds [1 ] + (bounds [1 ] - out_x ))
186
+ right_reflection <- f_dens(bounds [2 ] + (bounds [2 ] - out_x ))
187
+ out_y <- f_dens(out_x ) + left_reflection + right_reflection
171
188
172
- seq( from = left , to = right , length.out = length( grid ) )
189
+ list ( x = out_x , y = out_y )
173
190
}
0 commit comments