Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Current clamp: Part 2

In part 2 if the current clamp section we will extend the anaysis of spike features such as how spike shape changes with spike number. We will cover how rheobase, membrane resistance and spike width are related. We also cover differences between cells types. A note about the data we will be using. The PV cells come from layer 5 of the ACC from ~P16 mice, the pyramidal cells come from layer 5 of the ACC from ~P16 mice and the MSNs come from the DMS of adult mice. Since they PV cells are not mature they may not show the features you typically see from fast-spiking interneurons, namely the high spike rate (>100 Hz). The datasets have been cleaned to the point where I would run statistics on them and use the data for plotting.

from collections import defaultdict

import numpy as np
import pandas as pd
from bokeh.io import output_notebook, show
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure
from bokeh.transform import factor_cmap, jitter

url_path = "https://cdn.jsdelivr.net/gh/LarsHenrikNelson/PatchClampHandbook/data/"

pyramidal = pd.read_csv(url_path + "pyramidal/spike_data.csv")
msn = pd.read_csv(url_path + "msn/spike_data.csv")
pv = pd.read_csv(url_path + "pv/spike_data.csv")

# Normalizing some measures to the start of the spike.
for i in [msn, pyramidal, pv]:
    i["Spike (ms) adj"] = i["Spike (ms)"] - i["Threshold (ms)"]
    i["Max Velocity (ms) adj"] = i["Max Velocity (ms)"] - i["Threshold (ms)"]
    i["Min Velocity (ms) adj"] = i["Min Velocity (ms)"] - i["Threshold (ms)"]
    i["AHP (ms) adj"] = i["AHP (ms)"] - i["Threshold (ms)"]
    i.rename(columns={"pulse_amp": "Pulse Amp (pA)"}, inplace=True)
    i["y"] = i["Threshold (mV)"]

columns = [
    "Threshold (mV)",
    "HW (ms)",
    "HW (mV)",
    "FW (ms)",
    "AUC",
    "AUC Left",
    "AUC Right",
    "Min Velocity (ms) adj",
    "Min Velocity",
    "Max Velocity (ms) adj",
    "Max Velocity",
    "AHP (ms) adj",
    "AHP (mV)",
    "Spike (mV)",
    "Spike (ms) adj",
    "Spike Number",
]

output_notebook()
Loading...
Loading...

Changes in spike shape with increasing number of spikes

One important feature of spikes is how their shape adapts to increasing firing rate. Since there is a limit to how fast and often some ion channels can open the spike shape will change with increasing spike frequency. One interesting thing to look as is how the area under the curve (AUC) of the spike changes. You can see how the depolarization phase of the spike remains virtually unchanged where as the repolarization phase gets much longer. The reason the repolorization phase gets so much larger is due to progressive inactivation of BK channels (Ca2+ activated K+ channel) or Kv4 channels (voltage-gated K+ channel). By splitting the AUC into pre peak and post peak components we can see that Na+ currents likely remain relatively unchanged however, depolarization of the cell is prolonged due decreased K+/Ca2+ currents. Some cell types, such as parvalbumin interneurons, have extremely stable spike waveform features. Lastly, notice how some relationships look to be mostly linear whereas some seem to be linear. Spike threshold, Spike (mV), Depolarization time (ms) and Pre-peak AUC are all pretty much linear. This likely because there is a single type of channel regulating these features (i.e Na+ channels). The features that are regulated by K+ and Ca2+ channels seem to be logarithmic or, some other curve in the case of the AHP.

Source
def create_multline_source(data):
    data_groupby = data.groupby("Acq Number")
    data_groupby.groups
    data_source = defaultdict(list)
    for c in columns:
        for key, value in data_groupby.groups.items():
            data_source[c].append(data.loc[value, c])
    data_source["y"] = data_source[columns[0]]
    data_source = ColumnDataSource(data_source)
    return data_source

msn_source = create_multline_source(msn)
fig1 = figure(height=200, width=250, title="MSN",x_axis_label="Spike Number")
mline = fig1.multi_line("Spike Number", "y", source=msn_source, color="black")

pyramidal_source = create_multline_source(pyramidal)
fig2 = figure(height=200, width=250, title="Pyramidal", x_axis_label="Spike Number")
mline = fig2.multi_line("Spike Number", "y", source=pyramidal_source, color="black")

pv_source = create_multline_source(pv)
fig3 = figure(height=200, width=250, title="PV", x_axis_label="Spike Number")
mline = fig3.multi_line("Spike Number", "y", source=pv_source, color="black")
menu = Select(title="Y Axis", value=columns[0], options=columns[:-1])

