2023-02-07 • [input] to ‘AdEx Nto1’

Distilation of 2023-01-19 Fit-a-line.

Windows

winsize = 100;  # first 10 ms
function windows(v, times, Δt, winsize)
    # Assuming that times occur in [0, T)
    win_starts = floor.(Int, times / Δt) .+ 1
    wins = Vector{Vector{eltype(v)}}()
    for a in win_starts
        b = a + winsize - 1
        if b  lastindex(v)
            push!(wins, v[a:b])
        end
    end
    return wins
end

windows(i_sim, spiketimes) = windows(
    vrec(sims[i_sim]),
    spiketimes,
    Δt,
    winsize,
)

windows(i_sim, i_input::Int) = windows(i_sim, spiketimes(inps[i_sim].inputs[i_input]));
# check for type inferrability
# st = spiketimes(inp.inputs[1])
# @code_warntype windows(vrec(sim), st, Δt, winsize)
# ok ✔
# @time wins = windows(1, 1);
# println()
# print(Base.summary(wins))

Now to make the data matrix

Data matrix

We’ll fit slope and intercept. So each datapoint, each row of X, is [1, t]

function build_Xy(windows, timepoints = 1:100)
    T = eltype(eltype(windows))
    N = length(windows) * length(timepoints)
    X = Matrix{T}(undef, N, 2)
    y = Vector{T}(undef, N)
    i = 1
    for win in windows
        for (tᵢ, yᵢ) in zip(timepoints, win[timepoints])
            X[i,:] .= [1, tᵢ]
            y[i] = yᵢ
            i += 1
        end
    end
    @assert i == N + 1
    return (X, y)
end;


# @time X, y = build_Xy(wins);
# check for type inferrability
# @code_warntype build_Xy(wins, 1:100)
# ok ✔
# size(X)
# size(y)

Plot some windows

# ts = @view X[:,2]
# sel = 1:10000

# plot(ts[sel]*Δt/ms, y[sel]/mV, ".", alpha=0.1);
# Ny = length(y)

3M datapoints (one connection, 10 minutes recording)

# sel = 1:100_000

# plot(
#     ts[sel]*Δt/ms,
#     y[sel]/mV,
#     ".";
#     alpha = 0.01,
#     ylim = [-50, -40],  # mV
#     clip_on = true,
# );

(Not very informative)

Use as conntest

# inh_neurons = Nₑ+1:N
# niᵢ = Nₑ + argmax(actual_spike_rates[inh_neurons])
# actual_spike_rates[niᵢ] / Hz
"""
Fit straight line to first 100 ms of
windows cut out of output neuron's voltage signal,
aligned to given times `z`
(or spiketimes of input neuron w/ index `z`).
(for simulation index iₛ)
"""
fitwins(z, iₛ=i) = begin
    wins = windows(iₛ, z)
    X, y = build_Xy(wins)
    β̂ = vec(X \ y)
     = X * β̂
    ε̂ = y .- 
    return (;
        X, y, β̂,
        intercept   = β̂[1] / mV,       # in mV
        slope       = β̂[2] / mV / Δt,  # in mV/second
        predictions = ,
        residuals   = ε̂,
    )
end;
# check for type inferrability
# @code_warntype fitwins(niᵢ)
# ok ✔
# @time fitwins(niᵢ).slope
spiketimes(i::Int) = spiketimes(inp.inputs[i])

# stₑ = spiketimes(niₑ)

# @time fitwins(shuffle_ISIs(stₑ)).slope
using Distributions

Summary

function htest(fit)
    (; X, y, β̂) = fit
    n = length(y)
    p = 2  # Num params
    dof = n - p
    ε̂ = fit.residuals
    s² = ε̂' * ε̂ / dof
    Q = inv(X' * X)
    σ̂β₂ = √(s² * Q[2,2])
    t = β̂[2] / σ̂β₂
    𝒩 = Normal(0, 1)
    pval = cdf(𝒩, -abs(t)) + ccdf(𝒩, abs(t))
    noise_mV = √s² / mV
    return (; t, pval, noise_mV)
end;
# htest(fitt)
# @time htest(fitt);

That’s fast :)

