# Path analysis of latent variable relationships over time for hypothesis testing

Consider that I have a sample of 30 people (my real data are much bigger), and we ask them how often the would like to eat three types of food (candy, vegetables, and meat) at four points in their lives (ages 5, 10, 15, and 20).

I want to test the hypothesis that a decline in preference for candy at age 10 is more correlated with a stronger preference to eat vegetables more than with a stronger preference to eat meat at later in life (e.g. ages 15 and 20).

I guess that to test this hypothesis I need some kind of path analysis, so that I track the path of each individual's food preferences through the four time points, and then look at correlations.

Looking into the literature on path analysis, I see that structural equation models are relevant. So I've made a start with that, but I'm not sure if I'm correctly using the time variable here.

Here's some data that resemble my real data, I've pasted it online here:

ft <- readr::read_csv("http://www.sharecsv.com/dl/68214c230ccb32023dea371c794d2762/food-types.csv")


Here's a plot to give an overview of the trends

library(dplyr)
library(tidyr)
library(forcats)

cumsum_ft <-
ft %>%
select(person, Candy_5y:Meat_20y) %>%
gather(variable, value, -person) %>%
filter(value %in% c("Frequent", "Common")) %>%
separate(variable, into = c("food_category", "age"), sep = "_") %>%
mutate(age = parse_number(age) ) %>%
unite(food_category_value,
c('food_category',
'value'),
sep = " ") %>%
complete(food_category_value,
nesting(age)) %>%
group_by(food_category_value,
age) %>%
summarise(n = n()) %>%
ungroup() %>%
mutate(food_category_value =
fct_relevel(food_category_value,
c(
"Candy Common",
"Candy Frequent",
"Veg Common",
"Veg Frequent",
"Meat Common",
"Meat Frequent"
)))

# draw the plot
library(ggplot2)
ggplot(cumsum_ft,
aes(age,
n,
fill = food_category_value)) +
geom_area(position = 'stack') +
scale_fill_brewer(palette = "Set3")  +
theme_minimal(base_size = 10) We can see that many people have a preference for candy early in their life, and this changes to meat and vegetables later in their life.

I want to test if people who abandon candy early in life tend to prefer vegetables more so than meat later in life.

Here's my attempt at a structural equation model, first convert the data to ordered factors:

# convert to ordered factors
ft_cat <-
ft %>%
# convert consensus variables to ordinal factors
mutate_at(.vars = vars(Candy_5y:Meat_20y),
.funs = funs(case_when(. == "Frequent" ~ 3,
. == "Common" ~ 2,
. == "Rare" ~  1,
. == "None" ~ 0))) %>%
mutate_at(.vars = vars(Candy_5y:Meat_20y),
.funs = funs(factor(., ordered = TRUE)))


Here I specify the SEM model, is this the right way to use the time series information? I haven't found any obvious examples to follow that are similar to my question and data, so I'm not confident that I've specified the model correctly.

mod.food <- '
# latent variable definitions
candy  =~ Candy_5y + Candy_10y + Candy_15y + Candy_20y
vege  =~  Veg_5y +   Veg_10y +  Veg_15y + Veg_20y
meat  =~  Meat_5y +  Meat_10y +  Meat_15y + Meat_20y
# regressions
meat ~ candy
vege ~ candy
'


And here I estimate the parameters of the SEM model:

library(lavaan)
sem.fit.food <- sem(mod.food,
data = ft_cat[,-c(1)])
summary(sem.fit.food,
fit.measures=TRUE,
standardized = TRUE)



There are some warnings given here, I struggled to get this fake data to match the qualities of my real data, which does not emit warnings here.

Here's the output from the summary

lavaan 0.6-3 ended normally after 197 iterations

Optimization method                           NLMINB
Number of free parameters                         45

Used       Total
Number of observations                            30          31

Estimator                                       DWLS      Robust
Model Fit Test Statistic                      58.091      82.342
Degrees of freedom                                51          51
P-value (Chi-square)                           0.230       0.004
Scaling correction factor                                  0.989
Shift parameter                                           23.576
for simple second-order correction (Mplus variant)

Model test baseline model:

Minimum Function Test Statistic              724.953     346.076
Degrees of freedom                                66          66
P-value                                        0.000       0.000

User model versus baseline model:

Comparative Fit Index (CFI)                    0.989       0.888
Tucker-Lewis Index (TLI)                       0.986       0.855

Robust Comparative Fit Index (CFI)                            NA
Robust Tucker-Lewis Index (TLI)                               NA

