Surveying Neural Spiking in Visual Cortex

Authors
Dr. Ole Bialas | Dr. Nicholas Del Grosso

Setup

Import Libraries

Import the modules required for this notebook

import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
import pingouin as pg

Download Data

Download the data required for this notebook

import requests
url = "https://uni-bonn.sciebo.de/public.php/webdav/YTgrGyB5MekRP2n"
print("Downloading Data ...")
response = requests.get(("/").join(url.split("/")[:-1]), auth=(url.split("/")[-1], "ibots"))
response.raise_for_status()
with open("flash_spikes.parquet", "wb") as file:
    file.write(response.content)
print("Done!")
Downloading Data ...
Done!

Section 1: Exploring the Data

In this notebook, we’ll work with data from a recent study conducted at the Allen Institute for Brain Science. The data we’ll use contains the spikes recorded from over 300 neurons recorded across 6 different visual brain areas. In this section, we are going to read the data into a pandas data frame and explore its contents.

Code Description
df = pd.read_parquet("mydata.parquet") Read the file "mydata.parquet" into a data frame df
df.head(n) Print the first n lines of the data frame df
df.shape Get the shape of a data frame (i.e. the number of rows and columns)
df.col1 Access the column col1 of the data frame df
df.col1.unique() Get the .unique() values stored in the column col1
df.col1.nunique() Get the number of unique values in the column col
df.col1.max() Get the maximum value in column col1
df.groupby("col1") Group the data in df by the values in "col1"
df.groupby("col1").col2.count() Get the .count() of .col2 for every unique value in "col1"

Exercise: Load the data stored in flash_spikes.parquet into a data frame and print the first 5 rows.

Solution
df = pd.read_parquet("flash_spikes.parquet")
df.head(5)
unit_id brain_area spike_time
0 951031334 LM 1276.297339
1 951031154 LM 1276.298206
2 951031243 LM 1276.298739
3 951021543 PM 1276.299007
4 951031253 LM 1276.301272

Exercise: How many spikes are there in the data frame (Hint: each row contains one spike)

Solution
df.shape[0]
457706

Exercise: At what time was the first and last spike recorded?

Solution
df.spike_time.min(), df.spike_time.max()
(np.float64(1276.2973389650292), np.float64(1572.793837848813))

Exercise: From which (unique) brain areas were the spikes recorded?

Solution
df.brain_area.unique()
array(['LM', 'PM', 'AL', 'AM', 'V1', 'RL'], dtype=object)

Exercise: What is the total number of units recorded?

Solution
df.unit_id.nunique()
387

Exercise: What is the number of spikes recorded from every unit?

Solution
df.groupby("unit_id").spike_time.count()
unit_id
951015628      84
951015643      75
951015704      31
951015717     598
951015731      30
             ... 
951039462     753
951039560     168
951039854      76
951039863     114
951039870    3403
Name: spike_time, Length: 387, dtype: int64

Exercise: What is the number of units recorded in each brain area (Hint: use df.groupby("brain_area")["unit_id"])?

Solution
df.groupby("brain_area").unit_id.nunique()
brain_area
AL    82
AM    77
LM    62
PM    68
RL    13
V1    85
Name: unit_id, dtype: int64

Exercise: What is the smallest and largest number of spikes recorded from any single unit?

Solution
print(df.groupby("unit_id").spike_time.count().min())
print(df.groupby("unit_id").spike_time.count().max())
2
15498

Section 2: Visualizing Neural Firing with Rasterplots

Even though the metrics we computed in the previous section are useful for describing and understanding the data, they only provide a very coarse and incomplete picture. In this section, we are going to visualize the spike trains to get a more detailed view of what is going on. For this, we will use rasterplots, one of the most common visualizations of neural spiking activity. We can make rasterplots for single neurons or groups of neurons to show how the firing of neurons evolves over time.

Code Description
df[df.col1==1] Get all rows of df where the value of column .col1 equals 1
df.col1.tolist() Export the data in column .col1 as a list
plt.eventplot(x) Plot the events in x
plt.eventplot(x, linewidth=0.5, linelengths=0.5) Plot the events in x and change the with and legth of the lines
plt.eventplot([x1, x2], lineoffsets=1.5) Plot the events in x1 and x2 on separate lines with the given offset
plt.xlim(xmin, xmax) Limit the x-axis to the range between xmin and xmax

Exercise: Get all spikes recorded from unit 951021564

Solution
df[df.unit_id == 951021564]
unit_id brain_area spike_time
8597 951021564 PM 1282.610957
9338 951021564 PM 1283.347892
74265 951021564 PM 1324.115635
108936 951021564 PM 1344.918425
151441 951021564 PM 1370.074192
... ... ... ...
350744 951021564 PM 1510.104469
361159 951021564 PM 1517.702290
379406 951021564 PM 1529.660088
442723 951021564 PM 1565.361785
444842 951021564 PM 1566.201287

