├── .Rbuildignore ├── DESCRIPTION ├── LICENSE ├── Makefile ├── NAMESPACE ├── R ├── onyx.R ├── parser.OpenMx.R └── parser.lavaan.R ├── README.md ├── demo ├── 00Index ├── lavaanGrowth.R ├── lavaanHS.R ├── lavaanHSfit.R ├── lavaanMeans.R ├── lavaanMediation.R ├── lavaanMultigroup.R ├── lavaanPD.R ├── onyxProperties └── openmxFactor.R ├── inst ├── onyxR-demo.png └── openmx-factor.png ├── man └── onyx.Rd ├── tools └── install.R └── vignettes └── openmx.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: onyxR 2 | Type: Package 3 | Title: Interface to call Onyx from R 4 | Date: 2018-06-13 5 | Author: Andreas M. Brandmaier 6 | Maintainer: Andreas M. Brandmaier 7 | Description: Exports OpenMx and lavaan SEM specifications to Onyx XML and allows to directly call onyx from R. 8 | Imports: 9 | OpenMx, 10 | lavaan, 11 | png 12 | Version: 0.2 13 | License: LGPLv3 14 | Depends: 15 | R (>= 3.2.0) 16 | Suggests: 17 | knitr, 18 | rmarkdown 19 | VignetteBuilder: knitr 20 | RoxygenNote: 7.1.0 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this 92 | license 93 | document. 94 | 95 | c) For a Combined Work that displays copyright notices during 96 | execution, include the copyright notice for the Library among 97 | these notices, as well as a reference directing the user to the 98 | copies of the GNU GPL and this license document. 99 | 100 | d) Do one of the following: 101 | 102 | 0) Convey the Minimal Corresponding Source under the terms of this 103 | License, and the Corresponding Application Code in a form 104 | suitable for, and under terms that permit, the user to 105 | recombine or relink the Application with a modified version of 106 | the Linked Version to produce a modified Combined Work, in the 107 | manner specified by section 6 of the GNU GPL for conveying 108 | Corresponding Source. 109 | 110 | 1) Use a suitable shared library mechanism for linking with the 111 | Library. A suitable mechanism is one that (a) uses at run time 112 | a copy of the Library already present on the user's computer 113 | system, and (b) will operate properly with a modified version 114 | of the Library that is interface-compatible with the Linked 115 | Version. 116 | 117 | e) Provide Installation Information, but only if you would otherwise 118 | be required to provide such information under section 6 of the 119 | GNU GPL, and only to the extent that such information is 120 | necessary to install and execute a modified version of the 121 | Combined Work produced by recombining or relinking the 122 | Application with a modified version of the Linked Version. (If 123 | you use option 4d0, the Installation Information must accompany 124 | the Minimal Corresponding Source and Corresponding Application 125 | Code. If you use option 4d1, you must provide the Installation 126 | Information in the manner specified by section 6 of the GNU GPL 127 | for conveying Corresponding Source.) 128 | 129 | 5. Combined Libraries. 130 | 131 | You may place library facilities that are a work based on the 132 | Library side by side in a single library together with other library 133 | facilities that are not Applications and are not covered by this 134 | License, and convey such a combined library under terms of your 135 | choice, if you do both of the following: 136 | 137 | a) Accompany the combined library with a copy of the same work based 138 | on the Library, uncombined with any other library facilities, 139 | conveyed under the terms of this License. 140 | 141 | b) Give prominent notice with the combined library that part of it 142 | is a work based on the Library, and explaining where to find the 143 | accompanying uncombined form of the same work. 144 | 145 | 6. Revised Versions of the GNU Lesser General Public License. 146 | 147 | The Free Software Foundation may publish revised and/or new versions 148 | of the GNU Lesser General Public License from time to time. Such new 149 | versions will be similar in spirit to the present version, but may 150 | differ in detail to address new problems or concerns. 151 | 152 | Each version is given a distinguishing version number. If the 153 | Library as you received it specifies that a certain numbered version 154 | of the GNU Lesser General Public License "or any later version" 155 | applies to it, you have the option of following the terms and 156 | conditions either of that published version or of any later version 157 | published by the Free Software Foundation. If the Library as you 158 | received it does not specify a version number of the GNU Lesser 159 | General Public License, you may choose any version of the GNU Lesser 160 | General Public License ever published by the Free Software Foundation. 161 | 162 | If the Library as you received it specifies that a proxy can decide 163 | whether future versions of the GNU Lesser General Public License shall 164 | apply, that proxy's public statement of acceptance of any version is 165 | permanent authorization for you to choose that version for the 166 | Library. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKGNAME := $(shell sed -n "s/Package: *\([^ ]*\)/\1/p" DESCRIPTION) 2 | PKGVERS := $(shell sed -n "s/Version: *\([^ ]*\)/\1/p" DESCRIPTION) 3 | PKGSRC := $(shell basename `pwd`) 4 | 5 | all: rd readme check clean 6 | 7 | pkgdown: 8 | Rscript -e 'pkgdown::build_site()' 9 | 10 | rd: 11 | Rscript -e 'roxygen2::roxygenise(".")' 12 | 13 | readme: 14 | Rscript -e 'rmarkdown::render("README.rmd", "md_document")' 15 | 16 | build: 17 | cd ..;\ 18 | R CMD build $(PKGSRC) 19 | 20 | install: 21 | cd ..;\ 22 | R CMD INSTALL $(PKGNAME)_$(PKGVERS).tar.gz 23 | 24 | 25 | check: build 26 | cd ..;\ 27 | R CMD check --as-cran $(PKGNAME)_$(PKGVERS).tar.gz 28 | 29 | clean: 30 | cd ..;\ 31 | $(RM) -r $(PKGNAME).Rcheck/ 32 | 33 | vignette: 34 | Rscript -e 'devtools::build_vignettes()' 35 | 36 | winbuilder: 37 | lftp -c "open ftp://win-builder.r-project.org; put -O R-release/ ../$(PKGNAME)_$(PKGVERS).tar.gz" 38 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(onyx) 4 | -------------------------------------------------------------------------------- /R/onyx.R: -------------------------------------------------------------------------------- 1 | cacheEnv <- new.env() 2 | 3 | #' onyx 4 | #' 5 | #' Starts Onyx executable to display a path diagram from either an OpenMx or 6 | #' lavaan model representation. 7 | #' 8 | #' @param model An OpenMx or lavaan model. Defaults to NULL. If omitted, Onyx is started with an empty desktop, otherwise 9 | #' the specified model is rendered as path diagram 10 | #' @param onyyfile path to Onyx executable (onyx-***.jar). Defaults to NULL. If NULL, Onyx searches local directors, otherwise downloads Onyx from official repository. 11 | #' @param mode batch operation mode (experimental) 12 | #' 13 | #' @examples 14 | #' 15 | #' require(OpenMx) 16 | #' data(demoOneFactor) 17 | #' manifests <- names(demoOneFactor) 18 | #' latents <- c("G") 19 | #' factorModel <- mxModel("One Factor", 20 | #' type="RAM", 21 | #' manifestVars = manifests, 22 | #' latentVars = latents, 23 | #' mxPath(from=latents, to=manifests), 24 | #' mxPath(from=manifests, arrows=2), 25 | #' mxPath(from=latents, arrows=2, 26 | #' free=FALSE, values=1.0), 27 | #' mxData(cov(demoOneFactor), type="cov", 28 | #' numObs=500)) 29 | #' fit <- mxRun(factorModel) 30 | #' \dontrun{ 31 | #' onyx(fit) 32 | #' } 33 | #' 34 | #' @export 35 | 36 | onyx<-function(model=NULL, onyxfile=NULL, batch=NULL) 37 | { 38 | 39 | # attempt to retrieve the onyxfile from the package's cache environment 40 | # return NULL if nothing stored in cacheEnv 41 | if (is.null(onyxfile)) { 42 | onyxfile <- get0("onyxfile", envir=cacheEnv, ifnotfound=NULL) 43 | } 44 | 45 | # is there a local onyx file? 46 | if (is.null(onyxfile)) { 47 | files <- list.files() 48 | hasonyx <- sapply(files, function(x){length(grep("onyx.*jar",x,value=FALSE))>0},simplify = TRUE) 49 | if (sum(hasonyx) > 0) { 50 | if (sum(hasonyx)>2) { 51 | warning("Found several local onyx executables!") 52 | } 53 | idx <- which(hasonyx)[1] 54 | onyxfile <- names(hasonyx)[idx] 55 | } 56 | } 57 | 58 | # the following part tries to find onyx.jar from command line arguments. 59 | # May be removed in future 60 | #if (is.null(onyxfile)) { 61 | # options <- commandArgs(trailingOnly = F) 62 | # filearg <- "--file=" 63 | # 64 | # if (length(grep(filearg, options))!=0) { 65 | # onyxfile <- sub(filearg, "", options[grep(filearg, options)]) 66 | # } 67 | 68 | #} 69 | 70 | # if there is no Onyx jar, download it from official repository 71 | if (is.null(onyxfile)) { 72 | warning("Could not find a local Onyx version. Trying to download Onyx from the official repository.") 73 | onyxfile <- tempfile(pattern="onyx_", 74 | fileext=".jar") 75 | download.file(url="https://onyx-sem.com/wp-content/uploads/2021/08/onyx-stable.jar", 76 | destfile = onyxfile, mode="wb") 77 | if (!file.exists(onyxfile)) { 78 | stop("Download failed!") 79 | } 80 | # store the file reference in the package environment 81 | assign("onyxfile",onyxfile, envir=cacheEnv) 82 | } 83 | 84 | # catches if onyx.jar is not available 85 | 86 | if (is.null(onyxfile)) { 87 | stop("Could not find a valid Onyx executable (e.g., onyx.jar)!"+ 88 | "Please put Onyx in the local directory, specify a path to the file,"+ 89 | " or download Onyx from http:\\\\onyx.brandmaier.de.") 90 | } 91 | 92 | if (!file.exists(onyxfile)) { 93 | stop(paste("Onyx executable does not exist: ",onyxfile)) 94 | } 95 | 96 | 97 | 98 | # stores onyx or lavaan parsed model in temp file and calls onyx with this file, or empty call. 99 | if (!is.null(model)) { 100 | 101 | if (inherits(model,"MxModel") || inherits(model,"MxRAMModel")) { 102 | rep <- parser.OpenMx(model,"onyxR") 103 | } else if (inherits(model,"lavaan")) { 104 | rep <- parser.lavaan(model,"onyxR") 105 | } else { 106 | rep <- parser.lavaan(model,"onyxR", string.representations=TRUE) 107 | } 108 | 109 | xmlhead <- "" 110 | rep <- paste(xmlhead, rep) 111 | 112 | fn <- tempfile() 113 | cat(rep, file=fn) 114 | 115 | if (is.null(batch)) { 116 | cmd <- paste("java","-cp",onyxfile,"Master","--input-file ",fn) 117 | } else { 118 | outf <- tempfile(fileext = ".png") 119 | cmd <- paste("java -cp",onyxfile," Master --batch --output-filetype png --input-file",fn," --output-file",outf) 120 | 121 | } 122 | 123 | } else { 124 | cmd <- paste("java","-cp",onyxfile,"Master") 125 | } 126 | 127 | # system call depends on operating system 128 | sysname <- Sys.info()[['sysname']] 129 | if (sysname=="Windows") { 130 | # OS that need to spawn a new shell explicitly should go here: 131 | system("cmd.exe", input = cmd, wait = FALSE) 132 | } else { 133 | # OS that spawn a new shell for each subprocess should go here 134 | system(cmd,wait=FALSE) 135 | } 136 | 137 | if (!is.null(batch)) { 138 | rpng <- png::readPNG(outf, native=TRUE) 139 | plot(c(0,100),c(0,100),type="n",xlab="",ylab="",axes = FALSE) 140 | rasterImage(rpng,0,0,100,100) 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /R/parser.OpenMx.R: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # converter of OpenMx model into Onyxml 4 | # 5 | 6 | parser.OpenMx <- function(model, name="unnamed", submodel=FALSE, id.offset=0) 7 | { 8 | 9 | # generate Onyx file header 10 | if (!submodel) { 11 | xml <- paste( "\n\n",sep=""); 12 | } else { 13 | xml <- "" 14 | } 15 | 16 | has.submodels <- length(model@submodels)!=0 17 | 18 | # check for submodels 19 | if (has.submodels) { 20 | 21 | for (i in 1:length(model@submodels)) { 22 | smodel <- model@submodels[[i]] 23 | xml <- paste(xml, parser.OpenMx(smodel, paste0(name,"_",i),submodel=TRUE, id.offset=id.offset)) 24 | id.offset <- id.offset + length(smodel@manifestVars)+length(smodel@latentVars)+1 25 | } 26 | 27 | } else { 28 | 29 | 30 | 31 | # check for RAM matrices 32 | checkRAM = !is.null(model$matrices$A) & !is.null(model$matrices$S) & !is.null(model$matrices$F) 33 | if (!checkRAM & !has.submodels) { 34 | stop("Only RAM models supported!") 35 | } 36 | 37 | 38 | 39 | # extract latents and manifests 40 | manifests <- model@manifestVars 41 | latents <- model@latentVars 42 | variables <- c(manifests,latents) 43 | 44 | # mean structure ? 45 | triangle <- FALSE; 46 | triangleId <- length(variables) + id.offset 47 | triangleXml <- paste("\n",sep=""); 48 | 49 | 50 | 51 | xml <- paste (xml, "\n",sep="") 52 | 53 | # construct all manifest nodes 54 | if (length(manifests) > 0) { 55 | for (i in 1:length(manifests)) { 56 | xml <- paste(xml,"\n",sep=""); 57 | } 58 | } 59 | xml <- paste (xml, "\n",sep="") 60 | 61 | # construct all latent nodes 62 | if (length(latents) > 0) { 63 | for (i in 1:length(latents)) { 64 | xml <- paste(xml,"\n",sep=""); 65 | }} 66 | 67 | 68 | xml <- paste (xml, "\n",sep="") 69 | 70 | # construct all directed edges 71 | A <- model@matrices$A 72 | lenA <- dim(A)[1] 73 | for (i in 1:lenA) 74 | { 75 | for (j in 1:lenA) 76 | { 77 | dnames <- dimnames(model$A@labels)[[1]] 78 | sourceNodeId = id.offset + which(variables==dnames[j])-1; 79 | targetNodeId = id.offset + which(variables==dnames[i])-1; 80 | 81 | fixed <- !A@free[i,j]; 82 | value <- A@values[i,j]; 83 | 84 | if ((value==0) && (fixed)) next; 85 | 86 | 87 | label <- A@labels[i,j]; 88 | 89 | #definitionVariable <- is.character(value) && !is.na(label); 90 | definitionVariable <- !is.na(label) && !(label %in% manifests) 91 | definitionVariable <- FALSE 92 | 93 | pString <- ""; 94 | if (!is.na(label)) { 95 | pString <- paste("parameterName=\"",label,"\"",sep=""); 96 | 97 | } 98 | 99 | dString <- ""; 100 | if (definitionVariable) { 101 | dString <- "definitionVariable=\"true\""; 102 | } 103 | 104 | xml <- paste(xml,"\n", sep="") 105 | } 106 | } 107 | 108 | xml <- paste (xml, "\n",sep="") 109 | 110 | 111 | # construct all undirected edges 112 | S<- model@matrices$S 113 | lenS <- dim(S)[1] 114 | for (i in 1:lenS) 115 | { 116 | for (j in i:lenS) 117 | { 118 | 119 | dnames <- dimnames(model$S@labels)[[1]] 120 | sourceNodeId = id.offset + which(variables==dnames[i])-1; 121 | targetNodeId = id.offset + which(variables==dnames[j])-1; 122 | 123 | fixed <- !S@free[i,j]; 124 | value <- S@values[i,j]; 125 | label <- S@labels[i,j]; 126 | 127 | 128 | if ((value==0) && (fixed)) next; 129 | 130 | pString <- ""; 131 | if (!is.na(label)) { 132 | pString <- paste("parameterName=\"",label,"\"",sep=""); 133 | } 134 | 135 | 136 | xml <- paste(xml,"\n", sep="") 137 | } 138 | } 139 | 140 | xml <- paste (xml, "\n",sep="") 141 | 142 | 143 | if (!is.null(model$M)) { 144 | # construct mean edges (really mean...) 145 | M <- model@matrices$M 146 | lenM <- dim(M)[2] 147 | 148 | for (j in 1:lenM) 149 | { 150 | dnames <- dimnames(model$M@labels)[[2]] 151 | sourceNodeId = triangleId; 152 | targetNodeId = id.offset + which(variables==dnames[j])-1; 153 | 154 | fixed <- !M@free[1,j]; 155 | value <- M@values[1,j]; 156 | 157 | #cat(j, fixed, value, label, "\n") 158 | 159 | if ((value==0) && (fixed)) next; 160 | 161 | 162 | if (!triangle) { 163 | xml <- paste(xml, triangleXml); 164 | triangle <- TRUE; 165 | } 166 | 167 | label <- M@labels[1,j]; 168 | 169 | parampart <- "" 170 | if (!is.na(label)) { 171 | parampart <- paste("\" parameterName=\"",label,sep=""); 172 | } 173 | 174 | xml <- paste(xml,"\n", sep="") 175 | } 176 | } 177 | 178 | 179 | } 180 | 181 | if (!submodel) 182 | xml <- paste(xml, "\n\n\n" ,sep=""); 183 | 184 | return(xml); 185 | } 186 | 187 | 188 | #variables <- ls(); 189 | #for (i in 1:length(variables)) 190 | #{ 191 | # if (class(eval(parse(text=variables[i]))) == "MxRAMModel"){# 192 | # cat( parser(eval(parse(text=variables[i])), variables[i]) ); 193 | # cat("\n") 194 | # } 195 | #}# -------------------------------------------------------------------------------- /R/parser.lavaan.R: -------------------------------------------------------------------------------- 1 | 2 | isknown <- function(known, key, group = FALSE) 3 | { 4 | if ((!group)) 5 | return(key %in% names(known)) 6 | else 7 | return (paste(key, "_", group, sep = "") %in% names(known)) 8 | } 9 | 10 | setknown <- function(known, key, value, group = FALSE) 11 | { 12 | if ((!group)) 13 | known[[key]] <- value 14 | else 15 | known[[paste(key, "_", group, sep = "")]] <- value 16 | 17 | return(known) 18 | } 19 | 20 | getknown <- function(known, key, group = FALSE) 21 | { 22 | if ((!group)) 23 | return(known[[key]]) 24 | else 25 | return(known[[paste(key, "_", group, sep = "")]]) 26 | } 27 | 28 | parser.lavaan <- 29 | function(model, 30 | name = "", 31 | string.representations = FALSE) { 32 | 33 | if (string.representations) { 34 | lstr <- lavaan::lavaanify(model, auto.var = TRUE) 35 | } else { 36 | lstr <- lavaan::parameterTable(model) 37 | } 38 | xml <- 39 | paste( 40 | "\n\n", 43 | sep = "" 44 | ) 45 | 46 | 47 | num.groups <- length(unique(lstr$group)) 48 | if (0 %in% unique(lstr$group)) { 49 | num.groups <- num.groups - 1 50 | } 51 | 52 | multigroup <- num.groups > 1 53 | 54 | # reorder parameter table by operator 55 | if (sum(lstr$op == "=~") > 0) { 56 | reorder.ids <- c(which(lstr$op == "=="), 57 | which(lstr$op == "=~"), 58 | which(lstr$op != "=~" & lstr$op != "==") ) 59 | lstr <- lstr[reorder.ids,] 60 | } 61 | 62 | known <- list() 63 | 64 | mean.idx <- NULL 65 | 66 | fixed_parms <- list() 67 | mapped_parms <- list() 68 | 69 | idx <- 0 70 | for (i in 1:dim(lstr)[1]) { 71 | left <- lstr$lhs[i] 72 | op <- lstr$op[i] 73 | right <- lstr$rhs[i] 74 | free <- lstr$free[i] 75 | ustart <- lstr$ustart[i] 76 | latentleft <- FALSE 77 | latentright <- FALSE 78 | grp <- lstr$group[i] 79 | plabel <- lstr$plabel[i] 80 | label <- lstr$label[i] 81 | 82 | if ("est" %in% names(lstr)) { 83 | est <- lstr$est[i] 84 | } else { 85 | est <- NULL 86 | } 87 | 88 | if (!is.null(fixed_parms[[plabel]])) { 89 | free <- 0 90 | ustart <- fixed_parms[[plabel]] 91 | } 92 | 93 | if (!is.null(mapped_parms[[plabel]])) { 94 | mapped_plabel <- mapped_parms[[plabel]] 95 | plabel <- mapped_plabel 96 | label <- lstr$label[lstr$plabel==mapped_plabel] 97 | } 98 | 99 | if (op == "=~") { 100 | latentleft <- TRUE 101 | latentright <- FALSE 102 | } 103 | 104 | if (op == ":=") 105 | next 106 | 107 | if (op == "==") { 108 | suppressWarnings({ 109 | numeric_right <- as.numeric(right) 110 | }) 111 | if ((is.na(numeric_right)) || (right != as.character(numeric_right))) { 112 | mapped_parms[[left]] <- right 113 | next 114 | } 115 | # if this is a numeric equality constraint, save it for later 116 | fixed_parms[[left]] <- numeric_right 117 | 118 | next 119 | } 120 | 121 | meanpath <- FALSE 122 | if (op == "~1") { 123 | meanpath <- TRUE 124 | } 125 | 126 | if (isknown(known, 127 | key = left, 128 | group = ifelse(multigroup, grp, FALSE))) { 129 | lid <- 130 | getknown(known, left, ifelse(multigroup, grp, FALSE))#known[[left]] 131 | 132 | } else { 133 | mg <- "" 134 | if (multigroup) { 135 | mg <- paste("groupValue=\"", grp, "\"", sep = "") 136 | } 137 | 138 | xml <- 139 | paste( 140 | xml, 141 | "\n", 150 | sep = "" 151 | ) 152 | 153 | #known[[left]] <- idx 154 | known <- 155 | setknown( 156 | known, 157 | key = left, 158 | value = idx, 159 | group = ifelse(multigroup, grp, FALSE) 160 | ) 161 | lid <- idx 162 | idx <- idx + 1 163 | } 164 | 165 | if (free == 0) { 166 | fixed <- "true" 167 | pString <- "" 168 | } 169 | else { 170 | fixed <- "false" 171 | pname <- plabel 172 | if (label != "") { 173 | pname <- label 174 | } 175 | pString <- paste("parameterName=\"", pname, "\"", sep = "") 176 | } 177 | 178 | aString <- "arrowHead=\"1\" "#definitionVariable=\"false\"" 179 | 180 | value <- 1 181 | if (!is.na(ustart)) 182 | value <- ustart 183 | 184 | # some postprocessing. Onyx does not like fixed path with zero values 185 | if ((op == "~~")) { 186 | if (value == 0 && free == 0) { 187 | # value <- 1 188 | next 189 | # skip (co)variances fixed to zero 190 | } 191 | } 192 | 193 | if (!is.null(est)) { 194 | value <- est 195 | } 196 | 197 | 198 | if (meanpath) { 199 | if (is.null(mean.idx)) { 200 | mean.idx <- idx 201 | triangleXml <- 202 | paste("\n", 205 | sep = "") 206 | 207 | xml <- paste(xml, triangleXml) 208 | idx <- idx + 1 209 | } 210 | 211 | xml <- 212 | paste( 213 | xml, 214 | "\n", 227 | sep = "" 228 | ) 229 | 230 | 231 | } else { 232 | if (isknown(known, 233 | key = right, 234 | group = ifelse(multigroup, grp, FALSE))) { 235 | rid <- 236 | getknown(known, 237 | key = right, 238 | group = ifelse(multigroup, grp, FALSE)) #known[[right]] 239 | } else { 240 | mg <- "" 241 | if (multigroup) { 242 | mg <- paste("groupValue=\"", grp, "\"", sep = "") 243 | } 244 | 245 | xml <- 246 | paste( 247 | xml, 248 | "\n", 257 | sep = "" 258 | ) 259 | 260 | # known[[right]] <- idx 261 | known <- 262 | setknown( 263 | known, 264 | key = right, 265 | value = idx, 266 | group = ifelse(multigroup, grp, FALSE) 267 | ) 268 | rid <- idx 269 | idx <- idx + 1 270 | } 271 | 272 | 273 | 274 | if (op == "~~") 275 | doubleheaded <- "true" 276 | else 277 | doubleheaded <- "false" 278 | 279 | 280 | if (op == "~") { 281 | temp <- lid 282 | lid <- rid 283 | rid <- temp 284 | } 285 | 286 | 287 | 288 | 289 | xml <- 290 | paste( 291 | xml, 292 | "\n", 307 | sep = "" 308 | ) 309 | 310 | } 311 | } 312 | 313 | # collect all information on variables 314 | 315 | 316 | 317 | xml <- paste(xml, "\n\n\n" , sep = "") 318 | 319 | 320 | return(xml) 321 | 322 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # onyxR 2 | 3 | An R package for calling Onyx GUI from R when using OpenMx or lavaan packages for Structural Equation Modelling. 4 | The package provides a simple onyx() function that takes either an OpenMx model or a lavaan model (either a 5 | fitted lavaan model or string specification). If a local onyx executable is available, its path can be passed to the function. 6 | Otherwise, it will attempt to download a copy of Onyx from the official repository. A recent version of 7 | JAVA (version 1.6+) is required to be installed on the system. 8 | 9 | ## Install 10 | 11 | To install the onyxR package directly from GitHub, copy the following line into R: 12 | ```{r, eval=FALSE} 13 | source('https://raw.githubusercontent.com/brandmaier/onyxR/master/tools/install.R') 14 | ``` 15 | 16 | ## Usage 17 | 18 | Using onyxR is as simple as that. In lavaan 19 | 20 | ````{r, eval=FALSE} 21 | HS.model <- ' visual =~ x1 + x2 + x3 22 | textual =~ x4 + x5 + x6 23 | speed =~ x7 + x8 + x9 ' 24 | 25 | fit <- cfa(HS.model, data = HolzingerSwineford1939) 26 | 27 | summary(fit, fit.measures = TRUE) 28 | 29 | onyx(fit) 30 | ```` 31 | 32 | Alternatively, we can use onyxR to generate a path diagram for an OpenMx RAM-type model: 33 | 34 | ````{r, eval=FALSE} 35 | data(demoOneFactor) 36 | manifests <- names(demoOneFactor) 37 | latents <- c("G") 38 | factorModel <- mxModel("One Factor", 39 | type="RAM", 40 | manifestVars = manifests, 41 | latentVars = latents, 42 | mxPath(from=latents, to=manifests), 43 | mxPath(from=manifests, arrows=2), 44 | mxPath(from=latents, arrows=2, 45 | free=FALSE, values=1.0), 46 | mxData(cov(demoOneFactor), type="cov", 47 | numObs=500)) 48 | fit <- mxRun(factorModel) 49 | 50 | onyx(fit) 51 | ```` 52 | 53 | ![openmx](https://github.com/brandmaier/onyxR/blob/master/inst/openmx-factor.png?raw=true) 54 | 55 | ## Demo 56 | 57 | In a simple demo, we have wrapped the above example. We define the HS model with three independent factors in lavaan and pass this to Onyx to display the path diagram. 58 | ````{r, eval=FALSE} 59 | require(onyxR) 60 | demo(lavaanHS) 61 | ``` 62 | 63 | 64 | -------------------------------------------------------------------------------- /demo/00Index: -------------------------------------------------------------------------------- 1 | lavaanHS HS model specified in lavaan string representation 2 | lavvanHSfit HS model specified and fitted in lavaan 3 | lavaanPD lavaan political democracy dataset 4 | openmxFactor Single factor model in OpenMx 5 | lavaanMediation Mediation model in lavaan 6 | lavaanGrowth Growth curve with lavaan 7 | lavaanMultigroup Multigroup with lavaan 8 | -------------------------------------------------------------------------------- /demo/lavaanGrowth.R: -------------------------------------------------------------------------------- 1 | # 2 | # Growth Curve model 3 | # --------------- 4 | # Script by Yves Rosseel 5 | # Downloaded from: http://lavaan.ugent.be/tutorial/growth.html 6 | # on 2017/12/16 7 | # 8 | 9 | model <- ' i =~ 1*t1 + 1*t2 + 1*t3 + 1*t4 10 | s =~ 0*t1 + 1*t2 + 2*t3 + 3*t4 ' 11 | fit <- lavaan::growth(model, data=lavaan::Demo.growth) 12 | summary(fit) 13 | 14 | onyx(fit) -------------------------------------------------------------------------------- /demo/lavaanHS.R: -------------------------------------------------------------------------------- 1 | model <- 'visual =~ x1 + x2 + x3 2 | textual =~ x4 + x5 + x6 3 | speed =~ x7 + x8 + x9' 4 | 5 | onyx(model) 6 | -------------------------------------------------------------------------------- /demo/lavaanHSfit.R: -------------------------------------------------------------------------------- 1 | require("lavaan") 2 | 3 | HS.model <- ' visual =~ x1 + x2 + x3 4 | textual =~ x4 + x5 + x6 5 | speed =~ x7 + x8 + x9 ' 6 | 7 | fit <- cfa(HS.model, data = HolzingerSwineford1939) 8 | 9 | summary(fit, fit.measures = TRUE) 10 | 11 | require("onyxR") 12 | 13 | onyx(fit) -------------------------------------------------------------------------------- /demo/lavaanMeans.R: -------------------------------------------------------------------------------- 1 | # 2 | # Factor model with means 3 | # --------------- 4 | # Script by Yves Rosseel 5 | # Downloaded from: http://lavaan.ugent.be/tutorial/means.html 6 | # on 2017/12/16 7 | # 8 | 9 | require(onyxR) 10 | require(lavaan) 11 | 12 | HS.model <- ' 13 | # three-factor model 14 | visual =~ x1 + x2 + x3 15 | textual =~ x4 + x5 + x6 16 | speed =~ x7 + x8 + x9 17 | # intercepts 18 | x1 ~ 1 19 | x2 ~ 1 20 | x3 ~ 1 21 | x4 ~ 1 22 | x5 ~ 1 23 | x6 ~ 1 24 | x7 ~ 1 25 | x8 ~ 1 26 | x9 ~ 1 27 | ' 28 | 29 | fit <- cfa(HS.model, 30 | data = HolzingerSwineford1939, 31 | meanstructure = TRUE) 32 | summary(fit) 33 | 34 | 35 | onyx(fit) -------------------------------------------------------------------------------- /demo/lavaanMediation.R: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Mediation model 4 | # --------------- 5 | # Script by Yves Rosseel 6 | # Downloaded from: http://lavaan.ugent.be/tutorial/mediation.html 7 | # on 2017/12/16 8 | # 9 | 10 | require("onyxR") 11 | 12 | set.seed(1234) 13 | X <- rnorm(100) 14 | M <- 0.5*X + rnorm(100) 15 | Y <- 0.7*M + rnorm(100) 16 | Data <- data.frame(X = X, Y = Y, M = M) 17 | model <- ' # direct effect 18 | Y ~ c*X 19 | # mediator 20 | M ~ a*X 21 | Y ~ b*M 22 | # indirect effect (a*b) 23 | ab := a*b 24 | # total effect 25 | total := c + (a*b) 26 | ' 27 | fit <- sem(model, data = Data) 28 | summary(fit) 29 | 30 | 31 | onyx(fit) -------------------------------------------------------------------------------- /demo/lavaanMultigroup.R: -------------------------------------------------------------------------------- 1 | # 2 | # Multi group CFA 3 | # --------------- 4 | # Script by Yves Rosseel 5 | # Downloaded from: http://lavaan.ugent.be/tutorial/groups.html 6 | # on 2017/12/16 7 | # 8 | require("lavaan") 9 | 10 | HS.model <- ' visual =~ x1 + x2 + x3 11 | textual =~ x4 + x5 + x6 12 | speed =~ x7 + x8 + x9 ' 13 | fit <- cfa(HS.model, 14 | data = HolzingerSwineford1939, 15 | group = "school") 16 | summary(fit) 17 | 18 | 19 | onyx(fit) -------------------------------------------------------------------------------- /demo/lavaanPD.R: -------------------------------------------------------------------------------- 1 | model <- ' 2 | # latent variable definitions 3 | ind60 =~ x1 + x2 + x3 4 | dem60 =~ y1 + y2 + y3 + y4 5 | dem65 =~ y5 + y6 + y7 + y8 6 | # regressions 7 | dem60 ~ ind60 8 | dem65 ~ ind60 + dem60 9 | # residual (co)variances 10 | y1 ~~ y5 11 | y2 ~~ y4 + y6 12 | y3 ~~ y7 13 | y4 ~~ y8 14 | y6 ~~ y8 15 | ' 16 | 17 | onyx(model) 18 | 19 | -------------------------------------------------------------------------------- /demo/onyxProperties: -------------------------------------------------------------------------------- 1 | #---No Comment--- 2 | #Wed Feb 01 15:30:40 CET 2017 3 | CurrentTipOfTheDay=3 4 | ShowTipOfTheDay=true 5 | RPath= 6 | BackgroundImage= 7 | DefaultWorkingPath= 8 | CheckForUpdates=true 9 | -------------------------------------------------------------------------------- /demo/openmxFactor.R: -------------------------------------------------------------------------------- 1 | require(OpenMx) 2 | data(demoOneFactor) 3 | manifests <- names(demoOneFactor) 4 | latents <- c("G") 5 | factorModel <- mxModel("One Factor", 6 | type="RAM", 7 | manifestVars = manifests, 8 | latentVars = latents, 9 | mxPath(from=latents, to=manifests), 10 | mxPath(from=manifests, arrows=2), 11 | mxPath(from=latents, arrows=2, 12 | free=FALSE, values=1.0), 13 | mxData(cov(demoOneFactor), type="cov", 14 | numObs=500)) 15 | fitted.model <- mxRun(factorModel) 16 | 17 | onyx(fitted.model) -------------------------------------------------------------------------------- /inst/onyxR-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandmaier/onyxR/702a8772991bba5c08bbc3335e2df81778205a72/inst/onyxR-demo.png -------------------------------------------------------------------------------- /inst/openmx-factor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandmaier/onyxR/702a8772991bba5c08bbc3335e2df81778205a72/inst/openmx-factor.png -------------------------------------------------------------------------------- /man/onyx.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/onyx.R 3 | \name{onyx} 4 | \alias{onyx} 5 | \title{onyx} 6 | \usage{ 7 | onyx(model = NULL, onyxfile = NULL, batch = NULL) 8 | } 9 | \arguments{ 10 | \item{model}{An OpenMx or lavaan model. Defaults to NULL. If omitted, Onyx is started with an empty desktop, otherwise 11 | the specified model is rendered as path diagram} 12 | 13 | \item{onyyfile}{path to Onyx executable (onyx-***.jar). Defaults to NULL. If NULL, Onyx searches local directors, otherwise downloads Onyx from official repository.} 14 | 15 | \item{mode}{batch operation mode (experimental)} 16 | } 17 | \description{ 18 | Starts Onyx executable to display a path diagram from either an OpenMx or 19 | lavaan model representation. 20 | } 21 | \examples{ 22 | 23 | require(OpenMx) 24 | data(demoOneFactor) 25 | manifests <- names(demoOneFactor) 26 | latents <- c("G") 27 | factorModel <- mxModel("One Factor", 28 | type="RAM", 29 | manifestVars = manifests, 30 | latentVars = latents, 31 | mxPath(from=latents, to=manifests), 32 | mxPath(from=manifests, arrows=2), 33 | mxPath(from=latents, arrows=2, 34 | free=FALSE, values=1.0), 35 | mxData(cov(demoOneFactor), type="cov", 36 | numObs=500)) 37 | fit <- mxRun(factorModel) 38 | \dontrun{ 39 | onyx(fit) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tools/install.R: -------------------------------------------------------------------------------- 1 | #make sure that devtools are installed 2 | if (!requireNamespace('devtools')){ 3 | install.packages('devtools') 4 | } 5 | devtools::install_git('https://github.com/brandmaier/onyxR') 6 | 7 | -------------------------------------------------------------------------------- /vignettes/openmx.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Path diagrams from OpenMx" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Path diagrams form OpenMx} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | \usepackage[utf8]{inputenc} 8 | --- 9 | 10 | 11 | ````{r, eval=FALSE} 12 | require("onyxR") 13 | data(demoOneFactor) 14 | manifests <- names(demoOneFactor) 15 | latents <- c("G") 16 | factorModel <- mxModel("One Factor", 17 | type="RAM", 18 | manifestVars = manifests, 19 | latentVars = latents, 20 | mxPath(from=latents, to=manifests), 21 | mxPath(from=manifests, arrows=2), 22 | mxPath(from=latents, arrows=2, 23 | free=FALSE, values=1.0), 24 | mxData(cov(demoOneFactor), type="cov", 25 | numObs=500)) 26 | fit <- mxRun(factorModel) 27 | 28 | onyx(fit) 29 | ``` 30 | 31 | ![openmx](https://github.com/brandmaier/onyxR/blob/master/inst/openmx-factor.png?raw=true) 32 | --------------------------------------------------------------------------------