callback = CustomJS(
    args=dict(
        msn_source=msn_source,
        pyramidal_source=pyramidal_source,
        pv_source=pv_source,
        menu=menu,
    ),
    code="""
    msn_source.data.y = msn_source.data[menu.value];
    msn_source.change.emit();
    pyramidal_source.data.y = pyramidal_source.data[menu.value];
    pyramidal_source.change.emit();

    pv_source.data.y = pv_source.data[menu.value];
    pv_source.change.emit();
""",)
menu.js_on_change("value", callback)
show(column(menu, row(fig1, fig2, fig3)))
Loading...
Loading...

How would you compare whether there are differences between cells or genotypes or treatments? One of the easiest ways is to curve fit similar to what we did with FI curve and the sigmoid curve. Looking at the data there are couple curves you could choose. The primary curve I would is a logarithmic curve. Some curves look more linear or even polynomial in the case of the AHP. Choosing a curve to fit may also depend on the cell type. If you want to learn more about curve fitting check out the curves chapter.

Changes in IEI with frequency

The time between each spike, interevent interval (IEI) or interspike interval (ISI), can also change with spike frequency. Parvalbumin interneurons tend to have a very stable IEI. They can also burst fire a lower current injects with short IEIs followed by a longer IEI then short IEIs. Pyramidal cells on the other can have a lengthing of the IEI the more spikes there are. However, I have noticed in younger mice (P16) that this only occurs when the cell is spiking at higher frequencies.

Source
def create_iei_source(data):
    data_groupby = data.groupby("Acq Number")
    data_groupby.groups
    data_source = defaultdict(list)
    for key, value in data_groupby.groups.items():
        data_source["IEI"].append(np.diff(data.loc[value, "Spike (ms)"]))
        data_source["IEI Number"].append(data.loc[value, "Spike Number"][:-1])
    data_source = ColumnDataSource(data_source)
    return data_source

msn_source = create_iei_source(msn)
fig1 = figure(height=200, width=250, title="MSN")
mline = fig1.multi_line("IEI Number", "IEI", source=msn_source, color="black")

pyramidal_source = create_iei_source(pyramidal)
fig2 = figure(height=200, width=250, title="Pyramidal")
mline = fig2.multi_line("IEI Number", "IEI", source=pyramidal_source, color="black")

pv_source = create_iei_source(pv)
fig3 = figure(height=200, width=250, title="PV")
mline = fig3.multi_line("IEI Number", "IEI", source=pv_source, color="black")
show(row(fig1, fig2, fig3))
Loading...
Loading...

Changes in spike feature with current inject amplitude

Spike features can also change with current injection amplitude relative to the rheobase acquisition. If you see someone saying they are comparing spike features at 200 pA and they have a difference in rheobase between their groups this is a red flag. Always pull spike features from the rheobase acquisition and the first spike only if you are trying to get a single value per cell. If you want to pull across current injection amplitude you will need to curve fit the data and compare slopes or log growth or you will need to run some mixed model with appropriate transforms for non-linear curves.

pyr_fs = ColumnDataSource(pyramidal[pyramidal["Spike Number"] == 1])
msn_fs = ColumnDataSource(msn[msn["Spike Number"] == 1])
pv_fs = ColumnDataSource(pv[pv["Spike Number"] == 1])

fig1 = figure(height=200, width=250, title="MSN", x_axis_label="Current (pA)")
fig1.scatter("Pulse Amp (pA)", "y", source=msn_fs, color="black")
fig2 = figure(height=200, width=250, title="Pyramidal", x_axis_label="Current (pA)")
fig2.scatter("Pulse Amp (pA)", "y", source=pyr_fs, color="black")
fig3 = figure(height=200, width=250, title="PV", x_axis_label="Current (pA)")
fig3.scatter("Pulse Amp (pA)", "y", source=pv_fs, color="black")
menu = Select(title="Y Axis", value=columns[0], options=columns[:-1])

callback = CustomJS(
    args=dict(
        msn_source=msn_fs,
        pyramidal_source=pyr_fs,
        pv_source=pv_fs,
        menu=menu,
    ),
    code="""
    msn_source.data.y = msn_source.data[menu.value];
    msn_source.change.emit();
    pyramidal_source.data.y = pyramidal_source.data[menu.value];
    pyramidal_source.change.emit();

    pv_source.data.y = pv_source.data[menu.value];
    pv_source.change.emit();
""",)
menu.js_on_change("value", callback)
show(column(menu, row(fig1, fig2, fig3)))
Loading...
Loading...

Relationship between spike features

One important thing to understand is how all these spike features interact. Increases in spike width can offset decreases in spike amplitude thus maintaining the Na+ currents and the synaptic output of the cell. One interesting thing to note is that particularly for the repolarization time you can see how the first spikes of each acquisition are clustered together whereas the later spikes have an almost perfect correlation between peak amplitude and repolarization time. As peak voltage decreases repolarization time increases.

