Reproducing Knierim, McNaughton & Poe (2000) from dandiset 001754¶
Paper. Knierim JJ, McNaughton BL, Poe GR (2000). Three-dimensional spatial selectivity of
hippocampal neurons during space flight. Nature Neuroscience 3(3):211-212.
doi:10.1038/72910. The claims file,
claims/001754/10.1038-72910.yaml, was written from the full text of the version of record, a
two-page brief communication with no separate Methods section and no supplement; its methods are
in the main text and the figure legends. The paper links no code.
Dandiset. DANDI:001754, version
0.260728.1352 (doi:10.48324/dandi.001754/0.260728.1352). Why this version: the paper cites no
version of the dandiset, so the most recent published version is pinned, and 0.260728.1352 is it.
The two earlier published versions (0.260227.0029 and 0.260227.0031) hold the same files with the
same blobs (data check), so none of them matches the claims file better. The draft holds a new
conversion that addresses the findings of the first attempt, but it is not published, and it has
changed since 001754-knierim-2000-02 read it (the data check prints when it was last
modified), so it is not used. This is the version that 001754-knierim-2000-01 read;
001754-knierim-2000-02 read the draft.
This attempt, 001754-knierim-2000-03, replaces 001754-knierim-2000-02, whose claims were
written from the abstract and the figure titles. The claims file has been written again from the
full text. Three claims keep their ids with criteria rewritten from the paper
(flight-spatial-code, now judged by comparing spatial information with preflight,
fig3-stability-across-sessions and three-orthogonal-surfaces), four return
(si-comparable-to-preflight, fd4-abnormal-selectivity, theta-normal, ripples-normal), and
three are new (si-rat1-fd9-vs-preflight, si-rat3-fd4-vs-preflight, direction-selectivity).
The claims file names readings of the map (M-a, the deposited maps; M-b, maps recomputed from
spikes and position) and of the session (S-a, each candidate; S-b, the one closest before launch).
How each applies to these files is said under the claims. What the claims file did not settle was
asked in questions/001754.yaml; a reader answered on 2026-10-20 and the criteria now carry the
answers (how M-b is formed and that both sides of a comparison use one map reading; that each
preflight epoch is a candidate under S-a and S-b; "most" as more than half of the active cells; when
the deposited maps may be paired to units; and that the direction claim stays blocked). The
answers were applied and the claims they bear on re-run. A verifier then reviewed the attempt
(round 1, transcripts/001754/verifier-1-report.md). This revision answers its report: the M-a
pairing check of the stability claim now tests the criterion's condition as stated, and outputs
the verifier asked for are added (whose position the trace is in Rat 2's FD4 window, the maps with
cell_number -1, the pairing of every Rat 3 FD4 map).
What the dandiset holds. Three rats on the Neurolab mission (STS-90, April 1998): one preflight recording of each, flight day 4 (FD4) of each, and flight day 9 (FD9) of Rats 1 and 2. Each file has sorted units, task epochs (BL baseline, ES Escher staircase, MC Magic Carpet) and the 64 x 64 rate and occupancy maps of the original analysis for each ES and MC epoch. The flight files also hold the video position, at spike times only. There is no EEG.
| Claim | Source in the paper | Tested |
|---|---|---|
flight-spatial-code: normal spatially specific firing in flight, spatial information as preflight |
Main text and Figs. 2a, 2c, 3a | Yes, from the three comparisons below |
si-comparable-to-preflight: rat 2, FD9 against preflight |
Main text, Fig. 2a,b | Yes |
si-rat1-fd9-vs-preflight: rat 1, FD9 against preflight |
Main text, Fig. 2c | Yes |
si-rat3-fd4-vs-preflight: rat 3, FD4 session 2 against preflight |
Main text, Fig. 3a | Yes |
fd4-abnormal-selectivity: abnormal selectivity of rats 1 and 2 on FD4 |
Main text | Yes |
fig3-stability-across-sessions: fields kept from session 2 to session 3 |
Main text, Fig. 3 | Yes |
direction-selectivity: counterclockwise-only place fields |
Main text | Blocked: methods underspecified |
theta-normal: normal theta in flight |
Main text | Blocked: data missing |
ripples-normal: normal sharp waves and ripples in flight |
Main text | Blocked: data missing |
three-orthogonal-surfaces: representations on the three surfaces |
Main text, standfirst | Yes, from its two dependencies; part (ii) cannot be tested |
import json
import re
import time
import warnings
import zipfile
from collections import Counter
import h5py
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import remfile
import requests
from scipy import stats
import neurolab as nl
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 30)
pd.set_option("display.max_rows", 300)
warnings.filterwarnings("ignore", category=RuntimeWarning)
EARLIER = "001754-knierim-2000-02"
EARLIER_DRAFT_MODIFIED = "2026-10-14T21:55:09Z" # the draft as 001754-knierim-2000-02 read it (its record)
started = time.time()
sessions = nl.load_all()
by_label = {nl.label(s): s for s in sessions}
print(f"loaded {len(sessions)} files from dandiset {nl.DANDISET_ID} version {nl.DANDISET_VERSION} "
f"in {time.time() - started:.0f} s")
loaded 8 files from dandiset 001754 version 0.260728.1352 in 15 s
Data check¶
Each check of the metadata against the values is stated under its output. The status of each
finding of 001754-knierim-2000-02 on this version is given at the end of the data check.
api = "https://api.dandiarchive.org/api/dandisets/001754/versions"
listing = requests.get(f"{api}/", timeout=60).json()["results"]
print("Versions and when each was last modified:")
for v in listing:
print(f" {v['version']:15s} {v['modified']}")
draft_modified = next(v["modified"] for v in listing if v["version"] == "draft")
print(f"The draft was last modified {draft_modified}; {EARLIER} read it as last modified {EARLIER_DRAFT_MODIFIED}.")
blobs = {}
for v in [x["version"] for x in listing]:
assets = requests.get(f"{api}/{v}/assets/", params={"page_size": 100}, timeout=60).json()["results"]
blobs[v] = sorted((a["path"], a["size"], a.get("blob")) for a in assets)
published = [v for v in blobs if v != "draft"]
print("\nThe published versions hold the same files with the same blobs:", all(blobs[v] == blobs[published[0]] for v in published))
pinned = {p: b for p, _, b in blobs[nl.DANDISET_VERSION]}
print(f"Files of the draft that are a different blob from {nl.DANDISET_VERSION}:",
sum(b != pinned.get(p) for p, _, b in blobs["draft"]), "of", len(blobs["draft"]))
meta = {v: requests.get(f"{api}/{v}/", timeout=60).json() for v in ["draft", nl.DANDISET_VERSION]}
print(f"DOI of {nl.DANDISET_VERSION}:", meta[nl.DANDISET_VERSION].get("doi"))
print(f"The dandiset description is the same in the draft and {nl.DANDISET_VERSION}:",
meta["draft"]["description"] == meta[nl.DANDISET_VERSION]["description"])
print("\nDandiset description:\n", meta[nl.DANDISET_VERSION]["description"])
print("\nSentences of the draft's description that are not in the pinned version's:")
for sentence in meta["draft"]["description"].split(". "):
if sentence not in meta[nl.DANDISET_VERSION]["description"]:
print(" ", sentence.strip())
print("\nEvery group (with its neurodata type) and dataset (shape, type, description) of every file, unfiltered:")
types = {}
for path_url in nl.asset_urls():
tree = nl.h5_tree(path_url)
print(f"\n{path_url[0]}: {len(tree)} groups and datasets")
for line in tree:
print(" ", line)
if " group " in line and line.split(" group ")[1]:
types.setdefault(line.split(" group ")[1], set()).add(path_url[0])
print("\nNeurodata types of groups, and the number of files holding each:", {t: len(v) for t, v in sorted(types.items())})
Versions and when each was last modified: draft 2026-10-15T14:09:09.218930Z 0.260227.0029 2026-02-27T00:29:28.150330Z 0.260227.0031 2026-02-27T00:31:34.105096Z 0.260728.1352 2026-07-28T13:52:39.253571Z The draft was last modified 2026-10-15T14:09:09.218930Z; 001754-knierim-2000-02 read it as last modified 2026-10-14T21:55:09Z.
The published versions hold the same files with the same blobs: True Files of the draft that are a different blob from 0.260728.1352: 8 of 8
DOI of 0.260728.1352: 10.48324/dandi.001754/0.260728.1352 The dandiset description is the same in the draft and 0.260728.1352: False Dandiset description: This dataset contains extracellular recordings from hippocampal place cells in three freely moving rats during the Neurolab Space Shuttle mission (STS-90) in April 1998. The experiment investigated whether the hippocampus can maintain stable spatial representations during three-dimensional navigation in microgravity. Rats were implanted with multi-electrode recording arrays and trained to traverse a three-dimensional track called the "Escher staircase," in which three 90-degree yaw turns were interleaved with three 90-degree pitch turns, creating a closed loop after only 270 degrees of total yaw rotation. This design placed the spatial information from external landmarks in conflict with the expected input from the head-direction system, which normally requires 360 degrees of yaw to signal a return to the starting direction. The dataset includes both preflight recordings on a flat rectangular track and in-flight recordings on the Escher staircase track from flight days 4 and 9. Place cells eventually demonstrated normal spatially specific firing, with spatial information content comparable to preflight levels, though initial exposures on flight day 4 showed abnormal spatial selectivity patterns. Hippocampal EEG was also recorded, showing normal theta rhythm during locomotion and normal sharp waves and ripples during quiet rest. Rats received medial forebrain bundle stimulation as a reward for traversing the track. Sentences of the draft's description that are not in the pinned version's: Rats were implanted with multi-electrode recording arrays and trained to traverse a three-dimensional track called the “Escher staircase,” in which three 90-degree yaw turns were interleaved with three 90-degree pitch turns, creating a closed loop after only 270 degrees of total yaw rotation The dataset includes preflight recordings and in-flight recordings from flight days 4 and 9 Both are divided into Escher staircase (ES) and Magic Carpet (MC) epochs, each with its own rate maps Hippocampal EEG was recorded during the experiment, but it is not included in this dataset Every group (with its neurodata type) and dataset (shape, type, description) of every file, unfiltered:
sub-Rat1/sub-Rat1_ses-19980414T125300_ecephys.nwb: 65 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT11/ group ElectrodeGroup general/extracellular_ephys/TT3/ group ElectrodeGroup general/extracellular_ephys/TT4/ group ElectrodeGroup general/extracellular_ephys/TT5/ group ElectrodeGroup general/extracellular_ephys/TT6/ group ElectrodeGroup general/extracellular_ephys/TT7/ group ElectrodeGroup general/extracellular_ephys/TT8/ group ElectrodeGroup general/extracellular_ephys/TT9/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (32,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (32,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (32,) int64 general/extracellular_ephys/electrodes/location (32,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (2,) int64 intervals/epochs/session_type (2,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (2,) object Description of the task type intervals/epochs/start_time (2,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (2,) float64 Stop time of epoch, in seconds processing/ group processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (62,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (62,) int64 processing/ecephys/rate_maps/occupancy_map (62, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (62, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (62,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (62,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (62,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (35,) int64 Cluster ID from Xclust sorting units/electrodes (140,) int64 the electrodes that each spike unit came from units/electrodes_index (35,) uint8 Index for VectorData 'electrodes' units/id (35,) int64 units/spike_times (21995,) float64 the spike times for each unit in seconds units/spike_times_index (35,) uint16 Index for VectorData 'spike_times' units/tetrode (35,) object Tetrode name (e.g. TT0)
sub-Rat1/sub-Rat1_ses-19980420T095700_behavior+ecephys.nwb: 71 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT0/ group ElectrodeGroup general/extracellular_ephys/TT1/ group ElectrodeGroup general/extracellular_ephys/TT10/ group ElectrodeGroup general/extracellular_ephys/TT11/ group ElectrodeGroup general/extracellular_ephys/TT2/ group ElectrodeGroup general/extracellular_ephys/TT7/ group ElectrodeGroup general/extracellular_ephys/TT8/ group ElectrodeGroup general/extracellular_ephys/TT9/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (32,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (32,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (32,) int64 general/extracellular_ephys/electrodes/location (32,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (6,) int64 intervals/epochs/session_type (6,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (6,) object Description of the task type intervals/epochs/start_time (6,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (6,) float64 Stop time of epoch, in seconds processing/ group processing/behavior/ group ProcessingModule processing/behavior/position/ group Position processing/behavior/position/spatial_series/ group SpatialSeries processing/behavior/position/spatial_series/data (34537, 2) float64 pixels processing/behavior/position/spatial_series/reference_frame () object processing/behavior/position/spatial_series/timestamps (34537,) float64 seconds processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (67,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (67,) int64 processing/ecephys/rate_maps/occupancy_map (67, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (67, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (67,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (67,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (67,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (47,) int64 Cluster ID from Xclust sorting units/electrodes (188,) int64 the electrodes that each spike unit came from units/electrodes_index (47,) uint8 Index for VectorData 'electrodes' units/id (47,) int64 units/spike_times (39987,) float64 the spike times for each unit in seconds units/spike_times_index (47,) uint16 Index for VectorData 'spike_times' units/tetrode (47,) object Tetrode name (e.g. TT0)
sub-Rat1/sub-Rat1_ses-19980425T124500_behavior+ecephys.nwb: 71 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT0/ group ElectrodeGroup general/extracellular_ephys/TT1/ group ElectrodeGroup general/extracellular_ephys/TT10/ group ElectrodeGroup general/extracellular_ephys/TT11/ group ElectrodeGroup general/extracellular_ephys/TT2/ group ElectrodeGroup general/extracellular_ephys/TT6/ group ElectrodeGroup general/extracellular_ephys/TT8/ group ElectrodeGroup general/extracellular_ephys/TT9/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (32,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (32,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (32,) int64 general/extracellular_ephys/electrodes/location (32,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (4,) int64 intervals/epochs/session_type (4,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (4,) object Description of the task type intervals/epochs/start_time (4,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (4,) float64 Stop time of epoch, in seconds processing/ group processing/behavior/ group ProcessingModule processing/behavior/position/ group Position processing/behavior/position/spatial_series/ group SpatialSeries processing/behavior/position/spatial_series/data (191065, 2) float64 pixels processing/behavior/position/spatial_series/reference_frame () object processing/behavior/position/spatial_series/timestamps (191065,) float64 seconds processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (64,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (64,) int64 processing/ecephys/rate_maps/occupancy_map (64, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (64, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (64,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (64,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (64,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (39,) int64 Cluster ID from Xclust sorting units/electrodes (156,) int64 the electrodes that each spike unit came from units/electrodes_index (39,) uint8 Index for VectorData 'electrodes' units/id (39,) int64 units/spike_times (243527,) float64 the spike times for each unit in seconds units/spike_times_index (39,) uint32 Index for VectorData 'spike_times' units/tetrode (39,) object Tetrode name (e.g. TT0)
sub-Rat2/sub-Rat2_ses-19980413T163700_ecephys.nwb: 63 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT0/ group ElectrodeGroup general/extracellular_ephys/TT1/ group ElectrodeGroup general/extracellular_ephys/TT10/ group ElectrodeGroup general/extracellular_ephys/TT2/ group ElectrodeGroup general/extracellular_ephys/TT7/ group ElectrodeGroup general/extracellular_ephys/TT8/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (24,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (24,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (24,) int64 general/extracellular_ephys/electrodes/location (24,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (2,) int64 intervals/epochs/session_type (2,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (2,) object Description of the task type intervals/epochs/start_time (2,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (2,) float64 Stop time of epoch, in seconds processing/ group processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (59,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (59,) int64 processing/ecephys/rate_maps/occupancy_map (59, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (59, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (59,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (59,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (59,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (33,) int64 Cluster ID from Xclust sorting units/electrodes (132,) int64 the electrodes that each spike unit came from units/electrodes_index (33,) uint8 Index for VectorData 'electrodes' units/id (33,) int64 units/spike_times (36669,) float64 the spike times for each unit in seconds units/spike_times_index (33,) uint16 Index for VectorData 'spike_times' units/tetrode (33,) object Tetrode name (e.g. TT0)
sub-Rat2/sub-Rat2_ses-19980420T095700_behavior+ecephys.nwb: 69 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT12/ group ElectrodeGroup general/extracellular_ephys/TT13/ group ElectrodeGroup general/extracellular_ephys/TT14/ group ElectrodeGroup general/extracellular_ephys/TT17/ group ElectrodeGroup general/extracellular_ephys/TT19/ group ElectrodeGroup general/extracellular_ephys/TT22/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (24,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (24,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (24,) int64 general/extracellular_ephys/electrodes/location (24,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (5,) int64 intervals/epochs/session_type (5,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (5,) object Description of the task type intervals/epochs/start_time (5,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (5,) float64 Stop time of epoch, in seconds processing/ group processing/behavior/ group ProcessingModule processing/behavior/position/ group Position processing/behavior/position/spatial_series/ group SpatialSeries processing/behavior/position/spatial_series/data (25320, 2) float64 pixels processing/behavior/position/spatial_series/reference_frame () object processing/behavior/position/spatial_series/timestamps (25320,) float64 seconds processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (65,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (65,) int64 processing/ecephys/rate_maps/occupancy_map (65, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (65, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (65,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (65,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (65,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (72,) int64 Cluster ID from Xclust sorting units/electrodes (288,) int64 the electrodes that each spike unit came from units/electrodes_index (72,) uint16 Index for VectorData 'electrodes' units/id (72,) int64 units/spike_times (31769,) float64 the spike times for each unit in seconds units/spike_times_index (72,) uint16 Index for VectorData 'spike_times' units/tetrode (72,) object Tetrode name (e.g. TT0)
sub-Rat2/sub-Rat2_ses-19980425T124500_behavior+ecephys.nwb: 69 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT12/ group ElectrodeGroup general/extracellular_ephys/TT13/ group ElectrodeGroup general/extracellular_ephys/TT14/ group ElectrodeGroup general/extracellular_ephys/TT15/ group ElectrodeGroup general/extracellular_ephys/TT19/ group ElectrodeGroup general/extracellular_ephys/TT22/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (24,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (24,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (24,) int64 general/extracellular_ephys/electrodes/location (24,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (4,) int64 intervals/epochs/session_type (4,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (4,) object Description of the task type intervals/epochs/start_time (4,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (4,) float64 Stop time of epoch, in seconds processing/ group processing/behavior/ group ProcessingModule processing/behavior/position/ group Position processing/behavior/position/spatial_series/ group SpatialSeries processing/behavior/position/spatial_series/data (42858, 2) float64 pixels processing/behavior/position/spatial_series/reference_frame () object processing/behavior/position/spatial_series/timestamps (42858,) float64 seconds processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (80,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (80,) int64 processing/ecephys/rate_maps/occupancy_map (80, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (80, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (80,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (80,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (80,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (54,) int64 Cluster ID from Xclust sorting units/electrodes (216,) int64 the electrodes that each spike unit came from units/electrodes_index (54,) uint8 Index for VectorData 'electrodes' units/id (54,) int64 units/spike_times (52175,) float64 the spike times for each unit in seconds units/spike_times_index (54,) uint16 Index for VectorData 'spike_times' units/tetrode (54,) object Tetrode name (e.g. TT0)
sub-Rat3/sub-Rat3_ses-19980414T134900_ecephys.nwb: 67 groups and datasets acquisition/ group analysis/ group file_create_date (1,) object general/ group general/devices/ group general/devices/tetrode_array/ group Device general/experiment_description () object general/experimenter (3,) object general/extracellular_ephys/ group general/extracellular_ephys/TT0/ group ElectrodeGroup general/extracellular_ephys/TT1/ group ElectrodeGroup general/extracellular_ephys/TT10/ group ElectrodeGroup general/extracellular_ephys/TT11/ group ElectrodeGroup general/extracellular_ephys/TT2/ group ElectrodeGroup general/extracellular_ephys/TT4/ group ElectrodeGroup general/extracellular_ephys/TT5/ group ElectrodeGroup general/extracellular_ephys/TT6/ group ElectrodeGroup general/extracellular_ephys/TT8/ group ElectrodeGroup general/extracellular_ephys/TT9/ group ElectrodeGroup general/extracellular_ephys/electrodes/ group ElectrodesTable general/extracellular_ephys/electrodes/group (40,) object Reference to the ElectrodeGroup. general/extracellular_ephys/electrodes/group_name (40,) object Name of the ElectrodeGroup. general/extracellular_ephys/electrodes/id (40,) int64 general/extracellular_ephys/electrodes/location (40,) object Location of the electrode (channel). general/institution () object general/keywords (8,) object general/lab () object general/related_publications (1,) object general/subject/ group Subject general/subject/age () object general/subject/description () object general/subject/sex () object general/subject/species () object general/subject/strain () object general/subject/subject_id () object identifier () object intervals/ group intervals/epochs/ group TimeIntervals intervals/epochs/id (2,) int64 intervals/epochs/session_type (2,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet) intervals/epochs/session_type_description (2,) object Description of the task type intervals/epochs/start_time (2,) float64 Start time of epoch, in seconds intervals/epochs/stop_time (2,) float64 Stop time of epoch, in seconds processing/ group processing/ecephys/ group ProcessingModule processing/ecephys/rate_maps/ group DynamicTable processing/ecephys/rate_maps/cell_number (86,) int64 Cell number from CELL~N filename; -1 if not a per-cell map processing/ecephys/rate_maps/id (86,) int64 processing/ecephys/rate_maps/occupancy_map (86, 64, 64) int32 64x64 spatial occupancy map (bin visit counts) processing/ecephys/rate_maps/rate_map (86, 64, 64) float32 64x64 spatial firing rate map (Hz) processing/ecephys/rate_maps/session_type (86,) object Task type (ES/MC/BL) processing/ecephys/rate_maps/source_file (86,) object Original .RMA filename processing/ecephys/rate_maps/tetrode (86,) object Tetrode name (e.g. TT0) session_description () object session_start_time () object stimulus/ group stimulus/presentation/ group stimulus/templates/ group timestamps_reference_time () object units/ group Units units/cluster_id (48,) int64 Cluster ID from Xclust sorting units/electrodes (192,) int64 the electrodes that each spike unit came from units/electrodes_index (48,) uint8 Index for VectorData 'electrodes' units/id (48,) int64 units/spike_times (48005,) float64 the spike times for each unit in seconds units/spike_times_index (48,) uint16 Index for VectorData 'spike_times' units/tetrode (48,) object Tetrode name (e.g. TT0)
sub-Rat3/sub-Rat3_ses-19980420T152800_behavior+ecephys.nwb: 78 groups and datasets
acquisition/ group
analysis/ group
file_create_date (1,) object
general/ group
general/devices/ group
general/devices/tetrode_array/ group Device
general/experiment_description () object
general/experimenter (3,) object
general/extracellular_ephys/ group
general/extracellular_ephys/TT0/ group ElectrodeGroup
general/extracellular_ephys/TT10/ group ElectrodeGroup
general/extracellular_ephys/TT11/ group ElectrodeGroup
general/extracellular_ephys/TT13/ group ElectrodeGroup
general/extracellular_ephys/TT16/ group ElectrodeGroup
general/extracellular_ephys/TT17/ group ElectrodeGroup
general/extracellular_ephys/TT18/ group ElectrodeGroup
general/extracellular_ephys/TT19/ group ElectrodeGroup
general/extracellular_ephys/TT20/ group ElectrodeGroup
general/extracellular_ephys/TT22/ group ElectrodeGroup
general/extracellular_ephys/TT23/ group ElectrodeGroup
general/extracellular_ephys/TT3/ group ElectrodeGroup
general/extracellular_ephys/TT4/ group ElectrodeGroup
general/extracellular_ephys/TT5/ group ElectrodeGroup
general/extracellular_ephys/TT8/ group ElectrodeGroup
general/extracellular_ephys/electrodes/ group ElectrodesTable
general/extracellular_ephys/electrodes/group (60,) object Reference to the ElectrodeGroup.
general/extracellular_ephys/electrodes/group_name (60,) object Name of the ElectrodeGroup.
general/extracellular_ephys/electrodes/id (60,) int64
general/extracellular_ephys/electrodes/location (60,) object Location of the electrode (channel).
general/institution () object
general/keywords (8,) object
general/lab () object
general/related_publications (1,) object
general/subject/ group Subject
general/subject/age () object
general/subject/description () object
general/subject/sex () object
general/subject/species () object
general/subject/strain () object
general/subject/subject_id () object
identifier () object
intervals/ group
intervals/epochs/ group TimeIntervals
intervals/epochs/id (3,) int64
intervals/epochs/session_type (3,) object Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet)
intervals/epochs/session_type_description (3,) object Description of the task type
intervals/epochs/start_time (3,) float64 Start time of epoch, in seconds
intervals/epochs/stop_time (3,) float64 Stop time of epoch, in seconds
processing/ group
processing/behavior/ group ProcessingModule
processing/behavior/position/ group Position
processing/behavior/position/spatial_series/ group SpatialSeries
processing/behavior/position/spatial_series/data (48910, 2) float64 pixels
processing/behavior/position/spatial_series/reference_frame () object
processing/behavior/position/spatial_series/timestamps (48910,) float64 seconds
processing/ecephys/ group ProcessingModule
processing/ecephys/rate_maps/ group DynamicTable
processing/ecephys/rate_maps/cell_number (65,) int64 Cell number from CELL~N filename; -1 if not a per-cell map
processing/ecephys/rate_maps/id (65,) int64
processing/ecephys/rate_maps/occupancy_map (65, 64, 64) int32 64x64 spatial occupancy map (bin visit counts)
processing/ecephys/rate_maps/rate_map (65, 64, 64) float32 64x64 spatial firing rate map (Hz)
processing/ecephys/rate_maps/session_type (65,) object Task type (ES/MC/BL)
processing/ecephys/rate_maps/source_file (65,) object Original .RMA filename
processing/ecephys/rate_maps/tetrode (65,) object Tetrode name (e.g. TT0)
session_description () object
session_start_time () object
stimulus/ group
stimulus/presentation/ group
stimulus/templates/ group
timestamps_reference_time () object
units/ group Units
units/cluster_id (47,) int64 Cluster ID from Xclust sorting
units/electrodes (188,) int64 the electrodes that each spike unit came from
units/electrodes_index (47,) uint8 Index for VectorData 'electrodes'
units/id (47,) int64
units/spike_times (58717,) float64 the spike times for each unit in seconds
units/spike_times_index (47,) uint16 Index for VectorData 'spike_times'
units/tetrode (47,) object Tetrode name (e.g. TT0)
Neurodata types of groups, and the number of files holding each: {'Device': 8, 'DynamicTable': 8, 'ElectrodeGroup': 8, 'ElectrodesTable': 8, 'Position': 5, 'ProcessingModule': 8, 'SpatialSeries': 5, 'Subject': 8, 'TimeIntervals': 8, 'Units': 8}
The three published versions hold the same eight files with the same blobs; every file of the draft
is a different blob, and the draft's description has been rewritten (the sentences printed above:
it no longer places the preflight recordings on a flat rectangular track, and it says the EEG is
not included). Those changes are in the draft only; the pinned version keeps the description
printed above. The listing shows every group and dataset of every file. The neurodata types are
only those printed under it: there is no ElectricalSeries or LFP, so no file holds EEG or any
continuous voltage. The units table has tetrode, cluster_id, spike_times and electrodes;
the epochs table session_type and its description; the rate-map table tetrode, source_file,
session_type, cell_number, rate_map and occupancy_map; the position one series of x and y.
# The descriptions of the tables, columns and series the analysis uses, from one flight file and
# one preflight file (the others carry the same text), and every subject's age and description.
for path_url in [p for p in nl.asset_urls() if "ses-19980425T" in p[0] and "Rat1" in p[0]] + [p for p in nl.asset_urls() if "ses-19980413T" in p[0]]:
print(f"== {path_url[0]}")
with h5py.File(remfile.File(path_url[1]), "r") as f:
for key in ["general/experiment_description", "general/session_id", "general/keywords"]:
if key in f:
print(f"{key}: {f[key][()]!r}")
for name in ["units", "intervals/epochs", "processing/ecephys/rate_maps", "processing/behavior/position/spatial_series"]:
if name not in f:
print(f"{name}: not in this file")
continue
g = f[name]
print(f"{name}: {nl._text(g.attrs.get('description', ''))!r}")
for col in g.keys():
if isinstance(g[col], h5py.Dataset):
note = {k: nl._text(v) for k, v in g[col].attrs.items() if k in ("description", "unit", "resolution", "conversion")}
print(f" {col}: {note}")
print()
for p, url in nl.asset_urls():
with h5py.File(remfile.File(url), "r") as f:
print(f"{p.split('/')[1][:31]}: age {nl._text(f['general/subject/age'][()])!r}; {nl._text(f['general/subject/description'][()])!r}")
== sub-Rat1/sub-Rat1_ses-19980425T124500_behavior+ecephys.nwb
general/experiment_description: b"Hippocampal place cell recordings from rats aboard the Space Shuttle during the Neurolab STS-90 mission (April\xe2\x80\x93May 1998). Three rats with chronically implanted tetrode arrays in hippocampal area CA1 were trained to traverse a three-dimensional track ('Escher staircase') and a flat two-dimensional track ('Magic Carpet') for medial forebrain bundle stimulation reward. Recordings were made preflight on the ground and in-flight on Flight Day 4 and Flight Day 9. Baseline sessions on a rectangular track were interleaved with task sessions."
general/keywords: array([b'hippocampus', b'place cells', b'tetrode', b'spaceflight',
b'microgravity', b'spatial navigation', b'Neurolab', b'STS-90'],
dtype=object)
units: 'Autogenerated by NWBFile'
cluster_id: {'description': 'Cluster ID from Xclust sorting'}
electrodes: {'description': 'the electrodes that each spike unit came from'}
electrodes_index: {'description': "Index for VectorData 'electrodes'"}
id: {}
spike_times: {'description': 'the spike times for each unit in seconds'}
spike_times_index: {'description': "Index for VectorData 'spike_times'"}
tetrode: {'description': 'Tetrode name (e.g. TT0)'}
intervals/epochs: 'Task epochs within the recording session'
id: {}
session_type: {'description': 'Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet)'}
session_type_description: {'description': 'Description of the task type'}
start_time: {'description': 'Start time of epoch, in seconds'}
stop_time: {'description': 'Stop time of epoch, in seconds'}
processing/ecephys/rate_maps: 'Spatial firing rate maps and occupancy maps from legacy analysis. Each row corresponds to one .RMA file. Cell-specific maps (cell_number != -1) can be matched to the cluster_id in the units table for the same tetrode and session_type.'
cell_number: {'description': 'Cell number from CELL~N filename; -1 if not a per-cell map'}
id: {}
occupancy_map: {'description': '64x64 spatial occupancy map (bin visit counts)'}
rate_map: {'description': '64x64 spatial firing rate map (Hz)'}
session_type: {'description': 'Task type (ES/MC/BL)'}
source_file: {'description': 'Original .RMA filename'}
tetrode: {'description': 'Tetrode name (e.g. TT0)'}
processing/behavior/position/spatial_series: 'Animal position from video tracking, sampled at spike occurrence times'
data: {'conversion': '1.0', 'resolution': '-1.0', 'unit': 'pixels'}
reference_frame: {}
timestamps: {'unit': 'seconds'}
== sub-Rat2/sub-Rat2_ses-19980413T163700_ecephys.nwb
general/experiment_description: b"Hippocampal place cell recordings from rats aboard the Space Shuttle during the Neurolab STS-90 mission (April\xe2\x80\x93May 1998). Three rats with chronically implanted tetrode arrays in hippocampal area CA1 were trained to traverse a three-dimensional track ('Escher staircase') and a flat two-dimensional track ('Magic Carpet') for medial forebrain bundle stimulation reward. Recordings were made preflight on the ground and in-flight on Flight Day 4 and Flight Day 9. Baseline sessions on a rectangular track were interleaved with task sessions."
general/keywords: array([b'hippocampus', b'place cells', b'tetrode', b'spaceflight',
b'microgravity', b'spatial navigation', b'Neurolab', b'STS-90'],
dtype=object)
units: 'Autogenerated by NWBFile'
cluster_id: {'description': 'Cluster ID from Xclust sorting'}
electrodes: {'description': 'the electrodes that each spike unit came from'}
electrodes_index: {'description': "Index for VectorData 'electrodes'"}
id: {}
spike_times: {'description': 'the spike times for each unit in seconds'}
spike_times_index: {'description': "Index for VectorData 'spike_times'"}
tetrode: {'description': 'Tetrode name (e.g. TT0)'}
intervals/epochs: 'Task epochs within the recording session'
id: {}
session_type: {'description': 'Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet)'}
session_type_description: {'description': 'Description of the task type'}
start_time: {'description': 'Start time of epoch, in seconds'}
stop_time: {'description': 'Stop time of epoch, in seconds'}
processing/ecephys/rate_maps: 'Spatial firing rate maps and occupancy maps from legacy analysis. Each row corresponds to one .RMA file. Cell-specific maps (cell_number != -1) can be matched to the cluster_id in the units table for the same tetrode and session_type.'
cell_number: {'description': 'Cell number from CELL~N filename; -1 if not a per-cell map'}
id: {}
occupancy_map: {'description': '64x64 spatial occupancy map (bin visit counts)'}
rate_map: {'description': '64x64 spatial firing rate map (Hz)'}
session_type: {'description': 'Task type (ES/MC/BL)'}
source_file: {'description': 'Original .RMA filename'}
tetrode: {'description': 'Tetrode name (e.g. TT0)'}
processing/behavior/position/spatial_series: not in this file
sub-Rat1_ses-19980414T125300_ec: age '/'; 'Adult male Fischer 344 rat (Rat1)'
sub-Rat1_ses-19980420T095700_be: age '/'; 'Adult male Fischer 344 rat (Rat1)'
sub-Rat1_ses-19980425T124500_be: age '/'; 'Adult male Fischer 344 rat (Rat1)'
sub-Rat2_ses-19980413T163700_ec: age '/'; 'Adult male Fischer 344 rat (Rat2)'
sub-Rat2_ses-19980420T095700_be: age '/'; 'Adult male Fischer 344 rat (Rat2)'
sub-Rat2_ses-19980425T124500_be: age '/'; 'Adult male Fischer 344 rat (Rat2)'
sub-Rat3_ses-19980414T134900_ec: age '/'; 'Adult male Fischer 344 rat (Rat3)'
sub-Rat3_ses-19980420T152800_be: age '/'; 'Adult male Fischer 344 rat (Rat3)'
The rate-map table says a map with cell_number other than -1 can be matched to the unit with
that cluster id on the same tetrode, and that the rate is in Hz; it does not say the maps are
smoothed or how unvisited bins are coded. The position is described as sampled at spike times, in
pixels, with no camera view or pixel scale. Every subject's age is "/". The experiment description
says baseline sessions were on a rectangular track, interleaved with task sessions; the paper
says baseline sessions were in the sleeping pouch (EEG paragraph) and that rat 2 ran a flat
rectangular track preflight (Fig. 2b). No file of the pinned version describes the preflight
apparatus.
rows = []
for s in sessions:
ep = s["epochs"]
iv = sorted(zip(ep.start, ep.stop))
rows.append({"file": s["path"].split("/")[1][:31], "rat": s["subject"], "day": s["day"], "start": s["start"][:16],
"units": len(s["units"]), "spikes": int(sum(len(x) for x in s["units"]["spikes"])),
"tetrodes": s["units"]["tetrode"].nunique(),
"epochs": " ".join(f"{n}[{a:.0f}-{b:.0f}]" for a, b, n in ep[["start", "stop", "name"]].itertuples(index=False)),
"overlapping_epochs": sum(1 for (a1, b1), (a2, b2) in zip(iv, iv[1:]) if a2 < b1),
"rate_maps": len(s["maps"]), "position_samples": len(s["t"]) if "t" in s else 0})
files = pd.DataFrame(rows)
print(files.to_string(index=False))
print("\nFiles per rat and day:"); print(pd.crosstab(files.rat, files.day).to_string())
for s in sessions:
print(f"{nl.label(s):15s} {s['description']}")
print("\nTetrodes with units, per file:")
for s in sessions:
print(f" {nl.label(s):15s}", sorted(s["units"].tetrode.unique(), key=lambda t: int(t[2:])))
for rat in ["Rat1", "Rat2", "Rat3"]:
pre = set(by_label[f"{rat} preflight"]["units"].tetrode)
for day in ["FD4", "FD9"]:
if f"{rat} {day}" in by_label:
print(f"{rat}: tetrodes in both preflight and {day}: {len(pre & set(by_label[f'{rat} {day}']['units'].tetrode))}")
file rat day start units spikes tetrodes epochs overlapping_epochs rate_maps position_samples sub-Rat1_ses-19980414T125300_ec Rat1 preflight 1998-04-14T12:53 35 21995 8 ES1[276-741] MC1[936-1349] 0 62 0 sub-Rat1_ses-19980420T095700_be Rat1 FD4 1998-04-20T09:57 47 39987 8 BL1[2525-3390] BL2[2720-3390] BL3[3850-4800] ES1[5935-6420] MC1[7020-7550] BL4[7850-8473] 1 67 34537 sub-Rat1_ses-19980425T124500_be Rat1 FD9 1998-04-25T12:45 39 243527 8 ES1[3178-4789] BL1[4871-7535] MC1[7888-9200] BL2[9220-10151] 0 64 191065 sub-Rat2_ses-19980413T163700_ec Rat2 preflight 1998-04-13T16:37 33 36669 6 ES1[1013-1573] MC1[1667-2234] 0 59 0 sub-Rat2_ses-19980420T095700_be Rat2 FD4 1998-04-20T09:57 72 31769 6 BL1[2525-3390] ES1[3850-4800] BL2[5935-6420] BL3[7020-7550] MC1[7850-8473] 0 65 25320 sub-Rat2_ses-19980425T124500_be Rat2 FD9 1998-04-25T12:45 54 52175 6 BL1[3178-4789] ES1[4871-7535] BL2[7888-9200] MC1[9220-10151] 0 80 42858 sub-Rat3_ses-19980414T134900_ec Rat3 preflight 1998-04-14T13:49 48 48005 10 ES1[148-762] MC1[1095-1481] 0 86 0 sub-Rat3_ses-19980420T152800_be Rat3 FD4 1998-04-20T15:28 47 58717 15 ES1[32-1345] ES2[2397-3729] MC1[5713-6349] 0 65 48910 Files per rat and day: day FD4 FD9 preflight rat Rat1 1 1 1 Rat2 1 1 1 Rat3 1 0 1 Rat1 preflight Preflight ground recording — Rat 1. Recorded 3 days before launch at Kennedy Space Center. Rat1 FD4 Flight Day 4 recording — Rat 1. Escher Staircase and Magic Carpet tasks with baseline sessions. Rat1 FD9 Flight Day 9 recording — Rat 1. Escher Staircase and Magic Carpet tasks with baseline sessions. Rat2 preflight Preflight ground recording — Rat 2. Recorded 4 days before launch at Kennedy Space Center. Rat2 FD4 Flight Day 4 recording — Rat 2. Recorded simultaneously with Rat 1 on shared acquisition system. Rat2 FD9 Flight Day 9 recording — Rat 2. Recorded simultaneously with Rat 1 on shared acquisition system. Rat3 preflight Preflight ground recording — Rat 3. Recorded 3 days before launch at Kennedy Space Center. Rat3 FD4 Flight Day 4 recording — Rat 3. Separate recording session; partial data recovered due to technical issues. Tetrodes with units, per file: Rat1 preflight ['TT3', 'TT4', 'TT5', 'TT6', 'TT7', 'TT8', 'TT9', 'TT11'] Rat1 FD4 ['TT0', 'TT1', 'TT2', 'TT7', 'TT8', 'TT9', 'TT10', 'TT11'] Rat1 FD9 ['TT0', 'TT1', 'TT2', 'TT6', 'TT8', 'TT9', 'TT10', 'TT11'] Rat2 preflight ['TT0', 'TT1', 'TT2', 'TT7', 'TT8', 'TT10'] Rat2 FD4 ['TT12', 'TT13', 'TT14', 'TT17', 'TT19', 'TT22'] Rat2 FD9 ['TT12', 'TT13', 'TT14', 'TT15', 'TT19', 'TT22'] Rat3 preflight ['TT0', 'TT1', 'TT2', 'TT4', 'TT5', 'TT6', 'TT8', 'TT9', 'TT10', 'TT11'] Rat3 FD4 ['TT0', 'TT3', 'TT4', 'TT5', 'TT8', 'TT10', 'TT11', 'TT13', 'TT16', 'TT17', 'TT18', 'TT19', 'TT20', 'TT22', 'TT23'] Rat1: tetrodes in both preflight and FD4: 4 Rat1: tetrodes in both preflight and FD9: 4 Rat2: tetrodes in both preflight and FD4: 0 Rat2: tetrodes in both preflight and FD9: 0 Rat3: tetrodes in both preflight and FD4: 6
Three rats, eight files: preflight and FD4 for each rat, FD9 for Rats 1 and 2. The preflight files were recorded 3 days (Rats 1 and 3) and 4 days (Rat 2) before launch, as their descriptions say; Rat 2's matches the paper's "4 days before launch" (Fig. 2b). Each preflight file holds one ES and one MC epoch and no baseline. Rat3 FD4 has two ES epochs, ES1 (32 to 1345 s) and ES2 (2397 to 3729 s); the paper says rat 3's first FD4 session was lost and its sessions 2 and 3 followed one another, so ES1 and ES2 are read as sessions 2 and 3. Rat1 FD4 has two baseline epochs that overlap (2525 to 3390 s and 2720 to 3390 s). Rat 2's preflight units are on tetrodes TT0 to TT10 and its flight units on TT12 to TT22, with no tetrode in common, while Rats 1 and 3 share 4 to 6 tetrodes between preflight and flight; nothing in the files says why, so the per-rat comparisons with preflight below assume the preflight files are assigned to the right rats.
print("Position, flight files. Stated unit:", {s['xy_unit'] for s in sessions if 'xy' in s}, "; description:", {s['xy_description'] for s in sessions if 'xy' in s})
rows = []
for s in sessions:
if "xy" not in s:
continue
xy, v, t = s["xy"], s["valid"], s["t"]
own = np.unique(np.concatenate(list(s["units"]["spikes"])))
row = {"session": nl.label(s), "samples": len(t), "lost_(0,255)": round(1 - v.mean(), 3),
"x_range": f"{xy[v, 0].min():.0f}-{xy[v, 0].max():.0f}", "y_range": f"{xy[v, 1].min():.0f}-{xy[v, 1].max():.0f}",
"t_range_s": f"{t[0]:.0f}-{t[-1]:.0f}", "at_own_spike_times": round(float(np.isin(t, own).mean()), 4)}
for typ in ["ES", "MC"]:
for a, b, n in s["epochs"][s["epochs"].type == typ][["start", "stop", "name"]].itertuples(index=False):
sp = own[(own >= a) & (own <= b)]
i = np.clip(np.searchsorted(t, sp), 1, len(t) - 1)
has = np.minimum(np.abs(t[i - 1] - sp), np.abs(t[i] - sp)) <= nl.LOOKUP_S
row[f"{n}_spike_times_with_sample"] = round(float(has.mean()), 4)
if not has.all():
row[f"{n}_without_sample_s"] = f"{sp[~has].min():.0f}-{sp[~has].max():.0f}"
rows.append(row)
print(pd.DataFrame(rows).to_string(index=False))
print("\nDo the Rat1 and Rat2 files of a flight day hold the same trace? Timestamps in both files, and of those, the share within 2 px (both valid):")
for day in ["FD4", "FD9"]:
a, b = by_label[f"Rat1 {day}"], by_label[f"Rat2 {day}"]
common, ia, ib = np.intersect1d(a["t"], b["t"], return_indices=True)
both = a["valid"][ia] & b["valid"][ib]
d = np.hypot(*(a["xy"][ia][both] - b["xy"][ib][both]).T)
print(f" {day}: {len(common)} common timestamps ({len(common) / len(a['t']):.3f} of Rat1's, {len(common) / len(b['t']):.3f} of Rat2's); "
f"within 2 px: {(d < 2).mean():.3f}; Rat1 samples span {a['t'][0]:.0f}-{a['t'][-1]:.0f} s, Rat2 {b['t'][0]:.0f}-{b['t'][-1]:.0f} s")
Position, flight files. Stated unit: {'pixels'} ; description: {'Animal position from video tracking, sampled at spike occurrence times'}
session samples lost_(0,255) x_range y_range t_range_s at_own_spike_times ES1_spike_times_with_sample MC1_spike_times_with_sample MC1_without_sample_s ES2_spike_times_with_sample
Rat1 FD4 34537 0.117 0-255 64-255 2523-8473 1.0 1.0 1.0000 NaN NaN
Rat1 FD9 191065 0.081 0-255 64-255 3178-10144 1.0 1.0 1.0000 NaN NaN
Rat2 FD4 25320 0.088 0-255 65-255 2530-8472 1.0 1.0 1.0000 NaN NaN
Rat2 FD9 42858 0.052 0-255 64-255 3178-10144 1.0 1.0 0.9991 10147-10148 NaN
Rat3 FD4 48910 0.084 0-255 54-255 32-6308 1.0 1.0 0.9471 6309-6348 1.0
Do the Rat1 and Rat2 files of a flight day hold the same trace? Timestamps in both files, and of those, the share within 2 px (both valid):
FD4: 2506 common timestamps (0.073 of Rat1's, 0.099 of Rat2's); within 2 px: 0.988; Rat1 samples span 2523-8473 s, Rat2 2530-8472 s
FD9: 12819 common timestamps (0.067 of Rat1's, 0.299 of Rat2's); within 2 px: 0.986; Rat1 samples span 3178-10144 s, Rat2 3178-10144 s
Position is in video pixels, 0 to 255 in x and 54 to 255 in y. The lost position (0, 255) is 5 to 12% of samples; it is not a position and is dropped. Every sample is at a spike time of the file's own units, and every spike of an ES epoch has a sample at its own time, so reading M-b can place every ES spike that is not at the lost position. In Rat3 FD4 the position ends at 6308 s, 41 s before the end of its MC epoch (5.3% of MC spikes have no sample), and in Rat2 FD9 at 10144 s, 7 s before the end of its MC epoch; no claim uses MC position. The Rat1 and Rat2 files of a flight day hold one trace: where they share a timestamp, 98.6 to 98.8% of positions agree within 2 pixels, and both span the whole session. Each file presents it as its own rat's position, though one tracker served both rats (the files say the rats were recorded on a shared acquisition system).
print("Distinct occupancy maps per task type, per file:", {nl.label(s): dict(Counter(t for t, _, _ in nl.occupancy_groups(s))) for s in sessions})
rows = []
for s in sessions:
if "xy" not in s:
continue
for typ, key, idx in nl.occupancy_groups(s):
occ = s["occupancy_maps"][idx[0]]
assigned = s["maps"].epoch[idx[0]]
scores = {n: round(nl.occupancy_match(s, occ, a, b), 2) for a, b, n in s["epochs"][["start", "stop", "name"]].itertuples(index=False)}
a, b = s["epochs"].set_index("name").loc[assigned, ["start", "stop"]]
m = (s["t"] >= a) & (s["t"] <= b) & s["valid"]
swapped = nl.count_map(*nl.bin_xy(s["xy"][m][:, ::-1])) > 0
rows.append({"session": nl.label(s), "maps": len(idx), "assigned_epoch": assigned, "match_assigned": scores[assigned],
"match_other_epochs": {k: v for k, v in scores.items() if k != assigned},
"match_x_y_swapped": round(float(np.corrcoef((nl.clean_occupancy(occ) > 0).ravel(), swapped.ravel())[0, 1]), 2),
"occupancy_counts": int(occ.sum()), "counts_per_s": round(occ.sum() / (b - a), 1)})
print("Correlation of each binary occupancy map with the binary histogram of an epoch's valid position samples:")
print(pd.DataFrame(rows).to_string(index=False))
print("\nEpoch of each map, per file and type:", {nl.label(s): s["maps"].groupby(["type", "epoch"]).size().to_dict() for s in sessions})
Distinct occupancy maps per task type, per file: {'Rat1 preflight': {'ES': 1, 'MC': 1}, 'Rat1 FD4': {'ES': 1, 'MC': 1}, 'Rat1 FD9': {'ES': 1, 'MC': 1}, 'Rat2 preflight': {'ES': 1, 'MC': 1}, 'Rat2 FD4': {'ES': 1, 'MC': 1}, 'Rat2 FD9': {'ES': 1, 'MC': 1}, 'Rat3 preflight': {'ES': 1, 'MC': 1}, 'Rat3 FD4': {'ES': 2, 'MC': 1}}
Correlation of each binary occupancy map with the binary histogram of an epoch's valid position samples:
session maps assigned_epoch match_assigned match_other_epochs match_x_y_swapped occupancy_counts counts_per_s
Rat1 FD4 35 ES1 0.75 {'BL1': -0.05, 'BL2': -0.04, 'BL3': 0.46, 'MC1': 0.29, 'BL4': 0.31} 0.47 26967 55.6
Rat1 FD4 32 MC1 0.69 {'BL1': 0.02, 'BL2': 0.0, 'BL3': 0.23, 'ES1': 0.29, 'BL4': 0.4} 0.16 25739 48.6
Rat1 FD9 26 ES1 0.88 {'BL1': 0.58, 'MC1': 0.54, 'BL2': 0.5} 0.58 84975 52.7
Rat1 FD9 38 MC1 0.88 {'ES1': 0.55, 'BL1': 0.44, 'BL2': 0.57} 0.01 59340 45.2
Rat2 FD4 39 ES1 0.68 {'BL1': -0.05, 'BL2': 0.42, 'BL3': 0.15, 'MC1': 0.3} 0.41 53333 56.1
Rat2 FD4 26 MC1 0.53 {'BL1': 0.04, 'ES1': 0.4, 'BL2': 0.3, 'BL3': 0.28} 0.08 34325 55.1
Rat2 FD9 41 ES1 0.68 {'BL1': 0.64, 'BL2': 0.26, 'MC1': 0.3} 0.46 123177 46.2
Rat2 FD9 39 MC1 0.46 {'BL1': 0.51, 'ES1': 0.43, 'BL2': 0.4} 0.18 46561 50.0
Rat3 FD4 19 ES1 0.81 {'ES2': 0.63, 'MC1': 0.34} 0.62 74454 56.7
Rat3 FD4 23 ES2 0.83 {'ES1': 0.67, 'MC1': 0.32} 0.55 65669 49.3
Rat3 FD4 23 MC1 0.79 {'ES1': 0.32, 'ES2': 0.32} 0.29 24357 38.3
Epoch of each map, per file and type: {'Rat1 preflight': {('ES', 'ES1'): 27, ('MC', 'MC1'): 35}, 'Rat1 FD4': {('ES', 'ES1'): 35, ('MC', 'MC1'): 32}, 'Rat1 FD9': {('ES', 'ES1'): 26, ('MC', 'MC1'): 38}, 'Rat2 preflight': {('ES', 'ES1'): 26, ('MC', 'MC1'): 33}, 'Rat2 FD4': {('ES', 'ES1'): 39, ('MC', 'MC1'): 26}, 'Rat2 FD9': {('ES', 'ES1'): 41, ('MC', 'MC1'): 39}, 'Rat3 preflight': {('ES', 'ES1'): 45, ('MC', 'MC1'): 41}, 'Rat3 FD4': {('ES', 'ES1'): 19, ('ES', 'ES2'): 23, ('MC', 'MC1'): 23}}
Each task type has one occupancy map per file, except Rat3 FD4, which has two ES maps. With position binned at 4 pixels per bin, x to columns and y to rows, each ES occupancy map matches the position samples of its own epoch best (0.68 to 0.88), better than the other epochs and better than with x and y swapped (0.41 to 0.62). The MC maps match their epochs at 0.46 to 0.88, and best in every file except Rat2 FD9, whose MC map matches the window in which Rat1 ran the staircase (BL1 in Rat2's file, 0.51) better than its own MC epoch (0.46). Rat3's two ES occupancy maps match ES1 (0.81) and ES2 (0.83), and the maps are assigned to those epochs. Rat2's FD9 ES map matches Rat2's ES epoch (0.68) nearly as well as Rat1's ES window (0.64), as a shared trace would. The occupancy counts are 38 to 57 per second of an epoch, the order of a video frame rate; the description calls them visit counts. The ES epoch names, times and position are therefore consistent with the original analysis.
r_all = np.concatenate([s["rate_maps"].ravel() for s in sessions])
o_all = np.concatenate([s["occupancy_maps"].ravel() for s in sessions])
print("Rate-map values: -999 bins", round(float((r_all == nl.UNVISITED).mean()), 3), "; -999 exactly where occupancy is 0:",
bool(((r_all == nl.UNVISITED) == (o_all == 0)).all()), "; other negative values:", int(((r_all < 0) & (r_all != nl.UNVISITED)).sum()),
"; NaN:", int(np.isnan(r_all).sum()))
print("Max rate per file (Hz):", {nl.label(s): round(float(s["rate_maps"].max()), 1) for s in sessions})
print("\nOccupancy in bin [63, 0], where the lost position (0, 255) falls, as a fraction of each occupancy map:")
for s in sessions:
print(" ", nl.label(s), [f"{s['maps'].epoch[idx[0]]} {s['occupancy_maps'][idx[0]][nl.SENTINEL_BIN] / s['occupancy_maps'][idx[0]].sum():.3f}" for _, _, idx in nl.occupancy_groups(s)])
print("\nIdentical copies of a rate map within a file (each group: type, tetrode/short name of every copy):")
for s in sessions:
groups = {}
for i, r in enumerate(s["rate_maps"]):
groups.setdefault(r.tobytes(), []).append(i)
for rows_ in groups.values():
if len(rows_) > 1:
print(f" {nl.label(s)}: {s['maps'].type[rows_[0]]} " + ", ".join(f"{s['maps'].tetrode[i]}/{s['maps'].source_file[i]}" for i in rows_))
print("Maps counted once per file:", {nl.label(s): int((~s["maps"].duplicate).sum()) for s in sessions})
print("\ncell_number -1 (\"not a per-cell map\"), per file:", {nl.label(s): f"{int((s['maps'].cell_number == -1).sum())} of {len(s['maps'])}" for s in sessions})
print("Rat3 FD4 ES maps with cell_number -1:", int(((by_label['Rat3 FD4']['maps'].type == 'ES') & (by_label['Rat3 FD4']['maps'].cell_number == -1)).sum()),
"of", int((by_label['Rat3 FD4']['maps'].type == 'ES').sum()), "; their names:", sorted(set(by_label['Rat3 FD4']['maps'].source_file[by_label['Rat3 FD4']['maps'].type == 'ES'])))
Rate-map values: -999 bins 0.606 ; -999 exactly where occupancy is 0: True ; other negative values: 0 ; NaN: 0
Max rate per file (Hz): {'Rat1 preflight': 16.0, 'Rat1 FD4': 10.5, 'Rat1 FD9': 277.8, 'Rat2 preflight': 16.8, 'Rat2 FD4': 30.5, 'Rat2 FD9': 5.2, 'Rat3 preflight': 15.4, 'Rat3 FD4': 26.7}
Occupancy in bin [63, 0], where the lost position (0, 255) falls, as a fraction of each occupancy map:
Rat1 preflight ['ES1 0.006', 'MC1 0.005']
Rat1 FD4 ['ES1 0.079', 'MC1 0.179']
Rat1 FD9 ['ES1 0.045', 'MC1 0.168']
Rat2 preflight ['ES1 0.007', 'MC1 0.009']
Rat2 FD4 ['ES1 0.175', 'MC1 0.113']
Rat2 FD9 ['ES1 0.043', 'MC1 0.172']
Rat3 preflight ['ES1 0.005', 'MC1 0.014']
Rat3 FD4 ['ES1 0.040', 'ES2 0.051', 'MC1 0.190']
Identical copies of a rate map within a file (each group: type, tetrode/short name of every copy):
Rat1 FD4: ES TT1/ESCELL~1.RMA, TT11/ESCELL~1.RMA
Rat2 FD4: ES TT13/ES2BC4~1.RMA, TT13/ES7D18~1.RMA, TT19/ES3BCC~1.RMA, TT22/ESCELL~4.RMA
Rat2 FD4: ES TT13/ES3BCA~1.RMA, TT13/ESCELL~4.RMA
Rat2 FD4: ES TT19/ES3BC8~1.RMA, TT22/ESCELL~1.RMA
Rat2 FD9: ES TT13/ES3BCC~1.RMA, TT14/ESCELL~4.RMA
Rat2 FD9: MC TT13/MC2DC6~1.RMA, TT14/MC2DC2~1.RMA
Maps counted once per file: {'Rat1 preflight': 62, 'Rat1 FD4': 66, 'Rat1 FD9': 64, 'Rat2 preflight': 59, 'Rat2 FD4': 60, 'Rat2 FD9': 78, 'Rat3 preflight': 86, 'Rat3 FD4': 65}
cell_number -1 ("not a per-cell map"), per file: {'Rat1 preflight': '14 of 62', 'Rat1 FD4': '19 of 67', 'Rat1 FD9': '14 of 64', 'Rat2 preflight': '18 of 59', 'Rat2 FD4': '28 of 65', 'Rat2 FD9': '33 of 80', 'Rat3 preflight': '23 of 86', 'Rat3 FD4': '46 of 65'}
Rat3 FD4 ES maps with cell_number -1: 42 of 42 ; their names: ['ES1CEL~1.RMA', 'ES1CEL~2.RMA', 'ES1CEL~3.RMA', 'ES1CEL~4.RMA', 'ES2CEL~1.RMA', 'ES2CEL~2.RMA', 'ES2CEL~3.RMA', 'ES2CEL~4.RMA', 'ES45A3~1.RMA', 'ES49A3~1.RMA', 'ESC5A3~1.RMA', 'ESC9A3~1.RMA']
# Can a rate map be linked to its unit? For each flight unit and task epoch with 50 or more spikes at
# a position, the correlation of the unit's spike-count map with rate x occupancy of every distinct map
# of its tetrode and epoch; the best match, and whether the table's stated link (same tetrode and type,
# cell_number = cluster_id) names it.
rows = []
for s in sessions:
if "xy" not in s:
continue
for a, b, typ, name in s["epochs"][["start", "stop", "type", "name"]].itertuples(index=False):
if typ == "BL":
continue
cand = s["maps"][(s["maps"].epoch == name) & ~s["maps"].duplicate]
for ui, u in s["units"].iterrows():
ok, r, c = nl.spike_positions(s, u.spikes[(u.spikes >= a) & (u.spikes <= b)])
if ok.sum() < 50:
continue
counts = nl.count_map(r, c)
cc = cand[cand.tetrode == u.tetrode]
rs = {i: np.corrcoef((np.clip(s["rate_maps"][i], 0, None) * nl.clean_occupancy(s["occupancy_maps"][i])).ravel(), counts.ravel())[0, 1] for i in cc.index}
rs = {i: v for i, v in rs.items() if np.isfinite(v)}
stated = list(cc.index[cc.cell_number == u.cluster])
best = max(rs, key=rs.get) if rs else None
row = {"session": nl.label(s), "epoch": name, "tetrode": u.tetrode, "cluster": int(u.cluster), "spikes": int(ok.sum()),
"maps_on_tetrode": len(cc), "best_r": rs[best] if best is not None else np.nan,
"stated_link_exists": bool(stated), "stated_link_is_best": best in stated}
if best is not None:
rate_i, occ_i = s["rate_maps"][best], nl.clean_occupancy(s["occupancy_maps"][best])
positive = (rate_i > 0) & (occ_i > 0)
row["positive_bins_without_spikes"] = float((positive & (counts == 0)).sum() / max(positive.sum(), 1))
rows.append(row)
link = pd.DataFrame(rows)
print(f"Unit-epochs with >= 50 spikes at a position: {len(link)}; with a map on their tetrode: {int(link.best_r.notna().sum())}; "
f"best match r > 0.5: {int((link.best_r > 0.5).sum())}")
print(f"The table's stated link exists for {int(link.stated_link_exists.sum())} and names the best-matching map for {int(link.stated_link_is_best.sum())}")
print(link.groupby(["session", "epoch"]).agg(n=("best_r", "size"), median_best_r=("best_r", "median"), stated_exists=("stated_link_exists", "sum"),
stated_is_best=("stated_link_is_best", "sum")).round(2).to_string())
good = link[link.best_r > 0.5]
print(f"\nBest-matching maps with r > 0.5 ({len(good)}): fraction of bins with a positive rate where the unit has no spike, median {good.positive_bins_without_spikes.median():.2f}")
Unit-epochs with >= 50 spikes at a position: 211; with a map on their tetrode: 211; best match r > 0.5: 135
The table's stated link exists for 98 and names the best-matching map for 84
n median_best_r stated_exists stated_is_best
session epoch
Rat1 FD4 ES1 20 0.71 11 9
MC1 18 0.38 9 5
Rat1 FD9 ES1 25 0.76 22 22
MC1 23 0.56 18 18
Rat2 FD4 ES1 17 0.47 7 6
MC1 14 0.54 6 4
Rat2 FD9 ES1 28 0.66 10 8
MC1 11 0.48 5 5
Rat3 FD4 ES1 16 0.82 0 0
ES2 19 0.73 0 0
MC1 20 0.21 10 7
Best-matching maps with r > 0.5 (135): fraction of bins with a positive rate where the unit has no spike, median 0.91
# The helper's measures on synthetic maps: a uniform rate carries no information; a rate confined to
# one of four equally occupied bins carries log2(4) = 2 bits per spike; the peak-in-field test.
occ = np.zeros((64, 64)); occ[:2, :2] = 10
print("uniform:", nl.skaggs_info(np.where(occ > 0, 3.0, 0.0), occ), "; one of four bins:", nl.skaggs_info(np.where(occ > 0, 0.0, 0.0) + (np.arange(64 * 64).reshape(64, 64) == 0) * 5.0, occ))
m1 = np.zeros((64, 64)); m1[0, 0], m1[0, 1] = 5.0, 2.0
m2 = np.zeros((64, 64)); m2[0, 1] = 4.0
m3 = np.zeros((64, 64)); m3[1, 1] = 4.0
print("peak in field (session 3 peak where session 2 has 40% of its peak):", nl.peak_in_field(m1, m2, occ > 0), "; (where it has 0%):", nl.peak_in_field(m1, m3, occ > 0))
assert abs(nl.skaggs_info(np.where(occ > 0, 3.0, 0.0), occ)) < 1e-12 and nl.peak_in_field(m1, m2, occ > 0) and not nl.peak_in_field(m1, m3, occ > 0)
uniform: 0.0 ; one of four bins: 2.0 peak in field (session 3 peak where session 2 has 40% of its peak): True ; (where it has 0%): False
Unvisited bins are -999, exactly where the occupancy is zero; the column description gives the rate in Hz and does not mention -999. No other value is negative. The lost position is counted in the original occupancy, in bin [63, 0] (up to 19% of a flight occupancy map, under 2% preflight); that bin is left out of every map here. Six groups of identical maps are stored in three files, several under different tetrodes (Rat2 FD4 TT13, TT19 and TT22); each map is counted once.
cell_number does not link maps to units as the table says. Of 211 flight unit-epochs with 50 or
more spikes at a position, the stated link exists for 98 and names the map that best matches the
unit's spikes for 84. Every one of Rat3 FD4's 42 ES maps has cell_number -1, "not a per-cell
map", though their names (ES1CEL~1 to ES2CEL~4 and four hashed names) are 8.3 short forms of
per-cell file names; so the dandiset gives no way to pair Rat 3's session 2 and session 3 maps by
cell. In the best-matching maps that correlate above 0.5 with their unit's spikes (135), the median
map has 91% of its positive-rate bins without a spike of the unit: the maps are smoothed, with a
kernel not stated. The synthetic tests show the helper's information measure and peak-in-field
test behave as intended.
ranges = [(0, 1000), (1000, 10000), (10000, np.inf)]
rows = []
for s in sessions:
allsp = np.concatenate(list(s["units"].spikes))
for lo, hi in ranges:
z = n = 0
for sp in s["units"].spikes:
w = sp[(sp >= lo) & (sp < hi)]
n += max(len(w) - 1, 0)
z += int((np.diff(w) == 0).sum())
distinct = np.unique(allsp[(allsp >= lo) & (allsp < hi)])
if len(distinct) > 1:
rows.append({"session": nl.label(s), "time_range_s": f"{lo}-{hi}", "spikes": int(((allsp >= lo) & (allsp < hi)).sum()),
"smallest_step_s": round(float(np.diff(distinct).min()), 4), "zero_intervals": z, "zero_fraction": round(z / max(n, 1), 3)})
print("Spike times are sorted in every unit:", all((np.diff(sp) >= 0).all() for s in sessions for sp in s["units"].spikes))
print(pd.DataFrame(rows).to_string(index=False))
outside = {nl.label(s): [int((~np.any([(sp >= a) & (sp <= b) for a, b in zip(s["epochs"].start, s["epochs"].stop)], axis=0)).sum()) for sp in s["units"].spikes] for s in sessions}
print("\nSpikes outside every epoch of their file (units with any, spikes):", {k: (sum(x > 0 for x in v), sum(v)) for k, v in outside.items()})
Spike times are sorted in every unit: True
session time_range_s spikes smallest_step_s zero_intervals zero_fraction
Rat1 preflight 0-1000 13362 0.0010 3 0.000
Rat1 preflight 1000-10000 8633 0.0100 1034 0.120
Rat1 FD4 1000-10000 39987 0.0100 3005 0.075
Rat1 FD9 1000-10000 238984 0.0100 12210 0.051
Rat1 FD9 10000-inf 4543 0.1000 2377 0.527
Rat2 preflight 1000-10000 36669 0.0100 2884 0.079
Rat2 FD4 1000-10000 31769 0.0100 4766 0.150
Rat2 FD9 1000-10000 51591 0.0100 5082 0.099
Rat2 FD9 10000-inf 584 0.1000 197 0.354
Rat3 preflight 0-1000 30094 0.0010 0 0.000
Rat3 preflight 1000-10000 17911 0.0100 974 0.055
Rat3 FD4 0-1000 13451 0.0004 0 0.000
Rat3 FD4 1000-10000 45266 0.0100 2980 0.066
Spikes outside every epoch of their file (units with any, spikes): {'Rat1 preflight': (3, 3), 'Rat1 FD4': (12, 14), 'Rat1 FD9': (4, 4), 'Rat2 preflight': (5, 5), 'Rat2 FD4': (3, 3), 'Rat2 FD9': (3, 3), 'Rat3 preflight': (5, 5), 'Rat3 FD4': (1, 1)}
Spike times are sorted. They are resolved to 1 ms or finer below 1000 s, to 10 ms from 1000 s and to 0.1 s from 10000 s, and zero intervals within a unit follow that resolution (none or nearly none below 1000 s, 5 to 15% at 10 ms, 35 to 53% at 0.1 s); nothing in the files states the resolution. 1 to 14 spikes per file lie outside every epoch. The analysis uses spikes within an epoch, so neither changes a result here except through the 10 ms binning of time, which is far finer than the 4-pixel bins and the epochs.
# Shared clock of spikes and position, and whose position the trace is. Each unit with >= 50 spikes at a
# position in a task window: spatial information of its spike-count map over the window's deposited
# occupancy (as in reading M-b) with its real spike times, against the same with its spikes shifted by
# +-30, 60, 90, 120 and 150 s (circularly within the window; each shifted spike takes the nearest sample
# within 0.25 s, else it is dropped). Shifted spikes are dropped more often than real ones, so for each
# shift the real spikes are subsampled to the same count (fixed seed). These are standard choices for a
# check; they decide no claim.
# Every unit is run with its own file's trace in every ES and MC window of both rats of a flight day.
SHIFTS = [s_ * k for k in (30, 60, 90, 120, 150) for s_ in (1, -1)]
rng_clock = np.random.default_rng(1754)
def shift_contrast(s, a, b, occ):
occ = nl.clean_occupancy(occ)
t, valid = s["t"], s["valid"]
out = []
for _, u in s["units"].iterrows():
sp = u.spikes[(u.spikes >= a) & (u.spikes <= b)]
ok, r, c = nl.spike_positions(s, sp)
if ok.sum() < 50:
continue
def info(rows, cols):
counts = nl.count_map(rows, cols)
counts[occ == 0] = 0
return nl.skaggs_info(np.where(occ > 0, counts / np.where(occ > 0, occ, 1), 0.0), occ) if counts.sum() else np.nan
real = info(r, c)
shifted, matched = [], []
for sh in SHIFTS:
x = a + (sp - a + sh) % (b - a)
i = np.clip(np.searchsorted(t, x), 1, len(t) - 1)
j = np.where(np.abs(t[i - 1] - x) <= np.abs(t[i] - x), i - 1, i)
keep = (np.abs(t[j] - x) <= 0.25) & valid[j]
shifted.append(info(*nl.bin_xy(s["xy"][j[keep]])) if keep.sum() else np.nan)
# the real spikes subsampled to the number of shifted spikes kept, as information rises when spikes are fewer
pick = rng_clock.choice(len(r), min(int(keep.sum()), len(r)), replace=False)
matched.append(info(r[pick], c[pick]) if len(pick) else np.nan)
diff = np.array(matched) - np.array(shifted)
out.append({"real": real, "shift_median": np.nanmedian(shifted), "above_all": bool(np.all(diff[np.isfinite(diff)] > 0)), "matched_median": np.nanmedian(matched)})
return pd.DataFrame(out)
rows = []
for day in ["FD4", "FD9"]:
pair = {r: by_label[f"{r} {day}"] for r in ["Rat1", "Rat2"]}
for units_of in ["Rat1", "Rat2"]:
for window_of in ["Rat1", "Rat2"]:
w = pair[window_of]
for a, b, typ, name in w["epochs"][["start", "stop", "type", "name"]].itertuples(index=False):
if typ == "BL":
continue
d = shift_contrast(pair[units_of], a, b, nl.epoch_occupancy(w, name))
rows.append({"day": day, "units_of": units_of, "window": f"{window_of} {name} {a:.0f}-{b:.0f}", "units": len(d),
"median_real": round(float(d.real.median()), 3) if len(d) else np.nan,
"median_real_count_matched": round(float(d.matched_median.median()), 3) if len(d) else np.nan,
"median_shifted": round(float(d.shift_median.median()), 3) if len(d) else np.nan,
"above_every_shift": int(d.above_all.sum()) if len(d) else 0,
"binom_p_vs_1/11": stats.binomtest(int(d.above_all.sum()), len(d), 1 / 11, alternative="greater").pvalue if len(d) else np.nan})
s3 = by_label["Rat3 FD4"]
for a, b, typ, name in s3["epochs"][["start", "stop", "type", "name"]].itertuples(index=False):
d = shift_contrast(s3, a, b, nl.epoch_occupancy(s3, name))
rows.append({"day": "FD4", "units_of": "Rat3", "window": f"Rat3 {name} {a:.0f}-{b:.0f}", "units": len(d),
"median_real": round(float(d.real.median()), 3),
"median_real_count_matched": round(float(d.matched_median.median()), 3), "median_shifted": round(float(d.shift_median.median()), 3),
"above_every_shift": int(d.above_all.sum()), "binom_p_vs_1/11": stats.binomtest(int(d.above_all.sum()), len(d), 1 / 11, alternative="greater").pvalue})
contrast = pd.DataFrame(rows)
print("Real against time-shifted spikes: units whose count-matched real information exceeds that of all 10 shifts (1/11 expected by chance):")
print(contrast.to_string(index=False))
Real against time-shifted spikes: units whose count-matched real information exceeds that of all 10 shifts (1/11 expected by chance): day units_of window units median_real median_real_count_matched median_shifted above_every_shift binom_p_vs_1/11 FD4 Rat1 Rat1 ES1 5935-6420 20 3.643 3.673 3.524 5 0.030175 FD4 Rat1 Rat1 MC1 7020-7550 18 4.066 4.083 3.856 2 0.496395 FD4 Rat1 Rat2 ES1 3850-4800 17 3.731 3.812 3.658 1 0.802155 FD4 Rat1 Rat2 MC1 7850-8473 22 4.048 4.120 4.185 2 0.606893 FD4 Rat2 Rat1 ES1 5935-6420 15 3.951 4.056 4.374 0 1.000000 FD4 Rat2 Rat1 MC1 7020-7550 20 4.868 5.141 5.179 0 1.000000 FD4 Rat2 Rat2 ES1 3850-4800 17 3.287 3.407 3.989 0 1.000000 FD4 Rat2 Rat2 MC1 7850-8473 14 3.870 4.108 4.638 1 0.736669 FD9 Rat1 Rat1 ES1 3178-4789 25 3.365 3.392 3.257 9 0.000223 FD9 Rat1 Rat1 MC1 7888-9200 23 3.701 3.701 3.339 3 0.348916 FD9 Rat1 Rat2 ES1 4871-7535 36 2.374 2.389 2.378 0 1.000000 FD9 Rat1 Rat2 MC1 9220-10151 25 4.034 4.037 4.253 0 1.000000 FD9 Rat2 Rat1 ES1 3178-4789 41 3.756 3.864 4.243 0 1.000000 FD9 Rat2 Rat1 MC1 7888-9200 32 5.111 5.358 5.798 0 1.000000 FD9 Rat2 Rat2 ES1 4871-7535 28 3.058 3.276 2.745 12 0.000002 FD9 Rat2 Rat2 MC1 9220-10151 11 4.385 4.627 5.296 1 0.649506 FD4 Rat3 Rat3 ES1 32-1345 16 2.984 3.040 2.809 6 0.002022 FD4 Rat3 Rat3 ES2 2397-3729 19 2.550 2.603 2.435 5 0.024413 FD4 Rat3 Rat3 MC1 5713-6349 20 4.446 4.499 5.012 1 0.851356
This contrast tests whether spikes and position share a clock, and whose position the trace is. In their own ES windows, units of Rat1 (5 of 20 on FD4, 9 of 25 on FD9), Rat3 (6 of 16 and 5 of 19) and Rat2 on FD9 (12 of 28) carry more information with their real spike times than with all ten time shifts, against 1/11 expected by chance (binomial p 0.030, 0.00022, 0.0020, 0.024 and 2e-6). So for those windows spikes and position share a clock, and in Rat2's FD9 ES window the trace behaves as Rat2's position; Rat1's units are not selective there (0 of 36). In Rat2's FD4 ES window neither rat's units are (0 of 17 for Rat2, 1 of 17 for Rat1), so whose position the trace is in that window, and whether Rat2's FD4 spikes share its clock, could not be established. In MC windows the contrast is weak for every rat (0 to 3 units). The FD4 results for Rat2 below carry this caveat. This check counts real spikes subsampled to the shifted count and uses 10 fixed shifts; it differs from the shuffle test of the earlier attempts, which found Rat2's FD9 units not selective.
# Whose position is the trace in Rat2's FD4 ES window (asked by the verifier of round 1)? Each of the Rat1
# and Rat2 files holds the trace at its own units' spike times. First, at timestamps both files hold within
# Rat2's ES windows, how far apart the positions they give are. Then the time-shift contrast above for
# Rat2's units, with each spike's position looked up on each file's trace in turn (nearest valid sample
# within 0.25 s, for the real spikes as for the shifted ones, so both traces are read the same way), over
# Rat2's deposited occupancy of the window. Rat2's FD9 ES window, where the trace behaves as Rat2's
# position, is the reference. Standard choices for a check; they decide no claim.
rng_trace = np.random.default_rng(17541)
def lookup(trace, x, tol=0.25):
t = trace["t"]
i = np.clip(np.searchsorted(t, x), 1, len(t) - 1)
j = np.where(np.abs(t[i - 1] - x) <= np.abs(t[i] - x), i - 1, i)
keep = (np.abs(t[j] - x) <= tol) & trace["valid"][j]
return keep, *nl.bin_xy(trace["xy"][j[keep]])
def contrast_on(units_s, trace, a, b, occ):
occ = nl.clean_occupancy(occ)
def info(rows, cols):
counts = nl.count_map(rows, cols)
counts[occ == 0] = 0
return nl.skaggs_info(np.where(occ > 0, counts / np.where(occ > 0, occ, 1), 0.0), occ) if counts.sum() else np.nan
out = []
for _, u in units_s["units"].iterrows():
sp = u.spikes[(u.spikes >= a) & (u.spikes <= b)]
keep, r, c = lookup(trace, sp)
if keep.sum() < 50:
continue
shifted, matched = [], []
for sh in SHIFTS:
k2, r2, c2 = lookup(trace, a + (sp - a + sh) % (b - a))
shifted.append(info(r2, c2) if k2.sum() else np.nan)
pick = rng_trace.choice(len(r), min(int(k2.sum()), len(r)), replace=False)
matched.append(info(r[pick], c[pick]) if len(pick) else np.nan)
diff = np.array(matched) - np.array(shifted)
out.append({"real": info(r, c), "above_all": bool(np.all(diff[np.isfinite(diff)] > 0))})
return pd.DataFrame(out)
rows = []
for day in ["FD4", "FD9"]:
r1, r2 = by_label[f"Rat1 {day}"], by_label[f"Rat2 {day}"]
a, b = r2["epochs"].set_index("name").loc["ES1", ["start", "stop"]]
m1, m2 = (r1["t"] >= a) & (r1["t"] <= b), (r2["t"] >= a) & (r2["t"] <= b)
common, i1, i2 = np.intersect1d(r1["t"][m1], r2["t"][m2], return_indices=True)
both = r1["valid"][m1][i1] & r2["valid"][m2][i2]
d = np.hypot(*(r1["xy"][m1][i1][both] - r2["xy"][m2][i2][both]).T)
print(f"{day}, Rat2's ES window {a:.0f}-{b:.0f} s: samples in the Rat1 file {int(m1.sum())}, in the Rat2 file {int(m2.sum())}; "
f"timestamps in both {len(common)}, both valid {int(both.sum())}, of those more than 2 px apart {int((d > 2).sum())}, "
f"valid in one file only {int((r1['valid'][m1][i1] != r2['valid'][m2][i2]).sum())}")
for trace_of, trace in [("Rat2", r2), ("Rat1", r1)]:
dd = contrast_on(r2, trace, a, b, nl.epoch_occupancy(r2, "ES1"))
rows.append({"day": day, "units_of": "Rat2", "window": f"Rat2 ES1 {a:.0f}-{b:.0f}", "trace_of_file": trace_of, "units": len(dd),
"median_real": round(float(dd.real.median()), 3), "above_every_shift": int(dd.above_all.sum()),
"binom_p_vs_1/11": stats.binomtest(int(dd.above_all.sum()), len(dd), 1 / 11, alternative="greater").pvalue})
print("\nRat2's units on each file's trace, real against time-shifted spikes (1/11 expected by chance):")
print(pd.DataFrame(rows).to_string(index=False))
FD4, Rat2's ES window 3850-4800 s: samples in the Rat1 file 8664, in the Rat2 file 9489; timestamps in both 828, both valid 707, of those more than 2 px apart 4, valid in one file only 1 FD9, Rat2's ES window 4871-7535 s: samples in the Rat1 file 50617, in the Rat2 file 20549; timestamps in both 3786, both valid 3693, of those more than 2 px apart 13, valid in one file only 1
Rat2's units on each file's trace, real against time-shifted spikes (1/11 expected by chance): day units_of window trace_of_file units median_real above_every_shift binom_p_vs_1/11 FD4 Rat2 Rat2 ES1 3850-4800 Rat2 17 3.287 0 1.000000e+00 FD4 Rat2 Rat2 ES1 3850-4800 Rat1 16 3.795 1 7.823709e-01 FD9 Rat2 Rat2 ES1 4871-7535 Rat2 28 3.058 12 2.399824e-06 FD9 Rat2 Rat2 ES1 4871-7535 Rat1 28 3.080 16 2.268057e-10
Within Rat 2's ES windows the two files hold one trace: of the timestamps both files hold with a
valid position, 4 of 707 on FD4 and 13 of 3693 on FD9 are more than 2 pixels apart. So the files
hold no second trace to compare with, and the contrast reads the same on either file's copy. Rat 2's
FD4 units carry more information than every shift for 0 of 17 on the Rat2 file's trace and 1 of 16
on the Rat1 file's (binomial p 1.0 and 0.78), while its FD9 units do for 12 of 28 and 16 of 28 (p
2e-6 and 2e-10). Whether the trace in Rat 2's FD4 ES window is Rat 2's position therefore remains
unknown. The deposited Rat2 FD4 ES occupancy map matches this trace in that window (0.68, above),
so the deposited maps were made from the same tracker and cannot settle it either.
fd4-abnormal-selectivity, judged on Rat 2's FD4 maps, carries this caveat.
Findings of 001754-knierim-2000-02 on this version¶
001754-knierim-2000-02 read the draft. The pinned version holds the files that
001754-knierim-2000-01 read, so each finding is in the state the first attempt found it, except
where the outputs above show more:
- The Rat1 and Rat2 files hold one shared trace presented as each rat's position (partly fixed in the draft). Still present in this version: the files share timestamps whose positions agree within 2 pixels for 98.6 to 98.8%, both span the whole session, and nothing names the tracked animal. The time-shift contrast shows the trace behaves as Rat2's position in Rat2's FD9 ES window (12 of 28 units); in Rat2's FD4 ES window it could not be attributed to either rat (0 of 17 and 1 of 16).
- Position only at spike times, none preflight. Still present.
- The dandiset description places the preflight recordings on a flat rectangular track while the preflight files label their epochs ES and MC. Still present in this version; the draft's description has dropped the sentence. The paper states the rectangular track for rat 2 only, and the files' experiment description puts the rectangular track in the baseline sessions, which the paper places in the sleeping pouch.
- The rate maps are smoothed with an unstated kernel (partly fixed in the draft). Still present: the pinned descriptions do not say the maps are smoothed; 91% of positive-rate bins of a well-matched map hold no spike.
- No EEG though the description reports it (partly fixed in the draft). Still present: no file holds EEG, and the pinned description reports theta and ripples; the draft's description now says the EEG is not included.
- The age is "/" and the pixel scale and camera view are not described (partly fixed in the draft). Still present, without the draft's statements that they were not recorded.
- Rat 2's preflight units share no tetrode with its flight units. Still present (0 tetrodes in common, against 4 to 6 for Rats 1 and 3).
The first attempt's other findings, which 001754-knierim-2000-02 found fixed in the draft, are
present in this version: cell_number does not link maps to units (84 of 211, and -1 for every
Rat3 FD4 ES map); -999 for unvisited bins and the lost position counted in bin [63, 0] are not
described; six groups of identical maps; spike-time resolution and zero intervals not described;
and Rat1 FD4's overlapping baseline epochs.
Spatial information in flight against preflight¶
The three comparisons (si-comparable-to-preflight, si-rat1-fd9-vs-preflight,
si-rat3-fd4-vs-preflight) share their method, from the claims file: active cells (mean rate above
0.05 Hz on the track), Skaggs' information per spike with off-track positions kept, flight against
the same rat's preflight cells by a two-sided Mann-Whitney test at p < 0.05, run under every reading.
How the readings apply here:
- M-a, the deposited maps. Each map, identical copies counted once, over its visited bins, without the lost-position bin. A map is active when its occupancy-weighted mean rate exceeds 0.05 Hz, as maps cannot be linked to units in this version (data check).
- M-b, maps recomputed from spikes and position. Position exists only at spike times and only in flight, so spike counts are divided by the deposited occupancy map of the epoch, as the criterion now says (answer of 2026-10-20), and M-b cannot be computed for any preflight cell. A unit is active when its spikes over the epoch's duration exceed 0.05 Hz. Spikes in bins with no deposited occupancy cannot be divided by an occupancy and are left out of M-b (33 to 1,188 per epoch over the active units, printed below). The criterion has both sides of a comparison use the same map reading, so M-b is not run for these comparisons; its flight values are shown, with a test of M-b flight values against M-a preflight values as a variant, not judged.
- S-a and S-b. Each preflight file is one recording day with one ES and one MC epoch, so S-a has two candidates, ES1 and MC1. S-b, the day closest before launch, is that same day, and the criterion runs each of its epochs on its own (answer of 2026-10-20), so S-b gives the same two readings as S-a. Each flight session has one ES epoch (for rat 3, session 2 is ES1).
The criterion's rule is applied to the M-a readings, the only ones with preflight values.
# Reading M-a: every deposited map, identical copies counted once, active when its occupancy-weighted
# mean rate exceeds 0.05 Hz. Reading M-b (flight only): every unit, active when its spikes in the epoch
# over the epoch's duration exceed 0.05 Hz. Active units by spike count are also given for preflight.
ma = nl.ma_table(sessions)
print("Deposited maps per file and epoch: all, identical copies, at or below 0.05 Hz (of those counted once), active:")
print(ma.groupby(["rat", "day", "epoch"]).agg(maps=("info", "size"), copies=("duplicate", "sum"),
at_or_below_threshold=("mean_rate", lambda r: int(((r <= nl.ACTIVE_HZ) & ~ma.loc[r.index, "duplicate"]).sum())),
active=("active", "sum")).to_string())
active_ma = ma[ma.active]
print("\nSpatial information of active M-a maps, range:", round(active_ma["info"].min(), 3), "to", round(active_ma["info"].max(), 3), "bits/spike")
rows = []
for s in sessions:
for a, b, typ, name in s["epochs"][["start", "stop", "type", "name"]].itertuples(index=False):
if typ == "BL":
continue
rates = np.array([((sp >= a) & (sp <= b)).sum() / (b - a) for sp in s["units"].spikes])
rows.append({"rat": s["subject"], "day": s["day"], "epoch": name, "units": len(rates), "active_units_by_spikes": int((rates > nl.ACTIVE_HZ).sum()),
"active_maps_M-a": int(active_ma[(active_ma.rat == s["subject"]) & (active_ma.day == s["day"]) & (active_ma.epoch == name)].shape[0])})
active_counts = pd.DataFrame(rows)
print("\nActive cells per epoch: units by spike count, and deposited maps (M-a):")
print(active_counts.to_string(index=False))
mb = {}
for s in sessions:
if "xy" not in s:
continue
for name in s["epochs"].name[s["epochs"].type != "BL"]:
mb[(s["subject"], s["day"], name)] = nl.mb_maps(s, name)
print("\nReading M-b, active units per flight epoch: spikes, spikes dropped at the lost position or with no sample, spikes in unvisited bins:")
print(pd.DataFrame([{"rat": k[0], "day": k[1], "epoch": k[2], "active": int(d.active.sum()), "spikes": int(d[d.active].spikes.sum()),
"dropped_lost_or_no_sample": int(d[d.active].spikes_lost_position.sum()), "in_unvisited_bins": int(d[d.active].spikes_unvisited_bin.sum()),
"info_range": f"{d[d.active]['info'].min():.3f}-{d[d.active]['info'].max():.3f}"} for k, d in mb.items()]).to_string(index=False))
print("The preflight files hold no position, so M-b cannot be computed for any preflight cell.")
def ma_values(rat, day, epoch):
return active_ma[(active_ma.rat == rat) & (active_ma.day == day) & (active_ma.epoch == epoch)]["info"].values
def mb_values(rat, day, epoch):
d = mb[(rat, day, epoch)]
return d[d.active]["info"].dropna().values
def readings(rat, day, epoch):
"""Every reading of a flight-against-preflight comparison: M-a on both sides with each preflight
epoch (each is a candidate under S-a and, as the one preflight day is the closest before launch,
under S-b), and as variants M-b flight values against M-a preflight values."""
rows = []
for pre in ["ES1", "MC1"]:
rows.append({"reading": f"M-a, preflight {pre} (S-a, S-b)"} | nl.compare(ma_values(rat, day, epoch), ma_values(rat, "preflight", pre)))
for pre in ["ES1", "MC1"]:
rows.append({"reading": f"variant: M-b flight, M-a preflight {pre}"} | nl.compare(mb_values(rat, day, epoch), ma_values(rat, "preflight", pre)))
return pd.DataFrame(rows)
def within(df, expected):
"""Whether each expected value is within its tolerance, per reading."""
(mf, tf), (mp, tp), (nf, tnf), (np_, tnp) = expected
return df.assign(flight_mean_ok=(df.mean_flight - mf).abs() <= tf, pre_mean_ok=(df.mean_pre - mp).abs() <= tp,
n_flight_ok=(df.n_flight - nf).abs() <= tnf, n_pre_ok=(df.n_pre - np_).abs() <= tnp)
def judge_absence(df):
"""The criterion's rule on the readings of the criterion (M-a rows; M-b has no preflight values)."""
d = df[df.reading.str.startswith("M-a")]
if not d.significant.any():
return "reproduced" if (d.flight_mean_ok & d.pre_mean_ok).any() else "partially_reproduced"
if d.significant.all():
return "not_reproduced"
return "blocked (methods_underspecified)"
show = ["reading", "n_flight", "n_pre", "mean_flight", "sem_flight", "mean_pre", "sem_pre", "difference", "U", "p", "significant",
"flight_mean_ok", "pre_mean_ok", "n_flight_ok", "n_pre_ok"]
Deposited maps per file and epoch: all, identical copies, at or below 0.05 Hz (of those counted once), active:
maps copies at_or_below_threshold active
rat day epoch
Rat1 FD4 ES1 35 1 14 20
MC1 32 0 13 19
FD9 ES1 26 0 2 24
MC1 38 0 16 22
preflight ES1 27 0 2 25
MC1 35 0 11 24
Rat2 FD4 ES1 39 5 20 14
MC1 26 0 13 13
FD9 ES1 41 1 23 17
MC1 39 1 30 8
preflight ES1 26 0 5 21
MC1 33 0 8 25
Rat3 FD4 ES1 19 0 8 11
ES2 23 0 4 19
MC1 23 0 7 16
preflight ES1 45 0 7 38
MC1 41 0 8 33
Spatial information of active M-a maps, range: 0.019 to 3.67 bits/spike
Active cells per epoch: units by spike count, and deposited maps (M-a):
rat day epoch units active_units_by_spikes active_maps_M-a
Rat1 preflight ES1 35 26 25
Rat1 preflight MC1 35 32 24
Rat1 FD4 ES1 47 24 20
Rat1 FD4 MC1 47 20 19
Rat1 FD9 ES1 39 24 24
Rat1 FD9 MC1 39 23 22
Rat2 preflight ES1 33 21 21
Rat2 preflight MC1 33 26 25
Rat2 FD4 ES1 72 19 14
Rat2 FD4 MC1 72 15 13
Rat2 FD9 ES1 54 25 17
Rat2 FD9 MC1 54 14 8
Rat3 preflight ES1 48 42 38
Rat3 preflight MC1 48 37 33
Rat3 FD4 ES1 47 16 11
Rat3 FD4 ES2 47 19 19
Rat3 FD4 MC1 47 23 16
Reading M-b, active units per flight epoch: spikes, spikes dropped at the lost position or with no sample, spikes in unvisited bins: rat day epoch active spikes dropped_lost_or_no_sample in_unvisited_bins info_range Rat1 FD4 ES1 24 5928 266 42 1.310-6.704 Rat1 FD4 MC1 20 11166 1933 922 1.194-5.647 Rat1 FD9 ES1 24 67133 2552 221 0.429-6.354 Rat1 FD9 MC1 23 83553 11209 1188 0.655-6.525 Rat2 FD4 ES1 19 12189 1561 732 1.386-7.298 Rat2 FD4 MC1 15 4299 436 88 2.420-7.035 Rat2 FD9 ES1 25 24409 541 297 0.967-5.549 Rat2 FD9 MC1 14 3572 462 45 2.820-8.108 Rat3 FD4 ES1 16 16748 560 113 1.067-5.304 Rat3 FD4 ES2 19 11703 602 33 1.815-7.793 Rat3 FD4 MC1 23 30061 5291 282 1.686-8.624 The preflight files hold no position, so M-b cannot be computed for any preflight cell.
# Maps with cell_number -1, which the rate-map table describes as "not a per-cell map" (asked by the
# verifier of round 1). Every map counted as active in M-a, per file and epoch, as tetrode/name/cell_number.
print("Active M-a maps, per file and epoch (tetrode/source_file/cell_number):")
for (rat, day, ep), g in active_ma.groupby(["rat", "day", "epoch"]):
print(f" {rat} {day} {ep}: {len(g)} active, {int((g.cell_number == -1).sum())} with cell_number -1")
print(" ", ", ".join(sorted(f"{t}/{n}/{c}" for t, n, c in zip(g.tetrode, g.source_file, g.cell_number))))
# In flight, does a map with cell_number -1 behave as one cell's map? Its best correlation with a single
# unit of its tetrode, against its correlation with all the tetrode's spikes summed (a combined or
# multi-unit map would match the sum at least as well). Preflight has no position, so it cannot be checked.
rows = []
for s in sessions:
if "xy" not in s:
continue
for name in s["epochs"].name[s["epochs"].type != "BL"]:
C = nl.map_unit_matches(s, name)
a, b = s["epochs"].set_index("name").loc[name, ["start", "stop"]]
summed = {}
for _, u in s["units"].iterrows():
ok, r, c = nl.spike_positions(s, u.spikes[(u.spikes >= a) & (u.spikes <= b)])
summed[u.tetrode] = summed.get(u.tetrode, 0) + nl.count_map(r, c)
for i, g in C.groupby("map_row"):
x = (np.clip(s["rate_maps"][i], 0, None) * nl.clean_occupancy(s["occupancy_maps"][i])).ravel()
m = ma[(ma.rat == s["subject"]) & (ma.day == s["day"]) & (ma.row == i)].iloc[0]
rows.append({"file": nl.label(s), "cell_number_-1": m.cell_number == -1, "active": bool(m.active),
"best_single_unit_r": g.r.max(), "tetrode_sum_r": np.corrcoef(x, summed[g.tetrode.iloc[0]].ravel())[0, 1]})
cn = pd.DataFrame(rows)
cn["single_above_sum"] = cn.best_single_unit_r > cn.tetrode_sum_r
cn["group"] = np.where(cn.file == "Rat3 FD4", "Rat3 FD4", "other flight files")
print("\nFlight maps with a unit on their tetrode: best single-unit correlation against the tetrode's summed spikes:")
print(cn.groupby(["group", "cell_number_-1", "active"]).agg(maps=("best_single_unit_r", "size"), median_best_single_unit_r=("best_single_unit_r", "median"),
median_tetrode_sum_r=("tetrode_sum_r", "median"), single_unit_better=("single_above_sum", "mean")).round(3).to_string())
# Variant, not a reading of the criterion: the comparisons with maps of cell_number -1 left out. Rat3's FD4 ES
# maps all have cell_number -1, so its flight side has none; its preflight counts are given.
numbered = active_ma[active_ma.cell_number != -1]
def nb_values(rat, day, epoch):
return numbered[(numbered.rat == rat) & (numbered.day == day) & (numbered.epoch == epoch)]["info"].values
rows = [{"rat": rat, "flight": f"{day} ES1", "preflight": pre} | nl.compare(nb_values(rat, day, "ES1"), nb_values(rat, "preflight", pre))
for rat, day in [("Rat2", "FD9"), ("Rat1", "FD9"), ("Rat1", "FD4"), ("Rat2", "FD4")] for pre in ["ES1", "MC1"]]
print("\nVariant: active maps with cell_number other than -1 only, two-sided Mann-Whitney:")
print(pd.DataFrame(rows).drop(columns="U").round(4).to_string(index=False))
print("Rat3 preflight active maps with cell_number other than -1: ES1", len(nb_values("Rat3", "preflight", "ES1")), ", MC1", len(nb_values("Rat3", "preflight", "MC1")))
Active M-a maps, per file and epoch (tetrode/source_file/cell_number):
Rat1 FD4 ES1: 20 active, 6 with cell_number -1
TT1/ES3BC8~1.RMA/-1, TT1/ES3BCA~1.RMA/-1, TT1/ESCELL~2.RMA/2, TT1/ESCELL~3.RMA/3, TT10/ES3BCA~1.RMA/-1, TT10/ES3BCC~1.RMA/-1, TT10/ESCELL~1.RMA/1, TT10/ESCELL~2.RMA/2, TT10/ESCELL~3.RMA/3, TT10/ESCELL~4.RMA/4, TT11/ES2BC0~1.RMA/-1, TT11/ESCELL~2.RMA/2, TT11/ESCELL~3.RMA/3, TT11/ESCELL~4.RMA/4, TT2/ES2BC6~1.RMA/-1, TT7/ESCELL~1.RMA/1, TT7/ESCELL~2.RMA/2, TT8/ESCELL~1.RMA/1, TT8/ESCELL~2.RMA/2, TT9/ESCELL~1.RMA/1
Rat1 FD4 MC1: 19 active, 5 with cell_number -1
TT0/MCCELL~2.RMA/2, TT1/MC2DC0~1.RMA/-1, TT1/MC2DC6~1.RMA/-1, TT1/MC3DCC~1.RMA/-1, TT1/MCCELL~2.RMA/2, TT1/MCCELL~3.RMA/3, TT10/MC2DC6~1.RMA/-1, TT10/MC3DCC~1.RMA/-1, TT10/MCCELL~2.RMA/2, TT10/MCCELL~3.RMA/3, TT10/MCCELL~4.RMA/4, TT11/MCCELL~1.RMA/1, TT11/MCCELL~2.RMA/2, TT11/MCCELL~4.RMA/4, TT2/MCCELL~4.RMA/4, TT7/MCCELL~1.RMA/1, TT7/MCCELL~2.RMA/2, TT8/MCCELL~1.RMA/1, TT9/MCCELL~2.RMA/2
Rat1 FD9 ES1: 24 active, 3 with cell_number -1
TT0/ESCELL~1.RMA/1, TT0/ESCELL~2.RMA/2, TT0/ESCELL~3.RMA/3, TT1/ES2BC0~1.RMA/-1, TT1/ESCELL~1.RMA/1, TT1/ESCELL~2.RMA/2, TT1/ESCELL~3.RMA/3, TT1/ESCELL~4.RMA/4, TT10/ES2BC0~1.RMA/-1, TT10/ES2BC6~1.RMA/-1, TT10/ESCELL~2.RMA/2, TT10/ESCELL~3.RMA/3, TT11/ESCELL~1.RMA/1, TT11/ESCELL~2.RMA/2, TT2/ESCELL~1.RMA/1, TT6/ESCELL~1.RMA/1, TT6/ESCELL~2.RMA/2, TT8/ESCELL~1.RMA/1, TT8/ESCELL~2.RMA/2, TT8/ESCELL~3.RMA/3, TT9/ESCELL~1.RMA/1, TT9/ESCELL~2.RMA/2, TT9/ESCELL~3.RMA/3, TT9/ESCELL~4.RMA/4
Rat1 FD9 MC1: 22 active, 5 with cell_number -1
TT0/MCCELL~1.RMA/1, TT0/MCCELL~2.RMA/2, TT1/MCCELL~1.RMA/1, TT1/MCCELL~2.RMA/2, TT1/MCCELL~3.RMA/3, TT10/MC2DC2~1.RMA/-1, TT10/MC2DC4~1.RMA/-1, TT10/MCCELL~3.RMA/3, TT11/MC2DC4~1.RMA/-1, TT11/MCCELL~1.RMA/1, TT11/MCCELL~2.RMA/2, TT11/MCCELL~3.RMA/3, TT2/MCCELL~1.RMA/1, TT6/MCCELL~1.RMA/1, TT6/MCCELL~2.RMA/2, TT8/MC2DC4~1.RMA/-1, TT8/MCCELL~1.RMA/1, TT8/MCCELL~2.RMA/2, TT9/MC2DC2~1.RMA/-1, TT9/MCCELL~1.RMA/1, TT9/MCCELL~2.RMA/2, TT9/MCCELL~4.RMA/4
Rat1 preflight ES1: 25 active, 5 with cell_number -1
TT11/ESCELL~1.RMA/1, TT11/ESCELL~2.RMA/2, TT11/ESCELL~3.RMA/3, TT3/ESCELL~1.RMA/1, TT3/ESCELL~2.RMA/2, TT3/ESCELL~3.RMA/3, TT4/ES2BC0~1.RMA/-1, TT4/ESCELL~1.RMA/1, TT4/ESCELL~2.RMA/2, TT4/ESCELL~3.RMA/3, TT4/ESCELL~4.RMA/4, TT5/ESCELL~1.RMA/1, TT6/ES2BC0~1.RMA/-1, TT6/ES2BC6~1.RMA/-1, TT6/ES3BCC~1.RMA/-1, TT6/ESCELL~1.RMA/1, TT6/ESCELL~2.RMA/2, TT6/ESCELL~3.RMA/3, TT6/ESCELL~4.RMA/4, TT7/ESCELL~1.RMA/1, TT7/ESCELL~2.RMA/2, TT8/ES2BC0~1.RMA/-1, TT8/ESCELL~3.RMA/3, TT8/ESCELL~4.RMA/4, TT9/ESCELL~1.RMA/1
Rat1 preflight MC1: 24 active, 7 with cell_number -1
TT11/MCCELL~1.RMA/1, TT11/MCCELL~4.RMA/4, TT3/MCCELL~1.RMA/1, TT3/MCCELL~2.RMA/2, TT3/MCCELL~3.RMA/3, TT3/MCCELL~4.RMA/4, TT4/MC2DC4~1.RMA/-1, TT4/MCCELL~3.RMA/3, TT4/MCCELL~4.RMA/4, TT5/MCCELL~2.RMA/2, TT6/MC2DC0~1.RMA/-1, TT6/MC2DC2~1.RMA/-1, TT6/MC2DC4~1.RMA/-1, TT6/MCCELL~1.RMA/1, TT6/MCCELL~3.RMA/3, TT7/MCCELL~2.RMA/2, TT7/MCCELL~3.RMA/3, TT8/MC2DC2~1.RMA/-1, TT8/MC2DC4~1.RMA/-1, TT8/MC2DC6~1.RMA/-1, TT8/MCCELL~2.RMA/2, TT8/MCCELL~3.RMA/3, TT8/MCCELL~4.RMA/4, TT9/MCCELL~1.RMA/1
Rat2 FD4 ES1: 14 active, 4 with cell_number -1
TT12/ES7128~1.RMA/-1, TT12/ES7138~1.RMA/-1, TT12/ES7528~1.RMA/-1, TT12/ESCELL~1.RMA/1, TT12/ESCELL~2.RMA/2, TT12/ESCELL~3.RMA/3, TT12/ESCELL~4.RMA/4, TT13/ES2BCE~1.RMA/-1, TT14/ESCELL~1.RMA/1, TT17/ESCELL~1.RMA/1, TT19/ESCELL~1.RMA/1, TT19/ESCELL~2.RMA/2, TT19/ESCELL~3.RMA/3, TT19/ESCELL~4.RMA/4
Rat2 FD4 MC1: 13 active, 4 with cell_number -1
TT12/MCCELL~1.RMA/1, TT12/MCCELL~2.RMA/2, TT12/MCCELL~3.RMA/3, TT12/MCCELL~4.RMA/4, TT13/MC2DC0~1.RMA/-1, TT13/MC2DC6~1.RMA/-1, TT13/MC2DCA~1.RMA/-1, TT14/MCCELL~1.RMA/1, TT14/MCCELL~2.RMA/2, TT17/MCCELL~1.RMA/1, TT19/MC3DCC~1.RMA/-1, TT19/MCCELL~2.RMA/2, TT22/MCCELL~4.RMA/4
Rat2 FD9 ES1: 17 active, 5 with cell_number -1
TT12/ESCELL~1.RMA/1, TT12/ESCELL~2.RMA/2, TT12/ESCELL~3.RMA/3, TT12/ESCELL~4.RMA/4, TT13/ES2BC4~1.RMA/-1, TT13/ESCELL~1.RMA/1, TT13/ESCELL~3.RMA/3, TT14/ES3BCA~1.RMA/-1, TT14/ES3BCC~1.RMA/-1, TT15/ESCELL~1.RMA/1, TT15/ESCELL~2.RMA/2, TT19/ES2BC6~1.RMA/-1, TT19/ES3BCA~1.RMA/-1, TT19/ESCELL~2.RMA/2, TT19/ESCELL~4.RMA/4, TT22/ESCELL~1.RMA/1, TT22/ESCELL~3.RMA/3
Rat2 FD9 MC1: 8 active, 3 with cell_number -1
TT12/MC7B2C~1.RMA/-1, TT12/MC7F2C~1.RMA/-1, TT12/MCCELL~3.RMA/3, TT12/MCCELL~4.RMA/4, TT13/MCCELL~1.RMA/1, TT14/MC2DC0~1.RMA/-1, TT15/MCCELL~2.RMA/2, TT22/MCCELL~3.RMA/3
Rat2 preflight ES1: 21 active, 5 with cell_number -1
TT0/ES2BC0~1.RMA/-1, TT0/ES2BC6~1.RMA/-1, TT0/ESCELL~1.RMA/1, TT0/ESCELL~2.RMA/2, TT0/ESCELL~3.RMA/3, TT0/ESCELL~4.RMA/4, TT1/ES2BC6~1.RMA/-1, TT1/ES3BCA~1.RMA/-1, TT1/ES3BCC~1.RMA/-1, TT1/ESCELL~3.RMA/3, TT10/ESCELL~1.RMA/1, TT10/ESCELL~2.RMA/2, TT10/ESCELL~3.RMA/3, TT2/ESCELL~1.RMA/1, TT2/ESCELL~2.RMA/2, TT2/ESCELL~3.RMA/3, TT7/ESCELL~1.RMA/1, TT7/ESCELL~2.RMA/2, TT7/ESCELL~3.RMA/3, TT8/ESCELL~1.RMA/1, TT8/ESCELL~2.RMA/2
Rat2 preflight MC1: 25 active, 9 with cell_number -1
TT0/MC2DC0~1.RMA/-1, TT0/MC2DC2~1.RMA/-1, TT0/MC2DC4~1.RMA/-1, TT0/MC2DC6~1.RMA/-1, TT0/MC2DCE~1.RMA/-1, TT0/MCCELL~1.RMA/1, TT0/MCCELL~3.RMA/3, TT0/MCCELL~4.RMA/4, TT1/MC2DC0~1.RMA/-1, TT1/MC2DC2~1.RMA/-1, TT1/MC2DC6~1.RMA/-1, TT1/MC3DCC~1.RMA/-1, TT1/MCCELL~1.RMA/1, TT1/MCCELL~2.RMA/2, TT1/MCCELL~4.RMA/4, TT10/MCCELL~1.RMA/1, TT10/MCCELL~2.RMA/2, TT2/MCCELL~1.RMA/1, TT2/MCCELL~2.RMA/2, TT2/MCCELL~3.RMA/3, TT2/MCCELL~4.RMA/4, TT7/MCCELL~2.RMA/2, TT7/MCCELL~3.RMA/3, TT8/MCCELL~1.RMA/1, TT8/MCCELL~2.RMA/2
Rat3 FD4 ES1: 11 active, 11 with cell_number -1
TT10/ES1CEL~2.RMA/-1, TT10/ES1CEL~3.RMA/-1, TT3/ES1CEL~1.RMA/-1, TT4/ES1CEL~1.RMA/-1, TT4/ES1CEL~2.RMA/-1, TT4/ES1CEL~3.RMA/-1, TT5/ES1CEL~1.RMA/-1, TT5/ES1CEL~2.RMA/-1, TT8/ES1CEL~1.RMA/-1, TT8/ES1CEL~4.RMA/-1, TT8/ES45A3~1.RMA/-1
Rat3 FD4 ES2: 19 active, 19 with cell_number -1
TT0/ES2CEL~1.RMA/-1, TT0/ES2CEL~2.RMA/-1, TT10/ES2CEL~1.RMA/-1, TT10/ES2CEL~2.RMA/-1, TT10/ES2CEL~3.RMA/-1, TT11/ES2CEL~1.RMA/-1, TT3/ES2CEL~1.RMA/-1, TT3/ES2CEL~2.RMA/-1, TT3/ES2CEL~3.RMA/-1, TT4/ES2CEL~1.RMA/-1, TT4/ES2CEL~3.RMA/-1, TT5/ES2CEL~1.RMA/-1, TT5/ES2CEL~2.RMA/-1, TT5/ES2CEL~3.RMA/-1, TT8/ES2CEL~1.RMA/-1, TT8/ES2CEL~2.RMA/-1, TT8/ES2CEL~3.RMA/-1, TT8/ES2CEL~4.RMA/-1, TT8/ESC9A3~1.RMA/-1
Rat3 FD4 MC1: 16 active, 2 with cell_number -1
TT13/MCCELL~1.RMA/1, TT13/MCCELL~2.RMA/2, TT16/MCCELL~2.RMA/2, TT17/MC3DCC~1.RMA/-1, TT17/MCCELL~1.RMA/1, TT18/MCCELL~1.RMA/1, TT19/MCCELL~1.RMA/1, TT19/MCCELL~2.RMA/2, TT20/MC3DCC~1.RMA/-1, TT20/MCCELL~1.RMA/1, TT20/MCCELL~3.RMA/3, TT20/MCCELL~4.RMA/4, TT22/MCCELL~1.RMA/1, TT22/MCCELL~2.RMA/2, TT23/MCCELL~1.RMA/1, TT23/MCCELL~2.RMA/2
Rat3 preflight ES1: 38 active, 9 with cell_number -1
TT0/ESCELL~1.RMA/1, TT0/ESCELL~2.RMA/2, TT1/ES2BC0~1.RMA/-1, TT1/ES2BC6~1.RMA/-1, TT1/ESCELL~1.RMA/1, TT1/ESCELL~2.RMA/2, TT1/ESCELL~3.RMA/3, TT10/ES2BC0~1.RMA/-1, TT10/ES3BCC~1.RMA/-1, TT10/ESCELL~2.RMA/2, TT10/ESCELL~3.RMA/3, TT11/ESCELL~1.RMA/1, TT11/ESCELL~2.RMA/2, TT11/ESCELL~3.RMA/3, TT11/ESCELL~4.RMA/4, TT2/ES2BC0~1.RMA/-1, TT2/ESCELL~1.RMA/1, TT2/ESCELL~2.RMA/2, TT2/ESCELL~3.RMA/3, TT2/ESCELL~4.RMA/4, TT4/ESCELL~1.RMA/1, TT4/ESCELL~2.RMA/2, TT4/ESCELL~3.RMA/3, TT4/ESCELL~4.RMA/4, TT5/ESCELL~1.RMA/1, TT6/ESCELL~1.RMA/1, TT6/ESCELL~2.RMA/2, TT6/ESCELL~3.RMA/3, TT8/ES2BC0~1.RMA/-1, TT8/ES2BC6~1.RMA/-1, TT8/ESCELL~1.RMA/1, TT8/ESCELL~2.RMA/2, TT8/ESCELL~3.RMA/3, TT8/ESCELL~4.RMA/4, TT9/ES2BC0~1.RMA/-1, TT9/ES2BC6~1.RMA/-1, TT9/ESCELL~1.RMA/1, TT9/ESCELL~2.RMA/2
Rat3 preflight MC1: 33 active, 10 with cell_number -1
TT1/MC2DC0~1.RMA/-1, TT1/MC2DC2~1.RMA/-1, TT1/MCCELL~1.RMA/1, TT1/MCCELL~2.RMA/2, TT1/MCCELL~3.RMA/3, TT10/MC2DC0~1.RMA/-1, TT10/MC2DC2~1.RMA/-1, TT10/MC2DC4~1.RMA/-1, TT10/MC2DC6~1.RMA/-1, TT10/MCCELL~1.RMA/1, TT10/MCCELL~2.RMA/2, TT10/MCCELL~3.RMA/3, TT2/MC2DC4~1.RMA/-1, TT2/MCCELL~1.RMA/1, TT2/MCCELL~2.RMA/2, TT2/MCCELL~3.RMA/3, TT2/MCCELL~4.RMA/4, TT4/MCCELL~1.RMA/1, TT4/MCCELL~2.RMA/2, TT4/MCCELL~3.RMA/3, TT4/MCCELL~4.RMA/4, TT5/MCCELL~2.RMA/2, TT6/MCCELL~1.RMA/1, TT6/MCCELL~2.RMA/2, TT6/MCCELL~3.RMA/3, TT8/MC2DC4~1.RMA/-1, TT8/MCCELL~2.RMA/2, TT8/MCCELL~3.RMA/3, TT8/MCCELL~4.RMA/4, TT9/MC2DC0~1.RMA/-1, TT9/MC2DC2~1.RMA/-1, TT9/MCCELL~2.RMA/2, TT9/MCCELL~4.RMA/4
Flight maps with a unit on their tetrode: best single-unit correlation against the tetrode's summed spikes:
maps median_best_single_unit_r median_tetrode_sum_r single_unit_better
group cell_number_-1 active
Rat3 FD4 False False 5 0.140 0.012 1.000
True 14 0.280 0.116 0.857
True False 12 0.551 0.357 0.917
True 32 0.785 0.549 0.844
other flight files False False 70 0.293 0.202 0.829
True 102 0.595 0.433 0.794
True False 55 0.261 0.128 0.909
True 35 0.555 0.332 0.943
Variant: active maps with cell_number other than -1 only, two-sided Mann-Whitney:
rat flight preflight n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference p significant
Rat2 FD9 ES1 ES1 12 16 1.367 0.139 1.343 0.119 0.024 0.8709 False
Rat2 FD9 ES1 MC1 12 16 1.367 0.139 1.245 0.137 0.122 0.5617 False
Rat1 FD9 ES1 ES1 21 20 1.540 0.155 1.801 0.182 -0.261 0.3966 False
Rat1 FD9 ES1 MC1 21 17 1.540 0.155 1.081 0.143 0.459 0.0204 True
Rat1 FD4 ES1 ES1 14 20 1.429 0.240 1.801 0.182 -0.371 0.2411 False
Rat1 FD4 ES1 MC1 14 17 1.429 0.240 1.081 0.143 0.349 0.4158 False
Rat2 FD4 ES1 ES1 10 16 0.616 0.085 1.343 0.119 -0.727 0.0002 True
Rat2 FD4 ES1 MC1 10 16 0.616 0.085 1.245 0.137 -0.629 0.0017 True
Rat3 preflight active maps with cell_number other than -1: ES1 29 , MC1 23
Maps with cell_number -1 are 2 to 10 of the active maps of each file and epoch, except in Rat3
FD4's two ES epochs, where every map has it. Their names are 8.3 short forms (ES2BC0~1, MC2DC4~1 and the like) of
names the files do not give. In flight they behave as one cell's maps: the active ones outside
Rat3 FD4 match one unit of their tetrode with a median correlation of 0.555, against 0.595 for the
numbered maps, and match it better than the tetrode's summed spikes for 94% of maps (79% for the
numbered maps). The preflight maps cannot be checked, as preflight has no position. Leaving them
out (a variant, not a reading of the criterion) changes no test's result: Rat 2 FD9 against
preflight p 0.87 and 0.56; Rat 1 FD9 p 0.40 against ES and 0.020 against MC (flight higher); Rat 1
FD4 p 0.24 and 0.42; Rat 2 FD4 lower than preflight, p 0.0002 and 0.0017. It brings Rat 1's FD9
count to 21, the paper's, and Rat 3's preflight counts to 29 (ES) and 23 (MC) against 24; the
other counts stay off.
si-comparable-to-preflight¶
For rat 2, the spatial information per spike of the 16 active cells recorded on the Escher staircase on flight day 9 (1.13 +/- 0.09 bits) did not differ significantly from that of its 19 active preflight cells (1.12 +/- 0.08 bits), by Mann-Whitney test.
Reproduced when no reading finds a difference and at least one puts both means within tolerance; partially reproduced when none finds a difference and no reading puts both means within tolerance.
si2 = within(readings("Rat2", "FD9", "ES1"), [(1.13, 0.09), (1.12, 0.08), (16, 1.6), (19, 1.9)])
print("Rat2, FD9 ES1 against preflight (expected 1.13 +/- 0.09 and 1.12 +/- 0.08 bits/spike, n 16 and 19):")
print(si2[show].round(4).to_string(index=False))
print("p of each reading, unrounded:", ", ".join(f"{r}: {v:.2e}" for r, v in zip(si2.reading, si2.p)))
outcome_si2 = judge_absence(si2)
print("\nBy the criterion's rule:", outcome_si2)
Rat2, FD9 ES1 against preflight (expected 1.13 +/- 0.09 and 1.12 +/- 0.08 bits/spike, n 16 and 19):
reading n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference U p significant flight_mean_ok pre_mean_ok n_flight_ok n_pre_ok
M-a, preflight ES1 (S-a, S-b) 17 21 1.377 0.120 1.320 0.112 0.057 173.0 0.8833 False False False True False
M-a, preflight MC1 (S-a, S-b) 17 25 1.377 0.120 1.284 0.116 0.092 230.0 0.6631 False False False True False
variant: M-b flight, M-a preflight ES1 25 21 2.965 0.224 1.320 0.112 1.646 489.0 0.0000 True False False False False
variant: M-b flight, M-a preflight MC1 25 25 2.965 0.224 1.284 0.116 1.681 579.0 0.0000 True False False False False
p of each reading, unrounded: M-a, preflight ES1 (S-a, S-b): 8.83e-01, M-a, preflight MC1 (S-a, S-b): 6.63e-01, variant: M-b flight, M-a preflight ES1: 6.23e-07, variant: M-b flight, M-a preflight MC1: 2.45e-07
By the criterion's rule: partially_reproduced
Result: partially reproduced. Under both readings the test finds no difference: Rat 2's 17
active FD9 ES maps (1.377 +/- 0.120 bits/spike) against its 21 preflight ES maps (1.320 +/- 0.112,
p = 0.88, difference 0.057) and its 25 preflight MC maps (1.284 +/- 0.116, p = 0.66, difference
0.092). No reading puts both means within tolerance: the FD9 mean (1.377) is above 1.13 + 0.09, and
the preflight means (1.320 and 1.284) are above 1.12 + 0.08. The FD9 count (17) is within 16 +/-
1.6; the preflight counts (21 and 25) are not within 19 +/- 1.9. Under M-b the 25 active FD9 units
have 1.6 to 1.7 bits/spike more than either preflight M-a set (2.965 +/- 0.224, p 6.2e-07 and 2.5e-07), but M-b
has no preflight values, and the criterion uses one map reading on both sides, so this mixed test is
a variant and is not judged. The answers of 2026-10-20 changed no number and no outcome here: they
confirm that the rule rests on the two M-a readings, and that S-b gives the same two epochs as S-a. The FD9 ES window is
the one in which the trace behaves as Rat 2's position (data check). Leaving out the maps with cell_number -1 (a variant) the test finds no
difference either (p 0.87 and 0.56). Rat 2's preflight units share
no tetrode with its flight units, and which preflight epoch was the rectangular track of Fig. 2b is
not stated.
si-rat1-fd9-vs-preflight¶
For rat 1, the mean spatial information of its 21 active cells on flight day 9 (1.19 +/- 0.09 bits) did not differ significantly from its preflight cells (1.37 +/- 0.10 bits, n = 28).
As for si-comparable-to-preflight, for rat 1; blocked as methods_underspecified when the readings
disagree on the test.
si1 = within(readings("Rat1", "FD9", "ES1"), [(1.19, 0.09), (1.37, 0.10), (21, 2.1), (28, 2.8)])
print("Rat1, FD9 ES1 against preflight (expected 1.19 +/- 0.09 and 1.37 +/- 0.10 bits/spike, n 21 and 28):")
print(si1[show].round(4).to_string(index=False))
outcome_si1 = judge_absence(si1)
print("\nBy the criterion's rule:", outcome_si1)
Rat1, FD9 ES1 against preflight (expected 1.19 +/- 0.09 and 1.37 +/- 0.10 bits/spike, n 21 and 28):
reading n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference U p significant flight_mean_ok pre_mean_ok n_flight_ok n_pre_ok
M-a, preflight ES1 (S-a, S-b) 24 25 1.588 0.143 1.804 0.149 -0.216 266.0 0.5029 False False False False False
M-a, preflight MC1 (S-a, S-b) 24 24 1.588 0.143 1.127 0.114 0.461 423.0 0.0055 True False False False False
variant: M-b flight, M-a preflight ES1 24 25 3.331 0.268 1.804 0.149 1.526 514.0 0.0000 True False False False False
variant: M-b flight, M-a preflight MC1 24 24 3.331 0.268 1.127 0.114 2.204 532.0 0.0000 True False False False False
By the criterion's rule: blocked (methods_underspecified)
Result: blocked (methods underspecified). The readings disagree on the test. Against the
preflight ES epoch, Rat 1's 24 active FD9 ES maps (1.588 +/- 0.143 bits/spike) do not differ from its
25 preflight maps (1.804 +/- 0.149, p = 0.50, difference -0.216); against the preflight MC epoch
they are higher than its 24 maps (1.127 +/- 0.114, p = 0.0055, difference 0.461). The paper does not
say on which track, or in which epoch, rat 1's preflight cells were recorded, and the criterion now
runs both epochs under S-a and S-b and prefers neither (answer of 2026-10-20), so the disagreement
stands and the claim stays blocked, as before the answer.
No reading puts a mean within tolerance (1.588 against 1.19 +/- 0.09; 1.804 and 1.127 against
1.37 +/- 0.10), and no count is within 10% of the paper's (24 against 21; 25 and 24 against 28).
The M-b variant has 3.331 +/- 0.268 bits/spike over 24 active units. Leaving out the maps with
cell_number -1 (a variant) the readings disagree in the same way (p 0.40 against ES, 0.020 against
MC), with 21 FD9 maps, the paper's count.
si-rat3-fd4-vs-preflight¶
For rat 3, the mean spatial information of all 7 active place cells on flight day 4 (1.12 +/- 0.27 bits, session 2) was not different from its preflight cells (1.48 +/- 0.11 bits, n = 24).
As for si-comparable-to-preflight, for rat 3, with the FD4 cells from session 2 (ES1).
si3 = within(readings("Rat3", "FD4", "ES1"), [(1.12, 0.27), (1.48, 0.11), (7, 0.7), (24, 2.4)])
print("Rat3, FD4 ES1 (session 2) against preflight (expected 1.12 +/- 0.27 and 1.48 +/- 0.11 bits/spike, n 7 and 24):")
print(si3[show].round(4).to_string(index=False))
outcome_si3 = judge_absence(si3)
print("\nBy the criterion's rule:", outcome_si3)
si3b = within(readings("Rat3", "FD4", "ES2"), [(1.12, 0.27), (1.48, 0.11), (7, 0.7), (24, 2.4)])
print("\nVariant, not the claim's session: FD4 ES2 (session 3) against preflight:")
print(si3b[show].round(4).to_string(index=False))
Rat3, FD4 ES1 (session 2) against preflight (expected 1.12 +/- 0.27 and 1.48 +/- 0.11 bits/spike, n 7 and 24):
reading n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference U p significant flight_mean_ok pre_mean_ok n_flight_ok n_pre_ok
M-a, preflight ES1 (S-a, S-b) 11 38 1.231 0.206 1.518 0.125 -0.287 163.0 0.2756 False True True False False
M-a, preflight MC1 (S-a, S-b) 11 33 1.231 0.206 1.337 0.121 -0.106 170.0 0.7656 False True False False False
variant: M-b flight, M-a preflight ES1 16 38 3.038 0.334 1.518 0.125 1.520 505.0 0.0001 True False True False False
variant: M-b flight, M-a preflight MC1 16 33 3.038 0.334 1.337 0.121 1.701 460.0 0.0000 True False False False False
By the criterion's rule: reproduced
Variant, not the claim's session: FD4 ES2 (session 3) against preflight:
reading n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference U p significant flight_mean_ok pre_mean_ok n_flight_ok n_pre_ok
M-a, preflight ES1 (S-a, S-b) 19 38 1.384 0.177 1.518 0.125 -0.134 297.0 0.2824 False True True False False
M-a, preflight MC1 (S-a, S-b) 19 33 1.384 0.177 1.337 0.121 0.046 304.0 0.8642 False True False False False
variant: M-b flight, M-a preflight ES1 19 38 2.930 0.310 1.518 0.125 1.412 626.0 0.0000 True False True False False
variant: M-b flight, M-a preflight MC1 19 33 2.930 0.310 1.337 0.121 1.593 582.0 0.0000 True False False False False
fig, ax = plt.subplots(figsize=(8, 3.5))
groups = [(f"{r}\n{d} {e}", ma_values(r, d, e)) for r, d, e in [("Rat1", "preflight", "ES1"), ("Rat1", "preflight", "MC1"), ("Rat1", "FD9", "ES1"),
("Rat2", "preflight", "ES1"), ("Rat2", "preflight", "MC1"), ("Rat2", "FD9", "ES1"), ("Rat3", "preflight", "ES1"), ("Rat3", "preflight", "MC1"), ("Rat3", "FD4", "ES1")]]
ax.boxplot([v for _, v in groups], showfliers=True)
ax.set_xticks(range(1, len(groups) + 1), [g for g, _ in groups], fontsize=7)
ax.set_ylabel("spatial information, active M-a maps (bits/spike)")
plt.tight_layout()
plt.show()
print("All plotted values are within the axes; range", round(min(v.min() for _, v in groups), 3), "to", round(max(v.max() for _, v in groups), 3))
All plotted values are within the axes; range 0.063 to 3.07
Result: reproduced. Under both readings the test finds no difference: Rat 3's 11 active
session 2 maps (1.231 +/- 0.206 bits/spike) against its 38 preflight ES maps (1.518 +/- 0.125, p =
0.28, difference -0.287) and its 33 preflight MC maps (1.337 +/- 0.121, p = 0.77, difference
-0.106). Under the ES reading both means are within tolerance (1.231 within 1.12 +/- 0.27, 1.518
within 1.48 +/- 0.11), as the criterion requires of one reading. The counts are not: 11 active maps
in session 2 against the paper's 7, and 38 or 33 preflight against 24, so the cells compared are not
the paper's set; the criterion's rule does not use the counts. Session 3 (ES2, 19 maps, 1.384 +/-
0.177) also does not differ from either preflight epoch (p = 0.28 and 0.86). The M-b variant has
3.038 +/- 0.334 bits/spike over 16 active units and is not judged, both sides using one map reading
(answer of 2026-10-20); the answers changed no number and no outcome here. Every Rat3 FD4 ES map has cell_number -1, so
the variant without those maps cannot be run for this claim.
flight-spatial-code¶
In flight, after an initial period, hippocampal place cells on the Escher staircase showed normal, spatially specific firing, with spatial information per spike not different from the same rats' preflight cells.
Judged from the three comparisons above: reproduced when all three are reproduced; partially reproduced when both FD9 comparisons are reproduced and the FD4 one is not, or when no FD9 comparison is not reproduced and one or more is partially reproduced; not reproduced when either FD9 comparison is not reproduced. When a dependency is blocked as methods_underspecified, so is this claim, unless the others already decide it.
OUTCOMES = ["reproduced", "partially_reproduced", "not_reproduced"]
def flight_rule(r2, r1, r3):
if r2 == r1 == r3 == "reproduced":
return "reproduced"
if "not_reproduced" in (r2, r1):
return "not_reproduced"
if r2 == r1 == "reproduced" and r3 != "reproduced":
return "partially_reproduced"
return "partially_reproduced"
deps = {"si-comparable-to-preflight": outcome_si2, "si-rat1-fd9-vs-preflight": outcome_si1, "si-rat3-fd4-vs-preflight": outcome_si3}
print("Dependencies:", deps)
options = [[o] if o in OUTCOMES else OUTCOMES for o in deps.values()]
possible = sorted({flight_rule(a, b, c) for a in options[0] for b in options[1] for c in options[2]})
print("Outcomes the rule gives over every value a blocked dependency could take:", possible)
outcome_flight = possible[0] if len(possible) == 1 else "blocked (methods_underspecified)"
print("By the criterion's rule:", outcome_flight)
Dependencies: {'si-comparable-to-preflight': 'partially_reproduced', 'si-rat1-fd9-vs-preflight': 'blocked (methods_underspecified)', 'si-rat3-fd4-vs-preflight': 'reproduced'}
Outcomes the rule gives over every value a blocked dependency could take: ['not_reproduced', 'partially_reproduced']
By the criterion's rule: blocked (methods_underspecified)
Result: blocked (methods underspecified). The rat 2 comparison is partially reproduced, the rat
3 comparison reproduced, and the rat 1 comparison blocked. The others do not decide the claim: if the
rat 1 comparison were reproduced or partially reproduced the claim would be partially reproduced,
and if it were not reproduced the claim would be not reproduced. Under the preflight ES reading for
rat 1 (p = 0.50) the claim would be partially reproduced, and under the preflight MC reading (p =
0.0055, flight higher) not reproduced. In 001754-knierim-2000-02 this claim was not reproduced
by a shuffle test the paper does not use; that test is not run here.
fd4-abnormal-selectivity¶
On the first experience of the Escher staircase on flight day 4, place cells of rats 1 and 2 showed abnormal patterns of spatial selectivity, which differed between the two rats.
Abnormal is read as spatial information of active cells on FD4 significantly lower than preflight (two-sided p < 0.05), in each of rats 1 and 2, with the map and session readings. Reproduced when this holds for both rats under every reading; partially reproduced when it holds for one rat; not reproduced when it holds for neither; blocked when readings disagree. M-b has no preflight values, so the readings are M-a against preflight ES1 and MC1.
rows = []
for rat in ["Rat1", "Rat2", "Rat3"]:
for pre in ["ES1", "MC1"]:
r = nl.compare(ma_values(rat, "FD4", "ES1"), ma_values(rat, "preflight", pre))
rows.append({"rat": rat, "reading": f"M-a, preflight {pre} (S-a, S-b)"} | r |
{"lower_and_significant": r["significant"] and r["difference"] < 0})
fd4 = pd.DataFrame(rows)
print("FD4 ES1 against preflight, two-sided Mann-Whitney (Rat3 is not part of the claim and is shown for reference):")
print(fd4[["rat", "reading", "n_flight", "n_pre", "mean_flight", "sem_flight", "mean_pre", "sem_pre", "difference", "U", "p", "lower_and_significant"]].round(4).to_string(index=False))
per_rat = {rat: set(fd4[fd4.rat == rat].lower_and_significant) for rat in ["Rat1", "Rat2"]}
print("\nAbnormal (lower and significant) under the readings, per rat:", {k: sorted(v) for k, v in per_rat.items()})
if any(len(v) > 1 for v in per_rat.values()):
outcome_fd4 = "blocked (methods_underspecified)"
else:
holds = sum(v == {True} for v in per_rat.values())
outcome_fd4 = {2: "reproduced", 1: "partially_reproduced", 0: "not_reproduced"}[holds]
print("By the criterion's rule:", outcome_fd4)
print("\nVariant, M-b FD4 ES1 values (no preflight M-b exists): mean +/- s.e.m. and n of active units")
for rat in ["Rat1", "Rat2"]:
v = mb_values(rat, "FD4", "ES1")
print(f" {rat}: {np.mean(v):.3f} +/- {stats.sem(v):.3f}, n {len(v)}; against FD9 ES1 M-b {np.mean(mb_values(rat, 'FD9', 'ES1')):.3f}, "
f"Mann-Whitney p {stats.mannwhitneyu(v, mb_values(rat, 'FD9', 'ES1')).pvalue:.3g}")
print("Variant, M-a FD4 ES1 against FD9 ES1 (not a reading of the criterion):")
for rat in ["Rat1", "Rat2"]:
print(f" {rat}:", nl.compare(ma_values(rat, "FD4", "ES1"), ma_values(rat, "FD9", "ES1")))
FD4 ES1 against preflight, two-sided Mann-Whitney (Rat3 is not part of the claim and is shown for reference):
rat reading n_flight n_pre mean_flight sem_flight mean_pre sem_pre difference U p lower_and_significant
Rat1 M-a, preflight ES1 (S-a, S-b) 20 25 1.409 0.192 1.804 0.149 -0.396 172.0 0.0767 False
Rat1 M-a, preflight MC1 (S-a, S-b) 20 24 1.409 0.192 1.127 0.114 0.282 279.0 0.3642 False
Rat2 M-a, preflight ES1 (S-a, S-b) 14 21 0.708 0.112 1.320 0.112 -0.612 45.0 0.0006 True
Rat2 M-a, preflight MC1 (S-a, S-b) 14 25 0.708 0.112 1.284 0.116 -0.576 66.0 0.0015 True
Rat3 M-a, preflight ES1 (S-a, S-b) 11 38 1.231 0.206 1.518 0.125 -0.287 163.0 0.2756 False
Rat3 M-a, preflight MC1 (S-a, S-b) 11 33 1.231 0.206 1.337 0.121 -0.106 170.0 0.7656 False
Abnormal (lower and significant) under the readings, per rat: {'Rat1': [False], 'Rat2': [True]}
By the criterion's rule: partially_reproduced
Variant, M-b FD4 ES1 values (no preflight M-b exists): mean +/- s.e.m. and n of active units
Rat1: 4.037 +/- 0.335, n 24; against FD9 ES1 M-b 3.331, Mann-Whitney p 0.177
Rat2: 3.739 +/- 0.362, n 19; against FD9 ES1 M-b 2.965, Mann-Whitney p 0.0925
Variant, M-a FD4 ES1 against FD9 ES1 (not a reading of the criterion):
Rat1: {'n_flight': 20, 'n_pre': 24, 'mean_flight': 1.409, 'sem_flight': 0.192, 'mean_pre': 1.588, 'sem_pre': 0.143, 'difference': -0.179, 'U': 202.0, 'p': 0.376759117811582, 'significant': False}
Rat2: {'n_flight': 14, 'n_pre': 17, 'mean_flight': 0.708, 'sem_flight': 0.112, 'mean_pre': 1.377, 'sem_pre': 0.12, 'difference': -0.669, 'U': 28.0, 'p': 0.0003277395594089004, 'significant': True}
Result: partially reproduced. It holds for rat 2 and not for rat 1, under both readings. Rat 2's
14 active FD4 ES maps (0.708 +/- 0.112 bits/spike) are lower than its preflight ES maps (1.320 +/-
0.112, p = 0.00063) and MC maps (1.284 +/- 0.116, p = 0.0015). Rat 1's 20 FD4 ES maps (1.409 +/-
0.192) are lower than its preflight ES maps without significance (1.804 +/- 0.149, p = 0.077) and
higher than its MC maps (1.127 +/- 0.114, p = 0.36). Rat 2's FD4 maps are also lower than its FD9
maps (1.377, p = 0.00033), and Rat 1's are not (1.588, p = 0.38). In Rat 2's FD4 ES window the
trace could not be shown to be Rat 2's position (data check): the Rat1 and Rat2 files hold one trace
there (4 of 707 shared positions more than 2 px apart), and Rat 2's FD4 units are selective on
neither file's copy (0 of 17 and 1 of 16). So it is not known whether Rat 2's low FD4 information
comes from its firing or from tracking; the criterion sets no condition on this, and the claim is
judged as it says. Leaving out the maps with cell_number -1 (a variant) gives the same pattern
(Rat 2 p 0.0002 and 0.0017; Rat 1 p 0.24 and 0.42). That file holds five identical
copies of maps (each counted once). That the patterns differed between the rats has no measure and
is not judged; Rat 2's FD4 maps carry less information than Rat 1's (0.708 against 1.409). The M-b
FD4 values (4.037 and 3.739 bits/spike) have no preflight counterpart, so M-b is not run for this
claim (answers of 2026-10-20, one map reading on both sides and each preflight epoch a candidate);
the answers changed no number and no outcome here.
fig3-stability-across-sessions¶
For rat 3 on flight day 4, most of the 7 cells kept the same firing fields in a third session that followed the second immediately; the fourth cell lost its field, and a few other cells gained a field in session 3.
Cells are the active cells of session 2 (ES1), followed into session 3 (ES2). A cell keeps its field by (a) when the correlation of its session 2 and 3 maps over bins visited in both exceeds the 95th percentile of the correlations between different cells across the two sessions, and by (b) when its session 3 peak lies in its session 2 field (bins with at least 20% of the session 2 peak). "Most" is more than half of the active cells of session 2 (answer of 2026-10-20; with the paper's 7 cells this was at least 4). Reproduced when more than half keep their field under every reading, not reproduced when half or fewer do under every reading, and blocked when the readings disagree.
Reading M-b uses the unsmoothed maps of the active units. Reading M-a needs the deposited maps of
one cell in both sessions, and the dandiset gives no link (every Rat3 FD4 ES map has
cell_number -1). The criterion now allows a pairing through the unit whose spike-count map a deposited map
best matches, when each map is tied to one unit and no unit to two maps (answer of 2026-10-20). The
code ties every distinct map of session 2 and of session 3 that has a unit on its tetrode (17 of 19 in session 2, all 11 active maps among them, and 23 of 23
in session 3) to the unit it best matches, checks
that no unit is tied to two maps in either session, and runs M-a as a judged reading only if so.
s3 = by_label["Rat3 FD4"]
occ2, occ3 = (nl.clean_occupancy(nl.epoch_occupancy(s3, e)) for e in ["ES1", "ES2"])
both = (occ2 > 0) & (occ3 > 0)
print("Rat3 FD4: ES1 (session 2)", s3["epochs"].set_index("name").loc["ES1", ["start", "stop"]].tolist(), "s; ES2 (session 3)",
s3["epochs"].set_index("name").loc["ES2", ["start", "stop"]].tolist(), "s; bins visited in both:", int(both.sum()))
def stability(maps2, maps3, cells, fraction=nl.FIELD_FRACTION):
"""Readings (a) and (b) for the cells; maps2/maps3 map a cell to its session 2 and 3 maps (None when absent)."""
C_ = np.array([[nl.map_correlation(maps2[i], maps3[j], both) if maps3[j] is not None else np.nan for j in cells] for i in cells])
off = C_[~np.eye(len(cells), dtype=bool)]
thr = float(np.percentile(off[np.isfinite(off)], 95))
same = np.diag(C_)
peak = [maps3[i] is not None and nl.peak_in_field(maps2[i], maps3[i], both, fraction) for i in cells]
return pd.DataFrame({"cell": cells, "r_same_cell": same, "keeps_a": np.nan_to_num(same, nan=-2) > thr, "keeps_b": peak}), thr, len(off[np.isfinite(off)])
def judge_most(n_keep, n_cells):
""""Most" is more than half of the active cells of session 2 (the criterion, answer of 2026-10-20)."""
return n_keep > n_cells / 2
# Reading M-b: the active units of session 2 (spike count over ES1's duration above 0.05 Hz).
b2, b3 = mb[("Rat3", "FD4", "ES1")], mb[("Rat3", "FD4", "ES2")]
cells_b = list(b2.index[b2.active])
maps2_b = {i: b2.rate_map[i] for i in cells_b}
maps3_b = {i: (b3.rate_map[i] if b3.spikes[i] > 0 else None) for i in cells_b}
tab_b, thr_b, n_null_b = stability(maps2_b, maps3_b, cells_b)
tab_b = tab_b.assign(tetrode=[b2.tetrode[i] for i in cells_b], cluster=[b2.cluster[i] for i in cells_b],
spikes_s2=[b2.spikes[i] for i in cells_b], spikes_s3=[b3.spikes[i] for i in cells_b], rate_s3_hz=[round(b3.mean_rate[i], 3) for i in cells_b])
print(f"\nM-b: {len(cells_b)} active cells in session 2 (expected 7 +/- 0.7); null 95th percentile of different-cell correlations {thr_b:.3f} over {n_null_b} pairs")
print(tab_b[["tetrode", "cluster", "spikes_s2", "spikes_s3", "rate_s3_hz", "r_same_cell", "keeps_a", "keeps_b"]].round(3).to_string(index=False))
print(f"Keep their field: (a) {int(tab_b.keeps_a.sum())} of {len(tab_b)}, (b) {int(tab_b.keeps_b.sum())} of {len(tab_b)}")
for name, k in [("(a)", int(tab_b.keeps_a.sum())), ("(b)", int(tab_b.keeps_b.sum()))]:
print(f" {name} more than half ({k} > {len(tab_b) / 2}):", judge_most(k, len(tab_b)))
print("Same-cell correlations, range:", round(np.nanmin(tab_b.r_same_cell), 3), "to", round(np.nanmax(tab_b.r_same_cell), 3))
print("\nField threshold variants for reading (b):", {f: int(stability(maps2_b, maps3_b, cells_b, f)[0].keeps_b.sum()) for f in [0.1, 0.2, 0.3, 0.5]})
Rat3 FD4: ES1 (session 2) [32.0, 1345.0] s; ES2 (session 3) [2397.0, 3729.0] s; bins visited in both: 940
M-b: 16 active cells in session 2 (expected 7 +/- 0.7); null 95th percentile of different-cell correlations 0.094 over 240 pairs
tetrode cluster spikes_s2 spikes_s3 rate_s3_hz r_same_cell keeps_a keeps_b
TT0 1 95 233 0.175 -0.009 False False
TT10 2 331 749 0.562 0.159 True False
TT10 3 978 1045 0.785 0.025 False False
TT3 1 294 474 0.356 0.099 True False
TT3 2 120 163 0.122 0.045 False False
TT3 3 85 175 0.131 0.048 False False
TT4 1 1603 449 0.337 0.366 True True
TT4 2 212 30 0.023 0.012 False False
TT4 3 638 449 0.337 0.033 False False
TT5 1 9102 2322 1.743 0.237 True False
TT5 2 422 816 0.613 0.012 False False
TT8 1 479 461 0.346 0.005 False False
TT8 3 124 500 0.375 0.151 True False
TT8 4 238 624 0.468 -0.024 False False
TT8 5 1851 847 0.636 0.020 False False
TT8 6 176 361 0.271 0.310 True False
Keep their field: (a) 6 of 16, (b) 1 of 16
(a) more than half (6 > 8.0): False
(b) more than half (1 > 8.0): False
Same-cell correlations, range: -0.024 to 0.366
Field threshold variants for reading (b): {0.1: 2, 0.2: 1, 0.3: 0, 0.5: 0}
# Reading M-a. The dandiset gives no link between a deposited map and a unit here (every Rat3 FD4 ES map
# has cell_number -1). The criterion allows a pairing through the unit whose spike-count map a deposited
# map best matches, when each map is tied to one unit and no unit to two maps; otherwise M-a is not run for
# this claim. So every distinct map of session 2 and of session 3 is tied to the unit of its tetrode that it
# best matches (rate x occupancy against the unit's spike-count map, as in the data check), and the
# condition is checked over all of them. Each unit's own best map is shown too, and a map that is the
# best of more than one unit is flagged.
cols_info = ma[(ma.rat == "Rat3") & (ma.day == "FD4")].set_index("row")[["active", "mean_rate", "info"]]
tied, unit_twice = {}, {}
for ep in ["ES1", "ES2"]:
C = nl.map_unit_matches(s3, ep)
by_map = C.loc[C.groupby("map_row").r.idxmax()].join(cols_info, on="map_row")
by_unit = C.loc[C.groupby("unit").r.idxmax()]
by_map["maps_tied_to_unit"] = by_map.unit.map(by_map.unit.value_counts())
by_map["unit_best_map"] = by_map.unit.map(by_unit.set_index("unit").map_name)
by_unit["units_whose_best_it_is"] = by_unit.map_row.map(by_unit.map_row.value_counts())
n_maps = int(((s3["maps"].epoch == ep) & ~s3["maps"].duplicate).sum())
print(f"{ep}: {n_maps} distinct maps, {len(by_map)} with a unit of their tetrode. Each map, the unit it best matches, and how many maps that unit is tied to:")
print(by_map.sort_values(["tetrode", "map_name"])[["map_name", "tetrode", "active", "mean_rate", "info", "cluster", "r", "maps_tied_to_unit", "unit_best_map"]].round(3).to_string(index=False))
print(f"{ep}: each unit, its best map, and for how many units that map is the best:")
print(by_unit.sort_values(["tetrode", "cluster"])[["tetrode", "cluster", "map_name", "r", "units_whose_best_it_is"]].round(3).to_string(index=False))
twice = by_map[by_map.maps_tied_to_unit > 1]
print(f"{ep}: units tied to two or more maps: {sorted(set(zip(twice.tetrode, twice.cluster)))}; maps that are the best of two or more units: "
f"{sorted(set(zip(by_unit.tetrode[by_unit.units_whose_best_it_is > 1], by_unit.map_name[by_unit.units_whose_best_it_is > 1])))}\n")
tied[ep] = dict(zip(by_map.map_row, by_map.unit))
unit_twice[ep] = set(twice.unit)
a3 = ma[(ma.rat == "Rat3") & (ma.day == "FD4")]
cells_a = list(a3[(a3.epoch == "ES1") & a3.active].row)
units_a = [tied["ES1"].get(i) for i in cells_a]
print(f"Active session 2 maps: {len(cells_a)}; their units tied to two or more session 2 maps: {sum(u in unit_twice['ES1'] for u in units_a)}, "
f"to two or more session 3 maps: {sum(u in unit_twice['ES2'] for u in units_a)}")
pairing_ok = not unit_twice["ES1"] and not unit_twice["ES2"] and None not in units_a
print("Each map tied to one unit and no unit to two maps, in both sessions:", pairing_ok)
if pairing_ok:
def deposited(i):
m = s3["rate_maps"][i].copy()
m[m == nl.UNVISITED] = np.nan
return m
s3_of_unit = {u: i for i, u in tied["ES2"].items()}
maps2_a = {i: deposited(i) for i in cells_a}
maps3_a = {i: (deposited(s3_of_unit[u]) if u in s3_of_unit else None) for i, u in zip(cells_a, units_a)}
tab_a, thr_a, n_null_a = stability(maps2_a, maps3_a, cells_a)
print(f"M-a: {len(cells_a)} active deposited maps in session 2; null 95th percentile {thr_a:.3f} over {n_null_a} pairs")
print(tab_a.round(3).to_string(index=False))
print(f"Keep their field: (a) {int(tab_a.keeps_a.sum())} of {len(tab_a)}, (b) {int(tab_a.keeps_b.sum())} of {len(tab_a)}")
else:
print("The condition does not hold, so, as the criterion says, M-a is not run for this claim.")
# Cells silent or below 0.05 Hz in session 2 that are active in session 3 (reported, not judged).
gained = b3[b3.active & ~b2.active]
print(f"\nUnits below 0.05 Hz in session 2 and active in session 3: {len(gained)}; their spikes in session 2: {b2.spikes[gained.index].tolist()}, in session 3: {gained.spikes.tolist()}")
print("Units active in session 2 and silent (no spike) in session 3:", int((b2.active & (b3.spikes == 0)).sum()))
judged = [("M-b", tab_b)] + ([("M-a", tab_a)] if pairing_ok else [])
by_reading = {f"{m} ({r})": judge_most(int(tab[f"keeps_{r}"].sum()), len(tab)) for m, tab in judged for r in ["a", "b"]}
print("\nReadings judged:", [m for m, _ in judged], "; more than half keep their field, per reading:", by_reading)
outcome_fig3 = ("reproduced" if all(by_reading.values()) else "not_reproduced" if not any(by_reading.values())
else "blocked (methods_underspecified)")
print("By the criterion's rule:", outcome_fig3)
ES1: 19 distinct maps, 17 with a unit of their tetrode. Each map, the unit it best matches, and how many maps that unit is tied to:
map_name tetrode active mean_rate info cluster r maps_tied_to_unit unit_best_map
ES1CEL~1.RMA TT0 False 0.036 1.947 1 0.566 1 ES1CEL~1.RMA
ES1CEL~2.RMA TT0 False 0.010 0.876 2 0.277 1 ES1CEL~2.RMA
ES1CEL~2.RMA TT10 True 0.099 2.150 2 0.861 1 ES1CEL~2.RMA
ES1CEL~3.RMA TT10 True 0.265 0.802 3 0.893 1 ES1CEL~3.RMA
ES1CEL~1.RMA TT3 True 0.087 0.973 1 0.821 1 ES1CEL~1.RMA
ES1CEL~2.RMA TT3 False 0.040 2.036 2 0.857 1 ES1CEL~2.RMA
ES1CEL~3.RMA TT3 False 0.029 0.486 3 0.544 1 ES1CEL~3.RMA
ES1CEL~1.RMA TT4 True 0.446 2.270 1 0.926 1 ES1CEL~1.RMA
ES1CEL~2.RMA TT4 True 0.063 1.677 2 0.832 1 ES1CEL~2.RMA
ES1CEL~3.RMA TT4 True 0.176 1.453 3 0.870 1 ES1CEL~3.RMA
ES1CEL~1.RMA TT5 True 2.500 0.825 1 0.966 1 ES1CEL~1.RMA
ES1CEL~2.RMA TT5 True 0.092 0.555 2 0.580 1 ES1CEL~2.RMA
ES1CEL~1.RMA TT8 True 0.129 0.164 1 0.779 1 ES1CEL~1.RMA
ES1CEL~3.RMA TT8 False 0.036 1.734 3 0.760 1 ES1CEL~3.RMA
ES1CEL~4.RMA TT8 True 0.061 1.828 4 0.555 1 ES1CEL~4.RMA
ES45A3~1.RMA TT8 True 0.491 0.842 5 0.823 1 ES45A3~1.RMA
ES49A3~1.RMA TT8 False 0.041 2.221 6 0.716 1 ES49A3~1.RMA
ES1: each unit, its best map, and for how many units that map is the best:
tetrode cluster map_name r units_whose_best_it_is
TT0 1 ES1CEL~1.RMA 0.566 1
TT0 2 ES1CEL~2.RMA 0.277 1
TT10 1 ES1CEL~2.RMA 0.009 2
TT10 2 ES1CEL~2.RMA 0.861 2
TT10 3 ES1CEL~3.RMA 0.893 1
TT3 1 ES1CEL~1.RMA 0.821 1
TT3 2 ES1CEL~2.RMA 0.857 1
TT3 3 ES1CEL~3.RMA 0.544 1
TT4 1 ES1CEL~1.RMA 0.926 1
TT4 2 ES1CEL~2.RMA 0.832 1
TT4 3 ES1CEL~3.RMA 0.870 1
TT5 1 ES1CEL~1.RMA 0.966 1
TT5 2 ES1CEL~2.RMA 0.580 1
TT8 1 ES1CEL~1.RMA 0.779 1
TT8 2 ES1CEL~3.RMA 0.059 2
TT8 3 ES1CEL~3.RMA 0.760 2
TT8 4 ES1CEL~4.RMA 0.555 1
TT8 5 ES45A3~1.RMA 0.823 1
TT8 6 ES49A3~1.RMA 0.716 1
ES1: units tied to two or more maps: []; maps that are the best of two or more units: [('TT10', 'ES1CEL~2.RMA'), ('TT8', 'ES1CEL~3.RMA')]
ES2: 23 distinct maps, 23 with a unit of their tetrode. Each map, the unit it best matches, and how many maps that unit is tied to:
map_name tetrode active mean_rate info cluster r maps_tied_to_unit unit_best_map
ES2CEL~1.RMA TT0 True 0.079 1.768 1 0.667 1 ES2CEL~1.RMA
ES2CEL~2.RMA TT0 True 0.146 1.480 2 0.648 1 ES2CEL~2.RMA
ES2CEL~1.RMA TT10 True 0.107 3.670 6 0.561 2 ES2CEL~1.RMA
ES2CEL~2.RMA TT10 True 0.237 1.242 2 0.802 1 ES2CEL~2.RMA
ES2CEL~3.RMA TT10 True 0.325 1.248 3 0.732 1 ES2CEL~3.RMA
ES2CEL~4.RMA TT10 False 0.023 0.286 4 0.595 1 ES2CEL~4.RMA
ESC5A3~1.RMA TT10 False 0.032 2.077 5 0.508 1 ESC5A3~1.RMA
ESC9A3~1.RMA TT10 False 0.006 2.035 6 0.558 2 ES2CEL~1.RMA
ES2CEL~1.RMA TT11 True 0.304 1.189 1 0.919 1 ES2CEL~1.RMA
ES2CEL~1.RMA TT3 True 0.159 1.034 1 0.811 1 ES2CEL~1.RMA
ES2CEL~2.RMA TT3 True 0.059 1.311 2 0.791 1 ES2CEL~2.RMA
ES2CEL~3.RMA TT3 True 0.059 0.517 3 0.589 1 ES2CEL~3.RMA
ES2CEL~1.RMA TT4 True 0.142 3.079 1 0.864 1 ES2CEL~1.RMA
ES2CEL~2.RMA TT4 False 0.014 0.786 3 0.469 2 ES2CEL~3.RMA
ES2CEL~3.RMA TT4 True 0.159 1.059 3 0.594 2 ES2CEL~3.RMA
ES2CEL~1.RMA TT5 True 0.739 1.214 1 0.946 1 ES2CEL~1.RMA
ES2CEL~2.RMA TT5 True 0.261 0.975 2 0.541 2 ES2CEL~2.RMA
ES2CEL~3.RMA TT5 True 0.118 0.845 2 0.447 2 ES2CEL~2.RMA
ES2CEL~1.RMA TT8 True 0.141 0.589 5 0.467 2 ES2CEL~4.RMA
ES2CEL~2.RMA TT8 True 0.178 1.451 3 0.470 1 ES2CEL~2.RMA
ES2CEL~3.RMA TT8 True 0.196 1.318 4 0.796 1 ES2CEL~3.RMA
ES2CEL~4.RMA TT8 True 0.277 0.891 5 0.815 2 ES2CEL~4.RMA
ESC9A3~1.RMA TT8 True 0.101 1.409 6 0.754 1 ESC9A3~1.RMA
ES2: each unit, its best map, and for how many units that map is the best:
tetrode cluster map_name r units_whose_best_it_is
TT0 1 ES2CEL~1.RMA 0.667 1
TT0 2 ES2CEL~2.RMA 0.648 1
TT10 1 ES2CEL~1.RMA 0.224 2
TT10 2 ES2CEL~2.RMA 0.802 1
TT10 3 ES2CEL~3.RMA 0.732 1
TT10 4 ES2CEL~4.RMA 0.595 1
TT10 5 ESC5A3~1.RMA 0.508 1
TT10 6 ES2CEL~1.RMA 0.561 2
TT11 1 ES2CEL~1.RMA 0.919 1
TT3 1 ES2CEL~1.RMA 0.811 1
TT3 2 ES2CEL~2.RMA 0.791 1
TT3 3 ES2CEL~3.RMA 0.589 1
TT4 1 ES2CEL~1.RMA 0.864 1
TT4 2 ES2CEL~2.RMA 0.435 1
TT4 3 ES2CEL~3.RMA 0.594 1
TT5 1 ES2CEL~1.RMA 0.946 1
TT5 2 ES2CEL~2.RMA 0.541 1
TT5 3 ES2CEL~3.RMA 0.446 1
TT8 1 ES2CEL~1.RMA 0.457 1
TT8 3 ES2CEL~2.RMA 0.470 1
TT8 4 ES2CEL~3.RMA 0.796 1
TT8 5 ES2CEL~4.RMA 0.815 1
TT8 6 ESC9A3~1.RMA 0.754 1
ES2: units tied to two or more maps: [('TT10', 6), ('TT4', 3), ('TT5', 2), ('TT8', 5)]; maps that are the best of two or more units: [('TT10', 'ES2CEL~1.RMA')]
Active session 2 maps: 11; their units tied to two or more session 2 maps: 0, to two or more session 3 maps: 3
Each map tied to one unit and no unit to two maps, in both sessions: False
The condition does not hold, so, as the criterion says, M-a is not run for this claim.
Units below 0.05 Hz in session 2 and active in session 3: 4; their spikes in session 2: [22, 46, 0, 0], in session 3: [491, 190, 986, 368]
Units active in session 2 and silent (no spike) in session 3: 0
Readings judged: ['M-b'] ; more than half keep their field, per reading: {'M-b (a)': False, 'M-b (b)': False}
By the criterion's rule: not_reproduced
Result: not reproduced. Under M-b, the one reading that can be run, 6 of 16 active session 2
cells keep their field by reading (a) (same-cell correlations -0.024 to 0.366 against a 95th
percentile of 0.094 over 240 different-cell pairs) and 1 of 16 by reading (b), neither more than
half (8). M-a is not run: tied to the units they best match, the session 3 maps leave four units
tied to two maps each (TT10 cluster 6, TT4 cluster 3, TT5 cluster 2 and TT8 cluster 5), three of
them units of active session 2 maps, so the criterion's condition fails. In session 2 no unit is
tied to two maps, though two maps are each the best map of two units (TT10 ES1CEL~2 and TT8
ES1CEL~3, the second unit matching at r 0.009 and 0.059). Reading (b) under M-b gives 2, 1, 0 and 0 cells at field thresholds of 0.1,
0.2, 0.3 and 0.5. Session 2 has 16 active units and 11 active deposited maps, not the paper's 7;
the 11 maps are listed above with their mean rates (0.061 to 2.500 Hz) and information, every one
with cell_number -1, and nothing in the files marks which 7 the paper showed. Four units below
0.05 Hz in session 2 are active in session 3 (two of them silent in session 2), which fits the
legend's cells that gained a field; no active cell of session 2 is silent in session 3.
What changed after round 1 of verification. The earlier version tied each unit to its best map and,
when two units chose one map, kept the better match without flagging it, so it did not test the
criterion's condition; it found the pairing one to one, judged M-a, and its reading (a) (6 of 11)
kept the claim blocked. With the condition tested as the criterion states it, M-a is not run,
both readings run fail, and the claim is not reproduced. The answers of 2026-10-20 had made "most"
more than half of the active cells, under which M-b fails under (a) and (b). In
001754-knierim-2000-02 this claim was reproduced by a median correlation of smoothed maps, a
criterion the paper does not give.
direction-selectivity¶
Some cells had place fields only when the rat moved counterclockwise through a location, showing the direction selectivity seen on such tracks on Earth.
Blocked: methods underspecified. The criterion forms, for each bin, a counterclockwise rate and the clockwise spike count expected from it, which need the time the rat spent in the bin moving in each direction. The dandiset gives position only at spike times, irregularly (7.6 to 35.7 samples per second in the ES epochs, with gaps up to 12.7 s, printed below), and one occupancy map per epoch with no direction. Nothing in the files or the conversion code gives the track's layout in the image, from which the direction along the circuit could be read. The claims file names no outside data for this claim, and in the NASA record (below) no entry of its zip files has a position-like name, though the spike files (.cel) and the archives nested in its processed files were not opened, so whether it holds continuous position is not known. The answer of 2026-10-20 says the paper gives no method, no time base by direction and no track layout, and allows no reading without them, so the criterion now blocks the claim as methods_underspecified when the data lack these. Before the answer the attempt blocked it as data_missing; the claim is untested in both cases.
# What the criterion's rates per direction would need: time spent in each bin in each direction. The
# position is sampled only at spike times; the gaps between samples in the flight ES windows, and the
# occupancy maps, one per epoch, with no direction.
rows = []
for s in sessions:
if "xy" not in s:
continue
for a, b, name in s["epochs"][s["epochs"].type == "ES"][["start", "stop", "name"]].itertuples(index=False):
dt = np.diff(s["t"][(s["t"] >= a) & (s["t"] <= b)])
rows.append({"session": nl.label(s), "epoch": name, "samples_per_s": round((len(dt) + 1) / (b - a), 1), "median_gap_s": round(float(np.median(dt)), 4),
"gaps_over_1s": int((dt > 1).sum()), "longest_gap_s": round(float(dt.max()), 1),
"occupancy_maps_for_epoch": len({s["occupancy_maps"][i].tobytes() for i in s["maps"].index[s["maps"].epoch == name]})})
print(pd.DataFrame(rows).to_string(index=False))
session epoch samples_per_s median_gap_s gaps_over_1s longest_gap_s occupancy_maps_for_epoch Rat1 FD4 ES1 10.9 0.040 11 1.9 1 Rat1 FD9 ES1 35.7 0.020 21 10.1 1 Rat2 FD4 ES1 10.0 0.020 118 3.0 1 Rat2 FD9 ES1 7.7 0.030 469 12.7 1 Rat3 FD4 ES1 12.3 0.023 136 6.0 1 Rat3 FD4 ES2 7.6 0.040 131 10.0 1
theta-normal and ripples-normal¶
Hippocampal EEG showed normal theta rhythm during active locomotion, in all rats on flight days 4 and 9.
Hippocampal EEG showed normal sharp waves and ripples during quiet inactivity in the sleeping pouch, on both flight days.
Blocked: data missing. No file holds EEG or any continuous voltage (data check: no ElectricalSeries or LFP in any file). The NASA record of the study (OSD-968) has no passage on EEG, theta, ripples or LFP, and none of the names inside its zip files suggests EEG (below), so the EEG is not public there either, as far as its names show; the zip files were not opened beyond their directories.
# The NASA record of the study (OSD-968): what its metadata says of EEG and of the cameras, its files, and
# the names inside each zip file (read from each zip's directory, by range requests; nothing is saved).
osd_text = json.dumps(requests.get("https://osdr.nasa.gov/osdr/data/osd/meta/968", timeout=60).json(), ensure_ascii=False)
for word in ["EEG", "theta", "ripple", "LFP", "camera"]:
hits = [m.group(0) for m in re.finditer(r"[^.\"]{0,150}" + re.escape(word) + r"[^\"]{0,300}", osd_text)]
print(f"OSD-968 metadata, '{word}': {len(hits)} passage(s)")
for h in hits[:1]:
print(" ", h[:450])
osd_files = requests.get("https://osdr.nasa.gov/osdr/data/osd/files/968", timeout=60).json()["studies"]["OSD-968"]["study_files"]
print(f"\nOSD-968 files ({len(osd_files)}):")
eeg_like = re.compile(r"eeg|lfp|csc|theta|ripple|\.ncs$|\.eeg$", re.I)
pos_like = re.compile(r"pos|track|video|vt1|\.nvt$|\.pvd$|coord|xy", re.I)
for f in osd_files:
line = f" {f['file_name']} {f['file_size'] / 1e6:.1f} MB"
if f["file_name"].endswith(".zip"):
url = requests.head("https://osdr.nasa.gov" + f["remote_url"], allow_redirects=True, timeout=60).url
names = [n for n in zipfile.ZipFile(remfile.File(url)).namelist() if "__MACOSX" not in n]
line += f"; {len(names)} entries, {sum(bool(eeg_like.search(n)) for n in names)} with an EEG-like name"
pos_names = [n for n in names if pos_like.search(n.rsplit("/", 1)[-1])]
exts = sorted({n.rsplit(".", 1)[-1].lower() for n in names if "." in n.rsplit("/", 1)[-1]})
line += f", {len(pos_names)} with a position-like name {pos_names[:3]}; extensions {exts}"
print(line)
OSD-968 metadata, 'EEG': 0 passage(s)
OSD-968 metadata, 'theta': 0 passage(s)
OSD-968 metadata, 'ripple': 0 passage(s)
OSD-968 metadata, 'LFP': 0 passage(s)
OSD-968 metadata, 'camera': 1 passage(s)
Position and orientation were recorded using two synchronized cameras integrated with the Data Acquisition System (DAS). Neural activity from chronically implanted tetrodes in the hippocampus was recorded simultaneously during behavioral navigation.
OSD-968 files (27):
OSD-968_metadata_OSD-968-ISA.zip 0.0 MB; 3 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['txt']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_PREFLI3.zip 1.5 MB; 109 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_PREFLI2.zip 1.1 MB; 73 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_PREFLI1.zip 0.8 MB; 80 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_FD9RAT2.zip 2.1 MB; 193 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_FD9RAT1.zip 8.6 MB; 157 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_FD4RAT3.zip 2.1 MB; 98 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_FD4RAT2.zip 1.4 MB; 208 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original_Raw_FD4RAT1.zip 1.7 MB; 215 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'csv', 'ds_store', 'log']
LSDS-173_Extracellular-Electrophysiology-Recording_Original Processed Files.zip 272.3 MB; 751 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['app', 'docx', 'ds_store', 'fp5', 'log', 'mcd', 'rma', 'sitx', 'xlsx', 'zip']
LSDS-173_Extracellular-Electrophysiology-Recording_PREFLI3.nwb.zip 0.7 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_PREFLI2.nwb.zip 0.5 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_PREFLI1.nwb.zip 0.4 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb'] LSDS-173_Extracellular-Electrophysiology-Recording_NWB_Conversion_Notes.docx 0.0 MB
LSDS-173_Extracellular-Electrophysiology-Recording_FD9RAT2.nwb.zip 0.8 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_FD9RAT1.nwb.zip 1.9 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_FD4RAT3.nwb.zip 0.7 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_FD4RAT2.nwb.zip 0.6 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_FD4RAT1.nwb.zip 0.6 MB; 1 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['nwb']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_PREFLI3.zip 2.4 MB; 109 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['ds_store', 'xls', 'xls (no data)']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_PREFLI2.zip 3.0 MB; 76 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['ds_store', 'xls']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_PREFLI1.zip 1.2 MB; 80 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['ds_store', 'xls']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_FD9RAT2.zip 3.6 MB; 193 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['ds_store', 'xls']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_FD9RAT1.zip 14.2 MB; 156 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel', 'ds_store', 'xls']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_FD4RAT3.zip 3.7 MB; 98 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['ds_store', 'xls', 'xls(no data)']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_FD4RAT2.zip 2.6 MB; 221 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel(no data)', 'ds_store', 'xls', 'xls(no data)']
LSDS-173_Extracellular-Electrophysiology-Recording_Converted_Raw_FD4RAT1.zip 3.3 MB; 212 entries, 0 with an EEG-like name, 0 with a position-like name []; extensions ['cel (no data)', 'cel(no data)', 'ds_store', 'x', 'xls', 'xls(no data)']
three-orthogonal-surfaces¶
Hippocampal cells formed distinct, reliable representations of position on the three orthogonal surfaces of the Escher staircase in microgravity.
Part (i) needs flight-spatial-code and fig3-stability-across-sessions reproduced; the criterion
gives not reproduced when either is not reproduced, whatever part (ii) gives. Part (ii) could not be tested in any case: it needs the
track's three surfaces drawn in the position frame, and the dandiset holds one two-dimensional pixel
position with no camera view, track layout or pixel scale (data check), while the NASA record says
position was recorded with two synchronised cameras (printed above). The conversion code that made
these files, read by the first attempt at commit b538d9e, describes the ES track only as a
three-dimensional track with 90 degree turns and the position as pixel coordinates (lines below).
repo, commit = "catalystneuro/mcnaughton2000-to-nwb", "b538d9e07079394362a64e5118a226e91bdd7ab5"
tree = requests.get(f"https://api.github.com/repos/{repo}/git/trees/{commit}?recursive=1", timeout=60).json().get("tree", [])
paths = [t["path"] for t in tree if t["type"] == "blob" and t["path"].endswith((".py", ".md"))]
print(f"Conversion code at {commit[:7]} ({len(paths)} .py and .md files), every line on the track, camera, surfaces or coordinates:")
for path in paths:
text = requests.get(f"https://raw.githubusercontent.com/{repo}/{commit}/{path}", timeout=60).text
for n, line in enumerate(text.splitlines(), 1):
if re.search(r"camera|surface|staircase|layout|geometry|reference_frame|coordinate", line, re.I):
print(f" {path}:{n}: {line.strip()[:160]}")
Conversion code at b538d9e (8 .py and .md files), every line on the track, camera, surfaces or coordinates:
README.md:7: This converts data from the Neurolab Space Shuttle mission (April 1998), in which hippocampal CA1 place cells were recorded from three freely moving rats in mic README.md:48: - **Epochs** — task intervals (Escher Staircase, Magic Carpet, Baseline) README.md:49: - **Position** — video-tracked x/y coordinates (flight sessions only)
conversion_notes.md:43: - **ES**: Escher Staircase — 3D track with 90° yaw/pitch turns conversion_notes.md:63: - Coordinate system for position tracking (pixel coordinates, exact camera resolution unknown)
src/mcnaughton2000_to_nwb/convert_session.py:4: with interleaved task epochs (BL=Baseline, ES=Escher Staircase, MC=Magic Carpet).
src/mcnaughton2000_to_nwb/convert_session.py:159: epochs.add_column("session_type", "Task type: BL (Baseline), ES (Escher Staircase), or MC (Magic Carpet)")
src/mcnaughton2000_to_nwb/convert_session.py:269: reference_frame="Video tracker pixel coordinates",
src/mcnaughton2000_to_nwb/metadata.py:28: "to traverse a three-dimensional track ('Escher staircase') and a flat "
src/mcnaughton2000_to_nwb/metadata.py:44: "ES": "Escher Staircase — three-dimensional track with 90° yaw and pitch turns",
src/mcnaughton2000_to_nwb/metadata.py:66: session_description="Flight Day 4 recording — Rat 1. Escher Staircase and Magic Carpet tasks with baseline sessions.",
src/mcnaughton2000_to_nwb/metadata.py:81: session_description="Flight Day 9 recording — Rat 1. Escher Staircase and Magic Carpet tasks with baseline sessions.",
# The criterion's rule from part (i): not reproduced when either dependency is not reproduced, whatever
# part (ii) gives; otherwise part (ii) is needed, and it cannot be tested here (above).
deps_tos = {"flight-spatial-code": outcome_flight, "fig3-stability-across-sessions": outcome_fig3}
print("Dependencies:", deps_tos)
outcome_tos = "not_reproduced" if "not_reproduced" in deps_tos.values() else "blocked (methods_underspecified)"
print("By the criterion's rule:", outcome_tos)
Dependencies: {'flight-spatial-code': 'blocked (methods_underspecified)', 'fig3-stability-across-sessions': 'not_reproduced'}
By the criterion's rule: not_reproduced
Result: not reproduced. fig3-stability-across-sessions is not reproduced, so part (i) fails,
and the criterion gives not reproduced whatever part (ii) gives. flight-spatial-code is blocked
as methods underspecified and part (ii) could not be tested (above), but neither can change this
outcome. Before round 1 of verification the stability claim was blocked, and so was this claim.
Summary¶
print(f"Run time: {(time.time() - started) / 60:.1f} minutes")
Run time: 1.3 minutes
| Claim | Outcome | In 001754-knierim-2000-02 |
|---|---|---|
flight-spatial-code |
Blocked: methods underspecified | Not reproduced (shuffle test) |
si-comparable-to-preflight |
Partially reproduced | Reproduced (removed from that record later) |
si-rat1-fd9-vs-preflight |
Blocked: methods underspecified | New claim |
si-rat3-fd4-vs-preflight |
Reproduced | New claim |
fd4-abnormal-selectivity |
Partially reproduced | Reproduced (removed from that record later) |
fig3-stability-across-sessions |
Not reproduced | Reproduced |
direction-selectivity |
Blocked: methods underspecified | New claim |
theta-normal |
Blocked: data missing | Blocked: data missing (removed later) |
ripples-normal |
Blocked: data missing | Blocked: data missing (removed later) |
three-orthogonal-surfaces |
Not reproduced | Blocked: metadata insufficient |
A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.
How the analysis differs from the paper's. The paper gives no bin size, smoothing or occupancy rule;
the deposited maps (64 x 64, smoothed with an unstated kernel) are used as the authors' maps (M-a),
and recomputed maps (M-b) can only be made for flight, with the deposited occupancy. M-a counts a map
as active by its own mean rate, as maps cannot be linked to units. The active-cell counts differ
from the paper's in most comparisons (for example 11 against 7 for rat 3 in session 2, 38 against
24 for its preflight ES maps), so the cells are not the paper's set, and the spatial information of
the deposited maps is above the paper's means for rat 2 and for rat 1's FD9 and preflight ES maps.
Which preflight epoch the paper used is not stated, and the criterion runs both; for rat 1 the
choice decides the test. The stability claim's "most" is more than half of the active cells, which
here are 11 maps or 16 units, not 7. Its M-a reading is not run, as tying the deposited maps to
the units they best match is not one to one in session 3, so it rests on unsmoothed recomputed
maps (M-b) alone, with few spikes per 4-pixel bin. Maps with cell_number -1 are counted in M-a;
in flight they behave as one cell's maps, and leaving them out changes no test.
The answers of 2026-10-20 changed one outcome's reason: direction-selectivity became blocked as
methods underspecified (before, data missing). After round 1 of verification, the stability
claim's M-a pairing check was corrected to test the criterion's condition; the condition fails, M-a
is not run, and fig3-stability-across-sessions is now not reproduced (before, blocked because the
M-a reading (a) passed and the three others failed). three-orthogonal-surfaces follows it, from
blocked to not reproduced. No other outcome changed.
What was found about the dandiset (version 0.260728.1352). The Rat1 and Rat2 files of each flight
day hold one tracker's trace, presented as each rat's position; it behaves as Rat 2's position in
Rat 2's FD9 ES window and could not be attributed in Rat 2's FD4 ES window. Position is given only
at spike times, none preflight. Rate maps cannot be linked to units: cell_number names the best
match for 84 of 211 unit-epochs and is -1 for every Rat3 FD4 ES map, and for maps elsewhere that
behave as one cell's maps. The maps are smoothed with an
unstated kernel, use -999 for unvisited bins and count the lost position in bin [63, 0], without
saying so, and six groups of maps are stored twice or more. The dandiset description reports EEG,
which no file holds, and places the preflight recordings on a rectangular track while the files
label them ES and MC; the files' experiment description puts the rectangular track in the baseline
sessions, which the paper places in the sleeping pouch. Rat 2's preflight and flight units share no
tetrode. Ages, pixel scale and camera view are missing. The draft fixes several of these; it is not
published.