103 rows × 3 columns

Exercise: Create a rasterplot of the spikes recorded from unit 951021564 (Hint: use plt.eventplot).

Solution
plt.eventplot(df[df.unit_id == 951021564].spike_time)

Exercise: Create a rasterplot of the spikes recorded from unit 951021564 and reduce the linewidth to 0.5.

Solution
plt.eventplot(df[df.unit_id == 951021564].spike_time, linewidth=0.5)

Exercise: Create a rasterplot for the units 951021564 and 951021835 (Hint: plt.eventplot accepts a lists as input).

Solution
spike_times = [
    df[df.unit_id == 951021564].spike_time,
    df[df.unit_id == 951021835].spike_time,
]
plt.eventplot(spike_times, linewidth=0.5)

Example: Extract the spikes recorded from every unit in the primary visual cortex "V1".

mask = df.brain_area == "V1"
spike_times = df[mask].groupby("unit_id").spike_time.unique().tolist()

Exercise: Create a rasterplot displaying the spike_times from each unit between 1300 and 1350 seconds.

Solution
plt.eventplot(spike_times, linewidth=0.5, linelengths=0.5, lineoffsets=1.5)
plt.xlim(1300, 1350)
(1300.0, 1350.0)

Exercise: Create a rasterplot displaying the spikes from all units recorded in the posteriomedial area "PM" between 1300 and 1350 seconds.

Solution
mask = df.brain_area == "PM"
spike_times = df[mask].groupby("unit_id").spike_time.unique().tolist()
plt.eventplot(spike_times, linewidth=0.5, linelengths=0.5, lineoffsets=1.5)
plt.xlim(1300, 1350)
(1300.0, 1350.0)

Exercise: Create a rasterplot displaying the spikes from all units recorded in the rostrolateral area "RL" between 1400 and 1500 seconds.

Solution
mask = df.brain_area == "RL"
spike_times = df[mask].groupby("unit_id").spike_time.unique().tolist()
plt.eventplot(spike_times, linewidth=0.5, linelengths=0.5, lineoffsets=1.5)
plt.xlim(1400, 1500)
(1400.0, 1500.0)

Section 3: Comparing Neural Firing Across Visual Areas

Raster plots are great for detailed visualization of neural firing but with all this detail, it is difficult to make out larger trends and compare the activity between larger groups of neurons. In this section, we are going to use the seaborn library to create plots that aggregate information across spikes and units which allows us to compare the different areas in the visual cortex.

Code Description
sns.countplot(df, x="col1") Create a barplot that counts how often each unique value in "col1" appears in df
sns.countplot(df, y="col1") Create the same bar plot but with horizontal bars
sns.countplot(df, x="col1", hue="col2") Create count plot for the unique values in "col1" and add a hue for the unique values in "col2"
sns.kdeplot(df, x="col1") Plot the kernel density estimate for the values in "col1"
sns.kdeplot(df, x="col1", hue="col2") Plot the kernel density estimate for the values in "col1" and add a hue for the unique values in "col2"
sns.barplot(x) Create a barplot for the aggregated values in the pandas series x

Exercise: Create a sns.countplot that displays the number of spikes recorded from each "brain_area".

Solution
sns.countplot(df, x="brain_area")

Exercise: Create a sns.countplot that displays the number of spikes recorded from each "unit" (Hint: you can remove the x-ticks with plt.xticks([])).

Solution
sns.countplot(df, x="unit_id")
plt.xticks([])

Exercise: Create a sns.countplot that displays the number of spikes recorded from each "unit" and add a hue to encode "brain_area".

Solution
sns.countplot(data=df, x="unit_id", hue="brain_area")
plt.xticks([])

Exercise: Create the same plot again but with horizontal bars

Solution
sns.countplot(data=df, y="unit_id", hue="brain_area")
plt.yticks([])

Exercise: Create a sns.kdeplot to visualize the distribution of "spike_time"

Solution
sns.kdeplot(df, x="spike_time")

Exercise: Create a sns.kdeplot to visualize the distribution of "spike_time" and add a hue for "brain_area".

Solution
sns.kdeplot(df, x="spike_time", hue="brain_area")

Exercise: Compute the number of units recorded in each brain area and plot the result as a sns.barplot (Hint: the function only requires a single argument).

Solution
grouping = df.groupby("brain_area").unit_id.nunique()
sns.barplot(grouping)

Exercise: Get the number of spikes recorded from each brain area and divide it by the number of units recorded in each area to get the average number of spikes per unit in each area. Then, plot the result as a sns.barplot.

Solution
grouping = (
    df.groupby("brain_area").spike_time.count()
    / df.groupby("brain_area").unit_id.nunique()
)

sns.barplot(grouping)

Section 4: Statistical Comparison of Firing Rates across Areas

