Code
knitr::include_graphics("figures/table_originalitems.png")
knitr::include_graphics("figures/table_originalitems.png")
library(tidyverse)
library(easystats)
library(patchwork)
library(ggside)
library(EGAnet)
library(tidygraph)
library(ggraph)
set.seed(42)df <- read.csv("../data/data_participants.csv")
items <- select(df, contains("_Q"))
byfacet <- items[df$Condition=="Dimensions",]
bymodality <- items[df$Condition=="Domains",]
byrandom <- items[df$Condition=="Random",]labels <- list(
Sexual_State_Q1 = "I always know when I am sexually aroused",
Sexual_State_Q2 = "I always feel in my body if I am sexually aroused",
Sexual_State_Q3 = "My body is always in the same specific state when I am sexually aroused",
Sexual_State_Q4 = "Being sexually aroused is a very different bodily feeling compared to other states (e.g., feeling anxious, relaxed, or after physical exercise)",
Sexual_State_Q5 = "I can always tell that I am sexually aroused from the way I feel inside",
Sexual_State_A = "I always know that I am attentively doing a study",
Sexual_Cardiac_Q1 = "When I am sexually aroused, I often feel changes in the way my heart beats (e.g., faster or stronger)",
Sexual_Respiratory_Q1 = "When I am sexually aroused, I often feel changes in my breathing (e.g., faster, shallower, or less regular)",
Sexual_Gastric_Q1 = "When I am sexually aroused, I often feel changes in my stomach (e.g., bloating, rumbling, discomfort)",
Sexual_Gastric_Q2 = "When I am sexually aroused, I often feel butterflies in my stomach",
Sexual_Genital_Q1 = "When I am sexually aroused, I often notice specific sensations in my genital area (e.g., tingling, warmth, wetness, stiffness, pulsations)",
Sexual_Genital_Q2 = "During sex or masturbation, I often feel very strong sensations coming from my genital areas",
Sexual_SkinThermo_Q1 = "When I am sexually aroused, I often feel changes in my temperature (e.g., feeling warm or cold)",
Sexual_SkinThermo_Q2 = "When I am sexually aroused, I often feel like some areas of my skin become sweaty (e.g., palms, back, forehead)",
Sexual_SkinThermo_Q3 = "When I am sexually aroused, I often feel my mouth becoming dry",
Sexual_ColonBladder_Q1 = "When I am sexually aroused, I often feel like I need to relieve myself by urinating or defecating",
Sexual_ColonBladder_Q2 = "During sex or masturbation, I often feel like I need to relieve myself by urinating or defecating",
Anxious_State_Q1 = "I always know when I am anxious",
Anxious_State_Q2 = "I always feel in my body if I am anxious",
Anxious_State_Q3 = "My body is always in the same specific state when I am anxious",
Anxious_State_Q4 = "Being anxious is a very different bodily feeling compared to other states (e.g., feeling sexually aroused, relaxed or after exercise)",
Anxious_State_Q5 = "I often realize that I am anxious only when others tell me",
Anxious_Cardiac_Q1 = "When I am anxious, I often feel changes in the way my heart beats (e.g., faster or stronger)",
Anxious_Respiratory_Q1 = "When I am anxious, I often feel changes in my breathing (e.g., faster, shallower, or less regular)",
Anxious_Gastric_Q1 = "When I am anxious, I often feel changes in my stomach (e.g., bloating, rumbling, discomfort)",
Anxious_Genital_Q1 = "When I am anxious, I often notice specific sensations in my genital area (e.g., contractions, dryness)",
Anxious_SkinThermo_Q1 = "When I am anxious, I often feel changes in my temperature (e.g., feeling warm or cold)",
Anxious_SkinThermo_Q2 = "When I am anxious, I often feel like some areas of my skin become sweaty (e.g., palms, back, forehead)",
Anxious_SkinThermo_Q3 = "When I am anxious, I often feel my mouth becoming dry",
Anxious_SkinThermo_Q4 = "When I am anxious, I often have difficulty swallowing",
Anxious_SkinThermo_A = "Even if I am anxious, I should now answer all the way to the left",
Anxious_ColonBladder_Q1 = "When I am anxious, I often feel like I need to relieve myself by urinating or defecating",
Nociception_State_Q1 = "I always feel in my body if I am ill",
Nociception_State_Q2 = "I can easily tell when I am feeling ill (e.g., nauseous or sick)",
Nociception_Cardiac_Q1 = "I often feel painful sensations coming from my heart",
Nociception_Cardiac_Q2 = "I often experience painful sensations coming from my chest",
Nociception_Respiratory_Q1 = "I often feel like I have difficulties breathing normally",
Nociception_Respiratory_Q2 = "I often feel like I can't get enough oxygen by breathing normally",
Nociception_Gastric_Q1 = "I often feel pain in my stomach",
Nociception_Genital_Q1 = "My genital organs are very sensitive to pleasant stimulations",
Nociception_Genital_Q2 = "My genital organs are very sensitive to painful stimulations",
Nociception_SkinThermo_Q1 = "My skin is very sensitive to painful stimulations (e.g., pinching)",
Nociception_SkinThermo_Q2 = "My skin is very sensitive to pleasant stimulations (e.g., caressing)",
Nociception_SkinThermo_Q3 = "Changes in temperature (e.g., feeling feverish or cold) are the first things I notice when I am becoming ill",
Nociception_ColonBladder_Q1 = "I often experience a pleasant sensation when relieving myself when urinating or defecating)",
Nociception_ColonBladder_Q2 = "I often experience painful sensations when relieving myself when urinating or defecating",
Nociception_ColonBladder_A = "I often experience sensations, and I will answer zero to this question",
Sensitivity_State_Q1 = "I always know when I am relaxed",
Sensitivity_State_Q2 = "I always feel in my body if I am relaxed",
Sensitivity_State_Q3 = "My body is always in the same specific state when I am relaxed",
Sensitivity_State_Q4 = "Being relaxed is a very different bodily feeling compared to other states (e.g., feeling anxious, sexually aroused or after exercise)",
Sensitivity_State_Q5 = "When something important is happening in my life, I can feel it in my body",
Sensitivity_Cardiac_Q1 = "In general, I am very sensitive to changes in my heart rate",
Sensitivity_Cardiac_Q2 = "I often notice changes in my heart rate",
Sensitivity_Cardiac_Q3 = "I can notice even very subtle changes in the way my heart beats",
Sensitivity_Cardiac_Q4 = "I am always very aware of my heartbeats, even when I am calm",
Sensitivity_Cardiac_Q5 = "I only notice my heart when it is thumping in my chest",
Sensitivity_Cardiac_Q6 = "I often try to feel my heart with my hands (e.g., by putting my hand on my chest)",
Sensitivity_Cardiac_Q7 = "When something important is happening in my life, I can feel immediately feel changes in my heart rate",
Sensitivity_Cardiac_A = "In general, I am very sensitive and attentive to the questions I am currently answering",
Sensitivity_Respiratory_Q1 = "In general, I am very sensitive to changes in my breathing",
Sensitivity_Respiratory_Q = "I often notice changes in my breathing",
Sensitivity_Respiratory_Q3 = "I can notice even very subtle changes in my breathing",
Sensitivity_Respiratory_Q4 = "I am always very aware of how I am breathing, even when I am calm",
Sensitivity_Respiratory_Q5 = "I often only notice how I am breathing when it becomes loud",
Sensitivity_Respiratory_Q6 = "I often only notice how I am breathing when my breathing becomes shallow or irregular",
Sensitivity_Respiratory_Q7 = "When something important is happening in my life, I can immediately feel changes in my breathing",
Sensitivity_Gastric_Q1 = "In general, I am very sensitive to what my stomach is doing",
Sensitivity_Gastric_Q2 = "I can notice even very subtle changes in what my stomach is doing",
Sensitivity_Gastric_Q3 = "I am always very aware of what my stomach is doing, even when I am calm",
Sensitivity_Gastric_Q4 = "I often check the smell of my own breath",
Sensitivity_Gastric_Q5 = "I often check the smell of my farts",
Sensitivity_Gastric_Q6 = "I often pay attention to the noises of my stomach",
Sensitivity_Gastric_A = "I often pay attention to the answers I am giving",
Sensitivity_Genital_Q1 = "In general, I am very sensitive to changes in my genital organs",
Sensitivity_Genital_Q2 = "I can notice even very subtle changes in the state of my genital organs",
Sensitivity_Genital_Q3 = "I am always very aware of the state of my genital organs, even when I am calm",
Sensitivity_SkinThermo_Q1 = "In general, my skin is very sensitive",
Sensitivity_SkinThermo_Q2 = "I can notice even very subtle stimulations to my skin (e.g., very light touches)",
Sensitivity_SkinThermo_Q3 = "I can notice even very subtle changes if my skin becomes dry or sweaty",
Sensitivity_SkinThermo_Q4 ="I am always very aware if my hands and feet are cold or warm",
Sensitivity_SkinThermo_Q5 = "I often check the smell of my armpits",
Sensitivity_SkinThermo_Q6 = "I am very prone to having goosebumps",
Sensitivity_SkinThermo_Q7 ="My skin is susceptible to itchy fabrics and materials",
Sensitivity_SkinThermo_Q8 = "I enjoy the sensations of touching different materials (e.g., soft fabrics, wooden objects, smooth surfaces)",
Sensitivity_ColonBladder_Q1 = "In general, I am very aware of the sensations that are happening when I am defecating",
Sensitivity_ColonBladder_Q2 = "In general, I am very aware of the sensations that are happening when I am urinating",
Sensitivity_ColonBladder_Q3 = "I often check the colour of my urine",
Sensitivity_ColonBladder_Q4 = "I often check the colour of my faeces",
Accuracy_State_Q1 = "I can always accurately feel when I am about to cough",
Accuracy_State_Q2 = "I can always accurately feel when I am about to sneeze",
Accuracy_State_Q3 = "I can always accurately feel when I am about to vomit",
Accuracy_State_Q4 = "I can always accurately feel when I am starting to be hungry",
Accuracy_State_Q5 = "I can always accurately feel when I am starting to be thirsty",
Accuracy_Cardiac_Q1 = "I can always accurately feel if my heart rate is slow or fast",
Accuracy_Cardiac_Q2 = "I sometimes feel like my heart is racing or beating faster than usual, but when I check my pulse, it is not as intense as I thought",
Accuracy_Respiratory_Q1 = "I can always accurately feel how I am breathing (e.g., fast or slow, deep or shallow)",
Accuracy_Respiratory_A = "I can always accurately answer to the left on this question to show that I am reading it",
Accuracy_Gastric_Q1 = "I can always accurately feel when I am about to fart",
Accuracy_Gastric_Q2 = "I can always accurately feel when I am about to burp",
Accuracy_Gastric_Q3 = "I often feel thirsty even if I drank recently",
Accuracy_Gastric_Q4 = "I don't always feel the need to drink until I am really thirsty",
Accuracy_Gastric_Q5 = "I often feel hungry even if I ate recently",
Accuracy_Gastric_Q6 = "I don't always feel the need to eat until I am really hungry",
Accuracy_Gastric_Q7 = "I often sneeze suddenly without feeling the need building up",
Accuracy_Gastric_Q8 = "I sometimes feel that burping will produce some relief but then it doesn't",
Accuracy_Genital_Q1 = "I can always accurately perceive if my genital organs are in a state of arousal (e.g., hard, wet, pulsating)",
Accuracy_Genital_Q2 = "I sometimes feel like I am sexually aroused, but when I try to satisfy the feeling, I realise that I am not as sexually aroused as I initially thought",
Accuracy_Genital_A = "I can always accurately perceive that to this question I should answer the lowest option",
Accuracy_SkinThermo_Q1 = "I can always accurately feel when something is going to be itchy",
Accuracy_SkinThermo_Q2 = "I can always accurately feel when I start to have a fever",
Accuracy_SkinThermo_Q3 = "When something touches my skin, I can always accurately feel if it's hot or cold",
Accuracy_SkinThermo_Q4 = "I sometimes feel my skin itching, but when I scratch it, it doesn't produce the relief I expected",
Accuracy_ColonBladder_Q1 = "I often feel the need to urinate even when my bladder is not full",
Accuracy_ColonBladder_Q2 = "I don't always feel the need to urinate until my bladder is very full",
Accuracy_ColonBladder_Q3 = "I often feel the need to defecate even when my intestine is not full",
Accuracy_ColonBladder_Q4 = "I don't always feel the need to defecate until my intestine is very full",
Accuracy_ColonBladder_Q5 = "I sometimes feel like I need to urinate or defecate but when I go to the bathroom I produce less than I expected",
Confusion_State_Q1 = "Sometimes I can't tell if the sensations in my body are good or bad",
Confusion_State_Q2 = "Sometimes I am confused about what sensations in my body mean",
Confusion_Cardiac_Q1 = "Sometimes my heart starts racing and I often don't know why",
Confusion_Respiratory_Q1 = "Sometimes my breathing becomes erratic or shallow and I often don't know why",
Confusion_Gastric_Q1 = "Sometimes I feel negative and realise after eating that I was just hungry",
Confusion_Gastric_Q2 = "Sometimes I don't realise I was hungry until I ate something",
Confusion_Genital_Q1 = "Sometimes I notice arousal in my genital areas (e.g., stiffness, wetness) when I am not feeling sexually aroused",
Confusion_SkinThermo_Q1 = "Sometimes I have sensations on my skin (e.g., itches, goosebumps) without any clear cause",
Confusion_ColonBladder_Q1 = "Sometimes I am not sure whether I need to go to the toilet or not (to urinate or defecate)",
Confusion_ColonBladder_A = "Sometimes I notice that I need to answer all the way to the right"
)make_heatmap <- function(items, interactive=TRUE, title="Correlation Matrix") {
fn <- if(interactive) heatmaply::heatmaply else heatmaply::ggheatmap
fn(
cor(items),
symm = TRUE,
colors=heatmaply::cool_warm,
show_dendrogram =c(FALSE, TRUE),
main=title,
showticklabels = FALSE,
hide_colorbar = TRUE,
k_col = 4,
# k_row = 4,
limits = c(-1, 1)
)
}
make_heatmap(items, title="Full Sample")bootstrap_similarity <- function(df) {
rez <- data.frame()
for(i in 1:500) {
idx <- sample(1:nrow(df), nrow(df), replace=TRUE)
newdata <- df[idx,]
items <- select(df, contains("_Q"))
byfacet <- items[newdata$Condition=="Dimensions",]
bymodality <- items[newdata$Condition=="Domains",]
byrandom <- items[newdata$Condition=="Random",]
rez <- rbind(
MatrixCorrelation::allCorrelations(cor(byrandom), cor(byfacet),
ncomp1=10, ncomp2=10, plot=FALSE),
MatrixCorrelation::allCorrelations(cor(byrandom), cor(bymodality),
ncomp1=10, ncomp2=10, plot=FALSE),
MatrixCorrelation::allCorrelations(cor(items), cor(byfacet),
ncomp1=10, ncomp2=10, plot=FALSE),
MatrixCorrelation::allCorrelations(cor(items), cor(bymodality),
ncomp1=10, ncomp2=10, plot=FALSE)) |>
as.data.frame() |>
mutate(
Comparison = c("Random vs. Facet", "Random vs. Modality", "Full vs. Facet", "Full vs. Modality"),
Sample = c("Random", "Random", "Full", "Full"),
Iteration = i) |>
mutate(d_PSI = diff(PSI), d_RVadj = diff(RVadj), d_SMI = diff(SMI), .by="Sample") |>
rbind(rez)
}
rez
}
rez <- bootstrap_similarity(df)rez |>
summarize(PSI = mean(PSI),
p_PSI = as.numeric(bayestestR::pd(d_PSI, as_p=TRUE)),
RVadj = mean(RVadj),
p_RVadj = as.numeric(bayestestR::pd(d_RVadj, as_p=TRUE)),
SMI = mean(SMI),
p_SMI = as.numeric(bayestestR::pd(d_SMI, as_p=TRUE)),
.by = "Comparison") Comparison PSI p_PSI RVadj p_RVadj SMI p_SMI
1 Random vs. Facet 0.810526 0.448 0.768536 0.744 0.493086 0.524
2 Random vs. Modality 0.822352 0.448 0.784162 0.744 0.512444 0.524
3 Full vs. Facet 0.925638 0.212 0.908974 0.444 0.724340 0.436
4 Full vs. Modality 0.941368 0.212 0.929352 0.444 0.764880 0.436
rez |>
pivot_longer(-c("Comparison", "Sample", "Iteration")) |>
filter(name %in% c("PSI", "RVadj", "SMI")) |>
mutate(side = ifelse(str_detect(Comparison, "Facet"), "left", "right")) |>
ggplot(aes(x=name, y=value, fill=Comparison, color=Comparison)) +
ggdist::stat_halfeye(aes(side=side), position=position_dodge(width=0.3), alpha=0.7) +
facet_wrap(~Sample) +
scale_fill_manual(values=c("Random vs. Facet"="#F44336", "Random vs. Modality"="#2196F3", "Full vs. Facet"="#FF5722", "Full vs. Modality"="#00BCD4")) +
scale_color_manual(values=c("Random vs. Facet"="#F44336", "Random vs. Modality"="#2196F3", "Full vs. Facet"="#FF5722", "Full vs. Modality"="#00BCD4")) +
labs(x="Indices of Correlation Matrix Difference", y="Similarity") +
theme_minimal()
p_sim <- rez |>
pivot_longer(-c("Comparison", "Sample", "Iteration")) |>
filter(str_detect(name, "d_") & str_detect(Comparison, "Facet")) |>
filter(Sample == "Random") |>
mutate(name = str_remove(name, "d_"), value = -value) |> # Reverse score for figure aesthetics
ggplot(aes(x=value, y=name)) +
ggdist::stat_eye(aes(fill = after_stat(x < 0))) +
geom_vline(xintercept=0, linetype="dashed") +
labs(x="Similarity Difference", y="Indices of pattern difference",
title="Which matrix is the above more similar to?",
fill="More similar to") +
theme_minimal() +
scale_fill_manual(values=c("TRUE"="#4CAF50", "FALSE"="#2196F3"),
labels = c("By facet", "By modality"),
guide = guide_legend(reverse = TRUE)) +
theme(plot.title = element_text(hjust = 0.5, face="italic"))
r <- correlation::cor_sort(cor(byrandom))
order <- colnames(r)
make_corplot <- function(data, order) {
rownames_to_column(as.data.frame(cor(data)), "item") |>
pivot_longer(-item) |>
mutate(item = fct_relevel(item, order),
name = fct_relevel(name, order)) |>
ggplot(aes(x=name, y=item, fill=value)) +
geom_tile() +
# scale_fill_gradient2(low="blue", high="red", mid="white", midpoint=0, values=c(-1, 0, 1)) +
scale_fill_gradientn(
colors=c("blue", "white", "#FFEE58", "#FFC107", "#FF9800", "#FF5722",
"#F44336", "#E91E63", "#9C27B0", "#8231B4", "#673AB7", "white"),
values=c(-1, seq(0, 1, 0.1))) +
theme_minimal() +
theme(axis.text = element_blank(),
axis.title = element_blank(),
legend.position = "none")
}
p1 <- make_corplot(byrandom, order) +
ggtitle("Grouped randomly")
p2 <- make_corplot(byfacet, order) +
ggtitle("Grouped by facet")
p3 <- make_corplot(bymodality, order) +
ggtitle("Grouped by modality")
fig1 <- p1 / (p3 | p_sim | p2) +
patchwork::plot_layout(heights=c(3, 1)) +
patchwork::plot_annotation(
title="Item Correlation Matrix Similarity",
theme = theme(plot.title = element_text(hjust = 0.5, face="bold")))
fig1
ggsave("figures/fig1_correlations.png", fig1, scale = 1.25, width=2100, height=2100, dpi=300, units = "px")items |>
bayestestR::estimate_density(method="kernSmooth") |>
separate(Parameter, into=c("Facet", "Modality", "Item"), sep="_") |>
ggplot(aes(x=x, y=y)) +
geom_line(aes(color=Facet, group=interaction(Facet, Item)), linewidth=1, alpha=0.9) +
facet_wrap(~Modality) +
theme_minimal() +
scale_color_material_d(palette = "rainbow") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
uva <- EGAnet::UVA(items, cut.off = 0.25, reduce.method="remove", verbose=FALSE)
toremove1 <- unique(uva$keep_remove$remove)
keep <- uva$keep_remove$keep[!uva$keep_remove$keep %in% toremove1]
sorted <- correlation::cor_sort(cor(items[toremove1],items[keep]))
correlation::correlation(items[toremove1], items[keep]) |>
mutate(Parameter1 = fct_relevel(Parameter1, rownames(sorted)),
Parameter2 = fct_relevel(Parameter2, colnames(sorted))) |>
ggplot(aes(x=Parameter1, y=Parameter2, fill=r)) +
geom_tile() +
geom_text(aes(label=insight::format_value(r)), color="white") +
scale_fill_gradient2(low="blue", high="red", mid="white", midpoint=0, guide="none") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(x="Removed Items", y="Kept Items", fill="Correlation")
items <- select(items, -all_of(toremove1))
byfacet <- select(byfacet, -all_of(toremove1))
bymodality <- select(bymodality, -all_of(toremove1))
byrandom <- select(byrandom, -all_of(toremove1))We removed 4 items (Sexual_ColonBladder_Q1, Nociception_Respiratory_Q1, Nociception_Cardiac_Q2, Sensitivity_ColonBladder_Q4) based on Unique Variable Analysis (UVA).
ega1 <- bootEGA(
items,
EGA.type = "hierEGA",
model = "glasso", # BGGM
algorithm = "leiden",
type="resampling",
plot.itemStability=FALSE,
typicalStructure=TRUE,
plot.typicalGraph=FALSE,
iter=500,
seed=3, ncores = 4)
save(ega1, file="models/ega1.RData")load("models/ega1.RData")
# EGAnet::dimensionStability(ega)
itemstability <- EGAnet::itemStability(ega1, IS.plot=FALSE)
plot(itemstability)
make_loadingtable <- function(x) {
t <- as.data.frame(x) |>
datawizard::data_addprefix("C")
t$Cluster <- colnames(t)[max.col(t, ties.method='first')]
t |>
rownames_to_column(var="Item") |>
rowwise() |>
mutate(Max = max(c_across(-c(Item, Cluster)))) |>
arrange(Cluster, desc(Max)) |>
as.data.frame()
}
itemstability_table <- make_loadingtable(itemstability$lower_order$item.stability$all.dimensions)
# Remove items
toremove2 <- filter(itemstability_table, Max < 0.8)$ItemWe removed 40 items (Confusion_SkinThermo_Q1, Sensitivity_SkinThermo_Q6, Accuracy_SkinThermo_Q4, Accuracy_Gastric_Q7, Accuracy_Gastric_Q8, Accuracy_Gastric_Q3, Accuracy_Gastric_Q5, Nociception_State_Q2, Nociception_State_Q1, Accuracy_SkinThermo_Q2, Nociception_SkinThermo_Q3, Accuracy_SkinThermo_Q3, Confusion_Gastric_Q1, Accuracy_ColonBladder_Q4, Accuracy_ColonBladder_Q2, Accuracy_SkinThermo_Q1, Nociception_SkinThermo_Q2, Nociception_SkinThermo_Q1, Sensitivity_SkinThermo_Q8, Sensitivity_SkinThermo_Q4, Accuracy_Cardiac_Q2, Nociception_Gastric_Q1, Nociception_ColonBladder_Q2, Sensitivity_Cardiac_Q6, Sexual_Gastric_Q1, Sexual_ColonBladder_Q2, Anxious_State_Q5, Accuracy_Genital_Q2, Accuracy_Genital_Q1, Anxious_SkinThermo_Q1, Anxious_SkinThermo_Q2, Anxious_SkinThermo_Q4, Anxious_SkinThermo_Q3, Sexual_SkinThermo_Q3, Sexual_Gastric_Q2, Confusion_State_Q1, Confusion_State_Q2, Anxious_ColonBladder_Q1, Anxious_Gastric_Q1, Confusion_Genital_Q1) that had a low stability (< 0.8).
toremove2b <- itemstability_table |>
filter(!Item %in% toremove2) |>
mutate(Item, n=n(), .by="Cluster", .keep="used") |>
filter(n < 3) |> # Remove small clusters
pull(Item)
toremove2 <- c(toremove2, toremove2b)We removed 9 items (Accuracy_State_Q5, Accuracy_State_Q4, Anxious_Genital_Q1, Sexual_State_Q3, Anxious_State_Q3, Anxious_Cardiac_Q1, Anxious_Respiratory_Q1, Anxious_State_Q1, Anxious_State_Q2) that were in clusters with less than 3 items after the first removal.
# plot(itemstability)itemstability_table |>
select(-Cluster, -Max) |>
gt::gt() |>
gt::data_color(columns=-Item,
method = "numeric",
palette = c("white", "orange", "green"),
domain = c(0, 1)) |>
gt::data_color(columns=Item, fn=\(x) ifelse(x %in% toremove2, "#FFEBEE", "white")) |>
gt::tab_header(
title = gt::md("**Item Stability**"),
subtitle = "Proportion of times an item is assigned to the same cluster"
) |>
gt::fmt_number(columns=-Item, decimals=3)items <- select(items, -all_of(toremove2))
byfacet <- select(byfacet, -all_of(toremove2))
bymodality <- select(bymodality, -all_of(toremove2))
byrandom <- select(byrandom, -all_of(toremove2))ega2 <- bootEGA(
items,
EGA.type = "hierEGA",
model = "glasso", # BGGM
algorithm = "leiden", # walktrap
type="resampling",
plot.itemStability=FALSE,
typicalStructure=TRUE,
plot.typicalGraph=FALSE,
iter=500,
seed=3, ncores = 4)
save(ega2, file="models/ega2.RData")load("models/ega2.RData")
plot(itemStability(ega2, IS.plot=FALSE))
# itemstability_table <- make_loadingtable(itemstability$lower_order$item.stability$all.dimensions)
# toremove2 <- filter(itemstability_table, Max < 0.7)$Itemlower <- make_loadingtable(net.loads(ega2$EGA$lower_order)$std)
toremove3 <- lower$Item[!lower$Item %in% slice_max(lower, Max, n=3, by="Cluster")$Item]
lower |>
select(-Cluster, -Max) |>
gt::gt() |>
gt::data_color(columns=-Item,
method = "numeric",
palette = c("red", "white", "green"),
domain = c(-1, 1)) |>
gt::data_color(columns=Item, fn=\(x) ifelse(x %in% toremove3, "#FFEBEE", "white")) |>
gt::tab_header(
title = gt::md("**Item Loadings**"),
subtitle = "Node centrality"
) |>
gt::fmt_number(columns=-Item, decimals=3)items <- select(items, -all_of(toremove3))
byfacet <- select(byfacet, -all_of(toremove3))
bymodality <- select(bymodality, -all_of(toremove3))
byrandom <- select(byrandom, -all_of(toremove3))We kept the 3 items with the highest loading in their lower-level structure, renoving 13 items (Accuracy_ColonBladder_Q3, Sensitivity_Respiratory_Q2, Accuracy_Respiratory_Q1, Accuracy_State_Q1, Accuracy_State_Q3, Accuracy_Cardiac_Q1, Sensitivity_Cardiac_Q4, Nociception_Genital_Q2, Sexual_SkinThermo_Q2, Nociception_Cardiac_Q1, Sensitivity_SkinThermo_Q3, Sensitivity_Gastric_Q6, Sensitivity_ColonBladder_Q3).
ega3 <- bootEGA(
items,
EGA.type = "hierEGA",
model = "glasso", # BGGM
algorithm = "leiden", # walktrap
type="resampling",
plot.itemStability=FALSE,
typicalStructure=TRUE,
plot.typicalGraph=TRUE,
iter=500,
seed=3, ncores = 4)
save(ega3, file="models/ega3.RData")load("models/ega3.RData")
plot(itemStability(ega3))
dimensions <- list(
C01 = "UrIn", # Urointestinal Inaccuracy
C09 = "CaCo", # Cardiorespiratory Confusion
C13 = "CaNo", # Cardiorespiratory Noticing
C18 = "Olfa", # Olfactory Compensation
C03 = "Sati", # Satiety Noticing
C04 = "SexA", # Sexual Arousal Awareness
C06 = "SexS", # Sexual Arousal Sensitivity
C15 = "SexO", # Sexual Organs Sensitivity
C08 = "UrSe", # Urointestinal Sensitivity
C10 = "RelA", # Relaxation Awareness
C05 = "StaS", # State Specificity
C02 = "ExAc", # Expulsion Accuracy
C17 = "Card", # Cardioception
C14 = "Resp", # Respiroception
C12 = "Sign", # Signalling
C16 = "Gast", # Gastroception
C11 = "Derm", # Dermatoception
C07 = "SexC" # Sexual Arousal Changes
)
higher <- make_loadingtable(t(net.loads(ega3$EGA$higher_order, loading.method="revised")$std))
cluster_order <- names(higher)[!names(higher) %in% c("Item", "Cluster", "Max")]
lower <- make_loadingtable(net.loads(ega3$EGA$lower_order, loading.method="revised")$std) |>
select(names(higher)) |>
mutate(Cluster = fct_relevel(Cluster, cluster_order)) |>
arrange(Cluster, desc(Max))
higher$Item <- paste0("M", higher$Item)
higher$Label <- c("Interoceptive Deficit", "Interoceptive Awareness", "Interoceptive Sensitivity")
lower <- mutate(lower, Label = labels[Item], ID= Item, Item = 1:n())
tab1 <- rbind(higher, select(lower, -ID)) |>
datawizard::data_relocate("Label", after = "Item") |>
select(-Cluster, -Max) |>
# datawizard::data_rename(dimensions) |> # https://github.com/easystats/datawizard/issues/576
datawizard::data_rename(names(dimensions), dimensions) |>
mutate(`|` = "") |>
gt::gt() |>
gt::tab_row_group(
label = "Items",
rows = 1:(nrow(lower) + nrow(higher))
) |>
gt::tab_row_group(
label = "Metaclusters",
rows = 1:nrow(higher)
) |>
gt::data_color(columns=-Item,
method = "numeric",
palette = c("red", "white", "green"),
domain = c(-1, 1)) |>
gt::tab_style(
style = gt::cell_text(size="small", style="italic"),
locations = gt::cells_body(columns="Label", rows=c(4:57))
) |>
gt::tab_style(
style = list(gt::cell_text(weight="bold"),
gt::cell_fill(color = "#F5F5F5")),
locations = list(
gt::cells_row_groups(groups = "Items"),
gt::cells_row_groups(groups = "Metaclusters")
)
) |>
# Metacluster 1
gt::tab_style(
style = gt::cell_fill(color = "#B39DDB"),
locations = list(
gt::cells_column_labels(columns = 3),
gt::cells_body(columns="Item", rows=c(4:6))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#9FA8DA"),
locations = list(
gt::cells_column_labels(columns = 4),
gt::cells_body(columns="Item", rows=c(7:9))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#90CAF9"),
locations = list(
gt::cells_column_labels(columns = 5),
gt::cells_body(columns="Item", rows=c(1, 10:12))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#81D4FA"),
locations = list(
gt::cells_column_labels(columns = 6),
gt::cells_body(columns="Item", rows=c(13:15))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#80DEEA"),
locations = list(
gt::cells_column_labels(columns = 7),
gt::cells_body(columns="Item", rows=c(16:18))
)
) |>
# Metacluster 2
gt::tab_style(
style = gt::cell_fill(color = "#F3F29D"),
locations = list(
gt::cells_column_labels(columns = 8),
gt::cells_body(columns="Item", rows=c(19:21))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#E6EE9C"),
locations = list(
gt::cells_column_labels(columns = 9),
gt::cells_body(columns="Item", rows=c(22:24))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#D6E8A1"),
locations = list(
gt::cells_column_labels(columns = 10),
gt::cells_body(columns="Item", rows=c(25:27))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#C5E1A5"),
locations = list(
gt::cells_column_labels(columns = 11),
gt::cells_body(columns="Item", rows=c(2, 28:30))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#B5DCA6"),
locations = list(
gt::cells_column_labels(columns = 12),
gt::cells_body(columns="Item", rows=c(31:33))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#A5D6A7"),
locations = list(
gt::cells_column_labels(columns = 13),
gt::cells_body(columns="Item", rows=c(34:36))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#93D1B6"),
locations = list(
gt::cells_column_labels(columns = 14),
gt::cells_body(columns="Item", rows=c(37:39))
)
) |>
# Metacluster 3
gt::tab_style(
style = gt::cell_fill(color = "#CE93D8"),
locations = list(
gt::cells_column_labels(columns = 15),
gt::cells_body(columns="Item", rows=c(40:42))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#F48FB1"),
locations = list(
gt::cells_column_labels(columns = 16),
gt::cells_body(columns="Item", rows=c(43:45))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#EF9A9A"),
locations = list(
gt::cells_column_labels(columns = 17),
gt::cells_body(columns="Item", rows=c(3, 46:48))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#FFAB91"),
locations = list(
gt::cells_column_labels(columns = 18),
gt::cells_body(columns="Item", rows=c(49:51))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#FFCC80"),
locations = list(
gt::cells_column_labels(columns = 19),
gt::cells_body(columns="Item", rows=c(52:54))
)
) |>
gt::tab_style(
style = gt::cell_fill(color = "#FFE082"),
locations = list(
gt::cells_column_labels(columns = 20),
gt::cells_body(columns="Item", rows=c(55:57))
)
) |>
gt::fmt_number(columns=-Item, decimals=2) |>
gt::tab_header(
title = gt::md("**Item Loadings**"),
subtitle = "Node centrality"
) |>
gt::opt_vertical_padding(scale = 0.7) |>
gt::opt_horizontal_padding(scale = 0.5) |>
# gt::tab_options(table.width = gt::pct(100)) |>
gt::cols_width(Label ~ gt::pct(66),
`|` ~ gt::pct(1))
gt::gtsave(gt::cols_hide(tab1, `|`), "table1.html", inline_css = TRUE) # Save it and reload it otherwise formatting not ideal
Sys.setenv(CHROMOTE_CHROME = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe")
gt::gtsave(tab1, "table1.png", vwidth=1660, expand=0, quiet=TRUE)# REMOVE ONCE https://github.com/hfgolino/EGAnet/pull/167 is merged
convert2tidygraph <- function(EGA.object)
{
if("bootEGA" %in% class(EGA.object)) {
# Bootstrapped EGA ---
if(!"typicalGraph" %in% names(EGA.object)) {
stop("The bootEGA object must contain a typicalGraph object. Set `typicalStructure=TRUE`.")
}
if("lower_order" %in% names(EGA.object$typicalGraph)) {
# Hierarchical EGA ---
lower_nodes <- .convert2tidygraph_nodes(EGA.object$typicalGraph$lower_order$wc)
lower_nodes$dimension <- paste0("L", lower_nodes$dimension) # Discriminate from higher
lower_nodes$level <- "Lower"
higher_nodes <- .convert2tidygraph_nodes(EGA.object$typicalGraph$higher_order$wc)
higher_nodes$name <- paste0("L", as.numeric(higher_nodes$name))
higher_nodes$dimension <- paste0("H", higher_nodes$dimension) # Discriminate from lower
higher_nodes$level <- "Higher"
nodes <- rbind(lower_nodes, higher_nodes)
lower_edges <- .convert2tidygraph_edges(EGA.object$typicalGraph$lower_order$graph)
higher_edges <- .convert2tidygraph_edges(EGA.object$typicalGraph$higher_order$graph)
higher_edges$from <- paste0("L", as.numeric(higher_edges$from))
higher_edges$to <- paste0("L", as.numeric(higher_edges$to))
edges <- rbind(lower_edges, higher_edges)
edges$type <- "real"
# Make edges from lower to higher order
for(i in 1:nrow(lower_nodes)) {
edges <- rbind(
edges,
data.frame(from = lower_nodes$name[i], to = lower_nodes$dimension[i], link = 1, type = "virtual")
)
}
} else {
# Non-hierarchical EGA ---
nodes <- .convert2tidygraph_nodes(EGA.object$typicalGraph$wc)
edges <- .convert2tidygraph_edges(EGA.object$typicalGraph$graph)
}
} else {
# Normal EGA ---
nodes <- .convert2tidygraph_nodes(EGA.object$wc)
edges <- .convert2tidygraph_edges(EGA.object$network)
}
# Return result
return(
list(
nodes = nodes,
edges = edges[edges$link != 0,]
)
)
}
#' @keywords internal
#' @noRd
.convert2tidygraph_nodes <- function(wc){
data.frame(
name = names(wc),
dimension = as.character(wc)
)
}
#' @keywords internal
#' @noRd
.convert2tidygraph_edges <- function(network){
data.frame(
from = rep(rownames(network), times = ncol(network)),
to = rep(colnames(network), each = nrow(network)),
link = as.vector(network)
)
}#| warning: false
graph <- convert2tidygraph(ega3)
replace_lowernames <- function(names, lower) {
rownames(lower) <- lower$ID
sprintf("%02d", as.numeric(lower[names, "Item"]))
}
replace_highernames <- function(names) {
x <- as.numeric(str_remove(names, "L"))
paste0("C", sprintf("%02d", x))
}
# Arrange lower nodes in circle
lower_nodes <- graph$nodes |>
filter(level == "Lower") |>
mutate(name = fct_relevel(name, as.character(lower$ID)),
ID = name) |>
arrange(name)
lower_edges <- graph$edges |>
filter(from %in% lower_nodes$name & to %in% lower_nodes$name)
lower_layout <- tidygraph::tbl_graph(lower_nodes, edges=lower_edges, directed=FALSE) |>
ggraph::create_layout(layout = 'circle') |>
as.data.frame()
rownames(lower_layout) <- lower_nodes$name
# Arrange higher nodes in circle
higher_nodes <- graph$nodes |>
filter(level == "Higher") |>
mutate(ID = name,
name = replace_highernames(name),
name = fct_relevel(name, names(select(higher, -Item, -Cluster,-Max,-Label)))) |>
arrange(name)
higher_edges <- graph$edges |>
filter(from %in% higher_nodes$name & to %in% higher_nodes$name)
higher_layout <- tidygraph::tbl_graph(higher_nodes, edges=higher_edges, directed=FALSE) |>
ggraph::create_layout(layout = 'circle') |>
as.data.frame()
rownames(higher_layout) <- higher_nodes$ID
# Put circles together
layout <- tidygraph::tbl_graph(graph$nodes, edges=graph$edges, directed=FALSE) |>
ggraph::create_layout(layout = 'linear', circular = TRUE)
layout[layout$level == "Lower", "x"] <- lower_layout[layout[layout$level == "Lower", ]$name, "x"]
layout[layout$level == "Lower", "y"] <- lower_layout[layout[layout$level == "Lower", ]$name, "y"]
layout[layout$level == "Higher", "x"] <- higher_layout[layout[layout$level == "Higher", ]$name, "x"] * 0.5
layout[layout$level == "Higher", "y"] <- higher_layout[layout[layout$level == "Higher", ]$name, "y"] * 0.5
layout[layout$level == "Lower", "name"] <- replace_lowernames(layout[layout$level == "Lower", ]$name, lower)
layout[layout$level == "Higher", "name"] <- replace_highernames(layout[layout$level == "Higher", ]$name)
# Final renaming
layout[layout$level == "Higher", "name"] <- unlist(dimensions)[layout[layout$level == "Higher", ]$name]
layout[layout$level == "Higher", "dimension"] <- layout[layout$level == "Higher", ]$dimension |>
str_replace("H1", "Deficit") |>
str_replace("H2", "Awareness") |>
str_replace("H3", "Sensitivity")
# layout$dimmension_label <- layout$dimension
# layout[layout$level == "Higher", "dimmension_label"] <- NA
# layout[layout$level == "Higher", "dimension"] <- layout[layout$level == "Higher", "dimension"]
ggraph(layout) +
geom_edge_link(aes(edge_alpha=as.numeric(type == "virtual"), edge_width = abs(link)), edge_width=0.2, color="grey") +
geom_edge_arc(aes(edge_color = link, edge_width = abs(link), edge_alpha=as.numeric(type == "real"))) +
geom_node_point(aes(color=dimension, shape=level), size=12) +
geom_node_text(aes(label=name), fontface="bold") +
# geom_node_text(aes(label = dimmension_label, angle = node_angle(x, y), hjust = 'outward')) +
scale_edge_alpha(range = c(0, 0.9), guide="none") +
scale_edge_width(range = c(0.2, 7), guide="none") +
scale_edge_colour_gradientn(colors=c("#E53935", "#E53935", "white", "#43A047", "#43A047"),
values=c(-1, -0.5, 0, 0.5, 1), guide="none") +
scale_shape_manual(values=c("Higher"="square", "Lower"="circle"), guide="none") +
scale_color_manual(values=c(
"Deficit"="#90CAF9", "Awareness"="#C5E1A5", "Sensitivity"="#EF9A9A",
"L1"="#B39DDB", "L9"="#9FA8DA", "L13"="#90CAF9", "L18"="#81D4FA", "L3"="#80DEEA",
"L4"="#F3F29D", "L6"="#E6EE9C", "L15"="#D6E8A1", "L8"="#C5E1A5", "L10"="#B5DCA6", "L5"="#A5D6A7", "L2"="#93D1B6",
"L17"="#CE93D8", "L14"="#F48FB1", "L12"="#EF9A9A", "L16"="#FFAB91", "L11"="#FFCC80", "L7"="#FFE082"),
breaks=c("Deficit", "Awareness", "Sensitivity")) +
theme_void() +
theme(legend.title = element_text(face="bold")) +
labs(color="Metaclusters")
# EGAnet::EGM(items[, 1:60])
EGAnet::EGM.compare(items, model = "glasso", algorithm = "leiden")
rez <- EGAnet::network.compare(byfacet, bymodality, model = "glasso", seed=3)
plot(rez)# Modelbased scores
scores <- EGAnet::net.scores(
items[names(ega3$typicalGraph$lower_order$wc)],
A=ega3$typicalGraph$lower_order$graph,
wc=ega3$typicalGraph$lower_order$wc)$scores$std.scores |>
as.data.frame()
names(scores) <- paste0("C", names(scores))
# Empirical scores
clusters <- data.frame(Item = names(ega3$typicalGraph$lower_order$wc),
Cluster = ega3$typicalGraph$lower_order$wc) |>
mutate(Cluster = paste0("E", sprintf("%02d", Cluster)))
empirical <- data.frame(Participant=df$Participant)
for(c in unique(clusters$Cluster)) {
s <- rowMeans(items[clusters[clusters$Cluster==c, "Item"]])
empirical <- cbind(empirical, setNames(data.frame(x = s), c))
}
correlation::correlation(empirical, scores) |>
ggplot(aes(x=Parameter1, y=Parameter2, fill=r)) +
geom_tile() +
geom_text(aes(label=sprintf("%.2f", r)), size=3) +
scale_fill_gradient2(low="blue", high="red", mid="white", midpoint=0) +
labs(x="Empirical Scores", y="Model Scores") +
theme_minimal()