This notebook contains the preregistered analyses of training preferences: background variables, COM-B factors, and mean awareness/engagement as predictors of interest in receiving training in Open Research Practices.
library(readxl)
library(broom)
library(ggplot2)
library(sandwich)
library(lmtest)
The file contains background variables, COM-B composites, mean
awareness, mean engagement, and the recoded training interest outcome
(WantTraining_recoded: 0 = No, 1 = Yes).
df_train <- read_excel("TrainingPreferences_Analysis.xlsx")
df_train$Gender_new <- as.factor(df_train$Gender_new)
df_train$Role_new <- as.factor(df_train$Role_new)
df_train$Methodology_new <- as.factor(df_train$Methodology_new)
df_train$Rank_new <- as.factor(df_train$Rank_new)
df_train$Discipline_new <- as.factor(df_train$Discipline_new)
The preregistered plan specified logistic regression for the binary
outcome (WantTraining_recoded: 0 = No, 1 = Yes). However,
standard logistic regression did not converge due to complete separation
in sparse categorical predictors.
Train_logit <- glm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new,
data = df_train,
family = binomial)
summary(Train_logit)
# Check for complete separation
table(df_train$Rank_new, df_train$WantTraining_recoded, useNA = "ifany")
The standard errors for the affected categories are in the thousands, indicating that the model cannot estimate these coefficients. The crosstab above shows the reason: Rank = “Other” has 0 respondents who said “No” to training, so the outcome is perfectly predicted by that category.
A linear probability model (LPM) was therefore used for the reported analyses. The LPM is a standard and interpretable alternative for binary outcomes; coefficients represent changes in the probability of the outcome.
Train_baseline <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new,
data = df_train)
summary(Train_baseline)
Train_PC <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
PhysicalCapability, data = df_train)
Train_PsyC <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
PsychologicalCapability, data = df_train)
Train_PO <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
PhysicalOpportunity, data = df_train)
Train_SO <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
SocialOpportunity, data = df_train)
Train_RM <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
ReflectiveMotivation, data = df_train)
Train_AM <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
AutomaticMotivation, data = df_train)
Extract just the COM-B coefficient from each separate model:
summary(Train_PC)$coefficients["PhysicalCapability", ]
summary(Train_PsyC)$coefficients["PsychologicalCapability", ]
summary(Train_PO)$coefficients["PhysicalOpportunity", ]
summary(Train_SO)$coefficients["SocialOpportunity", ]
summary(Train_RM)$coefficients["ReflectiveMotivation", ]
summary(Train_AM)$coefficients["AutomaticMotivation", ]
Train_AllComb <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
PhysicalCapability + PsychologicalCapability +
PhysicalOpportunity + SocialOpportunity +
ReflectiveMotivation + AutomaticMotivation,
data = df_train)
summary(Train_AllComb)
Train_aware_bg <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
MeanAwareness, data = df_train)
Train_engage_bg <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
MeanEngagement, data = df_train)
Train_AE_bg <- lm(WantTraining_recoded ~ Gender_new + Role_new +
Methodology_new + Rank_new + Discipline_new +
MeanAwareness + MeanEngagement, data = df_train)
summary(Train_aware_bg)
summary(Train_engage_bg)
summary(Train_AE_bg)
The LPM assumes homoscedasticity, which is not strictly met with a binary outcome. Robust (heteroscedasticity-consistent) standard errors are reported below for the main models.
coeftest(Train_baseline, vcov = vcovHC(Train_baseline, type = "HC1"))
coeftest(Train_AllComb, vcov = vcovHC(Train_AllComb, type = "HC1"))
coeftest(Train_AE_bg, vcov = vcovHC(Train_AE_bg, type = "HC1"))
# Means and SDs
aggregate(cbind(MeanAwareness, MeanEngagement) ~ Discipline_new,
data = df_train, FUN = mean)
aggregate(cbind(MeanAwareness, MeanEngagement) ~ Discipline_new,
data = df_train, FUN = sd)
# Sample size per discipline
table(df_train$Discipline_new, useNA = "ifany")
# Subset: three main disciplines only (exclude "Other")
df_disc <- df_train[df_train$Discipline_new %in% c(0, 1, 2), ]
# Awareness
disc_aware <- lm(MeanAwareness ~ Discipline_new, data = df_disc)
summary(disc_aware)
anova(disc_aware)
# Engagement
disc_engage <- lm(MeanEngagement ~ Discipline_new, data = df_disc)
summary(disc_engage)
anova(disc_engage)
# Subset: three main methodologies (exclude "Other")
df_meth <- df_train[df_train$Methodology_new %in% c(0, 1, 2), ]
# Confirm counts
table(df_meth$Methodology_new)
# Awareness across methodologies
meth_aware <- lm(MeanAwareness ~ Methodology_new, data = df_meth)
summary(meth_aware)
anova(meth_aware)
# Engagement across methodologies
meth_engage <- lm(MeanEngagement ~ Methodology_new, data = df_meth)
summary(meth_engage)
anova(meth_engage)
# Recode so Qualitative (1) is the reference
df_meth$Methodology_factor <- factor(df_meth$Methodology_new,
levels = c(1, 0, 2))
# Now: 1 = Qualitative (reference), 0 = Quantitative, 2 = Mixed
meth_engage2 <- lm(MeanEngagement ~ Methodology_factor, data = df_meth)
summary(meth_engage2)
# Create a binary flag: research-active vs not
df_train$Role_group <- ifelse(df_train$Role_new == 0, "Research/Academic", "Other roles")
# Descriptives by role group
aggregate(cbind(MeanAwareness, MeanEngagement) ~ Role_group,
data = df_train, FUN = mean)
aggregate(cbind(MeanAwareness, MeanEngagement) ~ Role_group,
data = df_train, FUN = sd)
table(df_train$Role_group, useNA = "ifany")
# Ensure Role_group is a factor with Research/Academic as the reference
df_train$Role_group <- factor(df_train$Role_group,
levels = c("Research/Academic", "Other roles"))
# Awareness
role_aware <- lm(MeanAwareness ~ Role_group, data = df_train)
summary(role_aware)
anova(role_aware)
# Engagement
role_engage <- lm(MeanEngagement ~ Role_group, data = df_train)
summary(role_engage)
anova(role_engage)
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
# Compute means by discipline (three main disciplines only)
disc_means <- df_train %>%
filter(Discipline_new %in% c(0, 1, 2)) %>%
group_by(Discipline_new) %>%
summarise(
Awareness = mean(MeanAwareness, na.rm = TRUE),
Engagement = mean(MeanEngagement, na.rm = TRUE),
N = n()
) %>%
mutate(Discipline = factor(Discipline_new,
levels = c(0, 1, 2),
labels = c("Science", "Social Sciences", "Humanities")))
# Print to verify numbers
print(disc_means)
# Reshape to long format
disc_long <- disc_means %>%
pivot_longer(cols = c(Awareness, Engagement),
names_to = "Outcome",
values_to = "Mean") %>%
mutate(Outcome = factor(Outcome, levels = c("Awareness", "Engagement")))
# Publication-ready plot
ggplot(disc_long, aes(x = Discipline, y = Mean, fill = Outcome)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65,
colour = "black", linewidth = 0.3) +
geom_text(aes(label = sprintf("%.0f%%", Mean * 100)),
position = position_dodge(width = 0.75),
vjust = -0.5, size = 3, family = "serif") +
scale_fill_manual(values = c("Awareness" = "#A8C4C4", "Engagement" = "#4A6E8A")) +
scale_y_continuous(limits = c(0, 1.05),
breaks = seq(0, 1, 0.2),
labels = percent_format(accuracy = 1),
expand = c(0, 0)) +
labs(x = NULL, y = "Mean (% of practices)", fill = NULL) +
theme_classic(base_family = "serif", base_size = 11) +
theme(
legend.position = "top",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 10),
axis.text = element_text(colour = "black", size = 10),
axis.title.y = element_text(size = 11, margin = margin(r = 8)),
axis.line = element_line(colour = "black", linewidth = 0.4),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
plot.margin = margin(10, 10, 10, 10)
)
# Save as PNG (300 DPI) and PDF (vector)
ggsave("FigureA2_discipline.png", width = 6.5, height = 4.5, dpi = 300)
ggsave("FigureA2_discipline.pdf", width = 6.5, height = 4.5)
library(ggplot2)
library(dplyr)
library(tidyr)
# Compute COM-B means by discipline (three main disciplines only)
comb_means <- df_train %>%
filter(Discipline_new %in% c(0, 1, 2)) %>%
group_by(Discipline_new) %>%
summarise(
PhysicalCapability = mean(PhysicalCapability, na.rm = TRUE),
PsychologicalCapability = mean(PsychologicalCapability, na.rm = TRUE),
PhysicalOpportunity = mean(PhysicalOpportunity, na.rm = TRUE),
SocialOpportunity = mean(SocialOpportunity, na.rm = TRUE),
ReflectiveMotivation = mean(ReflectiveMotivation, na.rm = TRUE),
AutomaticMotivation = mean(AutomaticMotivation, na.rm = TRUE),
N = n()
) %>%
pivot_longer(cols = -c(Discipline_new, N),
names_to = "COM_B",
values_to = "Mean") %>%
mutate(
Discipline = factor(Discipline_new,
levels = c(0, 1, 2),
labels = c("Science", "Social Sciences", "Humanities")),
COM_B = factor(COM_B,
levels = c("PhysicalCapability", "PsychologicalCapability",
"PhysicalOpportunity", "SocialOpportunity",
"ReflectiveMotivation", "AutomaticMotivation"),
labels = c("Physical Capability", "Psychological Capability",
"Physical Opportunity", "Social Opportunity",
"Reflective Motivation", "Automatic Motivation"))
)
# Print to verify numbers
print(comb_means %>% select(Discipline, COM_B, Mean))
# Publication-ready plot
ggplot(comb_means, aes(x = COM_B, y = Mean, fill = Discipline)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65,
colour = "black", linewidth = 0.3) +
geom_text(aes(label = sprintf("%.2f", Mean)),
position = position_dodge(width = 0.75),
hjust = -0.15, size = 3.5, family = "serif") +
scale_fill_manual(values = c("Science" = "#4A6E8A",
"Social Sciences" = "#A8C4C4",
"Humanities" = "#D9E2E2")) +
scale_y_continuous(limits = c(0, 7.5),
breaks = seq(0, 7, 1),
expand = c(0, 0)) +
coord_flip() +
labs(x = NULL, y = "Mean score", fill = NULL) +
theme_classic(base_family = "serif", base_size = 11) +
theme(
legend.position = "top",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 12),
axis.text = element_text(colour = "black", size = 12),
axis.title.x = element_text(size = 11, margin = margin(t = 8)),
axis.line = element_line(colour = "black", linewidth = 0.4),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
plot.margin = margin(10, 10, 10, 10)
)
# Save
ggsave("Figure4_comb.png", width = 8, height = 7, dpi = 300)
ggsave("Figure4_comb.pdf", width = 8, height = 7)
library(ggplot2)
library(dplyr)
library(tidyr)
library(forcats) # Added for fct_reorder
# Compute COM-B means by discipline (three main disciplines only)
comb_means <- df_train %>%
filter(Discipline_new %in% c(0, 1, 2)) %>%
group_by(Discipline_new) %>%
summarise(
PhysicalCapability = mean(PhysicalCapability, na.rm = TRUE),
PsychologicalCapability = mean(PsychologicalCapability, na.rm = TRUE),
PhysicalOpportunity = mean(PhysicalOpportunity, na.rm = TRUE),
SocialOpportunity = mean(SocialOpportunity, na.rm = TRUE),
ReflectiveMotivation = mean(ReflectiveMotivation, na.rm = TRUE),
AutomaticMotivation = mean(AutomaticMotivation, na.rm = TRUE),
N = n()
) %>%
pivot_longer(cols = -c(Discipline_new, N),
names_to = "COM_B",
values_to = "Mean") %>%
mutate(
# 1. Science on top: Reverse levels so Science is last (top in coord_flip)
Discipline = factor(Discipline_new,
levels = c(2, 1, 0),
labels = c("Humanities", "Social Sciences", "Science")),
COM_B = factor(COM_B,
levels = c("PhysicalCapability", "PsychologicalCapability",
"PhysicalOpportunity", "SocialOpportunity",
"ReflectiveMotivation", "AutomaticMotivation"),
labels = c("Physical Capability", "Psychological Capability",
"Physical Opportunity", "Social Opportunity",
"Reflective Motivation", "Automatic Motivation"))
) %>%
mutate(
# 2. Increasing order top to bottom: Largest mean gets first level (bottom)
COM_B = fct_reorder(COM_B, Mean, .fun = mean, .desc = TRUE)
)
# Print to verify numbers
print(comb_means %>% select(Discipline, COM_B, Mean))
# Publication-ready plot
ggplot(comb_means, aes(x = COM_B, y = Mean, fill = Discipline)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65,
colour = "black", linewidth = 0.3) +
geom_text(aes(label = sprintf("%.2f", Mean)),
position = position_dodge(width = 0.75),
hjust = -0.15, size = 3.5, family = "serif") +
scale_fill_manual(values = c("Science" = "#4A6E8A",
"Social Sciences" = "#A8C4C4",
"Humanities" = "#D9E2E2")) +
scale_y_continuous(limits = c(0, 7.5),
breaks = seq(0, 7, 1),
expand = c(0, 0)) +
coord_flip() +
labs(x = NULL, y = "Mean score", fill = NULL) +
theme_classic(base_family = "serif", base_size = 11) +
theme(
legend.position = "top",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 12),
axis.text = element_text(colour = "black", size = 12),
axis.title.x = element_text(size = 11, margin = margin(t = 8)),
axis.line = element_line(colour = "black", linewidth = 0.4),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
plot.margin = margin(10, 10, 10, 10)
)
# Save
ggsave("Figure4_comb.png", width = 8, height = 7, dpi = 300)
ggsave("Figure4_comb.pdf", width = 8, height = 7)
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
# Compute means by discipline (three main disciplines only)
disc_means <- df_train %>%
filter(Discipline_new %in% c(0, 1, 2)) %>%
group_by(Discipline_new) %>%
summarise(
Awareness = mean(MeanAwareness, na.rm = TRUE),
Engagement = mean(MeanEngagement, na.rm = TRUE),
N = n()
) %>%
mutate(Discipline = factor(Discipline_new,
levels = c(0, 1, 2),
labels = c("Science", "Social Sciences", "Humanities")))
# Print to verify numbers
print(disc_means)
# Reshape to long format
disc_long <- disc_means %>%
pivot_longer(cols = c(Awareness, Engagement),
names_to = "Outcome",
values_to = "Mean") %>%
mutate(Outcome = factor(Outcome, levels = c("Awareness", "Engagement")))
# Publication-ready plot
ggplot(disc_long, aes(x = Discipline, y = Mean, fill = Outcome)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65,
colour = "black", linewidth = 0.3) +
scale_fill_manual(values = c("Awareness" = "#A8C4C4", "Engagement" = "#4A6E8A")) +
scale_y_continuous(limits = c(0, 1.05),
breaks = seq(0, 1, 0.2),
labels = percent_format(accuracy = 1),
expand = c(0, 0)) +
labs(x = NULL, y = NULL, fill = NULL) +
theme_classic(base_family = "serif", base_size = 11) +
theme(
legend.position = "top",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 10),
axis.text = element_text(colour = "black", size = 10),
axis.line = element_line(colour = "black", linewidth = 0.4),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
plot.margin = margin(10, 10, 10, 10)
)
# Save as PNG (300 DPI) and PDF (vector)
ggsave("FigureA2_discipline.png", width = 3.5, height = 4.5, dpi = 300)
ggsave("FigureA2_discipline.pdf", width = 3.5, height = 4.5)
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
# Compute means by methodology
meth_means <- df_train %>%
filter(Methodology_new %in% c(0, 1, 2)) %>%
group_by(Methodology_new) %>%
summarise(
Awareness = mean(MeanAwareness, na.rm = TRUE),
Engagement = mean(MeanEngagement, na.rm = TRUE),
N = n()
) %>%
mutate(Methodology = factor(Methodology_new,
levels = c(0, 1, 2),
labels = c("Quantitative", "Qualitative", "Mixed methods")))
# Print to verify numbers
print(meth_means)
# Reshape to long format
meth_long <- meth_means %>%
pivot_longer(cols = c(Awareness, Engagement),
names_to = "Outcome",
values_to = "Mean") %>%
mutate(Outcome = factor(Outcome, levels = c("Awareness", "Engagement")))
# Publication-ready plot
ggplot(meth_long, aes(x = Methodology, y = Mean, fill = Outcome)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65,
colour = "black", linewidth = 0.3) +
scale_fill_manual(values = c("Awareness" = "#A8C4C4", "Engagement" = "#4A6E8A")) +
scale_y_continuous(limits = c(0, 1.05),
breaks = seq(0, 1, 0.2),
labels = percent_format(accuracy = 1),
expand = c(0, 0)) +
labs(x = NULL, y = NULL, fill = NULL) +
theme_classic(base_family = "serif", base_size = 11) +
theme(
legend.position = "top",
legend.key.size = unit(0.4, "cm"),
legend.text = element_text(size = 10),
axis.text.x = element_text(colour = "black", size = 10),
axis.text.y = element_blank(),
axis.line = element_line(colour = "black", linewidth = 0.4),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
plot.margin = margin(10, 10, 10, 10)
)
# Save as PNG (300 DPI for submission) and PDF (vector)
ggsave("FigureA2_methodology.png", width = 3.5, height = 4.5, dpi = 300)
ggsave("FigureA2_methodology.pdf", width = 3.5, height = 4.5)