function conntest(z; α = 0.05)
    fit = fitwins(z)
    test = htest(fit)
    if test.pval < α
        predtype = (fit.slope > 0 ? :exc : :inh)
    else
        predtype = :unconn
    end
    return (;
        fit.slope,
        test.pval,
        predtype,
    )
end;
# conntest(niₑ)
# conntest(niᵢ)

Let’s try on shuffled spiketrains

# shuffled(ni) = shuffle_ISIs(spiketimes(ni));
# conntest(shuffled(niₑ))

Eval

# DataFrame(conntest(shuffled(niₑ)) for _ in 1:10)

Ok this is similar as in prev instantiation of this notebook / prev sim.

(The three unconns above were thus lucky).

Proper eval

I didn’t sim a 100 unconnected spikers, as before.
So we can’t use that for an FPR estimate.
But we can shuffle some real spiketrains to get sth similar.
Let’s draw from all, so there’s a mix of spikerates.

# ids = sample(1:N, 100, replace=true)
# unconnected_trains = shuffle_ISIs.(spiketimes.(ids));

Our perftable expects a dataframe with :predtype and :conntype columns

# inh_neurons
5201:6500
# real_spiketrains = spiketimes.(1:N);
# all_spiketrains = [real_spiketrains; unconnected_trains];
conntype(i) = 
    if i < Nₑ
        conntype = :exc
    elseif i  N
        conntype = :inh
    else
        conntype = :unconn
    end

makerow(i; α=0.001) = begin
    spikes = all_spiketrains[i]
    test = conntest(spikes; α)
    (; conntype = conntype(i), test...)
end;
# @time makerow(1)
  0.075884 seconds (312.28 k allocations: 68.547 MiB)
(conntype = :exc, slope = 17.9, pval = 0.000771, predtype = :exc)
# @time makerow(6600)
  0.077716 seconds (236.50 k allocations: 51.938 MiB)
(conntype = :unconn, slope = 11.6, pval = 0.0702, predtype = :unconn)
conntest_all() = @showprogress map(makerow, eachindex(all_spiketrains));

# rows = cached(conntest_all, [], key="2023-01-19__Fit-a-line");
# df = DataFrame(rows)
# df |> disp(20)
6600×4 DataFrame
6575 rows omitted
Rowconntypeslopepvalpredtype
SymbolFloat64Float64Symbol
1exc17.90.000771exc
2exc32.21.31E-08exc
3exc-18.10.000375inh
4exc45.45.06E-08exc
5exc-46.14.47E-07inh
6exc-8.750.0415unconn
7exc-28.80.000224inh
8exc6.10.279unconn
9exc10.10.167unconn
10exc-25.85.28E-06inh
11exc-11.80.156unconn
12exc-19.12.65E-09inh
13exc-10.70.0209unconn
&vellip;&vellip;&vellip;&vellip;&vellip;
6589unconn8.590.144unconn
6590unconn-2.190.727unconn
6591unconn-150.0281unconn
6592unconn23.81.56E-10exc
6593unconn24.40.00157unconn
6594unconn9.960.309unconn
6595unconn-4.630.101unconn
6596unconn1311.65E-29exc
6597unconn-16.51.81E-07inh
6598unconn-27.91.36E-06inh
6599unconn0.5940.932unconn
6600unconn11.60.0702unconn
# perftable(df)
Tested connections: 6600                                                                                
┌───────Real type───────┐Precision
unconnexcinh
unconn6631777062%
Predicted typeexc20125912090%
inh1476347538%
Sensitivity66%24%37%

(Code should be written / dug up to sweep threshold i.e. get AUC scores etc, but):

At this arbitrary ‘α’ = 0.001:
FPR: 34%
TPRₑ: 24%
TPRᵢ: 37%