1- # ' Get estimates of ZOI from response curves
1+ # ' Get estimates of zone of influence (ZOI) from response curves
2+ # '
3+ # ' This generic function computes ZOI metrics—**maximum effect size**,
4+ # ' **ZOI radius**, and **impact**—
5+ # ' for ZOI predictor variables based on response curves.
6+ # ' The ZOI radius is estimated as the
7+ # ' It supports two types of input: a data.frame of predictions
8+ # ' or a bag of models.
9+ # '
10+ # ' @param x Either a `data.frame` containing response curve predictions for a single variable,
11+ # ' or a `bag` object containing an ensemble of models.
12+ # ' @param ... Additional arguments passed to the appropriate method.
13+ # '
14+ # ' @return A `data.frame` or a `list` containing ZOI metrics:
15+ # ' - `max_effect_size`: Maximum effect size on the relative selection strength (y) axis.
16+ # ' - `zoi_radius`: Distance at which the effect drops below a threshold,
17+ # ' defined by the parameter `percentage`.
18+ # ' - `effect_zoi_radius`: Relative selection strength (y axis) valye in which the ZOI
19+ # ' is reached.
20+ # ' - `impact`: Area under the curve up to the ZOI radius,
21+ # ' combining the varying effect size with distance.
22+ # ' Each ZOI measure presents mean, median, CI lower, and CI upper.
223# '
324# ' @example examples/zoi_from_curve_example.R
25+ # ' @seealso [oneimpact::predict], [oneimpact::plot_response], [oneimpact::weirdness]
426# '
27+ # ' @name zoi_from_curve
528# ' @export
6- zoi_from_curve <- function (x ,
7- percentage = 0.95 ,
8- curve = c(" median" , " mean" ),
9- ci = TRUE ,
10- type = c(" linear" , " exp" )[1 ],
11- # n_features = 1,
12- mean_col_name = " mean" ,
13- median_col_name = " quantile:0.5" ,
14- ci_col_name = c(" quantile:0.025" , " quantile:0.975" )) {
29+ zoi_from_curve <- function (x , ... ) {
1530 UseMethod(" zoi_from_curve" )
1631}
1732
33+ # ' @param percentage `[numeric(1)=0.95]` \cr Numeric between 0 and 1. Defines the
34+ # ' threshold for ZOI radius as a proportion of the maximum effect size.
35+ # ' Default is `0.95`.
36+ # ' @param curve `[character(1)=c("mean", "median")]` \cr Character vector.
37+ # ' Which central tendency curves to use: `"median"`, `"mean"`, or both.
38+ # ' @param ci `[logical(1)=TRUE]` \cr Logical. Whether to compute ZOI estimates for
39+ # ' the upper and lower limits of the confidence interval. Default is TRUE.
40+ # ' @param type `[character(1)="linear"]{"linear", "exp"}` \cr Character. Defines whether
41+ # ' the calculation of ZOI should be based on the prediction of at linear
42+ # ' or response (exponential) scale: `"linear"` or `"exp"`, respectively.
43+ # ' @param mean_col_name `[character="mean"]` \cr Name of the column containing
44+ # ' the mean response curve.
45+ # ' @param median_col_name `[character="quantile:0.5"]` \cr Name of the column
46+ # ' containing the median response curve.
47+ # ' @param ci_col_name `[character=c("quantile:0.255", "quantile:0.975")]` \cr Character
48+ # ' vector of length 2. Names of columns for lower and upper confidence intervals.
49+ # '
50+ # ' @rdname zoi_from_curve
1851# ' @export
19- zoi_from_curve <- function (x ,
20- percentage = 0.95 ,
21- curve = c(" median" , " mean" ),
22- ci = TRUE ,
23- type = c(" linear" , " exp" )[1 ],
24- # n_features = 1,
25- mean_col_name = " mean" ,
26- median_col_name = " quantile:0.5" ,
27- ci_col_name = c(" quantile:0.025" , " quantile:0.975" )) {
52+ zoi_from_curve.data.frame <- function (x ,
53+ percentage = 0.95 ,
54+ curve = c(" median" , " mean" ),
55+ ci = TRUE ,
56+ type = c(" linear" , " exp" )[1 ],
57+ # n_features = 1,
58+ mean_col_name = " mean" ,
59+ median_col_name = " quantile:0.5" ,
60+ ci_col_name = c(" quantile:0.025" , " quantile:0.975" )) {
2861
2962 # get predictor / ZOI variable
3063 xvar <- colnames(x )[1 ]
3164
65+ if (type == " linear" ) {
66+ ref <- 0
67+ } else {
68+ ref <- 1
69+ }
70+
3271 # initialize output
3372 # mean, median, 0.025, 0.975
3473 max_effect_size <- rep(NA , 4 )
@@ -49,75 +88,239 @@ zoi_from_curve <- function(x,
4988 main_response <- x [[mean_col_name ]]
5089 }
5190
52- # max effect size
53- max_effect_size_main <- main_response [which.max(abs(main_response ))]
91+ # # max effect size
92+ # if(type == "linear") {
93+ # max_effect_size_main <- main_response[which.max(abs(main_response))]
94+ # } else {
95+ max_effect_size_main <- main_response [which.max(abs(main_response - ref ))]
96+ # }
5497
5598 # zoi radius main
56- y_value_percentage <- (1 - percentage ) * (max_effect_size_main )
57- x_radius_index <- which(abs(main_response ) < abs(y_value_percentage ))[1 ] - 1
99+ # y_value_percentage <- (1 - percentage) * (max_effect_size_main)
100+ # x_radius_index <- which(abs(main_response) < abs(y_value_percentage))[1] - 1
101+ # zoi_radius_main <- x[[xvar]][x_radius_index]
102+
103+ y_value_percentage <- (1 - percentage ) * (max_effect_size_main - ref )
104+ if (type == " linear" ) {
105+ x_radius_index <- which(abs(main_response ) < abs(y_value_percentage + ref ))[1 ] - 1
106+ } else {
107+ x_radius_index <- which(abs(main_response ) > abs(y_value_percentage + ref ))[1 ] - 1
108+ }
109+ if (is.na(x_radius_index )) {
110+ x_radius_index <- length(main_response )
111+ }
58112 zoi_radius_main <- x [[xvar ]][x_radius_index ]
59113
60114 # impact
61115 x_vals <- x [[xvar ]][1 : x_radius_index ]
62- y_vals <- main_response [1 : x_radius_index ]
63- if ( type == " exp " ) y_vals <- exp (y_vals ) - 1
116+ y_vals <- main_response [1 : x_radius_index ] - ref
117+ signal <- ifelse (y_vals [ 1 ] < 0 , - 1 , 1 )
64118 y_vals <- abs(y_vals ) - min(abs(y_vals )) # get positive and discount are above y(ZOI)
65119
66- impact_main <- DescTools :: AUC(x_vals , y_vals )
120+ impact_main <- signal * DescTools :: AUC(x_vals , y_vals )
67121
68- max_effect_size [id ] <- ifelse( type == " exp " , exp( max_effect_size_main ), max_effect_size_main )
122+ max_effect_size [id ] <- max_effect_size_main
69123 zoi_radius [id ] <- zoi_radius_main
70- effect_zoi_radius [id ] <- ifelse( type == " exp " , exp( y_value_percentage ), y_value_percentage )
124+ effect_zoi_radius [id ] <- y_value_percentage + ref
71125 impact [id ] <- impact_main
72126 }
73127
74128 # repeat that for the CI
75129 if (ci ) {
76130
77- ci_id <- 1
78-
131+ ci_id <- 2
79132 for (ci_id in seq(ci_col_name )) {
80133
81134 ci_response <- x [[ci_col_name [ci_id ]]]
82135
83136 # max effect size
84- ci_max <- ci_response [which.max(abs(main_response ))]
137+ ci_max <- ci_response [which.max(abs(main_response - ref ))]
85138
86139 # zoi radius main
87140 # y_value_percentage <- (1 - percentage) * max_effect_size
88- x_radius_ci_index <- which(abs(ci_response ) < abs(y_value_percentage ))[1 ] - 1
141+ if (type == " linear" ) {
142+ x_radius_ci_index <- which(abs(ci_response ) < abs(y_value_percentage + ref ))[1 ] - 1
143+ } else {
144+ x_radius_ci_index <- which(abs(ci_response ) > abs(y_value_percentage + ref ))[1 ] - 1
145+ }
146+ if (is.na(x_radius_ci_index )) {
147+ x_radius_ci_index <- length(main_response )
148+ }
89149 zoi_radius_ci <- x [[xvar ]][x_radius_ci_index ]
90150
151+ if (length(zoi_radius_ci ) == 0 ) {
152+ zoi_radius_ci <- NA
153+ }
154+
91155 # impact
92156 x_vals <- x [[xvar ]][1 : x_radius_ci_index ]
93- y_vals <- ci_response [1 : x_radius_ci_index ]
94- if ( type == " exp " ) y_vals <- exp (y_vals ) - 1
157+ y_vals <- ci_response [1 : x_radius_ci_index ] - ref
158+ signal <- ifelse (y_vals [ 1 ] < 0 , - 1 , 1 )
95159 y_vals <- abs(y_vals ) - min(abs(y_vals )) # get positive and discount are above y(ZOI)
96160
97- impact_ci <- DescTools :: AUC(x_vals , y_vals )
161+ impact_ci <- signal * DescTools :: AUC(x_vals , y_vals )
98162
99- max_effect_size [ci_id + 2 ] <- ifelse( type == " exp " , exp( ci_max ), ci_max )
163+ max_effect_size [ci_id + 2 ] <- ci_max
100164 zoi_radius [ci_id + 2 ] <- zoi_radius_ci
101- effect_zoi_radius [ci_id + 2 ] <- ifelse( type == " exp " , exp( y_value_percentage ), y_value_percentage )
165+ effect_zoi_radius [ci_id + 2 ] <- y_value_percentage + ref
102166 impact [ci_id + 2 ] <- impact_ci
103167 }
104168 }
105169
106- out <- list (max_effect_size = max_effect_size ,
170+ out <- data.frame (max_effect_size = max_effect_size ,
107171 zoi_radius = zoi_radius ,
108172 effect_zoi_radius = effect_zoi_radius ,
109173 impact = impact ) | >
110- dplyr :: bind_rows() | >
111- t()
174+ # dplyr::bind_rows() |>
175+ t() | >
176+ as.data.frame() | >
177+ tibble :: rownames_to_column(var = " zoi_measure" )
112178
113- colnames(out ) <- c(" mean " , " median " , ci_col_name [1 ], ci_col_name [2 ])
179+ colnames(out )[ - 1 ] <- c(mean_col_name , median_col_name , ci_col_name [1 ], ci_col_name [2 ])
114180 out
115181}
116182
183+ # ' @param data `[data.frame]` \cr The original dataset used for model fitting.
184+ # ' @param include `[character="all"]` \cr Character. Either `"all"` or a
185+ # ' regex pattern to filter selected ZOI variables.
186+ # ' @param return_predictions `[logical=FALSE]` \cr Logical. Whether to return
187+ # ' the prediction curves alongside ZOI metrics. If `TRUE`, the output is necessarily
188+ # ' a `list` with predictions and the ZOI parameters.
189+ # ' @param return_format `[character="df"]{"list", "df"}` \cr
190+ # ' Format of the returned ZOI metrics. Either a list of data.frames (if `return_format = "list"`),
191+ # ' one for each variable, or a single `data.frame` (default, if `return_format = "df"`).
192+ # ' @param wq_probs `[numeric,vector=c(0.025, 0.975)]` \cr Numeric vector of quantiles
193+ # ' used for prediction summaries.
194+ # ' @param n_features `[numeric=1]` \cr Number of features used in ZOI prediction.
195+ # ' It can a single number (considered the same for all ZOI variables) or a vector
196+ # ' with the same number of elements as ZOI variables in the model.
197+ # ' @param radius_max `[numeric=NULL]` \cr Numeric. Maximum distance/radius to use for
198+ # ' prediction curves. If `NULL` (default), the maximum value present in the bag's
199+ # ' predictor table is used.
200+ # ' @param baseline `[character="zero"]` \cr Character. Baseline used in `predict()` (e.g., `"zero"`).
201+ # ' @param type_feature `[character="point"]` \cr Character or vector. Type of spatial feature used in
202+ # ' `predict()`.
203+ # ' @param type_feature_recompute `[logical=FALSE]` \cr Logical. Whether to recompute spatial
204+ # ' features within `predict()`, for linear features.
205+ # ' @param resolution `[numeric=200]` \cr Integer. Resolution used in the recomuptation
206+ # ' of ZOIs for linear features.
207+ # ' @param radii `[vector]` \cr Numeric vector. Radii used for ZOI modeling.
208+ # ' @param zoi_shape `[character]` \cr Character. Shape of the ZOI used in the model
209+ # ' (e.g., `"circle"`, `"Gauss"`, `"exp_decay"`).
210+ # '
211+ # ' @return If `x` is a bag object, the function returns wither a `list` or
212+ # ' `data.frame` of ZOI measures for each ZOI variable in the bag.
213+ # ' If `return_predictions = TRUE`, also returns the prediction curves.
214+ # '
215+ # ' @rdname zoi_from_curve
216+ # ' @export
217+ zoi_from_curve.bag <- function (x ,
218+ data ,
219+ include = " all" ,
220+ percentage = 0.95 ,
221+ curve = c(" median" , " mean" ),
222+ type = c(" linear" , " exp" )[1 ],
223+ return_predictions = FALSE ,
224+ return_format = c(" list" , " df" )[2 ],
225+ ci = TRUE ,
226+ wq_probs = c(0.025 , 0.5 , 0.975 ),
227+ n_features = 1 ,
228+ mean_col_name = " mean" ,
229+ median_col_name = " quantile:0.5" ,
230+ ci_col_name = c(" quantile:0.025" , " quantile:0.975" ),
231+ radius_max = NULL ,
232+ baseline = " zero" ,
233+ type_feature = " line" ,
234+ type_feature_recompute = TRUE ,
235+ resolution = 200 ,
236+ radii = c(100 , 250 , 500 , 1000 , 2500 , 5000 , 10000 ),
237+ zoi_shape = c(" circle" , " Gauss" , " rectangle" , " exp_decay" , " bartlett" , " threshold" ,
238+ " mfilter" )[1 ],
239+ ... ) {
240+
241+ # get ZOI variables and terms from predictor table
242+ pred_table <- x $ parms $ predictor_table
243+ zoi_vars <- pred_table $ variable [pred_table $ is_zoi == 1 ]
244+ zoi_terms <- pred_table $ term_zoi [pred_table $ is_zoi == 1 ]
245+ zoi_radii <- pred_table $ zoi_radius [pred_table $ is_zoi == 1 ]
117246
118- # test n_features
119- # test exp vs linear
120- # implement function.df this one
247+ # set radius from predictor table, if null
248+ if (is.null(radius_max )) radius_max <- max(zoi_radii )
249+
250+ # unique variables
251+ zoi_vars_unique <- unique(zoi_vars )
252+ if (include != " all" ) {
253+ vars <- grep(include , zoi_vars_unique , value = TRUE )
254+ if (length(vars ) < 1 ) {
255+ stop(paste0(" Variable(s) " , paste0(include , collapse = " ," ), " not present in the bag." ))
256+ }
257+ zoi_vars_unique <- vars
258+ }
259+
260+ # check parameters
261+ if (length(type_feature ) == 1 ) {
262+ type_feature <- rep(type_feature , times = length(zoi_vars_unique ))
263+ }
264+
265+ if (length(n_features ) == 1 ) {
266+ n_features <- rep(n_features , times = length(zoi_vars_unique ))
267+ }
268+
269+ # compute predictions
270+ i <- 3
271+ dfs <- lapply(seq_along(zoi_vars_unique ), function (i ) {
272+ dfvar <- data.frame (var = seq(0 , radius_max , length.out = 10001 ))
273+ names(dfvar ) <- zoi_vars_unique [i ]
274+ type_feat <- type_feature [i ]
275+ zoi_r <- zoi_radii [zoi_vars == zoi_vars_unique [i ]]
276+ pred <- oneimpact :: predict(x ,
277+ newdata = dfvar ,
278+ data = data ,
279+ type = type ,
280+ wq_probs = wq_probs ,
281+ zoi = TRUE ,
282+ n_features = n_features [i ],
283+ baseline = baseline ,
284+ type_feature = type_feat ,
285+ type_feature_recompute = type_feature_recompute ,
286+ resolution = resolution ,
287+ radii = zoi_r ,
288+ zoi_shape = zoi_shape , ... )
289+ cbind(dfvar , pred )
290+ })
291+ names(dfs ) <- zoi_vars_unique
292+
293+ # compute zoi
294+ i <- 1
295+ zois <- lapply(seq_along(dfs ), function (i ) {
296+ zoi_from_curve(dfs [[i ]], type = type ,
297+ percentage = percentage ,
298+ curve = curve ,
299+ ci = ci ,
300+ mean_col_name = mean_col_name ,
301+ median_col_name = median_col_name ,
302+ ci_col_name = ci_col_name )
303+ })
304+ names(zois ) <- zoi_vars_unique
305+
306+ if (return_format == " df" ) {
307+ # i <- 1
308+ zois <- lapply(seq_along(zois ), function (i ) {
309+ zois [[i ]] | >
310+ dplyr :: mutate(variable = zoi_vars_unique [i ])
311+ }) | >
312+ dplyr :: bind_rows() | >
313+ dplyr :: relocate(variable , .before = 1 ) | >
314+ tibble :: as_tibble()
315+ }
316+
317+ # return tables with zois
318+ if (return_predictions ) {
319+ return (list (predictions = df , zoi = zois ))
320+ } else {
321+ return (zois )
322+ }
323+ }
121324# implement function var
122325# implement function bag - all vars
123326
0 commit comments