Cross-wavelet analysis allows obtaining high temporal and frequency resolution in heart rate synchrony analysis.
The 3 matches
- [1] § Principles of cross-wavelet power analysis ↔ CrossWaveletPower_RScript.Rmd, lines 161–230 · score 0.80 · imaginary part, mother wavelet, Morlet wavelet, daughter wavelet, sine waves, Wavelet transform
- [2] § Principles of cross-wavelet power analysis ↔ CrossWaveletPower_RScript.Rmd, lines 76–159 · score 0.64 · widely known Fourier, Fourier transform, frequency components, Wavelet transform, amplitudes, power
- [3] § Principles of cross-wavelet power analysis ↔ CrossWaveletPower_RScript.Rmd, lines 161–230 · score 0.57 · shifted version, Morlet wavelet, daughter wavelet, compressed
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R Markdown · 663 lines · 23 KB · no license · 3 matches
- ---
- title: "R Script for the manuscript ''Cross-wavelet power analysis allows obtaining high temporal and spatial resolution in physiological synchrony analysis"
- output: html_document
- date: "2026-04-20"
- ---
- ```{r setup, include=FALSE}
- knitr::opts_chunk$set(echo = TRUE)
- ```
- ## Synchrony analysis using cross-wavelet power
- Simulated data to visualize different types of synchrony using cross-wavelet power plots, compared to cross-correlation analysis.
- ### Load libraries and set working directory:
- ```{r lib}
- setwd("/home/bernadette/Documents/Papers/Wavelet_paper")
- library(WaveletComp)
- library(ggplot2)
- library(SUSY)
- library(grid)
- set.seed(100)
- ```
- ### Different synchrony types
- Interpersonal synchrony is defined as the correspondence between individuals' trajectories,which can take on different forms: in-phase, anti-phase, and shifted synchrony are basic synchrony types.
- ```{r synctype_examples}
- # base time series
- ts1 <- sin(seq(1,20, length.out = 1000))
- # in-phase synchrony
- ts2 <- ts1-0.6
- # anti-phase synchrony
- ts3 <- -ts1-0.2
- # shifted synchrony (person 1 leading)
- ts4 <- cos(seq(1, 20, length.out = 1000))-0.2
- # shifted synchrony (person 1 following)
- ts5 <- -cos(seq(1, 20, length.out = 1000))-0.2
- # in-phase synchrony with higher-frequency signal
- ts6 <- sin(seq(1,50, length.out = 1000))
- ts7 <- ts6-0.6
- # change from synchronized to de-synchronized
- ts8 <- periodic.series(start.period = 330,
- length = 1000, phase = 54,
- end.period = 115)-0.6
- # data frame to store time series combinations
- combs_df <- data.frame(ts_a = c(rep("ts1", 4), "ts6", "ts1"),
- ts_b = c(paste("ts", 2:5, sep = ""),
- "ts7", "ts8"))
- df <- data.frame(index = 1:1000,
- ts1 = ts1, ts2 = ts2, ts3 = ts3,
- ts4 = ts4, ts5 = ts5,
- ts6 = ts6, ts7 = ts7,
- ts8 = ts8)
- # Plot all relevant combinations of time series
- par(mfrow = c(4,2))
- plt_list1 <- list()
- for (comb in 1:nrow(combs_df)){
- ts_a_comb <- combs_df$ts_a[comb]
- ts_b_comb <- combs_df$ts_b[comb]
- plot(df$index, df[, ts_a_comb],
- type = "l", xlab = "Time",
- ylab = "Marker", ylim = c(-2, 2),
- cex.lab=1.5, cex.axis=1.5, cex.main = 2, col = "darkgrey",
- lwd = 1.5, main = LETTERS[comb])
- lines(df$index, df[, ts_b_comb], col = "#00A9E0",
- lwd = 1.5)
- }
- ```
- ### Principles of cross-wavelet power
- To understand cross-wavelet power, we start with the principle behind wavelet transform. Wavelet transform is similar to the more widely-known Fourier transform. In both analyses, a signal is decomposed into its frequency components.
- ```{r fourier_example}
- # Define periodic time series with different frequencies
- x1 <- periodic.series(start.period = 80, length = 1000)
- x2 <- periodic.series(start.period = 30, length = 1000)
- x3 <- periodic.series(start.period = 100, length = 1000)
- x4 <- periodic.series(start.period = 10, length = 1000)
- # Add x1-x4
- x_add <- 3*x1 + 1.4*x2 + 0.7*x3 + 1*x4
- # Plot the underlying frequency components and the
- # added resulting time series
- layout(matrix(c(0,0,0,0,0,0,
- 0,1,0,2,0,5,
- 0,0,0,0,0,5,
- 0,3,0,4,0,5,
- 0,0,0,0,0,0),
- 5, 6, byrow=TRUE),
- heights = c(0.4,1.1,0.1,1.1,0.1),
- widths = c(0.05,0.5,0.05,0.5,0.05, 0.7))
- par(mar = c(2.5, 2.5, 1, 1), oma = c(0, 0, 0, 0))
- plot(1:1000, 3*x1, type = "l", col = "black", lwd = 1,
- main = "",
- ylab = "Amplitude", xlab = "Time")
- par(xpd = TRUE)
- mtext("A", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 0.1, cex = 1.5)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(1:1000, 1.4*x2, type = "l", col = "black", lwd = 1,
- main = "", ylim = c(-3,3),
- ylab = "Amplitude", xlab = "Time")
- mtext("B", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 0.1, cex = 1.5)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(1:1000, 0.7*x3, type = "l", col = "black", lwd = 1,
- main = "", ylim = c(-3,3),
- ylab = "Amplitude", xlab = "Time")
- par(mar = c(2.5, 2.5, 1, 1))
- par(xpd = TRUE)
- mtext("C", side = 3, line = 1, adj = -0.05, col = "grey40",
- padj = -0.1, cex = 1.5)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(1:1000, 1*x4, type = "l", col = "black", lwd = 1,
- main = "",
- ylab = "Amplitude", xlab = "Time",
- ylim = c(-3,3))
- par(mar = c(2.5, 2.5, 1, 1))
- par(xpd = TRUE)
- mtext("D", side = 3, line = 1, adj = -0.05, col = "grey40",
- padj = -0.1, cex = 1.5)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(1:1000, x_add, type = "l", col = "black", lwd = 1,
- main = "",
- ylab = "Amplitude", xlab = "Time")
- par(mar = c(2.5, 2.5, 1, 1))
- par(xpd = TRUE)
- mtext("E", side = 3, line = 1, adj = -0.05, col = "grey40",
- padj = -0.1, cex = 1.5)
- par(xpd = FALSE)
- # Demonstrate the decomposition into frequency bands
- # 1. using Fourier transform
- spec.pgram(x_add,
- taper = 0.1,
- log = "no",
- xlab = "Frequency (Hz)",
- ylab = "Power",
- main = "Periodogram", xlim = c(0,0.2))
- # -> the peaks correspond to the frequencies used in the data
- # 2. using wavelet transform
- x_add_df <- data.frame(x_add = x_add)
- wp_x_add <- analyze.wavelet(x_add_df, loess.span = 0,
- verbose = F)
- wt.image(wp_x_add)
- # -> the red lines correspond to the frequencies used in the data
- ```
- Instead of decomposing the signal into sine waves, wavelet transform decomposes the signal into daughter wavelets. Below are the Morlet wavelet and some exemplary daughter wavelets.
- ```{r wavelets}
- # Define a function to create the wavelet
- morlet_func <- function(t, omega0 = 6) {
- pi^(-1/4) * exp(1i * omega0 * t) * exp(-0.5 * t^2)
- }
- # Cycling frequency set to 6
- omega0 = 6
- # Time vector
- t <- seq(-5, 5, length.out = 500)
- # Morlet mother wavelet
- psi <- morlet_func(t)
- # Compressed daugther wavelet
- # Scaling factor 0.5
- s = 0.5
- psi_compr <- pi^(-1/4) *
- (1 / sqrt(s)) *
- exp(1i * 6 * (t / s)) *
- exp(-0.5 * (t / s)^2)
- # Shifted version
- t2 <- seq(-5, 10, length.out = 550)
- psi2 <- morlet_func(t2)
- psi_shftd <-
- pi^(-1/4) * exp(1i * omega0 * (t2-4)) * exp(-0.5 * (t2-4)^2)
- # Plot the different wavelets
- layout(matrix(c(0,0,0,0,0,
- 0,1,0,2,0,
- 0,1,0,0,0,
- 0,1,0,3,0,
- 0,0,0,0,0),
- 5, 5, byrow=TRUE),
- heights = c(0.4,1.1,0.1,1.1,0.1),
- widths = c(0.05,0.5,0.05,0.5,0.05))
- par(mar = c(2.5, 2.5, 1, 1), oma = c(0, 0, 0, 0))
- plot(t, Im(psi), type = "l", col = "grey", lwd = 2,
- main = "",
- ylab = "Amplitude", xlab = "Time",
- ylim = c(-1.5, 1.5))
- lines(t, Re(psi), col = "black", lwd = 2)
- legend("topright", legend = c("Real part", "Imaginary part"),
- col = c("black", "grey"), lty = 1, lwd = 2)
- par(xpd = TRUE)
- mtext("A", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 0.1, cex = 2)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(t, Re(psi), type = "l", col = "black", lwd = 2,
- main = "",
- ylab = "Amplitude", xlab = "Time",
- ylim = c(-1.5, 1.5))
- lines(t, Re(psi_compr), col = "#00A9E0", lwd = 2)
- mtext("B", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 0.1, cex = 2)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(t2, Re(psi2), type = "l", col = "black", lwd = 2,
- main = "",
- ylab = "Amplitude", xlab = "Time",
- xlim = c(-5, 9), ylim = c(-1.5, 1.5))
- lines(t2, Re(psi_shftd), col = "#00A9E0", lwd = 2)
- par(mar = c(2.5, 2.5, 1, 1))
- par(xpd = TRUE)
- mtext("C", side = 3, line = 1, adj = -0.05, col = "grey40",
- padj = -0.1, cex = 2)
- par(xpd = FALSE)
- ```
- Cross-wavelet power is the overlap between two wavelet-transformed signals.
- ```{r create_wt_to_cwp}
- # Add noise to each time series (newly generated every time)
- noise_func <- function(sd = 1){
- 0.2*rnorm(1000, sd = sd)
- }
- # Simulate time series with increasing and decreasing period, respectively
- ts1 <- periodic.series(start.period = 2, end.period = 64,
- length = 1000)+noise_func()
- ts2 <- periodic.series(start.period = 64, end.period = 2,
- length = 1000)+noise_func()
- # Combine into a data frame
- df1 <- data.frame(time = 1:1000, ts1 = ts1,
- ts2 = ts2)
- # Calculate wavelet transform of each time series and cross-wavelet power
- w1 <- analyze.coherency(df1, c("ts1", "ts2"), verbose = F)
- # Plot the raw and wavelet-transformed time series as well as the cross-wavelet power
- layout(matrix(c(0,0,0,0,0,
- 0,1,0,0,0,
- 0,2,0,5,0,
- 0,0,0,5,0,
- 0,3,0,5,0,
- 0,4,0,0,0,
- 0,0,0,0,0),
- 7, 5, byrow=TRUE),
- heights = c(0.1,1.1, 1.1, 0.1,1.1, 1.1,0.1),
- widths = c(0.05,0.5,0.05, 0.5,0.05,0.05))
- par(mar = c(2.5, 2.5, 1, 1), oma = c(0, 0, 0, 0))
- plot(ts1, type = "l", cex.axis = 1.5)
- par(xpd = TRUE)
- mtext("A", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 1.6, cex = 2)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wt.image(w1, my.series = 1, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(ts2, type = "l", cex.axis = 1.5)
- par(xpd = TRUE)
- mtext("B", side = 3, line = 1, adj = -0.1, col = "grey40",
- padj = 1.6, cex = 2)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wt.image(w1, my.series = 2, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(w1, color.key = "i", graphics.reset = FALSE)
- grid.rect(x = 0.25, y = 0.74, width = 0.48, height = 0.48,
- gp = gpar(col = "grey40", lwd = 1.3, fill = NA))
- grid.rect(x = 0.25, y = 0.25, width = 0.48, height = 0.48,
- gp = gpar(col = "grey40", lwd = 1.3, fill = NA))
- grid.rect(x = 0.73, y = 0.53, width = 0.47, height = 0.55,
- gp = gpar(col = "grey40", lwd = 1.3, fill = NA))
- par(xpd = TRUE)
- mtext("C", side = 3, line = 1, adj = -0.05, col = "grey40",
- padj = -0.1, cex = 2)
- par(xpd = FALSE)
- ```
- ### Synchrony analysis of simulated data
- Next, we apply cross-wavelet power and cross-correlational analysis to simulated data.
- ```{r data_sim}
- # Simulate time series
- ts1 = periodic.series(start.period = 64,
- length = 1000)+noise_func()
- # TS2: in-phase synchrony
- ts2 = ts1-0.9+noise_func()
- # TS3: antiphase synchrony
- ts3 = -ts1-0.2+ noise_func()
- # TS4: shifted synchrony
- ts4 = periodic.series(start.period = 64, phase = -20,
- length = 1000)+noise_func()
- # TS5: non-synchronous time series
- ts5 = periodic.series(start.period = 15, phase = -20,
- length = 1000)+noise_func()
- # Store time series in data frame
- df <- data.frame(time = 1:1000, ts1 = ts1,
- ts2 = ts2, ts3 = ts3,
- ts4 = ts4, ts5 = ts5)
- ```
- Calculate cross-wavelet power for the simulated time series
- ```{r types_cwp}
- wc1 <- analyze.coherency(df, c("ts1", "ts2"), verbose = F,
- lowerPeriod = 8, upperPeriod = 128)
- wc.image(wc1, color.key = "i")
- wc2 <- analyze.coherency(df, c("ts1", "ts3"), verbose = F,
- lowerPeriod = 8, upperPeriod = 128)
- wc.image(wc2, color.key = "i")
- wc3 <- analyze.coherency(df, c("ts1", "ts4"), verbose = F,
- lowerPeriod = 8, upperPeriod = 128)
- wc.image(wc3, color.key = "i")
- wc4 <- analyze.coherency(df, c("ts1", "ts5"), verbose = F,
- lowerPeriod = 8, upperPeriod = 128)
- wc.image(wc4, color.key = "i")
- ```
- Comparison to SUSY:
- Customized SUSY function (only plot title is changed).
- ```{r plot_susy_function}
- plot_susy_custom <- function (x, type = c(4, 5), ...)
- {
- if (!inherits(x, "susy"))
- stop("'x' must be an object of class 'susy'")
- if (!is.numeric(type))
- stop("'type' argument must be numeric")
- type = as.integer(type)
- if (anyNA(type) || any(type > 5L) || any(type < 1L))
- stop("'type' values must be in range of 1 to 5")
- if (anyDuplicated(type))
- stop("'type' values must be unique")
- first.plot = TRUE
- plot1 = function(x) {
- a = x$data[[1L]]
- b = x$data[[2L]]
- variablenname1 = names(x$data)[1L]
- variablenname2 = names(x$data)[2L]
- size = x$params$size
- segment = x$params$segment
- anzahlPseudosProEpoche = x$params$anzahlPseudosProEpoche
- numberEpochen = x$params$numberEpochen
- maxlagHz = x$params$maxlagHz
- meanccorrReal = x$lagtimes2.data$meanccorrReal
- meanccorrPseudo = x$lagtimes2.data$meanccorrPseudo
- meanccorrRealZ = x$lagtimes2.data$meanccorrRealZ
- meanccorrPseudoZ = x$lagtimes2.data$meanccorrPseudoZ
- meanccorrRealZNotAbs = x$lagtimes2.data$meanccorrRealZNotAbs
- meanccorrPseudoZNotAbs = x$lagtimes2.data$meanccorrPseudoZNotAbs
- nReal = x$segment.data$nReal
- nPseudo = x$segment.data$nPseudo
- for (t in type) {
- if (!first.plot)
- dev.new()
- else first.plot <<- FALSE
- if (t == 1L) {
- min0 = min(meanccorrReal, meanccorrPseudo)
- max0 = max(meanccorrReal, meanccorrPseudo)
- title0 = ""
- plot(seq(from = -maxlagHz, to = maxlagHz), meanccorrReal,
- ylim = c(min0, max0 + 0.19 * (max0 - min0)),
- main = title0, xlab = "lag", ylab = "correlation",
- type = "l", col = "green", lwd = 4)
- lines(seq(from = -maxlagHz, to = maxlagHz), meanccorrPseudo,
- col = "red", lwd = 4)
- legend("topright", pch = c(3), col = c("green",
- "red"), legend = c("meanccorr", "meanccorr pseudo"))
- }
- else if (t == 2L) {
- title0 = ""
- plot(seq(from = 1, to = numberEpochen), nReal,
- ylim = c(min(nReal, nPseudo), max(nReal, nPseudo)),
- main = title0, xlab = "segment", ylab = "correlation",
- type = "l", col = "green")
- lines(seq(from = 1, to = numberEpochen), nPseudo,
- col = "red", lwd = 2)
- legend("topright", pch = c(3), col = c("green",
- "red"), legend = c("real", "pseudo"))
- }
- else if (t == 3L) {
- min0 = min(meanccorrRealZ, meanccorrPseudoZ)
- max0 = max(meanccorrRealZ, meanccorrPseudoZ)
- title0 = ""
- plot(seq(from = -maxlagHz, to = maxlagHz), meanccorrRealZ,
- ylim = c(min0, max0 + 0.19 * (max0 - min0)),
- main = title0, xlab = "lag", ylab = "correlation",
- type = "l", col = "green", lwd = 4)
- lines(seq(from = -maxlagHz, to = maxlagHz), meanccorrPseudoZ,
- col = "red", lwd = 4)
- legend("topright", pch = c(3), col = c("green",
- "red"), legend = c("Z(meanccorr)", "Z(meanccorr pseudo)"))
- }
- else if (t == 4L) {
- title0 = ""
- plot(seq(from = 1, to = size), a, ylim = c(min(a,
- b), max(a, b)), main = "", xlab = "Zeit",
- ylab = "Wert", type = "l", pch = 20, col = "green")
- points(seq(from = 1, to = size), b, type = "l",
- pch = 20, col = "red", lwd = 2)
- legend("topright", pch = c(20, 20), col = c("green",
- "red"), legend = c(variablenname1, variablenname2))
- }
- else if (t == 5L) {
- min0 = min(meanccorrRealZNotAbs, meanccorrPseudoZNotAbs)
- max0 = max(meanccorrRealZNotAbs, meanccorrPseudoZNotAbs)
- title0 = ""
- plot(seq(from = -maxlagHz, to = maxlagHz), meanccorrRealZNotAbs,
- ylim = c(min0, max0 + 0.19 * (max0 - min0)),
- main = title0, xlab = "lag", ylab = "correlation",
- type = "l", col = "green", lwd = 4)
- lines(seq(from = -maxlagHz, to = maxlagHz), meanccorrPseudoZNotAbs,
- col = "red", lwd = 4)
- legend("topright", pch = c(3), col = c("green",
- "red"), legend = c("real", #"Z(meanccorr not ABS)",
- "pseudo"))#"Z(meanccorr pseudo not ABS)"))
- }
- }
- }
- lapply(x, plot1)
- invisible()
- }
- ```
- Make data frame compatible with SUSY package:
- ```{r susy_example_1}
- susy_df1 <- data.frame(ts1 = ts1,
- ts2 = ts2,
- ts3 = ts3,
- ts4 = ts4,
- ts5 = ts5)
- ```
- Plot of simulated data with cross-wavelet power and cross-correlation outcomes:
- ```{r plot_cwp_susy_results}
- layout(matrix(c(0,0,0,0,0,0,0,
- 0,1,0,5,0,9,0,
- 0,2,0,6,0,10,0,
- 0,3,0,7,0,11,0,
- 0,4,0,8,0,12,0,
- 0,0,0,0,0,0,0),
- 6, 7, byrow=TRUE),
- heights = c(0.1,1.1, 1.1, 1.1,1.1,0.1),
- widths = c(0.05,0.5,0.05, 0.5,0.05,0.5,0.05))
- par(mar = c(2.5, 2.5, 1, 1), oma = c(0, 0, 0, 0))
- plot(ts1, type = "l",
- ylim = c(min(c(ts1, ts2)),
- max(c(ts1, ts2))))
- lines(ts2, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("A", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(ts1, type = "l",
- ylim = c(min(c(ts1, ts3)),
- max(c(ts1, ts3))))
- lines(ts3, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("B", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(ts1, type = "l",
- ylim = c(min(c(ts1, ts4)),
- max(c(ts1, ts4))))
- lines(ts4, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("C", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(ts1, type = "l",
- ylim = c(min(c(ts1, ts5)),
- max(c(ts1, ts5))))
- lines(ts5, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("D", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc1, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc2, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc3, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc4, color.key = "i", graphics.reset = FALSE)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df1[, c(1,2)], segment = 60, Hz = 1, maxlag = 25), type = 5)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df1[, c(1,3)], segment = 60, Hz = 1, maxlag = 25), type = 5)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df1[, c(1,4)], segment = 60, Hz = 1, maxlag = 25), type = 5)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df1[, c(1,5)], segment = 60, Hz = 1, maxlag = 25), type = 5)
- ```
- Next, we consider simulated data containing changes in synchrony over time, specifically:
- - changes from non-synchronized to synchronized
- - changes from synchronized on 1 frequency band to synchronized on 2 frequency bands
- - changes from synchronized on some frequency bands to synchronized on other frequency bands
- ```{r cwp_temp}
- # define base TS
- tsb <- periodic.series(start.period = 64,
- length = 1000)+
- periodic.series(start.period = 16,
- length = 1000)+
- periodic.series(start.period = 4,
- length = 1000)+
- noise_func()
- # changes from non-synchronized to synchronized
- ts6 <- periodic.series(start.period = 30,
- end.period = 64,
- length = 1000)+
- periodic.series(start.period = 8,
- end.period = 16,
- length = 1000)+
- periodic.series(start.period = 2,
- end.period = 4,
- length = 1000)+
- noise_func()
- # changes from synchronized on 1 frequency band to synchronized on 2 frequency bands
- ts7 <- periodic.series(start.period = 64,
- length = 1000)+
- periodic.series(start.period = 8,
- length = 1000)+
- periodic.series(start.period = 2,
- end.period = 4,
- length = 1000)+
- noise_func()
- # changes from synchronized on some frequency bands to synchronized on other frequency bands
- # define other base time series
- tsb_2 <- periodic.series(start.period = 64,
- end.period = 16,
- length = 1000)+
- periodic.series(start.period = 2,
- end.period = 2,
- length = 1000)+
- noise_func()
- # define synchronized time series
- ts8 <- periodic.series(start.period = 64,
- end.period = 16,
- length = 1000)+
- periodic.series(start.period = 8,
- length = 1000)+
- periodic.series(start.period = 4,
- end.period = 4,
- length = 1000)+
- noise_func()
- # Store simulated data in data frame
- df_time <- data.frame(time = 1:1000,
- tsb = tsb,
- tsb_2 = tsb_2,
- ts6 = ts6,
- ts7 = ts7,
- ts8 = ts8)
- # Calculate cross-wavelet power
- wc5 <- analyze.coherency(df_time, c("tsb", "ts6"),
- verbose = F)
- wc6 <- analyze.coherency(df_time, c("tsb", "ts7"),
- verbose = F)
- wc7 <- analyze.coherency(df_time, c("tsb_2", "ts8"),
- verbose = F)
- ```
- ```{r ccf_examples}
- # Store the simulated data in a data frame suitable for analysis with SUSY
- susy_df <- data.frame(f1 = tsb,
- f2 = ts6,
- f1 = tsb,
- f3 = ts7,
- f4 = tsb_2,
- f5 = ts8)
- ```
- ```{r cwp_ccf_time_freq_plot}
- # Plot synchrony changes over time using cross-wavelet power and cross-corelation analyses
- layout(matrix(c(0,0,0,0,0,0,0,
- 0,1,0,4,0,7,0,
- 0,2,0,5,0,8,0,
- 0,3,0,6,0,9,0,
- 0,0,0,0,0,0,0),
- 5, 7, byrow=TRUE),
- heights = c(0.1,1.1, 1.1, 1.1,1.1,0.1),
- widths = c(0.05,0.5,0.05, 0.5,0.05,0.5, 0.05))
- par(mar = c(2.5, 2.5, 1, 1), oma = c(0, 0, 0, 0))
- plot(tsb, type = "l",
- ylim = c(min(c(tsb, ts6)),
- max(c(tsb, ts6))))
- lines(ts6, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("A", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(tsb, type = "l",
- ylim = c(min(c(tsb, ts7)),
- max(c(tsb, ts7))))
- lines(ts7, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("B", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot(tsb_2, type = "l",
- ylim = c(min(c(tsb_2, ts8)),
- max(c(tsb_2, ts8))))
- lines(ts8, col = "#00A9E0")
- par(xpd = TRUE)
- mtext("C", side = 3, line = 1, adj = -0.2, col = "grey40",
- padj = 1.6, cex = 1.6)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc5, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc6, color.key = "i", graphics.reset = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- wc.image(wc7, color.key = "i", graphics.reset = FALSE)
- par(xpd = FALSE)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df[, c(1,2)], segment = 60, Hz = 1,
- maxlag = 25),
- type = 2)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df[, c(3,4)], segment = 60, Hz = 1,
- maxlag = 25),
- type = 2)
- par(mar = c(2.5, 2.5, 1, 1))
- plot_susy_custom(susy(susy_df[, c(5,6)], segment = 60, Hz = 1,
- maxlag = 25),
- type = 2)
- ```
CrossWaveletPower_RScript.Rmd at commit 3b960c7, no license · at the source
Overview
- Department of Psychology, University of Konstanz, Konstanz, Germany
- Centre for the Advanced Study of Collective Behaviour, University of Konstanz, Konstanz, Germany
- Department of Biology, Centre for Vision Research, York University, Toronto, ON, Canada
- Department of Child and Adolescent Psychiatry/Psychotherapy, University of Ulm, Ulm, Germany
Abstract
Interpersonal synchrony, the temporal correspondence of repeated behavioral, physiological, or neural measures between individuals, has been analyzed using a variety of different methods. However, the analysis method needs to be carefully chosen to ensure an adequate interpretation of the results. Here, we suggest using cross-wavelet power for interpersonal synchrony analysis due to several advantages. We demonstrate the proposed method using the example of heart rate synchrony. In cross-wavelet power analysis, synchrony is determined per frequency band and time point, allowing for both fine-grained frequency and temporal resolution. We argue that applying this approach to analyze synchrony in heart rate data provides additional information about underlying processes that may influence overall heart rate alignment between individuals. We describe the principles of cross-wavelet power analysis and compare the method to the frequently used cross-correlational approach using simulated and real data. Cross-correlation is a linear measure of the similarity between time series, which includes the quantification of leader-follower relationships. We illustrate different implications for which data series are considered to be synchronous and describe the advantages and drawbacks of using cross-wavelet analysis across various possible synchronization scenarios. The main advantage of cross-wavelet power is its high time- and frequency resolution, whereas cross-correlation is more suitable when researchers aim to differentiate between synchrony types. Finally, we provide recommendations for implementing cross-wavelet power analysis, including R code to facilitate the application to one’s own data.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.
gitlab.inf.uni-konstanz.de/bernadette.denk/cwp_for_sync_analysis
3b960c7d8262fd4498ff2ffe6bdb1a134ca750a6, 20 April 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
2 files
- CrossWaveletPower_RScrip
t.Rmd , R, 663 lines, 3 matches - README.md, Text, 7 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 1 script, each with its path and the digest of its content;
- 3 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
The original contributions presented in the study are included in the article/
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 6 authors, 8 keywords, 1 funder, 49 references.
Cite
This paper
Denk, B. F., Wienhold, S., Volkmer, N., Troje, N. F., Meier, M., & Pruessner, J. C. (2026). Cross-wavelet analysis allows obtaining high temporal and frequency resolution in heart rate synchrony analysis. Frontiers in network physiology, 6, 1869004. https://
BibTeX
@article{denk2026cross,
author = {Denk, Bernadette F. and Wienhold, Stella and Volkmer, Nina and Troje, Nikolaus F. and Meier, Maria and Pruessner, Jens C.},
title = {{Cross-wavelet analysis allows obtaining high temporal and frequency resolution in heart rate synchrony analysis}},
journal = {Frontiers in network physiology},
year = {2026},
month = jul,
volume = {6},
pages = {1869004},
publisher = {Frontiers Media SA},
issn = {2674-0109},
doi = {10.3389/
url = {https://
pmid = {42488130},
pmcid = {PMC13389767}
}
RIS
TY - JOUR
AU - Denk, Bernadette F.
AU - Wienhold, Stella
AU - Volkmer, Nina
AU - Troje, Nikolaus F.
AU - Meier, Maria
AU - Pruessner, Jens C.
TI - Cross-wavelet analysis allows obtaining high temporal and frequency resolution in heart rate synchrony analysis
T2 - Frontiers in network physiology
J2 - Front Netw Physiol
PY - 2026
DA - 2026/
VL - 6
SP - 1869004
SN - 2674-0109
PB - Frontiers Media SA
DO - 10.3389/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.3389/
"type": "article-journal",
"title": "Cross-wavelet analysis allows obtaining high temporal and frequency resolution in heart rate synchrony analysis",
"container-title": "Frontiers in network physiology",
"author": [
{
"family": "Denk",
"given": "Bernadette F."
},
{
"family": "Wienhold",
"given": "Stella"
},
{
"family": "Volkmer",
"given": "Nina"
},
{
"family": "Troje",
"given": "Nikolaus F."
},
{
"family": "Meier",
"given": "Maria"
},
{
"family": "Pruessner",
"given": "Jens C."
}
],
"container-title-short":
"volume": "6",
"page": "1869004",
"DOI": "10.3389/
"PMID": "42488130",
"PMCID": "PMC13389767",
"ISSN": "2674-0109",
"publisher": "Frontiers Media SA",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
8
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.3758/s13428-026-03060-7 [code]
- Synchronizing brains and hearts: A practical guide for caregiver-child fNIRS-ECG multimodal hyperscanning.Journal: Behavior research methodsIn common: 7 references
- [2] doi:10.1111/psyp.70271 [code]
- Disentangling Respiratory Phase-Dependent and Phase-Independent Components of Anticipatory Cardiac Deceleration.Journal: PsychophysiologyIn common: 3 references
- [3] doi:10.1038/s41398-026-04333-7
- Cardiac vagal activity during in-vivo threat exposure is associated with within-session inhibition of fear and avoidance.Journal: Translational psychiatryIn common: 3 references
- [4] doi:10.1093/nc/niag009 [code]
- Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm.Journal: Neuroscience of consciousnessIn common: 3 references
- [5] doi:10.3758/s13415-026-01442-0 [code]
- Empathy for pain persists across live two-way video interactions and viewing of prerecorded videos.Journal: Cognitive, affective & behavioral neuroscienceIn common: ggplot2, 2 references
- [6] doi:10.1016/j.isci.2026.116936 [code]
- Beyond neural oscillations: Stress-related aperiodic activity and aperiodic-oscillatory spectral covariation.Journal: iScienceIn common: ggplot2, systems, 1 reference
- [7] doi:10.3389/fnsys.2026.1880737
- Autonomic-salience stability as a candidate Gate for awake low-dose ketamine: a systems neuroscience framework with a clinical anchor.Journal: Frontiers in systems neuroscienceIn common: 2 references
- [8] doi:10.1038/s42003-026-10156-5 [code]
- Brain-heart interactions in late-onset major depressive disorder revealed by multimodal HRV-driven fMRI.Journal: Communications biologyIn common: 2 references
- [9] doi:10.7554/elife.107088 [code]
- Development of auditory and spontaneous movement responses to music over the first postnatal year.Journal: eLifeIn common: ggplot2, 1 reference
- [10] doi:10.1093/nc/niag029 [code]
- A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.Journal: Neuroscience of consciousnessIn common: ggplot2, 1 reference
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 1 script, and 3 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:e4d6e1a4e46eb959…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[.
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
