-
Notifications
You must be signed in to change notification settings - Fork 7
/
postestimate_doNonlinearEffectsAnalysis.R
336 lines (280 loc) · 12.3 KB
/
postestimate_doNonlinearEffectsAnalysis.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#' Do a nonlinear effects analysis
#'
#' \lifecycle{maturing}
#'
#' Calculate the expected value of the dependent variable conditional on the values of
#' an independent variables and a moderator variable. All other variables in the model
#' are assumed to be zero, i.e., they are fixed at their mean levels. Moreover, it produces
#' the input for the floodlight analysis.
#'
#' @usage doNonlinearEffectsAnalysis(
#' .object = NULL,
#' .dependent = NULL,
#' .independent = NULL,
#' .moderator = NULL,
#' .n_steps = 100,
#' .values_moderator = c(-2, -1, 0, 1, 2),
#' .value_independent = 0,
#' .alpha = 0.05
#' )
#'
#' @inheritParams csem_arguments
#'
#' @return A list of class `cSEMNonlinearEffects` with a corresponding method
#' for `plot()`. See: [plot.cSEMNonlinearEffects()].
#'
#' @seealso [csem()], [cSEMResults], [plot.cSEMNonlinearEffects()]
#'
#' @example inst/examples/example_doNonlinearEffectsAnalysis.R
#'
#' @export
doNonlinearEffectsAnalysis <- function(
.object = NULL,
.dependent = NULL,
.independent = NULL,
.moderator = NULL,
.n_steps = 100,
.values_moderator = c(-2, -1, 0, 1, 2),
.value_independent = 0,
.alpha = 0.05
){
## Install rootSolve if not already installed
if (!requireNamespace("rootSolve", quietly = TRUE)) {
stop2(
"Package `rootSolve` required. Use `install.packages(\"rootSolve\")` and rerun.")
}
if(inherits(.object, "cSEMResults_multi")) {
out <- lapply(.object, doNonlinearEffectsAnalysis,
.dependent = .dependent, .independent = .independent,
.moderator = .moderator, .n_steps = .n_steps)
class(out) <- c("cSEMNonlinearEffects", "cSEMNonlinearEffects_multi")
return(out)
} else {
## Check whether .object is of class cSEMResults_resampled; if not perform
## standard resampling (bootstrap with .R = 499 reps)
if(!inherits(.object, "cSEMResults_resampled")) {
if(inherits(.object, "cSEMResults_default")) {
args <- .object$Information$Arguments
} else {
args <- .object$Second_stage$Information$Arguments_original
}
args[".resample_method"] <- "bootstrap"
.object <- do.call(csem, args)
}
## Select relevant quantities
if(inherits(.object, "cSEMResults_default")) {
m <- .object$Information$Model
est <- .object$Estimates$Estimates_resample$Estimates1$Path_estimates
H <- .object$Estimates$Construct_scores
Q <- .object$Estimates$Reliabilities
}
else {
m <- .object$Second_stage$Information$Model
est <- .object$Second_stage$Information$Resamples$Estimates$Estimates1$Path_estimates
H <- .object$Second_stage$Estimates$Construct_scores
Q <- .object$Second_stage$Estimates$Reliabilities
}
# Character string containing the names of the dependent and independent variables
dep_vars <- rownames(m$structural[rowSums(m$structural) != 0, , drop = FALSE])
indep_vars <- colnames(m$structural[, colSums(m$structural[.dependent, ,drop = FALSE]) != 0 , drop = FALSE])
## Check if model is nonlinear.
if(m$model_type != "Nonlinear"){
stop2(
"The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"The structural model must contain contain nonlinear terms.")
}
## Works only for one significance level, i.e., no vector of significances is allowed
if(length(.alpha) != 1){
stop2(
"The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"Currently only a single significance level (.alpha) is allowed.")
}
## Check whether dependent, and two independent variables are provided
if(is.null(.dependent) | is.null(.independent) | is.null(.moderator)){
stop2(
"The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"All variables (.dependent, .independent , and .moderator) must be supplied.")
}
# Check if the name of the dependent variable is valid
if(!(.dependent %in% dep_vars)){
stop2(
"The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"The dependent variable supplied to `.dependent` is not a dependent variable in the original model.")
}
# Check if the name of the moderator (.moderator) and the dependent variable (.independent) supplied are used
# in the original model
if(!all(c(.moderator, .independent) %in% colnames(m$structural))){
stop2(
"The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"Independent and/or moderator variable are not part of the original model.")
}
if(length(.value_independent)!=1){
stop2("The following error occured in the `doNonlinearEffectsAnalysis()` function:\n",
"Only one level for the independent variable is allowed for floodlight analysis.")
}
### Calculation Floodlight Analysis-------------------------------------------
# Possible names of the interaction terms
possible_names <- c(paste(.moderator, .independent, sep = '.'),
paste(.independent, .moderator, sep= '.'))
# Name of the interaction term
xz <- possible_names[possible_names %in% indep_vars]
if(length(xz) != 1){
stop2(
"The defined interaction term does not exist in the model or is not",
" part of the equation of the dependent variable.")
}
## Compute spotlights (= effects of independent (z) on dependent (y) for given
## values of moderator (x))
steps_flood <- seq(min(H[, .moderator]), max(H[, .moderator]), length.out = .n_steps)
res_flood <- getValuesFloodlight(
.model = m,
.path_coefficients = est,
.dependent = .dependent,
.independent = .independent,
.moderator = .moderator,
.steps_mod = steps_flood,
.value_independent = .value_independent,
.alpha = .alpha
)
# Determine Johnson-Neyman point
# Create function for interpolation
# lower bound of the CI
Int_lb<-approxfun(x=res_flood[,'value_z'], y=res_flood[,'lb'])
# find zero places
zero_lb<-rootSolve::uniroot.all(Int_lb,
interval = range(res_flood[,'value_z']))
# Create function for Interpolation
# upper bound of the CI
Int_ub<-approxfun(x=res_flood[,'value_z'], y=res_flood[,'ub'])
# find zero places
zero_ub<-rootSolve::uniroot.all(Int_ub,
interval = range(res_flood[,'value_z']))
zeros=c(zero_lb,zero_ub)
if(length(zeros)>0){#At least one JN point
JN_matrix<-cbind(zeros,0)
colnames(JN_matrix)<-c('x','y')
} else if(length(zeros)==0){
JN_matrix<-matrix(nrow=0,ncol=2,dimnames = list(c(),c('x','y')))
}
out_flood <- list(
"out" = res_flood,
"Johnson_Neyman_points" = JN_matrix
)
# Calculate information necessary for print ----------------------------------
out_print <- getValuesFloodlight(
.model = m,
.path_coefficients = est,
.dependent = .dependent,
.independent = .independent,
.moderator = .moderator,
.steps_mod = c(-2,-1,0,1,2),
.value_independent = .value_independent,
.alpha =.alpha
)
colnames(out_print) <- c('direct_effect', 'value_z', sprintf("%.6g%%L", 100*(1-.alpha)),
sprintf("%.6g%%U", 100*(1-.alpha)))
### Calculation Simple effects and surface analysis---------------------------------------
if(inherits(.object, "cSEMResults_default")) {
sum_object = summarize(.object = .object)
} else if(inherits(.object, "cSEMResults_2ndorder")){
sum_object = summarize(.object = .object$Second_stage)
}
# Effects all
effects_all <- sum_object$Estimates$Path_estimates
nonlinear_vars <- indep_vars[grepl(pattern = '.',x = indep_vars,fixed = T)]
# interactions of the .independent and the .moderator
pointer=sapply(indep_vars,function(x){
temp=unlist(strsplit(x,'\\.'))
if(sum(grepl(paste(c(.moderator, .independent),collapse = '|' ),temp))==length(temp)){
TRUE
}else{
FALSE
}
})
if(!all(pointer)){
warning(paste0(
"The considered equation contains the following variables that do not\n",
"only involve ",.independent, " and ", .moderator, ":\n\n",
paste(indep_vars[!pointer],collapse =', '),"\n\n",
"They will be ignored in calculating the predicted values of ", .dependent," i.e., \n",
"The other variables are considered at their mean values."), call = FALSE)
}
vars_rel=indep_vars[pointer]
sum_rel=sum_object$Estimates$Path_estimates[sum_object$Estimates$Path_estimates$Name%in%
paste(.dependent,"~",vars_rel),]
steps_ind_simple = matrix(seq(min(H[, .independent ]), max(H[, .independent ]),
length.out = .n_steps),ncol=1)
colnames(steps_ind_simple)=.independent
# Calculation of simple effects analysis
steps_mod_simple = .values_moderator
y_pred_list_simple=lapply(steps_mod_simple,function(stepmode){
temp<-lapply(sum_rel$Name,function(x){
temp <- sum_rel[sum_rel$Name==x,]
indep_names <- unlist(strsplit(temp$Name,paste(.dependent,"~ ")))[2]
indep_rel <- unlist(strsplit(indep_names,'\\.'))
# Check which is the moderator and which is the independent variable
moderat <- indep_rel[.moderator==indep_rel]
indep <- indep_rel[.independent==indep_rel]
#Reliabilities of composites are 1 so nothing happens, except in case of
# SOC of the type composite of common factor
# The formula for the average is fine, as the expected value are only required
# for terms with a mximum power of 3. In this case, the formula is appropriate
average <- mean(matrixStats::rowProds(H[,indep_rel,drop=FALSE]))/prod(Q[indep_rel])
temp$Estimate*(matrixStats::rowProds(steps_ind_simple[,indep,drop=FALSE])*
stepmode^length(moderat)-
average)
}
)
temp
Reduce('+',temp)
})
out_simpleeffects <- data.frame(values_dep = unlist(y_pred_list_simple),
values_ind = rep(steps_ind_simple,length(steps_mod_simple)),
values_mod = factor(rep(steps_mod_simple,each=length(steps_ind_simple))))
# Calculation of surface analsyis
steps_ind_surface = seq(min(H[, .independent ]), max(H[, .independent ]),
length.out = .n_steps)
steps_mod_surface = seq(min(H[, .moderator ]), max(H[, .moderator ]),
length.out = .n_steps)
steps_independent = rep(steps_ind_surface,each=length(steps_mod_surface))
steps_moderator = rep(steps_mod_surface,times = length(steps_ind_surface))
steps_surface=cbind(steps_independent,steps_moderator)
colnames(steps_surface)=c(.independent ,.moderator )
y_pred_list_surface=lapply(sum_rel$Name,function(x){
temp <- sum_rel[sum_rel$Name==x,]
indep_names <- unlist(strsplit(temp$Name,paste(.dependent,"~ ")))[2]
indep_rel <- unlist(strsplit(indep_names,'\\.'))
#Reliabilities of composites are 1 so nothing happens, except in case of
# SOC of the type composite of common factor
# The formula for the average is fine, as the expected value are only required
# for terms with a mximum power of 3. In this case, the formula is appropriate
average <- mean(matrixStats::rowProds(H[,indep_rel,drop=FALSE]))/prod(Q[indep_rel])
temp$Estimate*(matrixStats::rowProds(steps_surface[,indep_rel,drop=FALSE])-average)
}
)
y_pred_surface = Reduce('+',y_pred_list_surface)
out_surface <- list(
values_dep = matrix(y_pred_surface,ncol=length(steps_ind_surface),
nrow=length(steps_mod_surface)),
values_ind1 = steps_ind_surface,values_ind2=steps_mod_surface
)
# Prepare and return output
out <- list(
"out_floodlight" = out_flood,
"out_surface" = out_surface,
"out_simpleeffects" = out_simpleeffects,
"Information" = list(
dependent = .dependent,
independent = .independent,
moderator = .moderator,
all_independent = vars_rel,
values_moderator = .values_moderator,
value_independent = .value_independent,
alpha = .alpha
),
"Information_print" = out_print
)
class(out) <- "cSEMNonlinearEffects"
return(out)
}
}