Skip to content

Note

Click here to download the full example code

Data analysis with pynapple

Learning objectives

  • Loading a NWB file
  • Compute tuning curves
  • Decode neural activity
  • Compute correlograms
  • Compute perievent

The pynapple documentation can be found here.

The documentation for objects and method of the core of pynapple is here.

The documentation for high level functions of pynapple is here.

Let's start by importing the pynapple package, matplotlib, numpy to see if everything is correctly installed. If an import fails, you can do !pip install pynapple matplotlib in a cell to fix it.

import pynapple as nap
import matplotlib.pyplot as plt
import workshop_utils
import numpy as np

Loading a NWB file

Pynapple commit to support NWB for data loading. If you have installed the repository, you can run the following cell:

path = workshop_utils.fetch_data("Mouse32-140822.nwb")

print(path)

Out:

/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/data/Mouse32-140822.nwb

Pynapple provides the convenience function nap.load_file for loading a NWB file.

Question: Can you open the NWB file giving the variable path to the function load_file and call the output data?

data = nap.load_file(path)

print(data)

Out:

/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/hdmf/utils.py:668: UserWarning: Ignoring cached namespace 'hdmf-common' version 1.5.0 because version 1.8.0 is already loaded.
  return func(args[0], **pargs)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/hdmf/utils.py:668: UserWarning: Ignoring cached namespace 'core' version 2.4.0 because version 2.7.0 is already loaded.
  return func(args[0], **pargs)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/hdmf/utils.py:668: UserWarning: Ignoring cached namespace 'hdmf-experimental' version 0.1.0 because version 0.5.0 is already loaded.
  return func(args[0], **pargs)
Mouse32-140822
┍━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┑
│ Keys                   Type        │
┝━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━┥
│ units                  TsGroup     │
│ sws                    IntervalSet │
│ rem                    IntervalSet │
│ position_time_support  IntervalSet │
│ epochs                 IntervalSet │
│ ry                     Tsd         │
┕━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┙

The content of the NWB file is not loaded yet. The object data behaves like a dictionnary.

Question: Can you load the spike times from the NWB and call the variables spikes?

spikes = data["units"]  # Get spike timings

Question: And print it?

print(spikes)

Out:

Index    rate     location    group
-------  -------  ----------  -------
0        2.96981  thalamus    1
1        2.42638  thalamus    1
2        5.93417  thalamus    1
3        5.04432  thalamus    1
4        0.30207  adn         2
5        0.87042  adn         2
6        0.36154  adn         2
...      ...      ...         ...
42       1.02061  thalamus    5
43       6.84913  thalamus    6
44       0.94002  thalamus    6
45       0.55768  thalamus    6
46       1.15056  thalamus    6
47       0.46084  thalamus    6
48       0.19287  thalamus    7

There are a lot of neurons. The neurons that interest us are the neurons labeled adn.

Question: Using the slicing method of your choice (getby_category method or boolean indexing), can you select only the neurons in adn that are above 1 Hz firing rate?

spikes = spikes[(spikes.location=='adn') & (spikes.rate>1.0)]

print(spikes)

Out:

Index    rate      location    group
-------  --------  ----------  -------
7        10.51737  adn         2
8        2.62475   adn         2
9        2.55818   adn         2
10       7.06715   adn         2
12       1.58248   adn         2
13       4.87837   adn         2
14       8.47337   adn         2
...      ...       ...         ...
26       4.0242    adn         3
28       1.78011   adn         4
29       4.23006   adn         4
30       2.15215   adn         4
32       1.12899   adn         4
33       5.26316   adn         4
34       1.57122   adn         4

The NWB file contains other informations about the recording. ry contains the value of the head-direction of the animal over time.

Question: Can you extract the angle of the animal in a variable called angle and print it?

angle = data["ry"]
print(angle)

Out:

Time (s)
----------  --------
8812.416    0.581795
8812.4416   0.578113
8812.4672   0.571791
8812.4928   0.554532
8812.5184   0.554532
8812.544    0.554532
8812.5696   0.554532
...
10771.123   5.67668
10771.149   5.67668
10771.174   5.7182
10771.2     5.74727
10771.226   5.74727
10771.251   5.74727
10771.277   5.72318
dtype: float64, shape: (71478,)

