def plot_controlling_spectra(exp): """ Plots the stimulation power spectrum for the bipolar referenced electrode that provided the feedback signal for stimulation and a copy of the stimulation command stored in the .ns5 files. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: A 1 x 3 matplotlib figure. Each subplot contains the normalized by sum of power spectrum for the controlling bipolar referenced electrode and a copy of the stimulation command for a particular condition. Shading is bootstrap standard error. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) sns.set(style="white", font_scale=config['font_scale'], rc={"lines.linewidth": config['font_scale']}) fig, axs = plt.subplots(1, 3, figsize=(24, 8)) types = ['ns2', 'ns5'] # hack the legend to be color agnostic legend = ['Neural Recording', 'Stimulation Command'] axs[2].axvline(-3, color='k', linestyle='--') axs[2].axvline(-3, color='k') axs[2].legend(legend) for i, condition in enumerate(config['conditions']): ax = axs[i] for typ in types: power, chs, times, freqs = load_power_data(exp, condition, typ) if typ == 'ns2': ch_ix = [ ix for ix in np.arange(len(chs)) if 'elec1-83' in chs[ix] ] linestyle = '--' else: ch_ix = [ ix for ix in np.arange(len(chs)) if 'ainp2' in chs[ix] ] linestyle = '-' power = power[:, ch_ix, :, :].squeeze() power = power.mean(axis=0).mean(axis=-1) power = power / power.sum() ax.plot(freqs, power, color=config['colors'][i], linestyle=linestyle) ax.set_title(condition) ax.set_xlabel('Frequency [Hz]') ax.set_xlim((freqs[0], freqs[-1])) ax.set_ylabel('Normalized Power') plt.tight_layout() sns.despine() return fig
def plot_before_during_after_spectra(exp): """ Plots the power spectrum for the 0.5 seconds immediately pre-stimulation, the stimulation period, and the 0.5 seconds immediately post-stimulation. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: A 1 x 3 matplotlib figure. Each subplot contains the normalized by sum of power spectrum for each condition for a period before, during, and after stimulation. Shading is bootstrap standard error. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) sns.set(style='white', font_scale=config['font_scale'], rc={"lines.linewidth": config['linewidth']}) fig, axs = plt.subplots(1, 3, figsize=(24, 8)) for i, time_period in enumerate(['Before', 'During', 'After']): ax = axs[i] for j, condition in enumerate(config['conditions']): power, chs, times, freqs = load_power_data(exp, condition) power = reduce_array_power(power, chs, config['%s_bad_chs' % exp], '1', axis=1) power = reduce_toi_power(power, times, config[time_period], axis=-1) bootstrap_dist = simple_bootstrap(power, axis=0) # reduce over trials power = power.mean(axis=0) bootstrap_dist = bootstrap_dist.mean(axis=1) # normalize spectra power /= power.sum() bootstrap_dist /= bootstrap_dist.sum(axis=-1)[:, np.newaxis] # extract bootstrap standard error bootstrap_std_err = bootstrap_dist.std(axis=0) # plot the spectra with standard error shading ax.plot(freqs, power, color=config['colors'][j]) ax.fill_between(freqs, power - bootstrap_std_err, power + bootstrap_std_err, color=config['colors'][j], alpha=0.5, label='_nolegend_') ax.set_title('%s Stimulation Power' % time_period) ax.set_xlabel('Frequency [Hz]') ax.set_ylabel('Normalized Power') ax.set_ylim((0, 0.5)) axs[-1].legend(config['conditions']) plt.tight_layout() sns.despine() return fig
def plot_early_vs_late_stim_spectra(exp): """ Plots the spectra (averaged TFR power) for the first 5 seconds of the stimulation period compared to last 5 seconds of the stimulation period. Inputs: - exp: main or saline indicating which experiment's data to load and plot Outputs: - fig: 1 x 3 plot where each plot contains the first and last 5 seconds of stimulation spectra for each condition. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) sns.set(style="white", font_scale=config['font_scale'], rc={"lines.linewidth": config['font_scale']}) indices = {'Early': (0, 5), 'Late': (5, 10)} linestyles = ['-', '--'] fig, axs = plt.subplots(1, 3, figsize=(24, 8)) for i, condition in enumerate(config['conditions']): ax = axs[i] power, chs, times, freqs = load_power_data(exp, condition) # average over trials power = power.mean(axis=0) # average over array1 power = reduce_array_power(power, chs, config['%s_bad_chs' % exp], '1', 0) for j, tp in enumerate(['Early', 'Late']): # reduce to early or late stim toi toi_power = reduce_toi_power(power, times, indices[tp], axis=-1) # normalize the spectra toi_power /= toi_power.sum() # plot the spectra ax.plot(freqs, toi_power, color=config['colors'][i], linestyle=linestyles[j]) # pretty axes ax.set_title(condition) ax.set_xlabel('Frequency [Hz]') ax.set_ylabel('Normalized Power') # add legend axs[-1].legend(['Early', 'Late']) leg = axs[-1].get_legend() leg.legendHandles[0].set_color('black') leg.legendHandles[1].set_color('black') plt.tight_layout() sns.despine() return fig
def plot_array_toi_comparison(exp): """ Plots the pre- and post-stimulation alpha and beta time of interest averages for all three conditions comparing the two arrays. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: A 1 x 2 matplotlib figure. Each subplot contains alpha and beta band toi average barplots for all three conditions split by recording array with bootstrap standard error bars and significance marking between array averages based on permutation testing. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) # plotting initialization sns.set(style='white', font_scale=config['font_scale'], rc={"lines.linewidth": config['linewidth']}) fig, axs = plt.subplots(1, 2, figsize=(22, 10)) plt.subplots_adjust(hspace=.3) stat_ys = [-.3, .15, -.4, -.2, .15, -.35] stat_hmults = [3, 1.5, 3, 3, 1.5, 3] stat_hs = [-.03, .02, -.03, -.03, .02, -.03] for i, c in enumerate(config['conditions']): f = '../data/stats/%s_experiment/%s_array_permutation_info.npz' perm_info = np.load(f % (exp, c)) power, chs, times, freqs = load_power_data(exp, c) power = baseline_normalize(power, config['baseline'], times) # array indices arr1_ix = [ ix for ix in np.arange(len(chs)) if 'elec1' in chs[ix] and chs[ix] not in config['%s_bad_chs' % exp] ] arr2_ix = [ix for ix in np.arange(len(chs)) if 'elec2' in chs[ix]] for j, band in enumerate(['alpha', 'beta']): band_power = reduce_band_power(power, freqs, config[band], axis=1) toi_power = reduce_toi_power(band_power, times, config['toi'], axis=-1) for k, arr in enumerate([arr1_ix, arr2_ix]): arr_power = toi_power[arr].mean(axis=0) dist = np.sort( simple_bootstrap(toi_power[arr][:, np.newaxis], axis=0).squeeze().mean(axis=0)) lower_ix = int(len(dist) * .025) upper_ix = int(len(dist) * .975) ci = [dist[lower_ix], dist[upper_ix]] bar_tick = i * 2 + k * .8 if k == 0: axs[j].bar(bar_tick, arr_power, color=config['colors'][i]) axs[j].plot([bar_tick + .4, bar_tick + .4], ci, color='k', label='_nolegend_') else: axs[j].bar(bar_tick, arr_power, facecolor='none', edgecolor=config['colors'][i], linewidth=4, hatch='/') axs[j].plot([bar_tick + .4, bar_tick + .4], ci, color='k', label='_nolegend_') # pretty axis axs[j].set_title('%s Power' % band.capitalize(), y=1.05) axs[j].set_xticks([x + .8 for x in [0, 2, 4]]) axs[j].set_xticklabels(config['conditions']) axs[j].set_ylim((-.7, .7)) axs[j].set_xlim((-.6, 6.4)) axs[j].set_ylabel('dB Change From Baseline') axs[j].axhline(0, color='k', label='_nolegend_') # statistical annotation p = perm_info['%s_p_value' % band] if p < .0002: p = 'p < .0002' else: p = 'p = %.04f' % p x1, x2 = i * 2 + .4, i * 2 + 1.2 y = stat_ys[j * 3 + i] hmult = stat_hmults[j * 3 + i] h = stat_hs[j * 3 + i] axs[j].plot([x1, x1, x2, x2], [y, y + h, y + h, y], lw=2.5, c='k', label='_nolegend_') axs[j].text((x1 + x2) * .5, y + hmult * h, p, ha='center', va='bottom', color='k', size=22) # set legend axs[1].legend(["Array 1", "Array 2"]) leg = axs[1].get_legend() leg.legendHandles[0].set_color('black') leg.legendHandles[1].set_edgecolor('black') plt.tight_layout() sns.despine() return fig
def plot_array_band_comparison(exp): """ Plots the pre- and post-stimulation alpha and beta time series for all three conditions compared between recording arrays. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: A 2 x 3 matplotlib figure (frequency band x condition). Each subplot contains array1 and array2 time series for a particular condition and frequency band with bootstrap standard error shading. The stimulation period is ignored and centered at 0 with a +- 0.5 blacked out period representing stimulation edge artifact. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) # plotting initialization sns.set(style='white', font_scale=config['font_scale'], rc={"lines.linewidth": config['linewidth']}) fig, axs = plt.subplots(2, 3, figsize=(22, 10)) plt.subplots_adjust(hspace=.3) window = 3 xticks = np.arange(-window, window + 1) xticklabels = ['Stim' if x == 0 else x for x in xticks] ls = ['-', '--'] # hack the legend to be color agnostic axs[0, 2].axvline(-3, color='k') axs[0, 2].axvline(-3, color='k', linestyle='--') axs[0, 2].legend(['Array 1', 'Array 2']) for i, c in enumerate(config['conditions']): power, chs, times, freqs = load_power_data(exp, c) power = baseline_normalize(power, config['baseline'], times) # select out pre and post stimulation # collapse stimulation into 0 and make pre and post stimulation times # relative to this 0 (so no longer 10 + for post stimulation) pre_mask = np.logical_and(times >= -5, times <= -.5) post_mask = np.logical_and(times >= 10.5, times <= 15) time_mask = np.where(np.logical_or(pre_mask, post_mask))[0] times = times[time_mask] power = power[:, :, time_mask] times[times >= 10] -= 10 # array indices arr1_ix = [ ix for ix in np.arange(len(chs)) if 'elec1' in chs[ix] and chs[ix] not in config['%s_bad_chs' % exp] ] arr2_ix = [ix for ix in np.arange(len(chs)) if 'elec2' in chs[ix]] for j, band in enumerate(['alpha', 'beta']): band_power = reduce_band_power(power, freqs, config[band], axis=1) for k, arr in enumerate([arr1_ix, arr2_ix]): arr_power = band_power[arr, :].mean(axis=0) arr_stderr = band_power[arr, :].std(axis=0) / \ np.sqrt(len(arr)) axs[j, i].plot(times, arr_power, color=config['colors'][i], linestyle=ls[k]) axs[j, i].fill_between(times, arr_power - arr_stderr, arr_power + arr_stderr, facecolor=config['colors'][i], alpha=0.2, edgecolor='none', label='_nolegend_') # pretty axis axs[j, i].set_title('%s %s Power' % (c, band.capitalize())) axs[j, i].set_xlim((-window, window)) axs[j, i].set_xticks(xticks) axs[j, i].set_xticklabels(xticklabels) axs[j, i].set_ylim((-1, 1)) if i == 0: axs[j, i].set_ylabel('dB Change From Baseline') if j == 1: axs[j, i].set_xlabel('Time (s)') # add blackout for stim period for x in np.arange(-.5, .5, .01): axs[j, i].axvline(x, color='k', alpha=0.8, label='_nolegend_') axs[j, i].axvline(x, color='k', alpha=0.8, label='_nolegend_') plt.tight_layout() sns.despine() return fig
def compute_array_permutation_distribution(exp): """ Computes permutation distributions for alpha and beta band power difference between the two recording arrays for all three conditions. For each condition, it loads those condition's raw tfr power data. It then baseline normalizes the power and reduces to band power and averages over a post-stimulation time of interest. Then it permutes recording array membership to generate a permutation distribution of differences between recording array averages. Finally, it computes a permutation p-value testing for post-stimulation toi power differences between recording arrays for each condition. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: None. It saves all of the permutation information, including the band power toi difference estimates, the permutation distributions, and the post-stimulation array difference p-values into a compressed numpy file. """ with open('./experiment_config.json', 'r') as f: config = json.load(f) for condition in config['conditions']: print(condition) np.random.seed(config['random_seed']) # load all data for condition power, chs, times, freqs = load_power_data(exp, condition) # baseline normalize and reduce to time of interest power = baseline_normalize(power, config['baseline'], times) power = reduce_toi_power(power, times, config['toi'], axis=-1) perm_info = {} for band in ['alpha', 'beta']: perm_info['%s_perm_dist' % band] = [] # build the permutation distribution for i in range(config['num_permutations'] + 1): if i > 0: # shuffle channel array membership np.random.shuffle(chs) # select out array 1 and array 2 channels arr1_ix = [ix for ix in np.arange(len(chs)) if 'elec1' in chs[ix] and chs[ix] not in config['%s_bad_chs' % exp]] arr2_ix = [ix for ix in np.arange(len(chs)) if 'elec2' in chs[ix]] for band in ['alpha', 'beta']: # reduce to desired band band_power = reduce_band_power(power, freqs, config[band], axis=-1) if i == 0: tmp = '%s_diff' % band perm_info[tmp] = ttest_ind(band_power[arr1_ix], band_power[arr2_ix])[0] else: tmp = '%s_perm_dist' % band perm_info[tmp].append(ttest_ind(band_power[arr1_ix], band_power[arr2_ix])[0]) for band in ['alpha', 'beta']: # compute the p-value tmp1 = '%s_p_value' % band tmp2 = '%s_diff' % band tmp3 = '%s_perm_dist' % band perm_info[tmp1] = compute_permutation_p_value(perm_info[tmp2], perm_info[tmp3]) # save permutation info to file f = '../data/stats/%s_experiment/' % exp + \ '%s_array_permutation_info.npz' % condition np.savez_compressed(f, alpha_dist=perm_info['alpha_perm_dist'], beta_dist=perm_info['beta_perm_dist'], alpha_diff=perm_info['alpha_diff'], beta_diff=perm_info['beta_diff'], alpha_p_value=perm_info['alpha_p_value'], beta_p_value=perm_info['beta_p_value']) print('Done!')
def compute_permutation_distributions(exp): """ Computes permutation distributions for alpha and beta band power for all three pairs stimulation conditions differences. For each condition pair, it loads those condition's raw tfr power data. It then runs through each sub-sample index and permutation index to equalize trial counts and then permute trial membership between the two conditions in order to calculate the difference between the mean post-stimulation band toi power creating a permutation distribution. Finally, it computes a permutation p-value testing for post-stimulation toi power differences between conditions. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: None. It saves all of the permutation information, including the band power toi difference estimates, the permutation distributions, and the post-stimulation condition difference p-values into a compressed numpy file. """ global all_conditions_power, trial_indices, times, freqs, chs, config global comp, exper, permutation_ix exper = exp with open('./experiment_config.json', 'r') as f: config = json.load(f) # load pre-sampled indices f = '../data/stats/%s_experiment/condition_permutation_indices.npz' % exp permutation_indices = np.load(f) f = '../data/stats/%s_experiment/condition_subsample_indices.npz' % exp trial_indices = np.load(f) tmp = {} for condition in config['conditions']: tmp[condition] = trial_indices[condition] trial_indices = tmp permutation_info = {} # loop through condition comparisons comparisons = [['Open', 'Closed'], ['Open', 'Brain'], ['Brain', 'Closed']] for comp in comparisons: print('Computing Permutation Distribution for Condition ' + 'Comparison: %s-%s' % (comp[0], comp[1])) # collect all power for the relevant conditions all_conditions_power = {} for condition in comp: power, chs, times, freqs = load_power_data(exp, condition) all_conditions_power[condition] = power # get the permutation index permutation_ix = permutation_indices['%s_%s' % (comp[0], comp[1])] # compute the base difference base_diffs = compute_permutation_sample(-1, all_conditions_power, trial_indices, permutation_ix, times, freqs, chs, config, comp, exp) for i, band in enumerate(['alpha', 'beta']): permutation_info['%s_diff' % band] = base_diffs[i] num_permutations = permutation_indices['num_permutations'] perm_diffs = [] par = Parallel(n_jobs=config['n_jobs']) perm_diffs = par(delayed(compute_permutation_wrapper)(ix) for ix in range(num_permutations)) permutation_info['alpha_perm_dist'] = [diff[0] for diff in perm_diffs] permutation_info['beta_perm_dist'] = [diff[1] for diff in perm_diffs] # compute p-values tmp = compute_permutation_p_value(permutation_info['alpha_diff'], permutation_info['alpha_perm_dist']) permutation_info['alpha_p_value'] = tmp tmp = compute_permutation_p_value(permutation_info['beta_diff'], permutation_info['beta_perm_dist']) permutation_info['beta_p_value'] = tmp # save the permutation information f = '../data/stats/%s_experiment/%s-%s_%s_permutation_info.npz' np.savez_compressed(f % (exp, comp[0], comp[1], exp), alpha_dist=permutation_info['alpha_perm_dist'], beta_dist=permutation_info['beta_perm_dist'], alpha_diff=permutation_info['alpha_diff'], beta_diff=permutation_info['beta_diff'], num_permutations=num_permutations, alpha_p_value=permutation_info['alpha_p_value'], beta_p_value=permutation_info['beta_p_value'])
def compute_bootstrap_distribution(exp): """ Computes bootstrap distributions for alpha and beta band power for all three stimulation conditions. For each condition, it loads that condition's raw tfr and pre-computed bootstrap sampled indices. It then runs through each re-sampled index and computes the re-sampled band power to create a bootstrap distribution. Finally, it computes a bootstrap p-value testing for post-stimulation toi power differences from 0. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: None. It saves all of the bootstrap information, including the band power estimates, the band power bootstrap distributions, and the post-stimulation toi bootstrap p-values into a compressed numpy file. """ global power, times, freqs, chs, bootstrap_cond_ix, config, exper exper = exp # load in configurations with open('./experiment_config.json', 'r') as f: config = json.load(f) # load in pre-computes bootstrap re-sample indices f = '../data/stats/%s_experiment/condition_bootstrap_indices.npz' % exp bootstrap_indices = np.load(f) num_bootstrap_samples = bootstrap_indices['num_samples'] for condition in config['conditions']: print('Computing Bootstrap Distribution for Condition: %s' % condition) power, chs, times, freqs = load_power_data(exp, condition) # compute the base band power base_ix = np.arange(power.shape[0]) alpha_power, beta_power = compute_bootstrap_sample(base_ix, power, times, freqs, chs, config, exp) # loop through all bootstrap samples in parallel bootstrap_cond_ix = bootstrap_indices[condition] par = Parallel(n_jobs=config['n_jobs']) bootstrap_samples = par(delayed(compute_bootstrap_wrapper)(ix) for ix in range(num_bootstrap_samples)) # collect all the bootstrap samples into single matrix alpha_bootstrap_samples = np.vstack([s[0] for s in bootstrap_samples]) beta_bootstrap_samples = np.vstack([s[1] for s in bootstrap_samples]) # compute p-values alpha_p = compute_bootstrap_p_value(alpha_power, alpha_bootstrap_samples, times, config['toi']) beta_p = compute_bootstrap_p_value(beta_power, beta_bootstrap_samples, times, config['toi']) # save f = '../data/stats/%s_experiment/%s_bootstrap_info.npz' % (exp, condition) np.savez_compressed(f, alpha=alpha_power, beta=beta_power, alpha_dist=alpha_bootstrap_samples, beta_dist=beta_bootstrap_samples, alpha_p=alpha_p, beta_p=beta_p, times=times)