Source
sources = []
figs = []
for df, key in [(msn, "MSN"), (pyramidal, "Pyramidal"), (pv, "PV")]:
    df["y"] = df[columns[0]]
    df["x"] = df[columns[1]]
    source = ColumnDataSource(df[columns+["x"]+["y"]])
    sources.append(source)
    xy_figure = figure(height=250, width=400, title=key)
    xy_line = xy_figure.scatter(x="x", y="y", source=source, alpha=0.6)
    figs.append(xy_figure)
    
menu1 = Select(
    title="y",
    value=columns[0],
    options=columns,
)
menu2 = Select(
    title="x",
    value=columns[1],
    options=columns,
)

callback = CustomJS(
    args=dict(
        sources=sources,
        menu1=menu1,
        menu2=menu2,
    ),
    code="""
    for (let step = 0; step < sources.length; step++) {
        sources[step].data.y = sources[step].data[menu1.value];
        sources[step].data.x = sources[step].data[menu2.value];
        sources[step].change.emit();
    }
""",
)

menu1.js_on_change("value", callback)
menu2.js_on_change("value", callback)

show(column(row(menu1, menu2), row(*figs)))
Loading...
Loading...

Relationship between cell features

Source
pv_cell = pd.read_csv(url_path + "pv/cell_data.csv")
pyr_cell = pd.read_csv(url_path + "pyramidal/cell_data.csv")
msn_cell = pd.read_csv(url_path + "msn/cell_data.csv")

sources = []
figs = []
columns = pv_cell.columns.to_list()[:-1]
for df, key in [(pyr_cell, "Pyramidal"), (pv_cell, "PV"), (msn_cell, "MSN")]:
    df = df.to_dict(orient="list")
    df["y"] = df[columns[0]]
    df["x"] = df[columns[1]]
    source = ColumnDataSource(df)
    sources.append(source)
    xy_figure = figure(height=250, width=300, title=key)
    xy_line = xy_figure.scatter(x="x", y="y", source=source, alpha=0.6)
    figs.append(xy_figure)
    
menu1 = Select(
    title="y",
    value=columns[0],
    options=columns,
)
menu2 = Select(
    title="x",
    value=columns[1],
    options=columns,
)

callback = CustomJS(
    args=dict(
        sources=sources,
        menu1=menu1,
        menu2=menu2,
    ),
    code="""
    for (let step = 0; step < sources.length; step++) {
        sources[step].data.y = sources[step].data[menu1.value];
        sources[step].data.x = sources[step].data[menu2.value];
        sources[step].change.emit();
    }
""",
)

menu1.js_on_change("value", callback)
menu2.js_on_change("value", callback)

show(column(row(menu1, menu2), row(*figs)))
Loading...
Loading...

Comparisons between different cell types

Source
df = pd.concat([pv_cell, pyr_cell, msn_cell])
df["y"] = df["Baseline (mV)"]
cts = list(sorted(df["cell_type"].unique()))
source = ColumnDataSource(df)
source1 = ColumnDataSource(df.groupby("cell_type", as_index=False).mean())
fig = figure(height=250, width=300, x_range=cts)
xy_line = fig.scatter(
    x=jitter("cell_type", 0.3, range=fig.x_range),
    y="y",
    source=source,
    alpha=0.6,
    color=factor_cmap("cell_type", "Light7", cts),
)

xy_line1 = fig.scatter(
    x="cell_type",
    y="y",
    size=15,
    marker="diamond",
    source=source1,
    alpha=0.8,
    color="grey",
)

menu1 = Select(
    title="y",
    value=columns[0],
    options=columns,
)

callback = CustomJS(
    args=dict(
        source=source,
        source1=source1,
        menu1=menu1,
    ),
    code="""
    source.data.y = source.data[menu1.value];
    source1.data.y = source1.data[menu1.value];
    source.change.emit();
    source1.change.emit();
""",
)

menu1.js_on_change("value", callback)

show(column(menu1, fig))
Loading...
Loading...

One reason we covered different cell types is to see how they compare in their active properties. These properties are important for how the cells respond to input and their eventual output. Differences in rheobase are related to how easily a cell is able to elicit an action potential. Interneurons, and particularly PV cells, are considered the “organizers” of circuit function. PV cells integrate large amounts of input (see m/sPSC) and only respond when input is strong (as seen by the higher rheobase) at least during the early postnatal period. Where as pyramidal cells have a fairly low rheobase and recieve much less input than interneurons meaning they respond less selectively to input. One thing to note is that excitatory currents are much smaller and slower in pyramidal cells and MSN. Some of the rheobase differences can be offset by larger synaptic currents. If we look at how these cells spike we see that MSNs and PV cells have little spike shape adaptations where as pyramidal cells have a massive increase in width and AUC. Since pyramidal cells are the primary output cell in the cortex they need to be able to sustain their output and offset the decrease in synaptic output due to synaptic depression. Increased action potential width can increase the time that Na+ is in the synaptic cleft by prolonging depolarization.