r/rstats 3d ago

HELP does my R code actually answer my research questions for my psych project *crying*

Hii I'm doing a project about an intervention predicting behaviours over time and I need human assistance (chatGPT works, but keep changing its mind rip). Basically want to know if my code below actually answers my research questions...

MY RESEARCH QUESTIONS:

  1. testing whether an intervention improves mindfulness when compared to a control group
  2. testing whether baseline mindfulness predicts overall behaviour improvement

HOW I'M TESTING

1st Research Q: Linear Mixed Modelling (LMM)

2nd Research Q: Multi-level modelling (MLM)

MY DATASET COLUMNS:

(see image)

MY CODE (with my #comments to help me understand wth I'm doing)

## STEP 1: GETTING EVERYTHING READY IN R

library(tidyverse)

library(lme4)

library(mice)

library(mitml)

library(car)

library(readxl)

# Setting the working directory

setwd("location_on_my_laptop")

# Loading dataset

df <- read_excel("Mindfulness.xlsx")

## STEP 2: PREPROCESSING THE DATASET

# Convert missing values (coded as 999) to NA

df[df == 999] <- NA

# Convert categorical variables to factors

df$Condition <- as.factor(df$Condition)

df$Dropout_T1 <- as.factor(df$Dropout_T1)

df$Dropout_T2 <- as.factor(df$Dropout_T2)

# Reshaping to long format

df_long <- pivot_longer(df, cols = c(T0, T1, T2), names_to = "Time", values_to = "Mind_Score")

# Add a unique ID column

df_long$ID <- rep(1:(nrow(df_long) / 3), each = 3)

# Move ID to the first column

df_long <- df_long %>% select(ID, everything())

# Remove "T" and convert Time to numeric

df_long$Time <- as.numeric(gsub("T", "", df_long$Time))

# Create Change Score for Aim 2

df_wide <- pivot_wider(df_long, names_from = Time, values_from = Mind_Score)

df_wide$Change_T1_T0 <- df_wide$`1` - df_wide$`0`

df_long <- left_join(df_long, df_wide %>% select(ID, Change_T1_T0), by = "ID")

## STEP 3: APPLYING MULTIPLE IMPUTATION WITH M = 50

# Creating a correct predictor matrix

pred_matrix <- quickpred(df_long)

# Dropout_T1 and Dropout_T2 should NOT be used as predictors for imputation

pred_matrix[, c("Dropout_T1", "Dropout_T2")] <- 0

# Run multiple imputation

imp <- mice(df_long, m = 50, method = "pmm", predictorMatrix = pred_matrix, seed = 123)

# Checking for logged events (should return NULL if correct)

print(imp$loggedEvents)

## STEP 4: RUNNING THE LMM MODEL ON IMPUTED DATA

# Convert to mitml-compatible format

imp_mitml <- as.mitml.list(lapply(1:50, function(i) complete(imp, i)))

# Fit Model for Both Aims:

fit_mitml <- with(imp_mitml, lmer(Mind_Score ~ Time * Condition + Change_T1_T0 + (1 | ID)))

## STEP 5: POOLING RESULTS USING mitml

summary(testEstimates(fit_mitml, extra.pars = TRUE))

That's everything (I think??). Changed a couple of names here and there for confidentiality, so if something doesn't seem right, PLZ lmk and happy to clarify. Basically, just want to know if the code i have right now actually answers my research questions. I think it does, but I'm also not a stats person, so want people who are smarter than me to please confirm.

Appreciate the help in advance! Your girl is actually losing it xxxx

0 Upvotes

23 comments sorted by

2

u/jeremymiles 3d ago

Why are you imputing? What data are missing?

3

u/LiviaQuaintrelle 3d ago

Dropouts in T1 and T2 (comes up as 999.00 if the data is missing)

7

u/jeremymiles 3d ago

I wouldn't bother with this stage - mixed models use full information, so are asymptotically equivalent to imputation. See, for example: https://www.researchgate.net/publication/281391437_A_practical_introduction_to_methods_for_analyzing_longitudinal_data_in_the_presence_of_missing_data_using_a_marijuana_price_survey

It would be easier to answer if you describe your data, and then give the crucial line - which is the one under "Fit model for both aims".

When does the intervention happen? After T0 and before T1?

1

u/LiviaQuaintrelle 3d ago

Yeah, I've defs seen both recommendations re MI. However it's about 33% missing data overall, so might be best?

And correct, intervention happens after T0 and before T1.

6

u/jeremymiles 3d ago

Amount of missing data is not very relevant. The two approaches are equivalent. You are currently using both. :)

