Hybrid spatial organization and evidence for magnitude-independent neural coding of linguistic information during sentence production.
The 3 matches
- [1] § MATERIALS AND METHODS › Temporal warping ↔ code.zip/code/temporal warping/1 - simple linear stretch to median RT.R, lines 188–238 · score 0.64 · Temporal warping, speech onset, stimulus onset, median, outlier, epochs
- [2] § MATERIALS AND METHODS › Data collection and preprocessing ↔ code.zip/code/preprocessing/6_7_reference_hilbert_epoch.m, lines 237–255 · score 0.61 · spaced frequency bands, Hilbert, high gamma, 70 Hz, amplitude, 150 Hz
- [3] § MATERIALS AND METHODS › Temporal warping ↔ code.zip/code/temporal warping/1 - simple linear stretch to median RT.R, lines 188–238 · score 0.61 · median RT, speech onset, stimulus onset, epochs, temporally, warping
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 · 510 lines · 23 KB · CC-BY-4.0 · 2 matches
- ### Linearly interpolate the middle section (150ms post stim to 150ms pre speech) of all trials to have the same RT (i.e., median task RT)
- ### May 2023
- ### [email hidden]
- ###
- ### Readme
- ###
- ###
- ### Setup
- ###
- ### Packages
- library('dplyr') # for data organization, including bind_rows()
- # Clean up
- rm(list=ls())
- cat("\014")
- message("Begin stretching trials. ",Sys.time())
- ### Set path
- if(Sys.info()['sysname'] == 'Darwin'){ # Mac
- path = '/Users/adam/Dropbox/Research/ChickenSyntax/'
- n.cores.to.use = 4
- if(Sys.info()['nodename'] == 'FLINKERLABMBP06'){
- path = '/Users/am4611/Dropbox/Research/ChickenSyntax/'
- n.cores.to.use = 5
- }
- if(Sys.info()['nodename'] == 'FLINKERLABMS01'){
- path = '/Users/am4611/Dropbox/Research/ChickenSyntax/'
- n.cores.to.use = 15
- }
- }
- if(Sys.info()['sysname'] == 'Linux'){ # Ubuntu
- path = '/home/adam/Dropbox/Research/ChickenSyntax/'
- n.cores.to.use = 40
- }
- ### Lemmas
- # Function to convert between samples, sample labels, and times
- source(paste0(path,'/analysis/R/functions/time_convert.R'))
- # Function for rolling average smoothing
- source(paste0(path,'/analysis/R/functions/smoothing.R'))
- # Close any old parallel backends
- source(paste0(path,'/analysis/R/functions/unregister_dopar.R'))
- # Subset to just good picture naming trials
- source(paste0(path,'/analysis/R/functions/just_good_trial_functions.R'))
- # Plot time series
- source(paste0(path,'/analysis/R/functions/plot_time_series.R'))
- # Add colored text to plots
- source(paste0(path,'/analysis/R/functions/add_text_line_multiple_colors.R'))
- set.seed(seed = 404)
- # Data type:
- for(freq.band.loop in c('high_gamma','beta')){
- # freq.band.loop = c('high_gamma','beta')[1]
- freq.band.read.path <- list('high_gamma' = 'z_scored', 'beta' = 'z_scored_beta')[[freq.band.loop]]
- # Type of hilbert transform
- hilbert.type <- 'multiband'
- ## Metadata
- patients = c('Patient001',
- 'Patient002',
- 'Patient003', # slow PN RTs; check closely
- 'Patient004',
- 'Patient005',
- 'Patient006', # slow PN RTs; check closely
- 'Patient007',
- 'Patient008', # slow PN RTs and generally weird
- 'Patient009',
- 'Patient010')
- ## Tasks
- tasks <- c('pn','sp','lp')
- ## Plot stuff:
- load(paste0(path,'analysis/R/color palettes/output/all palettes.RData'))
- text.size.big = 1.6
- text.size.med = 1.4
- text.size.small = 1.2
- zoom = 2
- # Elec MNI coords, etc.
- elec.info <-
- read.csv(paste0(path,
- '/analysis/R/brain plots/ecog/output/data/elec info/patients - combined/row_labels_and_localizations.csv'))
- elec.info$use.these.elecs <-
- as.numeric((! elec.info$region_clinical %in% c('Unknown',
- '',
- 'NaN',
- 'Left-Cerebral-White-Matter',
- 'Left-Inf-Lat-Vent',
- 'Right-Cerebral-White-Matter')) &
- (elec.info$bad_elec == 0) &
- (elec.info$visual_elec == 0) &
- (elec.info$active == 1))
- rownames(elec.info) <- elec.info$patient_elec
- use.these.elecs <- elec.info[elec.info$use.these.elecs == 1,]$patient_elec
- ## Get all trial info across patients in one dataframe to analyze RTs:
- # Define range of RTs per patient to use
- rt.quantile.range <- c(.025, .95) # .025 just to get rid of weird fast trials (e.g., second of two 'nurse' trials in a row?) and .8 because long tail, probably reflecing lots of different things, so get rid of a bunch. shouldn't matter long-run, still a decent amount of data for NMF clustering
- ### Load trial labels
- # Initialize storage
- trial.info <- list()
- for(task.loop in c(tasks,'all')){
- trial.info[[task.loop]] <- list()
- }; rm(task.loop)
- # Loop thru patients and load data
- for(patient in rev(patients)){
- # patient = patients[1]
- print(patient)
- # Load data
- trial.info[['all']][[patient]] <- read.csv(paste0(path,
- 'data/',patient,'/data/epoched/',patient,'_trial_labels_without_bad_trials.csv'))
- trial.info[['all']][[patient]]$patient <- patient
- trial.info[['all']][[patient]]$rt <- time.convert(trial.info[['all']][[patient]]$production_latency,
- "samples", "times")
- # Separate by task
- trial.info[['pn']][[patient]] <-
- just.good.pic.naming(trial.info[['all']][[patient]],
- .remove.outliers = TRUE,
- .rt.quantile.range = rt.quantile.range)
- trial.info[['sp']][[patient]] <-
- just.first.word.sentence.data(trial.info[['all']][[patient]],
- .remove.outliers = TRUE,
- .rt.quantile.range = rt.quantile.range)
- trial.info[['lp']][[patient]] <-
- just.first.word.list.data(trial.info[['all']][[patient]],
- .remove.outliers = TRUE,
- .rt.quantile.range = rt.quantile.range)
- }; rm(patient)
- # Clean up
- trial.info$all <- NULL
- trial.info[['pn']] <- data.frame(bind_rows(trial.info[['pn']]))
- trial.info[['sp']] <- data.frame(bind_rows(trial.info[['sp']]))
- trial.info[['lp']] <- data.frame(bind_rows(trial.info[['lp']][patients[patients != 'Patient006']]))
- ## Get median RT by tasks -- i.e., target RT for all adjusted trials
- half.median.rt.samples <- round(sapply(trial.info, function(x){median(x$production_latency)}) / 2)
- median.rt.samples <- 2 * half.median.rt.samples # double rounded halves so always even
- # Windows of data keep (i.e., not stretch) pre-stim and post-speech
- stimulus.samples.range <- c(time.convert(-500, "times", "samples"), time.convert(150, "times", "samples"))
- production.samples.range <- c(time.convert(-150, "times", "samples"), time.convert(1200, "times", "samples"))
- stimulus.samples <- stimulus.samples.range[1]:stimulus.samples.range[2]
- production.samples <- production.samples.range[1]:production.samples.range[2]
- stimulus.sample.labels <- time.convert(
- stimulus.samples,
- "samples", "sample.labels")
- production.sample.labels <- time.convert(
- production.samples,
- "samples", "sample.labels")
- ### Read in data from both time-locks
- stretch.data <- list()
- patient.trial.info <- list()
- elec.means.pre.post <- list()
- elec.mean.maxes.pre.post <- list()
- for(patient in rev(patients)){
- # patient = patients[1]
- # Initialize storage
- data <- list()
- keep.data.stimulus <- list()
- keep.data.production <- list()
- for(lock.loop in c('locked_to_stimulus_onset','locked_to_production_onset')){
- #lock.loop <- c('locked_to_production_onset','locked_to_stimulus_onset')[2]
- # Storage
- data[[lock.loop]] <- list()
- # Progress update
- message(patient,": Loading ",lock.loop," data. ",Sys.time())
- # Number of linguistic columns
- n.lx.cols = read.table(paste0(path,'data/',patient,'/data/epoched/',patient,'_n_linguistic_columns.txt'))[1,1]
- # Where data?
- data.dir <- paste0(path,'data/',
- patient,
- '/data/epoched/elec_data_without_bad_trials/',hilbert.type,'/',
- lock.loop,'/', # always train on data locked to stimulus onset since we care about early stages!
- freq.band.read.path,'/')
- # Read in data
- message('Attach...')
- attach(paste0(data.dir, patient,' elec data.RData')) # reads in list "all.data"
- data[[lock.loop]][['all']] <- all.data
- detach()
- message('...detach!')
- ## Limit trials to inner range of RTs
- data[[lock.loop]][['pn']] <- lapply(data[[lock.loop]][['all']], function(x){
- just.good.pic.naming(x, .remove.outliers = TRUE, .rt.quantile.range = rt.quantile.range)})
- data[[lock.loop]][['sp']] <- lapply(data[[lock.loop]][['all']], function(x){
- just.first.word.sentence.data(x, .remove.outliers = TRUE, .rt.quantile.range = rt.quantile.range)})
- data[[lock.loop]][['lp']] <- lapply(data[[lock.loop]][['all']], function(x){
- just.first.word.list.data(x, .remove.outliers = TRUE, .rt.quantile.range = rt.quantile.range)})
- # Clean up
- data[[lock.loop]][['all']] <- NULL
- }; rm(lock.loop)
- ## Set aside the "keep" data -- pre-stim and post-speech onsets (with a little extra from the middle)
- patient.trial.info[[patient]] <- list()
- for(task.loop in tasks){
- # task.loop = tasks[1]
- # Store the trial info (linguistic columns) for this patient
- patient.trial.info[[patient]][[task.loop]] <-
- data[['locked_to_production_onset']][[task.loop]][[1]][,1:n.lx.cols]
- # Get the "keep" data
- keep.data.stimulus[[task.loop]] <-
- lapply(data[['locked_to_stimulus_onset']][[task.loop]], function(x){
- x <- x[,stimulus.sample.labels]
- colnames(x) <- paste0('stim.',colnames(x)) # make unique column names to avoid duplicates
- return(x)
- })
- keep.data.production[[task.loop]] <-
- lapply(data[['locked_to_production_onset']][[task.loop]], function(x){
- x <- x[,production.sample.labels]
- colnames(x) <- paste0('prod.',colnames(x)) # make unique column names to avoid duplicates
- return(x)
- })
- # Remove the linguistic trial data from the data to be stretched
- for(lock.loop in c('locked_to_stimulus_onset','locked_to_production_onset')){
- data[[lock.loop]][[task.loop]] <-
- lapply(data[[lock.loop]][[task.loop]], function(x){
- x[,-c(1:n.lx.cols)]
- })
- }; rm(lock.loop)
- # Remove the "keep" data (and any even earlier/later than stimulus/production) from the data to be stretched
- data[['locked_to_stimulus_onset']][[task.loop]] <-
- lapply(data[['locked_to_stimulus_onset']][[task.loop]], function(x){
- x[,-which(time.convert(colnames(x), "sample.labels", "samples") <= max(stimulus.samples))]
- })
- data[['locked_to_production_onset']][[task.loop]] <-
- lapply(data[['locked_to_production_onset']][[task.loop]], function(x){
- x[,-which(time.convert(colnames(x), "sample.labels", "samples") >= min(production.samples))]
- })
- }; rm(task.loop)
- ### Set up metadata for stretching data
- # Electrodes
- elecs <- names(data$locked_to_stimulus_onset$pn)
- # Target number of samples
- stim.n.samples.out <- half.median.rt.samples - stimulus.samples.range[2]
- prod.n.samples.out <- half.median.rt.samples - (-production.samples.range[1])
- # Save names for the final, stretched sample labels
- stretch.sample.labels <- list()
- for(task.loop in tasks){ # task.loop = tasks[1]
- stretch.sample.labels[[task.loop]] <- list()
- # Get samples
- stretch.sample.labels[[task.loop]][['locked_to_stimulus_onset']] <- c(
- stimulus.samples.range[1]:(median.rt.samples[[task.loop]] + production.samples.range[2] + 1)
- )
- stretch.sample.labels[[task.loop]][['locked_to_production_onset']] <- c(
- stretch.sample.labels[[task.loop]][['locked_to_stimulus_onset']] - median.rt.samples[[task.loop]]
- )
- # Convert to sample labels
- stretch.sample.labels[[task.loop]][['locked_to_stimulus_onset']] <-
- time.convert(stretch.sample.labels[[task.loop]][['locked_to_stimulus_onset']],
- "samples", "sample.labels")
- stretch.sample.labels[[task.loop]][['locked_to_production_onset']] <-
- time.convert(stretch.sample.labels[[task.loop]][['locked_to_production_onset']],
- "samples", "sample.labels")
- }; rm(task.loop)
- ### For each trial, get the "stretch" data as a single list entry -- but *just* the data from 150ms post-stim to 150ms pre-production
- # Take the first half from the stim-locked data and second half from the prod-locked data to cover cases where the RT is longer than the epoch
- stretch.data[[patient]] <- list()
- for(task.loop in tasks){
- # task.loop = tasks[3]
- message(patient,': Beginning stretching of ',task.loop,' data. ',Sys.time())
- # Loop thru elecs
- stretch.data[[patient]][[task.loop]] <- list()
- for(elec.loop in elecs){
- # elec.loop = elecs[1]
- .n.trials <- nrow(patient.trial.info[[patient]][[task.loop]])
- if(.n.trials == 0){
- stretch.data[[patient]][[task.loop]][[elec.loop]]
- }else{
- # Loop thru trials and interpolate
- stretch.data[[patient]][[task.loop]][[elec.loop]] <- list()
- for(trial.loop in 1:nrow(patient.trial.info[[patient]][[task.loop]])){
- # trial.loop = 1
- # Get all the samples from 1 to production onset
- .rt.samples <- patient.trial.info[[patient]][[task.loop]]$production_latency[trial.loop]
- .all.rt.samples <- (stimulus.samples.range[2] + 1) : (.rt.samples + production.samples.range[1] - 1)
- .stim.locked.samples <-
- time.convert(.all.rt.samples[c(1:floor(length(.all.rt.samples) / 2))],
- "samples", "sample.labels")
- .prod.locked.samples <-
- time.convert(.all.rt.samples[-c(1:floor(length(.all.rt.samples) / 2))] - .rt.samples,
- "samples", "sample.labels")
- # Verify that the value at t=0 prod-locked is the same as the value at the RT stim-locked
- if(! all(round(data[['locked_to_stimulus_onset']][[task.loop]][[elec.loop]][
- trial.loop, c(.stim.locked.samples[length(.stim.locked.samples)],
- time.convert(time.convert(.stim.locked.samples[length(.stim.locked.samples)],
- "sample.labels", "samples") + 1,
- "samples", "sample.labels"))], 6) ==
- round(data[['locked_to_production_onset']][[task.loop]][[elec.loop]][
- trial.loop, c(time.convert(time.convert(.prod.locked.samples[1],
- "sample.labels", "samples") - 1,
- "samples", "sample.labels"),
- .prod.locked.samples[1])], 6))){
- message('WARNING!!! Stim- and prod-locked data not the same!')
- }
- # Interpolate
- .stim.locked.interpolated.data <-
- approx(x = time.convert(.stim.locked.samples, "sample.labels", "samples"),
- y = unlist(data[['locked_to_stimulus_onset']][[task.loop]][[elec.loop]][trial.loop, .stim.locked.samples]),
- n = stim.n.samples.out[[task.loop]])$y
- .prod.locked.interpolated.data <-
- approx(x = time.convert(.prod.locked.samples, "sample.labels", "samples"),
- y = unlist(data[['locked_to_production_onset']][[task.loop]][[elec.loop]][trial.loop, .prod.locked.samples]),
- n = prod.n.samples.out[[task.loop]])$y
- # Turn into a dataframe
- .stim.locked.interpolated.data <- data.frame(matrix(.stim.locked.interpolated.data, nrow = 1))
- .prod.locked.interpolated.data <- data.frame(matrix(.prod.locked.interpolated.data, nrow = 1))
- colnames(.stim.locked.interpolated.data) <- paste0('stim.', 1:stim.n.samples.out[[task.loop]])
- colnames(.prod.locked.interpolated.data) <- paste0('prod.', 1:prod.n.samples.out[[task.loop]])
- stretch.data[[patient]][[task.loop]][[elec.loop]][[trial.loop]] <-
- cbind(.stim.locked.interpolated.data, .prod.locked.interpolated.data)
- rm(.stim.locked.interpolated.data, .prod.locked.interpolated.data, .rt.samples, .all.rt.samples, .stim.locked.samples, .prod.locked.samples)
- }; rm(trial.loop)
- # Recombine trials into dataframe
- stretch.data[[patient]][[task.loop]][[elec.loop]] <- bind_rows(stretch.data[[patient]][[task.loop]][[elec.loop]])
- # Add stim-locked prod-locked data back in
- stretch.data[[patient]][[task.loop]][[elec.loop]] <- cbind(
- keep.data.stimulus[[task.loop]][[elec.loop]],
- stretch.data[[patient]][[task.loop]][[elec.loop]],
- keep.data.production[[task.loop]][[elec.loop]]
- )
- colnames(stretch.data[[patient]][[task.loop]][[elec.loop]]) <- stretch.sample.labels[[task.loop]][['locked_to_production_onset']]
- } # if(.n.trials > 0){
- }; rm(elec.loop)
- }; rm(task.loop)
- ### Visual check
- ## Plot random trials
- save.fig.path <- paste0(path,
- 'analysis/R/warp time series to standard RT/simple linear stretch to median RT/output - ',
- freq.band.loop,
- '/figures/',patient,'/')
- save.fig.path.trial <- paste0(save.fig.path, 'randomly sampled trials/')
- save.fig.path.mean <- paste0(save.fig.path, 'elec means/')
- dir.create(save.fig.path.trial, showWarnings = FALSE, recursive = TRUE)
- dir.create(save.fig.path.mean, showWarnings = FALSE, recursive = TRUE)
- elec.means.pre.post[[patient]] <- list()
- elec.mean.maxes.pre.post[[patient]] <- list()
- for(task.loop in tasks){
- # task.loop = tasks[1]
- elec.means.pre.post[[patient]][[task.loop]] <- list()
- elec.mean.maxes.pre.post[[patient]][[task.loop]] <- list()
- for(elec.loop in elecs){
- # elec.loop = elecs[3]
- ## Compare difference between [mean unstretched signal] & [mean stretched signal] as a function of signal:
- # low signal (noise) the should get lower (noise reduction)
- # high signal should get higher if this stretching approach isn't muddying things
- .n.trials <- nrow(patient.trial.info[[patient]][[task.loop]])
- if(.n.trials > 0){ # if they completed any trials this task (Patient006 didn't do listing block)
- # Samples to compare
- samples.to.compare <-
- time.convert((-(median.rt.samples[task.loop] - stimulus.samples.range[2] - 1)):
- (production.samples.range[1]),
- "samples", "sample.labels")
- # Stretched data
- .stretch.mean <- colMeans(stretch.data[[patient]][[task.loop]][[elec.loop]])
- .stretch.peak.time <-
- time.convert(names(which.max(abs(.stretch.mean))),
- "sample.labels", "times")
- # Means
- elec.means.pre.post[[patient]][[task.loop]][[elec.loop]] <-
- data.frame('sample.label' = samples.to.compare,
- 'elec' = elec.loop,
- 'task' = task.loop,
- 'localization' = elec.info[elec.loop, 'region_clinical'],
- 'stretch.mean' = abs(.stretch.mean[samples.to.compare]),
- row.names = NULL)
- # Maxes
- elec.mean.maxes.pre.post[[patient]][[task.loop]][[elec.loop]] <-
- data.frame('patient' = patient,
- 'elec' = elec.loop,
- 'task' = task.loop,
- 'localization' = elec.info[elec.loop, 'region_clinical'],
- 'stretch.max' = max(abs(.stretch.mean[samples.to.compare]), na.rm = TRUE),
- 'stretch.peak.time' = .stretch.peak.time,
- row.names = NULL)
- for(lock.loop in c('locked_to_production_onset','locked_to_stimulus_onset')){
- # lock.loop in c('locked_to_production_onset','locked_to_stimulus_onset')[1]
- # Redefine samples to compare if stim-locked
- if(lock.loop == 'locked_to_stimulus_onset'){
- samples.to.compare <-
- time.convert((stimulus.samples.range[2]):
- (median.rt.samples[task.loop] + production.samples.range[1] - 1),
- "samples", "sample.labels")
- } # if(lock.loop == 'locked_to_stimulus_onset'){
- # Get mean of OG data locked to lock.loop
- .original.mean <- colMeans(data[[lock.loop]][[task.loop]][[elec.loop]])
- .original.peak.time <-
- time.convert(names(which.max(abs(.original.mean))),
- "sample.labels", "times")
- # Store original mean and peak
- elec.means.pre.post[[patient]][[task.loop]][[elec.loop]][,paste0('original.mean_',lock.loop)] <-
- abs(.original.mean[samples.to.compare])
- elec.mean.maxes.pre.post[[patient]][[task.loop]][[elec.loop]][,paste0('original.max_',lock.loop)] <-
- max(abs(.original.mean[samples.to.compare]), na.rm = TRUE)
- elec.mean.maxes.pre.post[[patient]][[task.loop]][[elec.loop]][,paste0('original.peak.time_',lock.loop)] <- .original.peak.time
- rm(.original.mean)
- }; rm(lock.loop)
- } # if(.n.trials > 0)
- }; rm(elec.loop)
- }; rm(task.loop)
- ### Clean up
- elec.means.pre.post[[patient]] <-
- bind_rows(lapply(elec.means.pre.post[[patient]], function(x){bind_rows(x)}))
- elec.mean.maxes.pre.post[[patient]] <-
- lapply(elec.mean.maxes.pre.post[[patient]], bind_rows)
- ### Remove the "keep" data from the "adjust" data
- rm(data, keep.data.stimulus, keep.data.production)
- }; rm(patient)
- ###
- ### Save data
- ###
- ### Save all data
- save.these <- c(
- 'patient.trial.info',
- 'median.rt.samples',
- 'stretch.data',
- 'stretch.sample.labels',
- 'elec.mean.maxes.pre.post'
- )
- save.data.path <- paste0(path,'analysis/R/warp time series to standard RT/simple linear stretch to median RT/output - ',
- freq.band.loop,
- '/data/')
- dir.create(save.data.path, showWarnings = FALSE, recursive = TRUE)
- save(list = save.these,
- file = paste0(save.data.path,'elec data with RTs stretched to global median by task - bad trials and .025 to .95 RT outliers excluded.RData'))
- ### Save just median.rt.samples
- save.data.path <- paste0(path,'analysis/R/warp time series to standard RT/simple linear stretch to median RT/output/data/')
- dir.create(save.data.path, showWarnings = FALSE, recursive = TRUE)
- save(median.rt.samples,
- file = paste0(save.data.path,'median RT samples.RData'))
- ### Save just stretch sample labels
- save.data.path <- paste0(path,'analysis/R/warp time series to standard RT/simple linear stretch to median RT/output/data/')
- dir.create(save.data.path, showWarnings = FALSE, recursive = TRUE)
- save(stretch.sample.labels,
- file = paste0(save.data.path,'warped sample labels.RData'))
- } # freq.band.loop
- # Finish!
- message('Script completed successfully. ',Sys.time())
1 - simple linear stretch to median RT.R, under CC-BY-4.0 · at the source
Overview
- Department of Neurology, NYU Grossman School of Medicine, New York, NY 10016, USA
- Department of Neurosurgery, NYU Grossman School of Medicine, New York, NY 10016, USA
- Department of Biomedical Engineering, NYU Tandon School of Engineering, New York, NY 11201, USA
Abstract
Humans are the only species with the ability to systematically combine words to convey an unbounded number of complex meanings. This process is guided by combinatorial processes whose underlying neural mechanisms remain obscured by inherent limitations of noninvasive brain measures and a near-total focus on comprehension paradigms. Here, we address these limitations with high-resolution neurosurgical recordings (electrocorticography) and a controlled sentence production experiment. We uncover distinct cortical networks encoding word-level and higher-order information. These networks exhibited a hybrid spatial organization: broadly distributed across traditional language areas but with focal concentrations of sensitivity to semantic and structural contrasts in canonical language regions. In contrast to previous comprehension-based findings, we find that these networks are largely nonoverlapping. Most notably, higher-order linguistic information showed an unexpected dissociation from local activity magnitude. This result establishes an operational dissociation between activity magnitude and information content, pointing toward a potentially distinct neural coding scheme for higher-order language, with important implications for the neurobiology of language.
Reproduced under the paper's license (CC BY-NC), 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.
Zenodo 20543385
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
41 files
- code.zip/
code/ , R, 397 linesRSA/ 1a - event and syntactic encoding - rsa multiple regression - single elec differences - no CV - just SP ts.R - code.zip/
code/ , R, 391 linesRSA/ 1b - shuffled data RSA ts.R - code.zip/
code/ , R, 525 linesRSA/ 1c - get zs from shuffle distribution and adjust.R - code.zip/
code/ , R, 681 linesRSA/ 1e - nmf.R - code.zip/
code/ , R, 403 linesRSA/ 1f - shuffled data nmf.R - code.zip/
code/ , R, 617 linesRSA/ 1g - nmf stats - real vs. shuffled data clusters - max.R - code.zip/
code/ , R, 523 linesRSA/ 1i - permute cluster assignments orginal RSA models.R - code.zip/
code/ , R, 536 linesRSA/ 1k_a - scatterplots of adjusted ts at max t.R - code.zip/
code/ , R, 488 linesRSA/ 1k_c - correlations across elecs of ECoG and RSA terms.R - code.zip/
code/ , R, 646 linesRSA/ 1t_a - clustering just syntax.R - code.zip/
code/ , R, 627 linesRSA/ 1t_b get cluster stats from shuffling.R - code.zip/
code/ , R, 1,047 linesRSA/ 1t_c plot syntax clusters - binomial sampling tests.R - code.zip/
code/ , R, 311 linesRSA/ 1t_d - plot syntax clusters on brains by time.R - code.zip/
code/ , R, 484 linesRSA/ 1v - Venn diagrams of SP vs LP and sig RSA terms.R - code.zip/
code/ , R, 47 linesfunctions/ elementwise_matrix_apply .R - code.zip/
code/ , R, 17 linesfunctions/ elementwise_matrix_mean. R - code.zip/
code/ , R, 16 linesfunctions/ fisher_z_transform.R - code.zip/
code/ , R, 163 linesfunctions/ get_significant_windows. R - code.zip/
code/ , R, 108 linesfunctions/ just_good_trial_function s - lists.R - code.zip/
code/ , R, 150 linesfunctions/ just_good_trial_function s.R - code.zip/
code/ , R, 36 linesfunctions/ min_max.R - code.zip/
code/ , R, 35 linesfunctions/ smoothing.R - code.zip/
code/ , R, 37 linesfunctions/ time_convert.R - code.zip/
code/ , R, 8 linesfunctions/ unregister_dopar.R - code.zip/
code/ , R, 256 linespreprocessing/ 1 - clean up psychopy log.R - code.zip/
code/ , R, 201 linespreprocessing/ 2 - convert cleaned log to 512 Hz.R - code.zip/
code/ , R, 95 linespreprocessing/ 3 - downsample audio.R - code.zip/
code/ , R, 339 linespreprocessing/ 4 - align audio, ecog, psychopy log with drift adjustment.R - code.zip/
code/ , R, 240 linespreprocessing/ 5 - add production data to 512Hz log.R - code.zip/
code/ , R, 292 linespreprocessing/ 5point5 - get onset latencies.R - code.zip/
code/ , MATLAB, 510 lines, 1 matchpreprocessing/ 6_7_reference_hilbert_ep och.m - code.zip/
code/ , R, 366 linespreprocessing/ 8 - inspect and find visual electrodes.R - code.zip/
code/ , R, 241 linespreprocessing/ 9 - make sample matrices and onset times.R - code.zip/
code/ , R, 1,016 linestask ECoG comparisons/ ROI wilcox tests and squiggle plots/ 1a - squiggle plots - regions - data unwarped.R - code.zip/
code/ , R, 935 linestask ECoG comparisons/ ROI wilcox tests and squiggle plots/ 1b - squiggle plots - regions - data warped.R - code.zip/
code/ , R, 529 linestask ECoG comparisons/ electrode wilcox tests and brain plots/ 1 - get electrode wilcox tests for each electrode by patient.R - code.zip/
code/ , R, 119 linestask ECoG comparisons/ electrode wilcox tests and brain plots/ 2 - combine brain plot data from all patients.R - code.zip/
code/ , R, 434 linestask ECoG comparisons/ electrode wilcox tests and brain plots/ 3 - get electrode wilcox tests for each electrode by patient - all time.R - code.zip/
code/ , MATLAB, 225 linestask ECoG comparisons/ electrode wilcox tests and brain plots/ three_plot_elec_ds.m - code.zip/
code/ , R, 510 lines, 2 matchestemporal warping/ 1 - simple linear stretch to median RT.R - readme.txt, Text, 20 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;
- 40 scripts, 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, code, and materials availability
All data, and code needed to evaluate and reproduce the results in the paper are present in the paper, the Supplementary Materials, and/
Reproduced under the paper's license (CC BY-NC), 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, issue, pages, dates, 6 authors, 7 MeSH terms, 2 funders, 126 references.
Cite
This paper
Morgan, A. M., Devinsky, O., Doyle, W. K., Dugan, P., Friedman, D., & Flinker, A. (2026). Hybrid spatial organization and evidence for magnitude-independent neural coding of linguistic information during sentence production. Science advances, 12(32), eaec0518. https://
BibTeX
@article{morgan2026hybri
author = {Morgan, Adam M and Devinsky, Orrin and Doyle, Werner K and Dugan, Patricia and Friedman, Daniel and Flinker, Adeen},
title = {{Hybrid spatial organization and evidence for magnitude-independent neural coding of linguistic information during sentence production}},
journal = {Science advances},
year = {2026},
month = aug,
volume = {12},
number = {32},
pages = {eaec0518},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/
url = {https://
pmid = {42555736},
pmcid = {PMC13440437}
}
RIS
TY - JOUR
AU - Morgan, Adam M
AU - Devinsky, Orrin
AU - Doyle, Werner K
AU - Dugan, Patricia
AU - Friedman, Daniel
AU - Flinker, Adeen
TI - Hybrid spatial organization and evidence for magnitude-independent neural coding of linguistic information during sentence production
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/
VL - 12
IS - 32
SP - eaec0518
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1126/
"type": "article-journal",
"title": "Hybrid spatial organization and evidence for magnitude-independent neural coding of linguistic information during sentence production",
"container-title": "Science advances",
"author": [
{
"family": "Morgan",
"given": "Adam M"
},
{
"family": "Devinsky",
"given": "Orrin"
},
{
"family": "Doyle",
"given": "Werner K"
},
{
"family": "Dugan",
"given": "Patricia"
},
{
"family": "Friedman",
"given": "Daniel"
},
{
"family": "Flinker",
"given": "Adeen"
}
],
"container-title-short":
"volume": "12",
"issue": "32",
"page": "eaec0518",
"DOI": "10.1126/
"PMID": "42555736",
"PMCID": "PMC13440437",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
5
]
]
}
}
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.1038/s41467-026-73947-8 [code]
- Frontal cortex organization supporting audiovisual processing during naturalistic viewing.Journal: Nature communicationsIn common: intracranial EEG (iEEG / ECoG / SEEG), 7 references, 2 authors
- [2] doi:10.1162/nol.a.254
- A Unified Neural Time Course for Words, Phrases, and Sentences: MEG Evidence from Parallel Presentation.Journal: Neurobiology of language (Cambridge, Mass.)In common: 12 references
- [3] doi:10.1038/s41467-026-75745-8 [code]
- A language network in the individualized functional connectomes of 1199 human brains doing arbitrary tasks.Journal: Nature communicationsIn common: 11 references
- [4] doi:10.1162/nol.a.264 [code]
- The Temporal Dynamics of the Labeling Algorithm During Natural Language Comprehension: Neural Evidence for Phrase Grammatical Type Generation.Journal: Neurobiology of language (Cambridge, Mass.)In common: lmerTest, lme4, 9 references
- [5] doi:10.1038/s41586-026-10691-5 [code]
- Mapping the neuronal building blocks of human language with language models.Journal: NatureIn common: 10 references
- [6] doi:10.1038/s41467-026-75359-0 [code]
- Neural mechanisms of time-forward predictions for naturalistic auditory tone sequences.Journal: Nature communicationsIn common: 3 references, 2 authors
- [7] doi:10.1038/s41467-026-76598-x
- Preserved topography, lateralization, selectivity, and functional connectivity of the language network in older brains.Journal: Nature communicationsIn common: 8 references
- [8] doi:10.1162/imag.a.1246 [code]
- A 3.5-minute-long reading-based fMRI localizer for the language network.Journal: Imaging neuroscience (Cambridge, Mass.)In common: lmerTest, lme4, tidyverse, 5 references
- [9] doi:10.1111/cogs.70220 [code]
- Shared Neural Computations for Syntactic and Morphological Structures: Evidence From Mandarin Chinese.Journal: Cognitive scienceIn common: EEGLAB, 6 references
- [10] doi:10.1162/imag.a.1201 [code]
- All spectral frequencies of neural activity reveal semantic representation in the human anterior ventral temporal cortex.Journal: Imaging neuroscience (Cambridge, Mass.)In common: EEGLAB, Parallel Computing Toolbox, tidyverse, intracranial EEG (iEEG / ECoG / SEEG), 3 references
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, 40 scripts, 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:3761dfdf957c95ca…
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.
