def plot_bootstrap_distributions(exp): """ Plots the bootstrap toi power distributions for each stimulation condition and frequency band. Args: exp: The experiment to collect data for. 'main' or 'saline' Returns: A 2 x 3 matplotlib figure. Each subplot contains the bootstrap distribution for a particular condition and frequency band. Additionally, the estimated toi power and bootstrap 95% CI are plotted as vertical lines. """ 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(2, 3, figsize=(20, 12)) plt.subplots_adjust(hspace=0.4, wspace=0.2) for i, condition in enumerate(config['conditions']): f = '../data/stats/%s_experiment/%s_bootstrap_info.npz' % (exp, condition) bootstrap_info = np.load(f) for j, band in enumerate(['alpha', 'beta']): dist = bootstrap_info['%s_dist' % band] power = bootstrap_info['%s' % band] p = bootstrap_info['%s_p' % band] # reduce to toi power times = bootstrap_info['times'] dist = np.sort( reduce_toi_power(dist, times, config['toi'], axis=-1)) power = reduce_toi_power(power, times, config['toi'], axis=-1) # extract 95% confidence interval lower_ix = int(len(dist) * .025) upper_ix = int(len(dist) * .975) ci = [dist[lower_ix], dist[upper_ix]] # plot bootstrap distribution with actual value and ci marked ax = axs[j, i] sns.distplot(dist, ax=ax, color=config['colors'][i]) ax.axvline(ci[0], color='k') ax.axvline(ci[1], color='k') ax.axvline(power, color=config['colors'][i], linewidth=2) title = '%s %s Bootstrap Distribution \n Uncorrected p = %.3f' ax.set_title(title % (condition, band, p)) plt.tight_layout() sns.despine() return fig
def compute_bootstrap_p_value(power, bootstrap_dist, times, toi): """ Computes a bootstrap p-value to test the null hypothesis that post stimuation band power within a time period of interest is equal to zero. Checks to see how unlikely the given post-stimulation band power within a time period of interest is, given a bootstrapped null distribution that we shift to center around 0. It does this by calculating the % of bootstrapped values that are more extreme than the calculated value. Args: power: The time series consisting of the non-bootstrapped band power. bootstrap_dist: The bootstrap distribution of band power. times: List of time labels. toi: Tuple containing the limits for the time period of interest. Returns: Returns the p-value (float) corresponding to the percentage of bootstrapped toi values that were more extreme than the estimated toi band power value. """ # reduce to toi bootstrap_dist = reduce_toi_power(bootstrap_dist, times, toi, axis=-1) power = reduce_toi_power(power, times, toi, axis=-1) # sort by toi value bootstrap_dist = np.sort(bootstrap_dist) # center the distribution to make it a "null distribution" # assumes symmetry of the distribution bootstrap_dist = bootstrap_dist - power # compute the p-value as the percentage of bootstrap values larger # in absolute value than the p_num = np.sum(np.abs(bootstrap_dist) >= np.abs(power)) + 1. p_denom = len(bootstrap_dist) + 1. return p_num / p_denom
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_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_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 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_sample(perm_num, all_conditions_power, trial_indices, permutation_indices, times, freqs, chs, config, comp, exp): """ Helper function to compute the permuted toi band power difference for a particular permutation of trials between two conditions. This function takes in the tfr power data for two conditions, permutes the trial membership between the two conditions according to the given permutation index, and then computes the baseline-normalized band power averaged across the first recording array and a post-stimulation time period of interest for each condition and returns their difference. Args: perm_num: The permutation number used to index a particular permutation index and sub-sample index. all_conditions_power: Dictionary containing the tfr power data for each condition being tested. trial_indices: The pre-computed sub-sampling indices. permutation_indices: The pre-computed permutation indices. times: List of time labels. freqs: List of frequency labels. chs: List of channel names. config: Dictionary containing experiment wide configuration info. In this case it contains the baseline period to normalize to, bad chs to ignore, time period to average over, and the frequency ranges for alpha and beta band. comp: List of the two conditions to compare. Returns: List of two numbers representing the permuted difference between the two conditions for the alpha and beta bands. """ # collect power across conditions into single array # we downsample to match trial sizes power = [] for c in comp: if perm_num != -1 and c != 'Brain' and 'Brain' in comp: trial_ix = trial_indices[c][perm_num, :] power.append(all_conditions_power[c][trial_ix, :, :, :].squeeze()) else: power.append(all_conditions_power[c]) power = np.vstack(power) # permute the data if perm_num != -1: perm_ix = permutation_indices[perm_num, :] power = power[perm_ix, :, :, :].squeeze() # baseline normalize each condition separately if perm_num != -1: cond_len = power.shape[0] / 2 else: cond_len = all_conditions_power[comp[0]].shape[0] tmp = [] tmp.append(baseline_normalize(power[:cond_len, :], config['baseline'], times)) tmp.append(baseline_normalize(power[cond_len:, :], config['baseline'], times)) power = tmp # reduce over array power[0] = reduce_array_power(power[0], chs, config['%s_bad_chs' % exp], '1', axis=0) power[1] = reduce_array_power(power[1], chs, config['%s_bad_chs' % exp], '1', axis=0) # compute toi band power difference diffs = [] for band in [config['alpha'], config['beta']]: # reduce to band c1_power = reduce_band_power(power[0], freqs, band, axis=0) c2_power = reduce_band_power(power[1], freqs, band, axis=0) # reduce over time c1_power = reduce_toi_power(c1_power, times, config['toi'], axis=0) c2_power = reduce_toi_power(c2_power, times, config['toi'], axis=0) diffs.append(c1_power - c2_power) return diffs