Root Mean Square Error of Approximation:

RMSEA                                          0.069       0.146
90 Percent Confidence Interval          0.000  0.143       0.084  0.202
P-value RMSEA <= 0.05                          0.355       0.011

Robust RMSEA                                                  NA
90 Percent Confidence Interval                                NA     NA

Standardized Root Mean Square Residual:

SRMR                                           0.147       0.147

Parameter Estimates:

Information                                 Expected
Information saturated (h1) model        Unstructured
Standard Errors                           Robust.sem

Latent Variables:
Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
candy =~
Candy_5y          1.000                               0.056    0.056
Candy_10y        16.564   82.336    0.201    0.841    0.920    0.920
Candy_15y        15.356   76.142    0.202    0.840    0.853    0.853
Candy_20y        10.937   53.256    0.205    0.837    0.607    0.607
vege =~
Veg_5y            1.000                               0.360    0.360
Veg_10y           1.343    0.779    1.724    0.085    0.484    0.484
Veg_15y           2.441    1.242    1.966    0.049    0.880    0.880
Veg_20y           2.320    1.182    1.962    0.050    0.836    0.836
meat =~
Meat_5y           1.000                               0.435    0.435
Meat_10y         -0.957    0.309   -3.100    0.002   -0.416   -0.416
Meat_15y          1.166    0.419    2.784    0.005    0.507    0.507
Meat_20y         -1.440    0.464   -3.107    0.002   -0.627   -0.627

Regressions:
Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
meat ~
candy             8.871   43.802    0.203    0.840    1.132    1.132
vege ~
candy            -6.247   32.312   -0.193    0.847   -0.963   -0.963

Covariances:
Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
.vege ~~
.meat              0.025    0.019    1.349    0.177    1.125    1.125



The model fit is ok for the sake of an example, my real data are much better.

We can see in the output that meat ~ candy has a slope estimate of 8.871 and vege ~ candy at -6.247.

Can I say that as the preference for candy decreases over time in a person's life, then the preference for vegetables increases? And that this is not true for candy and meat? I see that the p-values are high here, but in my real data they are very low, so let's assume that this is a non-random result here.

And here is the diagram:

# look at the diagram:
library(lavaanPlot)
lavaanPlot(name="",
model=sem.fit.food,
coefs=TRUE,
covs=TRUE,
sig=1.00) Is this a the correct way to model these latent variable relationships over time? Specifically, to test this hypothesis that a decline in preference for candy at age 10 is more correlated with a stronger preference to eat vegetables more than with a stronger preference to eat meat at later in life (e.g. ages 15 and 20)?

• 30 is a very low sample size to try to do SEM. Even if you could, you will have very little power. – Jeremy Miles Feb 6 '19 at 16:20
• For looking at change you need growth models. In this model you are looking at relations between latent variables from regular CFA, not change. – Jeremy Miles Feb 6 '19 at 16:22
• Thanks Jeremy, my real data are much larger, but it was quite fiddly to make a fake dataset with similar properties, so I kept it small. How to specify the syntax of a growth model? That suggestion makes sense, and I'd love to know more about the details of how to specify a growth model. The examples I've seen online do not make it obvious to me for these dates data. – Ben Feb 6 '19 at 16:32

Your hypothesis does not necessitate path analysis. FWIW a simple logistic regression model is easily fit, described, and tested.

Being clear is important in science. The issue of conflating a few basic definitions is kind of a common pitfall of nutritional epi. For instance, you have not assessed "preference" in this case, but rather frequency--if your summaries are correctly labeled. Many healthy people prefer sweets and treats (to, say, pasta al forno, brocolli or nothing) but know to eat them infrequently.

Take as an input an indicator variable of decline in frequency of eating sweets: which you may define a number of ways, such as an endorsement of "frequent" at 10y followed by "common" at any follow-up. take as an output variable the yes/no outcome of frequently eating vegetables--or perhaps the discordant case of more frequently eating vegetables than meat. Summarize the association with an odds ratio and 95% CI and you are done.

To add to this, your path model is not correctly set up to identify change in candy as an exposure to predict eating at older life. You would define a latent variable like sweets growth as;

sweets_g =~ 0 * candy_5y + 1 * candy_10y + 2 * candy_15y + 3 * candy_20y

this should be pretty apparent if you read the lavaan documentation on growth modeling.

• Thanks for taking a look, lots of helpful suggestions here! – Ben Feb 7 '19 at 4:33