title: "Silas & Müllensiefen (2023) online supplement" output: bookdown::html_document2 vignette: > %\VignetteIndexEntry{Silas & Müllensiefen (2023) online supplement} %\VignetteEngine{bookdown::html_document2} %\VignetteEncoding{UTF-8} pkgdown: as_is: true
library(dplyr) library(tidyverse) library(itembankr) library(musicassessrdocs) library(papaja) library(r2glmm) library(lme4) library(car) knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
data_summary <- function(data, varname, groupnames){ library(plyr) summary_func <- function(x, col){ c(mean = mean(x[[col]], na.rm=TRUE), sd = sd(x[[col]], na.rm=TRUE), se = sd(x[[col]], na.rm=TRUE)/sqrt(length(x[[col]])) ) } data_sum<-ddply(data, groupnames, .fun=summary_func, varname) data_sum <- rename(data_sum, c("mean" = varname)) return(data_sum) } coef_variation <- function(v) { sd(v, na.rm = TRUE)/mean(v, na.rm = TRUE) } plot_descriptive_vs_predicted_participant <- function(model, pretty_var_name, var_name) { var_name <- as.name(var_name) prds <- tibble(pred = predict(model, na.action = na.exclude, type = "response"), attempt = musicassessrdocs::melodic_recall_paper_2023_main$attempt_numeric, p_id = musicassessrdocs::melodic_recall_paper_2023_main$p_id) prds <- prds %>% dplyr::group_by(attempt, p_id) %>% dplyr::summarise(pred = mean(pred, na.rm = TRUE)) %>% dplyr::ungroup() mean_by_attempt <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::group_by(attempt, p_id) %>% dplyr::summarise("{var_name}" := mean(!!var_name, na.rm = TRUE) ) %>% dplyr::ungroup() attempt_pred_vs_descript <- cbind(mean_by_attempt, prds %>% dplyr::select(-c(attempt, p_id))) %>% dplyr::rename(Attempt = attempt, `Predicted Model Values` = pred, `Empirical Descriptive Values` = !! var_name) %>% tidyr::pivot_longer(`Empirical Descriptive Values`:`Predicted Model Values`, names_to = "Name", values_to = pretty_var_name) ggplot(attempt_pred_vs_descript, aes(x = Attempt, y = !! as.name(pretty_var_name), group = Name, color = Name, linetype = Name)) + geom_point() + geom_line() + theme(legend.title=element_blank()) + facet_wrap(~p_id) + theme_minimal() } plot_descriptive_vs_predicted <- function(model, pretty_var_name, var_name) { var_name <- as.name(var_name) prds <- tibble(pred = predict(model, na.action = na.exclude, type = "response"), attempt = musicassessrdocs::melodic_recall_paper_2023_main$attempt) prds <- prds %>% dplyr::group_by(attempt) %>% dplyr::summarise(pred = mean(pred, na.rm = TRUE)) %>% ungroup() mean_by_attempt <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::group_by(attempt) %>% dplyr::summarise("{var_name}" := mean(!! var_name, na.rm = TRUE)) %>% dplyr::ungroup() attempt_pred_vs_descript <- cbind(mean_by_attempt, prds %>% dplyr::select(-attempt)) %>% dplyr::rename(Attempt = attempt, `Predicted Model Values` = pred, `Empirical Descriptive Values` = !! var_name) %>% tidyr::pivot_longer(`Empirical Descriptive Values`:`Predicted Model Values`, names_to = "Name", values_to = pretty_var_name) ggplot(attempt_pred_vs_descript, aes(x = Attempt, y = !! as.name(pretty_var_name), group = Name, color = Name, linetype = Name)) + geom_point() + geom_line() + theme(legend.title=element_blank()) + theme_minimal() } plot_descriptive_vs_predicted_melody <- function(model, pretty_var_name, var_name) { var_name <- as.name(var_name) prds <- tibble(pred = predict(model, na.action = na.exclude, type = "response"), attempt = musicassessrdocs::melodic_recall_paper_2023_main$attempt_numeric, unique_melody_name = musicassessrdocs::melodic_recall_paper_2023_main$unique_melody_name) prds <- prds %>% dplyr::group_by(attempt, unique_melody_name) %>% dplyr::summarise(pred = mean(pred, na.rm = TRUE)) %>% ungroup() mean_by_attempt <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::group_by(attempt, unique_melody_name) %>% dplyr::summarise("{var_name}" := mean(!!var_name, na.rm = TRUE)) %>% dplyr::ungroup() attempt_pred_vs_descript <- cbind(mean_by_attempt, prds %>% dplyr::select(-c(attempt, unique_melody_name))) %>% dplyr::rename(Attempt = attempt, `Predicted Model Values` = pred, `Empirical Descriptive Values` = !! var_name) %>% pivot_longer(`Empirical Descriptive Values`:`Predicted Model Values`, names_to = "Name", values_to = pretty_var_name) ggplot(attempt_pred_vs_descript, aes(x = Attempt, y = !! as.name(pretty_var_name), group = Name, color = Name, linetype = Name)) + geom_point() + geom_line() + theme(legend.title=element_blank()) + facet_wrap(~unique_melody_name) + theme_minimal() }
The following are supplementary materials for:
Silas, S., & Müllensiefen, D. (2023). Learning and recalling melodies: A computational investigation using the melodic recall paradigm. Music Perception.
stim_table <- musicassessrdocs::get_asset('melodic_recall_paper_2023/stimuli.xlsx') %>% readxl::read_excel() %>% dplyr::select(`Nr.`, DATEINAME, `KOMP./ INTERPRET`, GENRE, TEMPO) %>% dplyr::rename(Song = DATEINAME, `Composer/Interpreter` = `KOMP./ INTERPRET`, `Genre/Meter` = GENRE, Tempo = TEMPO) %>% dplyr::mutate(Song = tools::file_path_sans_ext(Song)) %>% dplyr::filter(!is.na(Song)) %>% dplyr::mutate(Song = case_when(Song == "Do You Want To Dance. mid" ~ "Do You Want To Dance?", TRUE ~ Song)) %>% dplyr::filter(`Nr.` %in% c(1, 4, 11, 25, 30, 31, 36, 2, 6, 7, 9, 26, 32, 35)) %>% dplyr::select(-`Nr.`) %>% dplyr::mutate(`No.` = row_number()) %>% dplyr::relocate(`No.`) %>% dplyr::mutate(`Composer/Interpreter` = stringr::str_remove(`Composer/Interpreter`, "u.a. ")) knitr::kable(stim_table)
# Create stimuli images mid_files <- musicassessrdocs::get_asset('melodic_recall_paper_2023/melausschnitte_midi') %>% list.files(pattern = '\\.mid$', full.names = TRUE, ignore.case = TRUE) purrr::walk(mid_files, ~midi2img::midi2img(f = .x))
knitr::include_graphics('1melchil-1.png')
knitr::include_graphics('melclimbup31-1.png')
knitr::include_graphics('melcoldcold26-1.png')
knitr::include_graphics('meldoyou30-1.png')
knitr::include_graphics('meldugehoerst9-1.png')
knitr::include_graphics('6mellonger-1.png')
knitr::include_graphics('melohcarol25-1.png')
knitr::include_graphics('meltakegood7-1.png')
knitr::include_graphics('2melthesky-1.png')
knitr::include_graphics('melyouare32-1.png')
knitr::include_graphics('4melgoodbye-1.png')
knitr::include_graphics('melenjoy11-1.png')
knitr::include_graphics('melloveis35-1.png')
knitr::include_graphics('melletmebe36-1.png')
musicassessrdocs::melodic_recall_paper_2023_melodies_with_features %>% itembankr::hist_item_bank() + theme_minimal()
musicassessrdocs::melodic_recall_paper_2023_melodies_with_features %>% dplyr::select(N, i.entropy, tonalness, step.cont.loc.var, d.entropy, mean_int_size, pitch_variety, int_variety, mean_information_content) %>% dplyr::rename(mean.int.size = mean_int_size, pitch.variety = pitch_variety, int.variety = int_variety, mean.information.content = mean_information_content) %>% dplyr::summarise(across(everything(), .fns = list(mean = mean, sd = sd, coef.variation = coef_variation)), .groups = 'rowwise') %>% dplyr::mutate(across(everything(), round, 2)) %>% pivot_longer(everything(), names_sep = "_", names_to = c("feature", "summary_var")) %>% pivot_wider(names_from = summary_var) %>% dplyr::rename(Feature = feature, Mean = mean, SD = sd, `Coefficient of Variation` = coef.variation) %>% arrange(desc(`Coefficient of Variation`)) %>% knitr::kable(caption = "Melodic feature summary statistics. Note, some are not used in our modelling, but are here to show other properties of the melodies.")
tibble::tibble( Variable = c("chorusin", "singinstr:", "yearsins", "musmakpa", "paidless", "paidgigs", "gigs"), Question = c( "Do you sing in a choir?", "Have you ever received singing instructions?", "For how many years have you been playing an instrument or making music?", "During your most active musical phase how many hours per week did you make music (practice+rehearsal+gigs+lessons+playing+etc.)", "For how many months have you received paid instrumental or singing lessons?", "How many gigs have you played that you have been paid for?", "Overall, how many gigs have you played in front of an audience in your life?" ), `Response Format` = c("Yes/No", "Yes/No", "__years", "__hours/week", " __ months", "___gigs", "___gigs") ) %>% knitr::kable()
fl <- lavaan::inspect(musicassessrdocs::melodic_recall_paper_2023_fit1, what="std")$lambda %>% as.data.frame() %>% tibble::rownames_to_column(var = "Variable") %>% dplyr::rename(Loading = f1) %>% dplyr::mutate(Loading = round(Loading, 2), h2 = round(Loading^2, 2), u2 = 1-h2) fl %>% knitr::kable()
plot_descriptive_vs_predicted_participant(model = musicassessrdocs::lm.A1.2, pretty_var_name = "Mean Attempt Lengh", var_name = 'attempt_length') + labs(title = "Figure 1", subtitle = "Model fitted vs. empirical descriptive means of attempt length, stratified by participant.") + theme(plot.subtitle=element_text(size=7))
plot_descriptive_vs_predicted_participant( model = musicassessrdocs::lm.B1.2, pretty_var_name = "Mean opti3", var_name = 'opti3') + labs(title = "Figure 2", subtitle = "Model fitted vs. empirical descriptive means of opti3, stratified by participant.") + theme(plot.subtitle=element_text(size=7))
Another way of visualising differences in performance is at the level of participant, coloured and ordered by level of musical experience. This is useful since it invokes no false dichotimisations and preserves the actual unit of participant (however, note that participant-level effects are captured by our mixed effects models).
As shown in Figure 2, participants seem to have vastly different slopes. The bottom right, lighter blue, higher musical experience participants (e.g., VP5, VP7, VP30, VP12, VP17, VP24, VP2) seem to have steeper slopes than the lower musical experience participants in the top left, darker coloured (VP14, VP28, VP21), suggesting that higher musical experience is related to quicker learning. However, note that this pattern is not the same for everyone e.g., VP22 has a steep slope, but scores low on musical experience
vp_summary <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::rename(`Musical Experience` = musical_experience) %>% dplyr::group_by(p_id, attempt) %>% dplyr::summarise(mean_opti3 = mean(opti3), mean_harmcore = mean(harmcore), mean_rhythfuzz = mean(rhythfuzz), mean_ngrukkon = mean(ngrukkon), `Musical Experience` = `Musical Experience`) vp_summary %>% ggplot(aes(x = as.numeric(attempt), y = as.numeric(mean_opti3))) + geom_smooth(method = "lm", size = .5) + geom_point(aes(color = `Musical Experience`)) + facet_wrap(~reorder(p_id, `Musical Experience`), nrow = 3) + labs(x = "Attempt", y = "Mean opti3", title = "Figure 3", subtitle = "Average by-participant development of opti3 across trials." ) + theme_minimal()
by_mel <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::group_by(unique_melody_name) %>% dplyr::summarise( attempt_length = mean(attempt_length, na.rm = TRUE), opti3 = mean(opti3, na.rm = TRUE), N = N, ) %>% ungroup() %>% arrange(desc(opti3)) %>% mutate(opti3 = round(opti3, 2)) %>% unique() by_mel_and_attempt <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::group_by(attempt, unique_melody_name) %>% dplyr::summarise( attempt_length = mean(attempt_length, na.rm = TRUE), opti3 = mean(opti3, na.rm = TRUE) ) %>% ungroup()
plot_descriptive_vs_predicted_melody( model = musicassessrdocs::lm.A1.2, pretty_var_name = "Mean Attempt Length", var_name = 'attempt_length') + labs(title = "Figure 4", subtitle = "Model fitted vs. empirical descriptive means attempt length, stratified by melody") + theme(plot.subtitle=element_text(size=7))
Melody r by_mel[[1, "unique_melody_name"]]
appears to be the easiest melody to recall (mean opti3 across all trials = r by_mel[[1, "opti3"]]
), whereas r by_mel[[nrow(by_mel), "unique_melody_name"]]
appears most difficult to recall (mean opti3 across all trials = r by_mel[[nrow(by_mel), "opti3"]]
). This shows that there can be substantial variation in the difficulty of each melody.
plot_descriptive_vs_predicted_melody( model = musicassessrdocs::lm.B1.2, pretty_var_name = "Mean opti3", var_name = 'opti3') + labs(title = "Figure 5", subtitle = "Model fitted vs. empirical descriptive means of opti3, stratified by melody") + theme(plot.subtitle=element_text(size=7))
\newpage
We proceed by using the log attempt as numeric predictor, owing to the observed non-linearities in both opti3 and attempt length across attempt. A comparison of linear vs non-linear models is shown below.
appen_g1 <- lmerTest::lmer(attempt_length ~ attempt + (1|unique_melody_name) + (1|p_id) + (1|p_id:unique_melody_name), data = musicassessrdocs::melodic_recall_paper_2023_main) appen_g1 |> apa_print() |> apa_table(caption = "A linear model of attempt length regressed onto attempt with melody item, participant and the interaction between melody item and participant as random effects. Note that the linear model is not taken forward.")
Figure 6 shows that the use of the log attempt as predictor is justified, capturing the systematic non-linear pattern generally well.
lmA1.p <- plot_descriptive_vs_predicted(model = musicassessrdocs::lm.A1.2, pretty_var_name = "Mean Attempt Length", var_name = 'attempt_length') + labs(caption = "Note: Dashed line represents changes in model-fitted values, solid line changes in empirical descriptive mean values.") + theme(legend.position = 'none') lmB1.p <- plot_descriptive_vs_predicted(model = musicassessrdocs::lm.B1.2, pretty_var_name = "Mean opti3", var_name = "opti3") + labs( title = "Figure 6", x = "", subtitle = "Model fitted vs. empirical descriptive means of attempt length and opti3, by attempt.") + theme(legend.position = 'top', plot.caption=element_text(size=7), plot.subtitle=element_text(size=10)) gridExtra::grid.arrange(lmA1.p, lmB1.p)
appen_g2 <- lmerTest::lmer(opti3 ~ attempt + (1|unique_melody_name) + (1|p_id) + (1|p_id:unique_melody_name), data = musicassessrdocs::melodic_recall_paper_2023_main) appen_g2 |> apa_print() |> apa_table()
make_vif_table <- function(mod) { round(car::vif(mod), 2) %>% as.data.frame() %>% tibble::rownames_to_column(var = "Predictor") %>% dplyr::rename(VIF = 2) }
# https://stats.stackexchange.com/questions/358927/compute-partial-eta2-for-all-fixed-effects-anovas-from-a-lme4-model # https://cran.r-project.org/web/packages/effectsize/vignettes/from_test_statistics.html#in-linear-mixed-models model_a_vif <- make_vif_table(musicassessrdocs::melodic_recall_paper_2023_lm.A2) save(model_a_vif, file = 'model_a_vif.rda')
load(file = 'model_a_vif.rda') model_a_vif %>% papaja::apa_table(caption = "Variation inflation factor (VIF) values for model with all features in and attempt length as dependent variable")
r2beta(musicassessrdocs::melodic_recall_paper_2023_lm.A2, method = "nsj") %>% papaja::apa_table("Partial R-Squared values for model with all features in and attempt length as dependent variable")
model_b_vif <- make_vif_table(musicassessrdocs::melodic_recall_paper_2023_lm.B2) save(model_b_vif, file = 'model_b_vif.rda')
load(file = 'model_b_vif.rda') model_b_vif %>% papaja::apa_table(caption = "Variation inflation factor (VIF) values for model with all features in and opti3 as dependent variable")
r2beta(musicassessrdocs::melodic_recall_paper_2023_lm.B2, method = "nsj") %>% papaja::apa_table("Partial R-Squared values for model with all features in and opti3 as dependent variable")
\newpage
musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::count(attempt, p_id) %>% dplyr::rename(Attempt = attempt) %>% data_summary(varname = "n", groupnames = "Attempt") %>% ggplot(aes(x = Attempt, y = n)) + geom_line(color = 'orange') + geom_point(color = 'orange') + geom_errorbar(aes(ymin=n-se, ymax=n+se), width=.2, position=position_dodge(0.05), color = "black") + ggplot2::labs(x = "Attempt", y = "Number of Trials", caption = "Error bars show the standard error.") + theme_minimal()
To assess whether the change across attempts depended on musical experience, we fitted a mixed effects model with trial count as the dependent variable, participant as random effect and the following fixed effects: linear terms for attempt and musical experience; an additional quadratic term for attempt; a linear interaction term for attempt and musical experience; and a quadratic interaction interaction term for musical experience. The model is presented below.
trial_counts <- musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::count(attempt_numeric, p_id) %>% left_join(musicassessrdocs::melodic_recall_paper_2023_main %>% dplyr::select(p_id, musical_experience) %>% unique(), by = "p_id") trial_counts_mod <- lmerTest::lmer(n ~ attempt_numeric + I(attempt_numeric^2) + musical_experience + attempt_numeric * musical_experience + I(attempt_numeric^2) * musical_experience + (1|p_id), data = trial_counts) trial_counts_mod |> apa_print() |> apa_table(caption = "Model of trial counts as a function of attempt number, musical experience and interactions")
early_late_o <- musicassessrdocs::melodic_recall_paper_2023_early_vs_late_thirds_long %>% mutate(attempt = as.numeric(attempt)) %>% lm(opti3 ~ melody_section + log(attempt), data = .) early_late_o |> apa_print() |> apa_table(caption = "Change in opti3 as a function of log attempt and section of sung recall (beginning, middle, end)")
early_late_ng <- musicassessrdocs::melodic_recall_paper_2023_early_vs_late_thirds_long %>% mutate(attempt = as.numeric(attempt)) %>% lm(ngrukkon ~ melody_section_n + log(attempt), data = .) early_late_ng |> apa_print() |> apa_table(caption = "Change in ngrukkon as a function of log attempt and section of sung recall (beginning, middle, end)")
early_late_r <- musicassessrdocs::melodic_recall_paper_2023_early_vs_late_thirds_long %>% mutate(attempt = as.numeric(attempt)) %>% lm(rhythfuzz ~ melody_section_r + log(attempt), data = .) early_late_r |> apa_print() |> apa_table(caption = "Change in rhythfuzz as a function of log attempt and section of sung recall (beginning, middle, end)")
early_late_hm <- musicassessrdocs::melodic_recall_paper_2023_early_vs_late_thirds_long %>% mutate(attempt = as.numeric(attempt)) %>% lm(harmcore ~ melody_section_h + log(attempt), data = .) early_late_hm |> apa_print() |> apa_table(caption = "Change in harmcore as a function of log attempt and section of sung recall (beginning, middle, end)")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.