The visualizations from the previous session seem to suggest differences in firing rates between visual areas. In this section we are going to quantify these differences. We are going to compute the firing rates for every unit and visualize their distribution across the different brain areas. Then, we are going to use the Mann-Whitney-U (MWU) test to check whether the observed differences are statistically significant. The MWU test is a non-parametric statistical test that estimates how likely it is that two sets of measurements (i.e. firing rates in two areas) come from the same distribution. Based on this probability, captured by the p-value, we can assess whether the difference is above what we would expect by chance.

Code Description
df.groupby(["col1", "col2"]) Group df by the columns "col1" and "col2"
df.reset_index() Reset the index of df and store the old index as a new column
df.columns = ["new1", "new2"] Rename the columns to "new1" and "new2" (the number of elements must match the number of columns)
sns.barplot(df, x="col2", y="col1", estimator="mean") Plot the "mean" of the "col1" for every category in "col2"
pg.mwu(x, y) Apply the MWU test to the measurements x and y
pg.multicomp(pvals, alpha, method="bonferroni") Apply the Bonferroni correction for multiple comparisons to the given pvals and check if they are significant given the threshold alpha

Exercise: Compute the number of spike times for each "brain_area" and "unit_id".

Solution
df.groupby(["brain_area", "unit_id"]).spike_time.count()
brain_area  unit_id  
AL          951034946      53
            951034962       7
            951034978     168
            951034993     402
            951035003    5060
                         ... 
V1          951028461       2
            951028472      35
            951028481      97
            951028618      70
            951028751     269
Name: spike_time, Length: 387, dtype: int64

Exercise: Divide the result from @exr-count by the duration of the recording to get the firing rate of every unit (Hint: you can approximate the duration as the difference between the first and last "spike_time"). Assign the result to a new variable fr.

Solution
dur = df.spike_time.max() - df.spike_time.min()
fr = df.groupby(["brain_area", "unit_id"]).spike_time.count() / dur

Exercise: Use fr.reset_index() to create a new data frame where each row represents one unit (Hint: the method does not modify fr so you must expliclity overwrite the original value). Then, rename the columns to label the new column with "firing_rate" and print the renamed data frame.

Solution
fr = fr.reset_index()
fr.columns = ["brain_area", "unit_id", "firing_rate"]
fr
brain_area unit_id firing_rate
0 AL 951034946 0.178754
1 AL 951034962 0.023609
2 AL 951034978 0.566617
3 AL 951034993 1.355834
4 AL 951035003 17.065969
... ... ... ...
382 V1 951028461 0.006745
383 V1 951028472 0.118045
384 V1 951028481 0.327154
385 V1 951028618 0.236090
386 V1 951028751 0.907262

387 rows × 3 columns

Example: Create a sns.barplot for the mean "firing_rate" of every "brain_area".

sns.barplot(fr, x="brain_area", y="firing_rate", estimator="mean")

Exercise: Create a sns.barplot for the standard deviation "std" of the "firing_rate" of every "brain_area".

Solution
sns.barplot(fr, x="brain_area", y="firing_rate", estimator="std")

Exercise: Create a sns.kdeplot to visualize the distribution of "firing_rate" and add a hue to encode the "brain_area".

Solution
sns.kdeplot(fr, x="firing_rate", hue="brain_area")

Exercise: Re-create the sns.kdeplot but use common_norm=False to account for the difference in the number of recorded units per area.

Solution
sns.kdeplot(fr, x="firing_rate", hue="brain_area", common_norm=False)

Example: Perform a Mann-Whitney-U-Test to test if there is a significant difference in firing rate between the primary visual cortex "V1" and the anterolateral area "AL"

x = fr[fr.brain_area == "V1"].firing_rate
y = fr[fr.brain_area == "AL"].firing_rate
pg.mwu(x, y)
U-val alternative p-val RBC CLES
MWU 3571.0 two-sided 0.784309 0.024677 0.512339

Exercise: Look at the graph created in @exm-mean that showed the mean firing rates for each brain area. Based on this plot, which differences would you expect to be significant? Test you expectation by performing Mann-Whitney-U-Test on the respective pair(s).

Solution
x = fr[fr.brain_area == "PM"].firing_rate
y = fr[fr.brain_area == "AM"].firing_rate
pg.mwu(x,y)
U-val alternative p-val RBC CLES
MWU 2332.5 two-sided 0.25882 -0.109053 0.445474

Example: Apply the Bonferroni correction to adjust the p-values for multiple comparisons and compare them to the given threshold alpha.

pvals = [0.01, 0.04]
alpha = 0.05
pg.multicomp(pvals, alpha, method="bonferroni")
(array([ True, False]), array([0.02, 0.08]))

Exercise: Apply the Bonferroni correction to adjust the p-values returned by the statistical tests you performed in the exercises before. Are the results still significant after correction?