Skip to article frontmatterSkip to article content

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/"

output_notebook()
Loading...
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)"]

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
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",
]
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")
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")
mline = fig2.multi_line("Spike Number", "y", source=pyramidal_source, color="black")
menu = Select(title="Column", value=columns[0], options=columns)

pv_source = create_multline_source(pv)
fig3 = figure(height=200, width=250, title="PV")
mline = fig3.multi_line("Spike Number", "y", source=pv_source, color="black")
menu = Select(title="Column", 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...

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.

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)
mline = fig1.multi_line("IEI Number", "IEI", source=msn_source, color="black")

pyramidal_source = create_iei_source(pyramidal)
fig2 = figure(height=200, width=250)
mline = fig2.multi_line("IEI Number", "IEI", source=pyramidal_source, color="black")
menu = Select(title="Column", value=columns[0], options=columns)

pv_source = create_iei_source(pv)
fig3 = figure(height=200, width=250)
mline = fig3.multi_line("IEI Number", "IEI", source=pv_source, color="black")
menu = Select(title="Column", value=columns[0], options=columns[:-1])
show(row(fig1, fig2, fig3))
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.

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...

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...

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...

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.