2020-11-27 • Permutation test for \(p(\)connected\()\)

Imports & time grid

from voltage_to_wiring_sim.support.notebook_init import *
Preloading:
 - numpy … (0.19 s)
 - matplotlib.pyplot … (0.30 s)
 - numba … (0.45 s)

Importing from submodules (compiling numba functions) … ✔

Imported `np`, `mpl`, `plt`
Imported codebase (`voltage_to_wiring_sim`) as `v`
Imported `*` from `v.support.units`
Setup autoreload
v.print_reproducibility_info()

This cell was last run by tfiers on yoga
on Mon 30 Nov 2020, at 15:59 (UTC+0100).

Last git commit (Mon 30 Nov 2020, 12:19).

Uncommited changes to:

A  notebooks/2020-11-11__unitlib.ipynb
tg = v.TimeGrid(T=10*minute, dt=0.1*ms);

Generate VI signal

Spike trains

‘Network’ definition.

N_in = 30
p_connected = 0.5

N_connected = round(N_in * p_connected)
N_unconnected = N_in - N_connected
15

Have all incoming neurons spike with the same mean frequency, for now.

f_spike = 20 * Hz;
gen_st = v.generate_Poisson_spike_train

v.fix_rng_seed()
%%time
spike_trains_connected = [gen_st(tg, f_spike) for _ in range(N_connected)]
spike_trains_unconnected = [gen_st(tg, f_spike) for _ in range(N_unconnected)];
Wall time: 1.95 s
all_spike_trains = spike_trains_connected + spike_trains_unconnected;

Inspect a time excerpt..

time_slice = 1 * minute + np.array([0, 1]) * second

slice_indices = np.round(time_slice / tg.dt).astype(int)
i_slice = slice(*slice_indices)
t_slice = tg.t[i_slice]
array([60, 60, 60, ..., 61, 61, 61])

..of one presynaptic neuron:

def plot_spike_train_excerpt(spike_train):
    return v.spike_train.plot(t_slice, spike_train[i_slice])
plot_spike_train_excerpt(all_spike_trains[0]);
../_images/2020-11-27__permutation_test_18_0.png

All connected presynaptic neurons:

all_incoming_spikes = sum(spike_trains_connected)

v.spike_train.plot(t_slice, all_incoming_spikes[i_slice]);
../_images/2020-11-27__permutation_test_20_0.png

Note that some time bins contain more than one spike.
(The simulator handles this, by increasing synaptic conductance by an integer multiple of Δg_syn in that timebin).

Synaptic conductance

See the previous notebook /notebooks/2020-07-27__Synaptic_conductances for some more explanation.

Δg_syn = 0.8 * nS
τ_syn = 7 * ms;
g_syn = v.calc_synaptic_conductance(
    tg, all_incoming_spikes, Δg_syn, τ_syn)

plt.plot(t_slice, g_syn[i_slice] / nS);
../_images/2020-11-27__permutation_test_25_0.png

Membrane voltage

params = v.params.cortical_RS
v.pprint(params)
IzhikevichParams
----------------
C = 1e-10
k = 7e-07
v_r = -0.06
v_t = -0.04
v_peak = 0.035
a = 30.0
b = -2e-09
c = -0.05
d = 1e-10
v_syn = 0.0
%%time
sim = v.simulate_izh_neuron(tg, params, g_syn)
Wall time: 246 ms
plt.plot(t_slice, sim.V_m[i_slice] / mV);
../_images/2020-11-27__permutation_test_29_0.png

Imaging model

As in /notebooks/2020-07-06__Single_neuron_sim.

Vm_noisy = v.add_VI_noise(sim.V_m, params)

plt.plot(t_slice, Vm_noisy[i_slice] / mV);
../_images/2020-11-27__permutation_test_32_0.png

Shuffle one spike train

We choose one spike train / incoming neuron, and will test the hypothesis that this neuron is connected to the simulated neuron.

  • H0 = neuron of incoming spike train is not connected to simulated neuron

  • H1 = they are connected

spike_train = all_spike_trains[0];

We will shuffle the real spike train \(S\) times to generate \(S\) ‘fake’ spike trains, which have the same number of spikes but at random times.

S = 100;
%%time
shuffled_spike_trains = []
for i in range(S):
    x = spike_train.copy()
    np.random.shuffle(x)  # in-place
    shuffled_spike_trains.append(x)
Wall time: 23.4 s

Inspect a few shuffled spike trains.

for i in range(3):
    plot_spike_train_excerpt(shuffled_spike_trains[i]);
../_images/2020-11-27__permutation_test_40_0.png ../_images/2020-11-27__permutation_test_40_1.png ../_images/2020-11-27__permutation_test_40_2.png

Real spike train again:

plot_spike_train_excerpt(spike_train);
../_images/2020-11-27__permutation_test_42_0.png

Same number of spikes in all:

def num_spikes(spike_train):
    spike_indices = v.get_spike_indices(spike_train)
    return len(spike_indices)
for i in range(3):
    print(num_spikes(shuffled_spike_trains[i]))
12020
12020
12020
num_spikes(spike_train)
12020

Spiking frequency of the chosen presynaptic neuron:

num_spikes(spike_train) / tg.T / Hz
20.03