But are the data actually loaded ... or not?

Question: Can you print the underlying data array of angle?

print(angle.d)

Out:

<HDF5 dataset "data": shape (71478,), type "<f8">

The animal was recorded during wakefulness and sleep.

Question: Can you extract the behavioral intervals in a variable called epochs?

epochs = data["epochs"]

print(epochs)

Out:

{'sleep':             start      end
       0      0     8812.3
       1  10771.3  22025
shape: (2, 2), time unit: sec., 'wake':             start      end
       0   8812.3  10771.3
shape: (1, 2), time unit: sec.}

NWB file can save intervals with multiple labels. By default, pynapple will group epochs with the same label in one IntervalSet and return a dictionnary of IntervalSet.

wake_ep = epochs['wake']

Compute tuning curves

Now that we have spikes and a behavioral feature (i.e. head-direction), we would like to compute the firing rate of neurons as a function of the variable angle during wake_ep. To do this in pynapple, all you need is a single line of code!

Question: can you compute the firing rate of ADn units as a function of heading direction, i.e. a head-direction tuning curve and call the variable tuning_curves?

tuning_curves = nap.compute_1d_tuning_curves(
    group=spikes, 
    feature=angle, 
    nb_bins=61, 
    ep = angle.time_support,
    minmax=(0, 2 * np.pi)
    )

Question: Can you plot some tuning curves?

plt.figure()
plt.subplot(221)
plt.plot(tuning_curves.iloc[:,0])
plt.subplot(222,projection='polar')
plt.plot(tuning_curves.iloc[:,0])
plt.subplot(223)
plt.plot(tuning_curves.iloc[:,1])
plt.subplot(224,projection='polar')
plt.plot(tuning_curves.iloc[:,1])

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f8729388dd0>]

Most of those neurons are head-directions neurons.

The next cell allows us to get a quick estimate of the neurons's preferred direction.

pref_ang = tuning_curves.idxmax()

Question: Can you add it to the metainformation of spikes?

spikes['pref_ang'] = pref_ang

This index maps a neuron to a preferred direction between 0 and 360 degrees.

Question: Can you plot the spiking activity of the neurons based on their preferred direction as well as the head-direction of the animal? For the sake of visibility, you should restrict the data to the following epoch : ex_ep = nap.IntervalSet(start=8910, end=8960).

ex_ep = nap.IntervalSet(start=8910, end=8960)


plt.figure()
plt.subplot(211)
plt.plot(angle.restrict(ex_ep))
plt.ylim(0, 2*np.pi)

plt.subplot(212)
plt.plot(spikes.restrict(ex_ep).to_tsd("pref_ang"), '|')

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f872902c990>]

Decode neural activity

Population activity clearly codes for head-direction. Can we use the spiking activity of the neurons to infer the current heading of the animal? The process is called bayesian decoding.

Question: Using the right pynapple function, can you compute the decoded angle from the spiking activity during wakefulness?

decoded, proba_feature = nap.decode_1d(
    tuning_curves=tuning_curves,
    group=spikes,
    ep=wake_ep,
    bin_size=0.3,  # second
)

Question: ... and display the decoded angle next to the true angle?

plt.figure()
plt.subplot(211)
plt.plot(angle.restrict(ex_ep))
plt.plot(decoded.restrict(ex_ep), label="decoded")
plt.ylim(0, 2*np.pi)

plt.subplot(212)
plt.plot(spikes.restrict(ex_ep).to_tsd("pref_ang"), '|')

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f8729321a50>]

Since the tuning curves were computed during wakefulness, it is a circular action to decode spiking activity during wakefulness. We can try something more interesting by trying to decode the angle during sleep.

rem_ep = data['rem']['1']

Question: Can you compute the decoded angle from the spiking activity during REM sleep?

decoded, proba_feature = nap.decode_1d(
    tuning_curves=tuning_curves,
    group=spikes,
    ep=rem_ep,
    bin_size=0.3,  # second
)

Question: ... and display the decoded angle next to the spiking activity?

plt.figure()
plt.subplot(211)
plt.plot(decoded.restrict(rem_ep), label="decoded")
plt.ylim(0, 2*np.pi)

