77# ' \code{\link[dsem]{make_dsem_ram}} for more description
88# ' @param tsdata time-series data, as outputted using \code{\link[stats]{ts}}, with \code{NA}
99# ' for missing values.
10- # ' @param family Character-vector listing the distribution used for each column of \code{tsdata}, where
11- # ' each element must be \code{fixed} (for no measurement error),
12- # ' \code{normal} for normal measurement error using an identity link,
13- # ' \code{gamma} for a gamma measurement error using a fixed CV and log-link,
14- # ' \code{bernoulli} for a Bernoulli measurement error using a logit-link, or
15- # ' \code{poisson} for a Poisson measurement error using a log-link.
16- # ' \code{family="fixed"} is default behavior and assumes that a given variable is measured exactly.
17- # ' Other options correspond to different specifications of measurement error.
10+ # ' @param family A named list of families, each returning a class \code{family},
11+ # ' including [fixed()], [gaussian()], [binomial()], [Gamma()], [poisson()],
12+ # ' [lognormal()], [tweedie()], or [gaussian_fixed_sd()] with names that match levels of
13+ # ' \code{colnames(tsdata)} to allow different
14+ # ' families by variable. Family [fixed()] specifies that states
15+ # ' are known (i.e., measurements for that variable have no error).
16+ # ' Other families allow users to supply a link function including `identity`,
17+ # ' `log`, `logit`, or `cloglog`. For example
18+ # ' \code{family = list(y = binomial("logit"), x = fixed())}
19+ # ' would specify logit-linked Bernoulli distribution for variable `tsdata$y`
20+ # ' and a fixed (no measurement error) distribution for `tsdata$x`.
21+ # ' For many variables, it is convenient to do e.g.,
22+ # ' \code{family = Map(function(.) gaussian(), colnames(tsdata))} rather than writing
23+ # ' them all manually.
1824# ' @param estimate_delta0 Boolean indicating whether to estimate deviations from equilibrium in initial year
1925# ' as fixed effects, or alternatively to assume that dynamics start at some stochastic draw away from
2026# ' the stationary distribution
4248# ' settings, and see documentation for that function for details.
4349# '
4450# ' @importFrom TMB compile dynlib MakeADFun sdreport summary.sdreport config
45- # ' @importFrom stats AIC sd .preformat.ts na.omit nlminb optimHess pnorm rbinom rgamma rpois rnorm simulate time tsp<- plogis pchisq
51+ # ' @importFrom stats AIC sd .preformat.ts na.omit nlminb optimHess pnorm rbinom
52+ # ' @importFrom stats rgamma rpois rnorm simulate time tsp<- plogis pchisq
53+ # ' @importFrom stats gaussian poisson Gamma binomial
4654# ' @importFrom Matrix solve Cholesky sparseMatrix mat2triplet drop0 t
4755# ' @importFrom igraph plot.igraph graph_from_data_frame with_sugiyama layout_ graph.adjacency clusters
4856# ' @importFrom ggraph ggraph geom_edge_arc create_layout rectangle geom_node_text theme_graph
4957# ' @importFrom ggplot2 aes
5058# ' @importFrom grid arrow
5159# ' @importFrom methods is
5260# ' @importFrom utils combn
61+ # ' @importFrom checkmate assertList assertClass assertNames assertLogical
5362# '
5463# ' @details
5564# ' A DSEM involves (at a minimum):
186195# ' "gwage","invest","capital")]
187196# '
188197# ' # Fit model
189- # ' fit = dsem( sem=sem,
190- # ' tsdata = tsdata,
191- # ' estimate_delta0 = TRUE,
192- # ' control = dsem_control(quiet=TRUE) )
198+ # ' fit = dsem(
199+ # ' sem = sem,
200+ # ' tsdata = tsdata,
201+ # ' estimate_delta0 = TRUE,
202+ # ' control = dsem_control(quiet=TRUE)
203+ # ' )
193204# ' summary( fit )
194205# ' plot( fit )
195206# ' plot( fit, edge_label="value" )
199210dsem <-
200211function ( sem ,
201212 tsdata ,
202- family = rep( " fixed" ,ncol (tsdata )),
213+ family = Map( function ( . ) fixed(), colnames (tsdata )),
203214 estimate_delta0 = FALSE ,
204215 estimate_mu = NULL ,
205216 prior_negloglike = NULL ,
206217 control = dsem_control(),
207218 covs = colnames(tsdata ) ){
208219 start_time = Sys.time()
209220
221+ # Temporary warning
222+ if ( isTRUE(is(family , " character" )) ){
223+ stop(" starting with release 3.0.0, `family` must be a named list of " )
224+ }
225+
210226 # General error checks
211- if ( isFALSE(is(control , " dsem_control" )) ) stop(" `control` must be made by `dsem_control()`" )
227+ # if( isFALSE(is(control, "dsem_control")) ) stop("`control` must be made by `dsem_control()`")
212228 if ( isTRUE(control $ gmrf_parameterization == " project" ) ){
213229 if ( isTRUE(any(family == " fixed" & colSums(! is.na(tsdata ))> 0 )) ){
214230 stop(" `family` cannot be `fixed` using `gmrf_parameterization=projection` for any variable with data" )
215231 }
216232 }
217- if ( isFALSE(is(tsdata ," ts" )) ) stop(" `tsdata` must be a `ts` object" )
233+ # if( isFALSE(is(tsdata,"ts")) ) stop("`tsdata` must be a `ts` object")
234+ assertClass(control , classes = " dsem_control" )
235+ assertClass(tsdata , classes = " ts" )
236+ assertNames( names(family ), must.include = colnames(tsdata ))
237+ assertList(family , types = " family" )
238+ assertLogical(estimate_delta0 , len = 1 )
218239
219240 # General warnings
220241 if ( isFALSE(control $ quiet ) ){
@@ -249,7 +270,7 @@ function( sem,
249270 stop(" If specifying `lower` or `upper`, please set `dsem_control('newton_loops'=0)`" )
250271 }
251272 }
252- if ( any(ram $ heads == 0 ) ){
273+ if ( any(ram $ heads %in% c( 3 , 4 ) ) ){
253274 # Using moderating variables, their raw values are used to construct Rho_kk and raw values follow standard-normal distribution
254275 # so Gamma_kk and Rho_kk are not properly applied to moderating variables
255276 if ( control $ gmrf_parameterization %in% c(" project" ) ){
@@ -261,7 +282,8 @@ function( sem,
261282 options = c(
262283 switch (control $ gmrf_parameterization , " full" = 0 , " project" = 1 , " mvn_project" = 2 , " gmrf_project" = 3 , NA ),
263284 switch (control $ constant_variance , " conditional" = 0 , " marginal" = 1 , " diagonal" = 2 ),
264- ifelse( isTRUE(control $ stabilize_Q ), 1 , 0 )
285+ ifelse( isTRUE(control $ stabilize_Q ), 1 , 0 ),
286+ ifelse( isTRUE(control $ logscale_moderating_variance ), 1 , 0 )
265287 )
266288
267289 # define variables to project to `project_k` unless provided by user
@@ -304,14 +326,89 @@ function( sem,
304326 }
305327 }
306328
329+ # Check for errors
330+ if ( ! all(colnames(tsdata ) %in% names(family )) ){
331+ stop(" some variable in `colnames(tsdata)` not found in `names(family)`" )
332+ }
333+
334+ # distribution/link
335+ build_distributions <-
336+ function ( variables ){
337+
338+ # Construct log_sigma based on family
339+ remove_last = function (x ) x [- length(x )]
340+
341+ # Fixed values
342+ sigma_j = lapply( family , FUN = function (x ){
343+ switch ( x $ family [length(x $ family )],
344+ " fixed" = c(),
345+ " gaussian" = NA ,
346+ " poisson" = c(),
347+ " binomial" = c(),
348+ " bernoulli" = c(),
349+ " Gamma" = NA ,
350+ " gaussian_fixed_sd" = c(),
351+ " lognormal" = NA ,
352+ " tweedie" = c(NA , NA )
353+ )} )
354+ Nsigma_j = sapply(sigma_j , length )
355+ sigmastart_j = remove_last(cumsum(c(0 ,Nsigma_j )))
356+ names(sigmastart_j ) = variables
357+
358+ # Extract known SDs
359+ sd_tj = sapply( family ,
360+ FUN = function (x ){
361+ if (length(x $ fixed_sd )== 0 ){
362+ return (rep(0 , nrow(tsdata )))
363+ }else {
364+ sd = rep(0 , nrow(tsdata ))
365+ sd [] = x $ fixed_sd
366+ return (sd )
367+ }
368+ }
369+ )
370+
371+ #
372+ family_code = sapply( family , FUN = function (x ){
373+ c(" fixed" = 0 ,
374+ " gaussian" = 1 ,
375+ " binomial" = 2 ,
376+ " bernoulli" = 2 ,
377+ " poisson" = 3 ,
378+ " Gamma" = 4 ,
379+ " gaussian_fixed_sd" = 5 ,
380+ " lognormal" = 6 ,
381+ " tweedie" = 7
382+ )[x $ family ]} )
383+ link_code = sapply( family , FUN = function (x ){
384+ c(" identity" = 0 ,
385+ " log" = 1 ,
386+ " logit" = 2 ,
387+ " cloglog" = 3
388+ )[x $ link ]} )
389+ out = list (
390+ family_code = family_code ,
391+ link_code = link_code ,
392+ Nsigma_j = Nsigma_j ,
393+ sigmastart_j = sigmastart_j ,
394+ sigma_j = sigma_j ,
395+ sd_tj = sd_tj
396+ )
397+ return (out )
398+ }
399+ family = family [match(colnames(tsdata ), names(family ))]
400+ distributions = build_distributions( colnames(tsdata ) )
401+
307402 #
308403 Data = list (
309- " options" = options ,
310- # "RAM" = as.matrix(na.omit(ram[,1:4])),
311- " RAM" = as.matrix(ram [,- 5 ]),
312- " RAMstart" = as.numeric(ram [,5 ]),
313- " familycode_j" = sapply(family , FUN = switch , " fixed" = 0 , " normal" = 1 , " bernoulli" = 2 , " poisson" = 3 , " gamma" = 4 ),
314- " y_tj" = tsdata
404+ options = options ,
405+ RAM = as.matrix(ram [,- 5 ]),
406+ RAMstart = as.numeric(ram [,5 ]),
407+ familycode_j = distributions $ family_code ,
408+ linkcode_j = distributions $ link_code ,
409+ sigmastart_j = distributions $ sigmastart_j ,
410+ eps_tj = distributions $ sd_tj ,
411+ y_tj = tsdata
315412 )
316413
317414 # Default ... unobs_idx for fixed variables with zero variance
@@ -323,10 +420,10 @@ function( sem,
323420 # Construct parameters
324421 if ( is.null(control $ parameters ) ){
325422 Params = list (
326- beta_z = rep(0 ,max(ram [,4 ],na.rm = TRUE )), # NA for spatially-varying paths in ram
327- lnsigma_j = rep(0 ,ncol( tsdata )),
328- mu_j = rep(0 ,ncol(tsdata )),
329- delta0_j = rep(0 ,ncol(tsdata )),
423+ beta_z = rep(0 , max(ram [,4 ],na.rm = TRUE )), # NA for spatially-varying paths in ram
424+ lnsigma_z = rep(0 , sum( distributions $ Nsigma_j )),
425+ mu_j = rep(0 , ncol(tsdata )),
426+ delta0_j = rep(0 , ncol(tsdata )),
330427 x_tj = ifelse( is.na(tsdata ), 0 , tsdata )
331428 )
332429 # if( control$gmrf_parameterization=="full" ){
@@ -368,9 +465,9 @@ function( sem,
368465 if ( is.null(control $ map ) ){
369466 Map = list ()
370467 # Map off x_tj for fixed when data is available
371- Map $ x_tj = ifelse( is.na(as.vector(tsdata )) | (Data $ familycode_j [col(tsdata )] %in% c(1 ,2 ,3 ,4 )), seq_len(prod(dim(tsdata ))), NA )
468+ Map $ x_tj = ifelse( is.na(as.vector(tsdata )) | (Data $ familycode_j [col(tsdata )] %in% c(1 ,2 ,3 ,4 , 5 , 6 , 7 )), seq_len(prod(dim(tsdata ))), NA )
372469 # Map off sigma_j for fixed / bernoulli / Poisson
373- Map $ lnsigma_j = factor ( ifelse(Data $ familycode_j %in% c(0 ,2 ,3 ), NA , seq_along(Params $ lnsigma_j )) )
470+ # Map$lnsigma_j = factor( ifelse(Data$familycode_j %in% c(0,2,3), NA, seq_along(Params$lnsigma_j)) )
374471
375472 # Map off mean for latent variables
376473 Map $ mu_j = factor ( ifelse(colnames(tsdata ) %in% estimate_mu , seq_len(ncol(tsdata )), NA ) )
@@ -645,6 +742,9 @@ function( sem,
645742# ' are then replaced with Inf and avoided during estimation
646743# ' @param stabilize_Q add \code{stability_eps = 1e-10} to stabilize precision
647744# ' (experimental)
745+ # ' @param logscale_moderating_variance When users supply a moderating variable
746+ # ' for an exogenous variance (i.e., double-headed arrow), whether to
747+ # ' exponentiate the variable prior to
648748# '
649749# ' @return
650750# ' An S3 object of class "dsem_control" that specifies detailed model settings,
@@ -673,7 +773,8 @@ function( nlminb_loops = 1,
673773 upper = Inf ,
674774 project_k = NULL ,
675775 suppress_nlminb_warnings = TRUE ,
676- stabilize_Q = FALSE ){
776+ stabilize_Q = FALSE ,
777+ logscale_moderating_variance = FALSE ){
677778
678779 gmrf_parameterization = match.arg(gmrf_parameterization )
679780 constant_variance = match.arg(constant_variance )
@@ -701,7 +802,8 @@ function( nlminb_loops = 1,
701802 upper = upper ,
702803 project_k = project_k ,
703804 suppress_nlminb_warnings = suppress_nlminb_warnings ,
704- stabilize_Q = stabilize_Q
805+ stabilize_Q = stabilize_Q ,
806+ logscale_moderating_variance = logscale_moderating_variance
705807 ), class = " dsem_control" )
706808}
707809
0 commit comments