That’s to spec.

Spike-triggered windowing & averaging

For each spike train (original and shuffleds), we extract spike-triggered windows from the simulated voltage imaging signal, and average those windows.

window_length = 100 * ms;
window_tg = v.TimeGrid(window_length, tg.dt);
def calc_STA(spike_train):
    return v.calculate_STA(Vm_noisy, spike_train, tg, window_tg)
original_STA = calc_STA(spike_train);
%%time
shuffled_STAs = []
for train in shuffled_spike_trains:
    shuffled_STAs.append(calc_STA(train))
Wall time: 9.14 s

Plot STA’s of the original spike train (top left) and three shuffled spike trains:

fig, axes = plt.subplots(ncols=2, nrows=2, sharey=True)

original_ax = axes.flat[0]
v.plot_STA(original_STA, window_tg, original_ax)
ylabel = original_ax.yaxis.get_label()
ylabel.set_horizontalalignment('right')


for i in range(3):
    ax = axes.flat[i+1]
    v.plot_STA(shuffled_STAs[i], window_tg, ax)
    ax.set(ylabel=None)
../_images/2020-11-27__permutation_test_58_0.png

Peak height of STA

shuffled_STA_peak_heights = []
for STA in shuffled_STAs:
    shuffled_STA_peak_heights.append(np.max(STA))
shuffled_STA_peak_heights = np.array(shuffled_STA_peak_heights);
    # Convert list to numpy array for division or comparison with scalars later.
original_STA_peak_height = np.max(original_STA);

Let’s look at the distribution of peak heights.

import seaborn as sns  # Extension of matplotlib for statistical data viz.

Shuffled spike trains only:

dist_ax = sns.distplot(shuffled_STA_peak_heights / mV, kde=False, rug=True, bins=12)
#   KDE=False means that the y-axis labels give count in each bin. (Otherwise, it's a density).
dist_ax.set_xlabel("STA peak height (mV)");
../_images/2020-11-27__permutation_test_65_0.png

Add original spike train:

dist_ax.axvline(original_STA_peak_height / mV, color="C1")
dist_ax.figure
../_images/2020-11-27__permutation_test_67_0.png

p-value

As proportion

If we define p-value as proportion of shuffled STA peak heights higher than the original, we get, of course:

p_proportion = np.sum(shuffled_STA_peak_heights > original_STA_peak_height) / S
0.0

(As integral of KDE)

We can fit a distribution to the H0 peak heights to get another type of p-value.

We choose a kernel-density estimate (KDE), i.e. a non-paramteric distribution (except hyperparams). (See here for code explanation: https://jakevdp.github.io/PythonDataScienceHandbook/05.13-kernel-density-estimation.html#Kernel-Density-Estimation-in-Practice)

from sklearn.neighbors import KernelDensity
kde = KernelDensity(kernel='gaussian', bandwidth=0.1 * mV)
kde.fit(shuffled_STA_peak_heights[:, np.newaxis])  # `fit` expects 2D array.

def plot_KDE(xrange):  # xrange given in mV
    x = np.linspace(*np.array(xrange) * mV, 1000)[:, np.newaxis]
    y = np.exp(kde.score_samples(x));  # `score_samples` returns log of prob.
    fig, ax = plt.subplots()
    ax.plot(x / mV, y)
    ax.axvline(original_STA_peak_height / mV, color="C1")
    return fig, ax

plot_KDE(dist_ax.get_xlim());
../_images/2020-11-27__permutation_test_75_0.png
plot_KDE((-45.6, -45.5));
../_images/2020-11-27__permutation_test_76_0.png
def pdf(x):
    return np.exp(kde.score_samples([[x]])).item()

pdf(original_STA_peak_height)
6.209E-68

p-value is area under the curve to the right of original spike train peak STA height.

from scipy.integrate import quad

Docs: https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quad.html

result, error = quad(pdf, a=original_STA_peak_height, b=0 * mV)
(result, error)
(1.099E-75, 2.186E-75)

The absolute error is larger than the estimated integral. The result is thus not reliable.

Anyway, from the plots it’s clear that this p-value is very small.

Next steps: describe influence of N_in, p_connected, SNR, … on p-value.

repeat for all connected/unconnected spike trains

Some feedback from code review

(see also supervision record 30th nov).

Three stages of pipeline:

  1. neuron model

  2. VI model

  3. test

Noise both for neuron and for inputs.

Lit on statistics of non-recorded spike trains noise

p < 0.01

1/S
0.01

Repro info

v.print_reproducibility_info(verbose=True)

This cell was last run by tfiers on yoga
on Mon 30 Nov 2020, at 16:01 (UTC+0100).

Last git commit (Mon 30 Nov 2020, 12:19).

Uncommited changes to:

A  notebooks/2020-11-11__unitlib.ipynb
 M notebooks/2020-11-27__permutation_test.ipynb

Platform:

Windows-10
CPython 3.8.3 (C:\conda\python.exe)
Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz

Dependencies of voltage_to_wiring_sim and their installed versions:

numpy                1.19.2
matplotlib           3.3.2
numba                0.51.2
seaborn              0.10.1
scipy                1.5.2
scikit-learn         0.23.2
preload              2.1
py-cpuinfo           7.0.0