plt.subplot(212)
plt.plot(spikes.restrict(rem_ep).to_tsd("pref_ang"), '|')

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f8728b0ead0>]

Compute correlograms

We see that some neurons have a correlated activity. Can we measure it?

Question:Can you compute cross-correlograms during wake for all pairs of neurons and call it cc_wake?

cc_wake = nap.compute_crosscorrelogram(spikes, 0.2, 20.0, ep=wake_ep)

Question: can you plot the cross-correlogram during wake of 2 neurons firing for the same direction?

index = spikes.keys()


plt.figure()
plt.subplot(121)
plt.plot(tuning_curves[7])
plt.plot(tuning_curves[20])
plt.subplot(122)
plt.plot(cc_wake[(7, 20)])

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f87288d5590>]

Question: can you plot the cross-correlogram during wake of 2 neurons firing for opposite directions?

index = spikes.keys()


plt.figure()
plt.subplot(121)
plt.plot(tuning_curves[7])
plt.plot(tuning_curves[26])
plt.subplot(122)
plt.plot(cc_wake[(7, 26)])

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f872870d8d0>]

Pairwise correlation were computed during wakefulness. The activity of the neurons was also recorded during sleep.

Question: can you compute the cross-correlograms during sleep?

cc_sleep = nap.compute_crosscorrelogram(spikes, 0.02, 1.0, ep=epochs['sleep'])

Question: can you display the cross-correlogram for wakefulness and sleep of the same pairs of neurons?

plt.figure()
plt.subplot(131, projection='polar')
plt.plot(tuning_curves[7])
plt.plot(tuning_curves[20])
plt.subplot(132)
plt.plot(cc_wake[(7, 20)])
plt.subplot(133)
plt.plot(cc_sleep[(7, 20)])

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f872876bbd0>]
plt.figure()
plt.subplot(131, projection='polar')
plt.plot(tuning_curves[7])
plt.plot(tuning_curves[26])
plt.subplot(132)
plt.plot(cc_wake[(7, 26)])
plt.subplot(133)
plt.plot(cc_sleep[(7, 26)])

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f872850d2d0>]

Compute perievent

Sometimes, some events occurs during recording such as rewards. There was no particular events during this recording but we can look for when the head-direction is close to a particular direction as an event.

plt.figure()
plt.plot(tuning_curves[9])
plt.axvline(1.5)

crossing_times = np.cos(angle).threshold(np.cos(1.5), "below").time_support.starts

02 data analysis with pynapple

Question: Can you compute a perievent time histogram around the timestamps defined in crossing_times for neuron 9?

peth = nap.compute_perievent(spikes[9], crossing_times, minmax=(-2, 2))

Question: ...and plot the spikes?

plt.figure()
plt.plot(peth.to_tsd(), '|')

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f87290f3290>]

Question: Can you compute the mean firing rate of the PETH around crossing_times in bins of 100 ms?

mean_fr = np.mean(peth.count(0.1)/0.1, 1)

Question: ... and plot it?

plt.figure()
plt.subplot(211)
plt.plot(peth.to_tsd(), '|')
plt.subplot(212)
plt.plot(mean_fr)

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f87422d2150>]

Is this a strong effect? We would like to compare this to surrogate dataset.

Question: Shuffling the spike trains, can you generate a mean random PETH to compare to the true mean PETH?

rand_ts = nap.shuffle_ts_intervals(spikes[9])
rand_peth = nap.compute_perievent(rand_ts, crossing_times, minmax=(-2, 2))

plt.figure()
plt.subplot(211)
plt.plot(peth.to_tsd(), '|')
plt.subplot(212)
plt.plot(mean_fr)
plt.plot(np.mean(rand_peth.count(0.1)/0.1, 1))

02 data analysis with pynapple

Out:

[<matplotlib.lines.Line2D object at 0x7f8729389050>]

Total running time of the script: ( 0 minutes 14.473 seconds)

Download Python source code: 02_data_analysis_with_pynapple.py

Download Jupyter notebook: 02_data_analysis_with_pynapple.ipynb

Gallery generated by mkdocs-gallery