-
Notifications
You must be signed in to change notification settings - Fork 0
2.2 Editing asn MCMSEM Model
We try to cover major model changes in the MCMmodel()
function, but we cannot possibly account for all possible models you might want to run using MCMSEM.
Therefore we have included the MCMedit()
function to allow you to make detailed changes to an MCM model.
The general syntax of MCMedit()
is the following: adjusted_model <- MCMedit(base_model, pointer, name, value)
where:
-
base_model
is the base model to be changed -
pointer
points to the location of values need to be changed, i.e. one of the matrices (A, Fm, S, Sk, or K), bounds, or starts -
name
represents the name or coordinate(s) of values to be changed -
value
the new value, this can be a new name (to create a new free parameter), a fixed value, or 0 (to disable a parameter for example)
This will return the edited model adjusted model
, which is another MCM model object with the edits applied.
For the purposes of this wiki we will start with the following base model:
my_model <- MCMmodel(data_summary, n_latent=2, scale_data=FALSE)
Our aim for this section is to:
- Drop the paths from
f1
tox4
andx5
- Drop the paths from
f2
tox4
andx5
- Drop the causal paths from
x1
tox5
(both ways)
So our goal for this section is to end up with the following model:
There are a number of ways to do this.
First parameters can be dropped based on their coordinates in their respective matrix. All a (factorloadings) and b (causal path) parameters reside in the A
matrix.
In order to change parameters by their coordinates we will first need to know their position in the matrix. Therefore we will start by displaying the A
matrix:
print(my_model, matrix="A")
Which prints the following:
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "0" "0" "0" "0" "0" "0" "0"
[2,] "0" "0" "0" "0" "0" "0" "0"
[3,] "a1" "0" "0" "b2_1" "b3_1" "b4_1" "b5_1"
[4,] "a1" "a2" "b1_2" "0" "b3_2" "b4_2" "b5_2"
[5,] "a1" "a2" "b1_3" "b2_3" "0" "b4_3" "b5_3"
[6,] "a1" "a2" "b1_4" "b2_4" "b3_4" "0" "b5_4"
[7,] "a1" "a2" "b1_5" "b2_5" "b3_5" "b4_5" "0"
From this matrix we can observe that a1
paths we want to drop are located at [6, 1]
(path f1 -> x4
) and [7, 1]
(path f1 -> x5
).
Dropping a parameter is done by setting its value to 0. So by running the followig
my_model2 <- MCMedit(my_model, # The base model
pointer = "A", # We are changing values in the A matrix
name = c(6, 1), # We want to change the values at coordinate [6, 1]
value = 0) # We want to set that value to 0 (i.e. remove the parameter)
my_model2 <- MCMedit(my_model2, pointer="A", name=c(7, 1), value=0) # Same for [7, 1]
There are more a2
paths we need to drop, namely those located at [3, 2]
(path f2 -> x1
), [4, 2]
(path f2 -> x2
) and [5, 2]
(path f2 -> x3
).
This can be done in a similar fashion, but you can also provide multiple coordinates at once by using a 2-element list with x-coordinates, and y-coordinates:
coords <- list( c(3, 4, 5),
c(2, 2, 2) )
my_model2 <- MCMedit(my_model, pointer="A", name=coords, value=0)
Turning your model into a uni-directional model, for instance, is likely also easiest via coordinates, for example, let's say we want to drop all b
parameters in the upper diagonal (i.e. all "reverse" causal paths):
nvars <- 5
n_latent <- 2
coords <- expand.grid(x=1:nvars+n_latent, y=1:nvars+n_latent)[upper.tri(matrix(0, nvars, nvars), diag=FALSE), ]
my_model2 <- MCMedit(my_model, pointer="A", name=list(coords$x, coords$y), value=0)
To verify our edits worked we can run print(my_model2, matrix="A")
again, which will now return the following:
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "0" "0" "0" "0" "0" "0" "0"
[2,] "0" "0" "0" "0" "0" "0" "0"
[3,] "a1" "0" "0" "0" "0" "0" "0"
[4,] "a1" "a2" "b1_2" "0" "0" "0" "0"
[5,] "a1" "a2" "b1_3" "b2_3" "0" "0" "0"
[6,] "a1" "a2" "b1_4" "b2_4" "b3_4" "0" "0"
[7,] "a1" "a2" "b1_5" "b2_5" "b3_5" "b4_5" "0"
Lastly, parameters can be dropped using their name. We will use this method to drop path x1 -> x5
and x5 -> x1
, which are labelled b1_5
and b5_1
respectively.
my_model2 <- MCMedit(my_model, pointer="A", name=c("b1_5", "b5_1"), value=0)
To verify our edits worked we can run print(my_model2, matrix="A")
again, which will now return the following:
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "0" "0" "0" "0" "0" "0" "0"
[2,] "0" "0" "0" "0" "0" "0" "0"
[3,] "a1" "0" "0" "b2_1" "b3_1" "b4_1" "0"
[4,] "a1" "a2" "b1_2" "0" "b3_2" "b4_2" "b5_2"
[5,] "a1" "a2" "b1_3" "b2_3" "0" "b4_3" "b5_3"
[6,] "a1" "a2" "b1_4" "b2_4" "b3_4" "0" "b5_4"
[7,] "a1" "a2" "0" "b2_5" "b3_5" "b4_5" "0"
Confirming our edits are properly applied
In this section we will continue from the previous section we will do the following:
- Split the
a1
parameter intoa1_1
,a1_2
anda1_3
so the paths fromf1
tox1
,x2
andx3
are free. - Split the
a2
parameter intoa2_4
anda2_5
so the paths fromf2
tox4
andx5
are free.
So our goal for this section is to end up with the following model:
Note: Splitting of factor loadings is typically more efficiently achieved by adding
contrained_a=FALSE
toMCMmodel()
. However, for the purposes of this demonstration we will do it manually.
This works similar to dropping parameters, only now instead of providing the value 0, we provide the new parameter names.
Looking at our A matrix again print(my_model, matrix="A")
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "0" "0" "0" "0" "0" "0" "0"
[2,] "0" "0" "0" "0" "0" "0" "0"
[3,] "a1" "0" "0" "b2_1" "b3_1" "b4_1" "0"
[4,] "a1" "a2" "b1_2" "0" "b3_2" "b4_2" "b5_2"
[5,] "a1" "a2" "b1_3" "b2_3" "0" "b4_3" "b5_3"
[6,] "a1" "a2" "b1_4" "b2_4" "b3_4" "0" "b5_4"
[7,] "a1" "a2" "0" "b2_5" "b3_5" "b4_5" "0"
We can see that the coordinates of a1
are [3,1]
, [4,1]
and [5,1]
. So using similar semantics as previously described:
coords <- list( c(3, 4, 5), c(2, 2, 2) ) # We start by making each factor load on 1 variable again
my_model_split_A <- MCMedit(my_model, pointer="A", name=coords, value=0)
coords <- list( c(3, 4, 5),
c(1, 1, 1) )
new_names <- c("a1_1", "a1_2", "a1_3")
my_model_split_A <- MCMedit(my_model_split_A, pointer="A", name=coords, value=new_names)
Typically splitting your parameter by its name is the easier solution. Remember we have already dropped 3 of the 5 paths of a2
previously, so two paths remain (those to x4
and x5
). Thus we will have to provide two new parameter names in this case.
new_names <- c("a2_4", "a2_5")
my_model_split_A <- MCMedit(my_model_split_A, pointer="A", name="a2", value=new_names)
Again we can verify these changes worked by running print(my_model_split_A, matrix="A")
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "0" "0" "0" "0" "0" "0" "0"
[2,] "0" "0" "0" "0" "0" "0" "0"
[3,] "a1_1" "0" "0" "b2_1" "b3_1" "b4_1" "0"
[4,] "a1_2" "0" "b1_2" "0" "b3_2" "b4_2" "b5_2"
[5,] "a1_3" "0" "b1_3" "b2_3" "0" "b4_3" "b5_3"
[6,] "0" "a2_4" "b1_4" "b2_4" "b3_4" "0" "b5_4"
[7,] "0" "a2_5" "0" "b2_5" "b3_5" "b4_5" "0"
The K
matrix contains free parameters, as well as products of the S matrix (depending on the layout of the model).
Running print(my_model, matrix="K")
will print a simplified version of the K matrix which only displays the free parameters in the K
matrix, and 0
otherwise.
In order to visualize the full product of K, run the following:
print(MCMparseK(my_model))
IMPORTANT: This is by no means necessary to run the model, this is purely for visualizing the result of matrix operations on K
before loss is determined. Additionally, parsing the K
matrix in this way may take a long time for larger models.
Currently, constraining parameters can only be done via coordinates. For example, to undo our previous change and constrain a2_4
and a2_5
to the same parameter, we can do the following:
my_model_single_a2 <- MCMedit(my_model_split_A, pointer="A", name=list(c(6, 7), c(2, 2)), value="a2")
Note this result is stored in a different model as the rest of this wiki will assume a2_4
and a2_5
are still separate paths.
Adding new parameters works the same as dropping parameters, but can only be done via coordinates (as there is no old name to replace).
In this example we will undo our previous change and reinstate the paths x1 -> x5
and x5 -> x1
:
my_model <- MCMedit(my_model, pointer="A", name=list(c(7, 3), c(3, 7)), value=c("b1_5", "b5_1"))
To see the current starting values per parameter, see
my_model$start_values
Changing starting values can only be done by name (as the starting values are bound to the parameters themselves, not the matrices).
You can change starting values by providing the pointer "start"
, and providing either the name of a single parameter, or a vector with parameter names to set multiple start values to the same value simultaneously:
my_model <- MCMedit(my_model, pointer="start", name="a2_4", value=0.1) # Set a2_4 to 0.1
my_model <- MCMedit(my_model, pointer="start", name="a2_5", value=0.1) # Set a2_4 to 0.1
my_model <- MCMedit(my_model, pointer="start", name=c("a1_1", "a1_2", "a1_3"), value=0.1) # Set a1_1, a1_2 a1_3 start values to 0.1
We recognized that this can become messy in larger models, therefore we have also implemented a way to change the starting values of all parameters of a certain type in one go.
So a more efficient way of applying the previous change (since it encompassess all a
parameters) would be:
my_model <- MCMedit(my_model, pointer="start", name="a", value=0.1)
In some cases you may want to set all starting values simultaneously, in which case you can run the following:
my_model <- MCMedit(my_model, pointer="start", name="all", value=0)
Note: Starting values cannot be changed using my_model$start_values["start", "a1"] <- .5
Note: If you are using bounds, ensure
use_bounds
is set to TRUE inMCMfit
To see the current bounds per parameter, see
my_model$bounds
To change the upper and lower bound of a single parameter, use the pointer bound
:
my_model <- MCMedit(my_model, pointer="bound", name="a1_1", value=c(-1, 1))
Alternatively you can change bounds for multiple parameters by supplying a vector of parameter names
For example, let's set the bounds of a1_1
, a1_2
and a1_3
to [-1, 1]
my_model <- MCMedit(my_model, pointer="bound", name=c("a1_1", "a1_2", "a1_3"), value=c(-1, 1))
Changing bounds of a whole group of parameters works the same as starting values, e.g. to change the bounds of all a
parameters to [-1, 1]:
my_model <- MCMedit(my_model, pointer="bound", name="a", value=c(-1, 1))
You can change the lower and upper bounds separately by using the pointers lbound
and ubound
respectively.
Throughout this wiki we have only made changes to the A
matrix as it is likely to be your first target for making changes to your model.
Changes to other matrices work the exact same way as changes to the A
matrix as outlined above, with a few caveats for the Sk
and K
matrices:
- Adding parameters to the
Sk
orK
matrix is tricky as they are 2D representations of 3D and 4D matrices, so interpreting what the coordinates mean, and which cell is which co-kurtosis is not trivial. It is therefore not recommended to change these yourself. - Starting values for
sk
andk
are determined algorithmically (from the skew and kurtosis of the observed data), which is very likely to be the best starting value. It is therefore not recommended to change these unless you absolutely have to (for example if you want to compare fit performance of 2 models at the exact same starting point).