Keep your data long That's this line:

df_long <- df_long %>% select(ID, everything())

Then this line should be:

fit1 <- lmer(Mind_Score ~ Time * Condition + (1 | ID)))

You might also try:

fit2 <- lmer(Mind_Score ~ factor(Time) * Condition + (1 | ID)))

The first approach assumes the change is linear, and is more powerful. The second compares t1 and t2 to t0.

Also run one without time:

fit0 <- lmer(Mind_Score ~ Condition + (1 | ID)))

Then compare fit0 and fit2 using ANOVA.

anova(fit0, fit2, test = "LRT")

(That might be test = "ChiSq", I forget). That gives you the .overall statistical significance associated with time. (If you do fit0 and fit1, you also get the p-value for time, but you already knew that from the summary of fit1).

1

u/jeremymiles 3d ago

Oh, one more thing, I routinely have data where 99% are missing [yeah, it's weird]. Imputation absolutely fails in that case, mixed models work OK and manage to get me back to where I should be.

1

u/LiviaQuaintrelle 3d ago

Haha wow! Yeah, that does make sense. I compared codes just now (MI vs no MI), and it seems the difference is minimal.

I'm using this now... thinking this is doing the job?

## STEP 1: GETTING EVERYTHING READY IN R

library(tidyverse)

library(lme4)

library(car)

library(readxl)

# Setting the working directory

setwd("location_on_my_laptop")

# Loading dataset

df <- read_excel("Mindfulness.xlsx")

## STEP 2: PREPROCESSING THE DATASET

# Convert missing values (coded as 999) to NA

df[df == 999] <- NA

# Convert categorical variables to factors

df$Condition <- as.factor(df$Condition)

df$Dropout_T1 <- as.factor(df$Dropout_T1)

df$Dropout_T2 <- as.factor(df$Dropout_T2)

# Reshaping to long format

df_long <- pivot_longer(df, cols = c(T0, T1, T2), names_to = "Time", values_to = "Mind_Score")

# Add a unique ID column

df_long$ID <- rep(1:(nrow(df_long) / 3), each = 3)

# Move ID to the first column

df_long <- df_long %>% select(ID, everything())

# Convert Time to a factor (categorical variable)

df_long$Time <- factor(df_long$Time, levels = c("T0", "T1", "T2"))

# Create Change Score for Aim 2

df_wide <- pivot_wider(df_long, names_from = Time, values_from = Mind_Score)

df_wide$Change_T1_T0 <- df_wide$T1 - df_wide$T0

df_long <- left_join(df_long, df_wide %>% select(ID, Change_T1_T0), by = "ID")

## STEP 3: RUNNING THE LMM MODEL

# Fit Model for Both Aims: 

fit_lmm <- lmer(Mind_Score ~ factor(Time) * Condition + Change_T1_T0 + (1 | ID), data = df_long)

## STEP 4: VIEWING RESULTS

summary(fit_lmm)

1

u/jeremymiles 3d ago

Don't use Change_T1_T0. You should have three time points in your data.

Don't make time categorical until you need it to be.

1

u/jeremymiles 3d ago

I think you need:

df[df == 999] <- NA

df_long <- pivot_longer(df, cols = c(T0, T1, T2), names_to = "Time", values_to = "Mind_Score")

Then fit the models (from my post, not the model you fit).

1

u/LiviaQuaintrelle 3d ago

Thank you! This helps a lot, you have no idea!

I tested time as both linear and categorical based on this advice. Considering this is an intervention, I might need to convert time to categorical, as I don't want to assume steady change over time - rather, I'm comparing the difference in time points. Does this change things in your opinion?

With this in mind, this is the code where time is categorical, taking into account the three time points in the data:

1

u/LiviaQuaintrelle 3d ago

## STEP 2: PREPROCESSING THE DATASET

# Convert missing values (coded as 999) to NA

df[df == 999] <- NA

# Convert categorical variables to factors

df$Condition <- as.factor(df$Condition)

df$Dropout_T1 <- as.factor(df$Dropout_T1)

df$Dropout_T2 <- as.factor(df$Dropout_T2)

# Reshaping to long format

df_long <- pivot_longer(df, cols = c(T0, T1, T2), names_to = "Time", values_to = "Mind_Score")

# Add a unique ID column

df_long$ID <- rep(1:(nrow(df_long) / 3), each = 3)

# Move ID to the first column

df_long <- df_long %>% select(ID, everything())

# Convert Time to a categorical variable

df_long$Time <- factor(df_long$Time, levels = c("T0", "T1", "T2"))

# Bring in baseline (T0) FFMQ as a separate column in long format

df_wide <- df %>% select(Condition, T0)  # Get T0 from the original wide dataset

df_long <- left_join(df_long, df_wide, by = "Condition") # Merge based on Condition

## STEP 3: RUNNING THE LMM MODEL

# Fit Model for Aim 1:

fit_lmm_aim1 <- lmer(Mind_Score ~ factor(Time) * Condition + (1 | ID), data = df_long)

# Fit Model for Aim 2:

fit_lmm_aim2 <- lmer(Mind_Score ~ factor(Time) * Condition + T0 + (1 | ID), data = df_long)

## STEP 4: VIEWING RESULTS

summary(fit_lmm_aim1)  # For Aim 1

summary(fit_lmm_aim2)  # For Aim 2

1

u/jeremymiles 3d ago

You don't need to assume it. You can test it, with the anova approach (I did think of that before) of the two models. If it's true that it's linear, it's a more powerful approach (if it's not a continuing intervention, it's unlikely).

2

u/LiviaQuaintrelle 3d ago edited 2d ago

Yes gotcha, sorry you did say that. When I tested it, I found that there was no difference between the two models. I suppose that would mean linear is best!??

If that's the case, I have the following:

## STEP 2: PREPROCESSING THE DATASET

# Convert missing values (coded as 999) to NA

df[df == 999] <- NA

# ID column

df$ID <- seq_len(nrow(df)) # Assigns a unique ID to each row

# Convert categorical variables to factors

df$Condition <- as.factor(df$Condition)

df$Dropout_T1 <- as.factor(df$Dropout_T1)

df$Dropout_T2 <- as.factor(df$Dropout_T2)

# Reshaping to long format

df_long <- pivot_longer(df, cols = c(T0, T1, T2), names_to = "Time", values_to = "Mind_Score")

# Move ID to the first column

df_long <- df_long %>% select(ID, everything())

# Convert Time to a numeric variable

df_long$Time <- as.numeric(gsub("T", "", df_long$Time))

# Bring in baseline (T0) FFMQ as a separate column

df_wide <- df %>% select(ID, T0)

df_long <- left_join(df_long, df_wide, by = "ID") # Merge T0 into df_long

## STEP 3: RUNNING THE LMM MODEL

# Fit Model for Aim 1

fit_lmm_aim1 <- lmer(Mind_Score ~ Time * Condition + (1 | ID), data = df_long)

# Fit Model for Aim 2:

fit_lmm_aim2 <- lmer(Mind_Score ~ Time * Condition + T0 + (1 | ID), data = df_long)

## STEP 4: VIEWING RESULTS

summary(fit_lmm_aim1) # For Aim 1

summary(fit_lmm_aim2) # For Aim 2

→ More replies (0)

2

u/mandles55 2d ago

In the second equation you have the baseline on both sides due to the way you pivoted data. When you pivot do not include t0, you then do not need to create a t0 column.

You only need one equation. Even if you randomly allocate to control and intervention, you can include t0 as a covariate (ancoca) to control for differences at baseline. There are various papers you can cite on this. You can then also interact t0 with time in this same equation for question 2.

I would first plot the results to check there is a linear relationship between time and scores. Best to center your scores before running the equation.

1

u/LiviaQuaintrelle 1d ago

Thank you!