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
In [1]:
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.

In [2]:
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.

In [3]:
# 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.

In [4]:
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.

In [5]:
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).

In [6]:
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.

In [7]:
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']
In [8]:
# 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
In [9]:
# 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.

In [10]:
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.

In [11]:
# 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.

In [12]:
# 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.

In [13]:
# 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.
In [14]:
# 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.

In [15]:
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.

In [16]:
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).

In [17]:
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
In [18]:
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))
No description has been provided for this image
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.

In [19]:
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.

In [20]:
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.

In [21]:
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}
In [22]:
# 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.

In [23]:
# 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.

In [24]:
# 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).

In [25]:
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.",
In [26]:
# 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¶

In [27]:
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.