Reproducing Chandravadia et al. (2020) from dandiset 000004¶
Paper. Chandravadia N, Liang D, Schjetnan AGP, Carlson A, Faraut M, Chung JM, Reed CM, Dichter B, Maoz U, Kalia SK, Valiante TA, Mamelak AN, Rutishauser U (2020). A NWB-based dataset and processing pipeline of human single-neuron activity during a declarative memory task. Scientific Data 7, 78. doi:10.1038/s41597-020-0415-9. The article is open access (CC BY 4.0); this attempt used its full text, including Tables 1 and 2 and Online-only Table 1.
Dandiset. DANDI:000004, version 0.220126.1852. The paper cites no DANDI version: its data statement points to the Open Science Framework (doi:10.17605/OSF.IO/HV7JA), and the dandiset was created on 2020-03-16, after the paper appeared. The archive has two published versions, 0.220126.1851 and 0.220126.1852, published a minute apart with the same 87 assets (checked in the first data cell) and metadata that differs only in version fields. The most recent is pinned. The draft holds the same files.
What the dandiset holds. One NWB file per session: 87 sessions of 59 subjects, with the trials table, the TTL event stream, the stimulus images, the spike-sorted units with mean waveforms, and the electrodes table. There are no raw broadband recordings.
Code read. rutishauserlab/recogmem-release-NWB
at commit 1d95162 (2025-01-14): the Python and MATLAB export and analysis code, the session list
defineNOsessions_release.ini, and plotFigure5.ipynb. The session list and the demo script
python/export/demo/export.py are fetched from that commit where the notebook relies on them, and
the lines used are printed. The code settles what the Methods leave
open (the AUC, the z-ROC, the high/low confidence split, the label coding). Where it disagrees with
the Methods (the analysis window, and correct trials only for memory selectivity), the Methods give
the main result and the code's choice is shown as a variant. plotFigure5.ipynb writes the Figure
5f percentages into the plot as constants, so the code does not show how they were computed.
Outside inputs. Online-only Table 1 of the paper is copied into the helper module as text, to compare with the files' subject metadata. Table 1 of Faraut et al. 2018 (Scientific Data 5, 180010, doi:10.1038/sdata.2018.10), the paper describing the earlier release, is copied the same way; it lists that release's sessions and is used only to mark which sessions of the dandiset are new (previous-release-link and previous-release-additions).
| Claim | Status |
|---|---|
| counts-neurons-subjects: 1863 neurons, 59 subjects | tested |
| counts-sessions: 87 sessions of Online-only Table 1 | tested |
| table1-demographics: age and sex of every subject | tested |
| task-structure: 100 encoding and 100 recognition trials, 5 categories | tested |
| new-old-label-coding: label 0 is old, 1 is new | tested |
| ttl-codes: event codes of Table 1 | tested |
| experiment-ids: block ids of Table 2 | tested |
| session-dates: day set to the first of the month | tested |
| units-table: columns, 100 kHz waveforms, one electrode per unit | tested |
| electrodes-table: area, coordinates, impedance, filter, channel | tested |
| isi-criterion: fewer than 3% of ISIs below 3 ms | tested |
| fig5a-mtl-locations: all units in amygdala or hippocampus | tested |
| fig5c-auc-all: AUC 0.74 ± 0.10 | tested |
| fig5c-auc-sites: AUC 0.74 ± 0.09 (CS) and 0.69 ± 0.13 (TWH) | tested |
| fig5b-roc-asymmetric: asymmetric ROC | tested |
| fig5d-confidence-accuracy: high confidence more accurate | tested |
| vs-cells-proportion: 343 VS cells (18.4%) | tested |
| ms-cells-proportion: 146 MS cells (7.8%) | tested |
| dual-cells-proportion: 26 dual cells (1.4%) | tested |
| fig5f-site-proportions: comparable proportions at CS and TWH | tested |
| twh-sessions: 9 TWH sessions | tested |
| fig4-example-vs-neuron: neuron 132-177-4-RA | tested |
| previous-release-link: earlier release's sessions found here | tested (with outside input) |
| previous-release-additions: 23 sessions, 17 subjects, 288 neurons added | tested (with outside input) |
| fig3-example-vs-neuron: neuron 144-140-3-RH | blocked (metadata insufficient) |
import time
import warnings
from itertools import combinations
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
import recogmem as rm
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 200)
warnings.filterwarnings("ignore", category=RuntimeWarning)
started = time.time()
sessions = rm.load_all()
by_id = {s["session"]: s for s in sessions}
print(f"loaded {len(sessions)} files from dandiset {rm.DANDISET_ID} version {rm.DANDISET_VERSION} "
f"in {time.time() - started:.0f} s")
loaded 87 files from dandiset 000004 version 0.220126.1852 in 72 s
Data check¶
Each check of the metadata against the values is stated under its output.
info = pd.DataFrame([{
"session": s["session"], "label": s["label"], "subject": s["subject_id"], "site": s["site"],
"institution": s["institution"], "start": s["session_start_time"][:10], "age": s["age"], "sex": s["sex"],
"trials": len(s["trials"]), "learn": int((s["trials"].stim_phase == "learn").sum()),
"recog": int((s["trials"].stim_phase == "recog").sum()), "units": len(s["spikes"]),
"electrodes": len(s["electrodes"]), "categories": s["trials"].stimCategory.nunique(),
"experiment_ids": tuple(int(x) for x in np.unique(s["experiment_ids"])),
"data_collection": s["data_collection"]} for s in sessions])
print("Files:", len(info), " subjects:", info.subject.nunique(), " species:", sorted({s["species"] for s in sessions}))
print("\nBy site (CS = subjects labelled H or C, TWH = labelled T):")
print(info.groupby("site").agg(sessions=("session", "size"), subjects=("subject", "nunique"), units=("units", "sum")).to_string())
print("\nInstitution strings:", info.institution.value_counts().to_dict())
print("Devices:", pd.Series([d for s in sessions for d in s["devices"]]).value_counts().to_dict())
print("\nTrials per file (learn, recog):", info.groupby(["learn", "recog"]).size().to_dict())
print("Units per file: min", info.units.min(), "median", info.units.median(), "max", info.units.max(), "total", info.units.sum())
print("Is there a session 144 (Figure 3 example)?", 144 in by_id)
client = rm.DandiAPIClient()
versions = {}
for v in ("0.220126.1851", "0.220126.1852", "draft"):
versions[v] = {a.path: (a.identifier, a.size) for a in client.get_dandiset(rm.DANDISET_ID, v).get_assets()}
print("Published versions 0.220126.1851 and 0.220126.1852 hold the same assets (ids and sizes):", versions["0.220126.1851"] == versions["0.220126.1852"])
print("The draft holds the same paths and sizes as the pinned version:",
{p: s for p, (_, s) in versions["draft"].items()} == {p: s for p, (_, s) in versions["0.220126.1852"].items()})
print()
print(info.drop(columns=["institution", "data_collection"]).to_string(index=False))
Files: 87 subjects: 59 species: ['Human']
By site (CS = subjects labelled H or C, TWH = labelled T):
sessions subjects units
site
CS 78 51 1787
TWH 9 8 77
Institution strings: {'Cedars-Sinai Medical Center': 44, 'Hunigton Memorial Hospital': 28, 'Toronto Western Hospital': 9, 'Hunington Memorial Hospital': 6}
Devices: {'Neuralynx-Atlas': 53, 'Neuralynx-cheetah': 29, 'Neuralynx-Cheetah': 5}
Trials per file (learn, recog): {(50, 100): 7, (97, 100): 1, (100, 100): 79}
Units per file: min 1 median 18.0 max 64 total 1864
Is there a session 144 (Figure 3 example)? False
Published versions 0.220126.1851 and 0.220126.1852 hold the same assets (ids and sizes): True
The draft holds the same paths and sizes as the pinned version: True
session label subject site start age sex trials learn recog units electrodes categories experiment_ids
5 H09 P9HMH CS 2006-03-01 55 M 200 100 100 24 14 5 (80, 81)
6 H09 P9HMH CS 2006-03-01 55 M 200 100 100 19 13 5 (83, 84)
7 H10 P10HMH CS 2006-09-01 37 M 200 100 100 38 18 5 (80, 81)
9 H11 P11HMH CS 2006-11-01 16 M 200 100 100 25 9 5 (80, 81)
17 H14 P14HMH CS 2007-06-01 31 M 200 100 100 23 9 5 (83, 84)
18 H14 P14HMH CS 2007-06-01 31 M 150 50 100 33 10 5 (80, 81)
20 H15 P15HMH CS 2007-09-01 45 M 150 50 100 20 11 5 (80, 81)
21 H15 P15HMH CS 2007-09-01 45 M 150 50 100 14 7 5 (83, 84)
23 H16 P16HMH CS 2007-10-01 34 F 150 50 100 18 9 5 (83, 84)
24 H16 P16HMH CS 2007-10-01 34 F 200 100 100 21 18 5 (80, 81)
26 H18 P18HMH CS 2008-06-01 40 M 150 50 100 33 16 5 (80, 81)
27 H19 P19HMH CS 2008-06-01 34 M 150 50 100 16 6 5 (83, 84)
28 H19 P19HMH CS 2008-06-01 34 M 200 100 100 15 7 5 (80, 81)
32 H17 P17HMH CS 2008-05-01 19 M 200 100 100 43 16 5 (80, 81)
38 H21 P21HMH CS 2009-01-01 20 M 200 100 100 20 8 5 (83, 84)
39 H21 P21HMH CS 2009-01-01 20 M 200 100 100 18 8 5 (80, 81)
41 H21 P21HMH CS 2009-01-01 20 M 200 100 100 15 6 5 (88, 89)
43 H23 P23HMH CS 2009-02-01 40 M 200 100 100 23 8 5 (88, 89)
44 H23 P23HMH CS 2009-02-01 40 M 150 50 100 23 8 5 (83, 84)
47 H23 P23HMH CS 2009-02-01 40 M 200 100 100 17 7 5 (80, 81)
48 H28 P28HMH CS 2010-03-13 22 M 200 100 100 45 19 5 (88, 89)
49 H29 P29HMH CS 2010-12-01 17 F 200 100 100 27 10 5 (88, 89)
50 H31 P31HMH CS 2011-04-01 30 M 200 100 100 14 7 5 (83, 84)
52 H33 P33HMH CS 2011-07-01 30 M 200 100 100 18 8 5 (80, 81)
54 H42 P42HMH CS 2013-03-01 29 M 200 100 100 32 13 5 (80, 81)
55 H42 P42HMH CS 2013-03-01 29 M 200 100 100 32 13 5 (80, 82)
56 H43 P43HMH CS 2013-04-01 27 F 200 100 100 15 6 5 (80, 81)
58 H27 P27HMH CS 2010-02-01 40 M 200 100 100 49 14 5 (83, 84)
59 CS24 P24CS CS 2012-09-01 57 F 200 100 100 21 7 5 (80, 81)
60 CS24 P24CS CS 2012-09-01 57 F 200 100 100 22 9 5 (83, 84)
61 CS25 P25CS CS 2012-09-01 36 F 200 100 100 8 6 5 (80, 81)
63 CS25 P25CS CS 2012-09-01 36 F 200 100 100 14 11 5 (83, 84)
64 CS26 P26CS CS 2012-12-01 56 F 200 100 100 33 9 5 (80, 81)
66 CS26 P26CS CS 2012-12-01 56 F 200 100 100 11 6 5 (83, 84)
67 CS27 P27CS CS 2013-01-01 44 M 200 100 100 15 8 5 (80, 81)
68 H44 P44HMH CS 2000-01-01 57 F 200 100 100 19 7 5 (80, 81)
69 CS29 P29CS CS 2013-10-01 19 M 200 100 100 64 17 5 (80, 81)
70 CS29 P29CS CS 2013-10-01 19 M 200 100 100 33 13 5 (80, 81)
73 CS31 P31CS CS 2014-02-01 32 M 200 100 100 4 2 5 (80, 81)
74 CS32 P32CS CS 2014-02-01 19 M 200 100 100 35 9 5 (80, 81)
76 CS33 P33CS CS 2014-03-01 44 F 200 100 100 53 14 5 (83, 84)
77 CS33 P33CS CS 2014-03-01 44 F 200 100 100 30 11 5 (81, 82)
78 CS33 P33CS CS 2014-03-01 44 F 200 100 100 13 9 5 (80, 81)
85 CS34 P34CS CS 2014-12-01 70 M 200 100 100 6 4 5 (83, 84)
92 H47 P47HMH CS 2014-06-01 20 M 200 100 100 7 3 5 (80, 81)
93 CS39 P39CS CS 2015-08-01 26 M 200 100 100 35 13 5 (80, 81)
96 CS37 P37CS CS 2015-03-01 33 F 200 100 100 13 8 5 (80, 81)
97 H47 P47HMH CS 2014-06-01 20 M 200 100 100 10 7 5 (80, 81)
99 H48 P48HMH CS 2014-12-01 32 F 200 100 100 30 20 5 (80, 81)
100 CS39 P39CS CS 2015-08-01 26 M 200 100 100 42 16 5 (82, 83)
101 CS40 P40CS CS 2016-01-01 25 M 200 100 100 14 10 5 (80, 81)
102 CS38 P38CS CS 2015-07-01 63 F 200 100 100 44 17 5 (80, 81)
104 H51 P51HMH CS 2016-02-01 24 M 200 100 100 42 19 5 (80, 81)
105 H51 P51HMH CS 2016-02-01 24 M 200 100 100 43 19 5 (82, 83)
111 CS42 P42CS CS 2016-08-01 25 F 200 100 100 14 10 5 (80, 81)
112 CS42 P42CS CS 2016-08-01 25 F 200 100 100 12 7 5 (80, 81)
113 CS43 P43CS CS 2016-11-01 42 F 200 100 100 21 14 5 (80, 81)
114 CS44 P44CS CS 2016-09-01 53 F 200 100 100 15 9 5 (80, 81)
115 CS47 P47CS CS 2017-02-01 32 M 200 100 100 35 17 5 (80, 81)
116 CS48 P48CS CS 2017-03-01 32 F 200 100 100 36 14 5 (80, 81)
117 CS49 P49CS CS 2017-05-01 24 F 200 100 100 11 7 5 (80, 81)
118 CS49 P49CS CS 2017-05-01 24 F 200 100 100 13 11 5 (80, 81)
119 CS51 P51CS CS 2017-07-01 17 M 200 100 100 16 10 5 (80, 81)
120 CS51 P51CS CS 2017-07-01 17 M 200 100 100 15 8 5 (80, 81)
122 CS53 P53CS CS 2017-11-01 60 M 200 100 100 27 15 5 (80, 81)
123 CS53 P53CS CS 2017-11-01 60 M 197 97 100 6 4 5 (80, 81)
124 CS54 P54CS CS 2018-01-01 59 F 200 100 100 43 17 5 (80, 81)
125 CS54 P54CS CS 2018-01-01 59 F 200 100 100 46 24 5 (80, 81)
126 CS54 P54CS CS 2018-01-01 59 F 200 100 100 34 15 5 (80, 81)
127 CS55 P55CS CS 2018-03-01 43 F 200 100 100 8 6 5 (80, 81)
128 CS56 P56CS CS 2018-04-01 48 M 200 100 100 7 7 5 (80, 81)
130 CS57 P57CS CS 2018-06-01 46 M 200 100 100 16 11 5 (80, 81)
131 CS57 P57CS CS 2018-06-01 46 M 200 100 100 11 9 5 (80, 81)
132 CS58 P58CS CS 2018-07-01 32 F 200 100 100 8 8 5 (80, 81)
133 CS58 P58CS CS 2018-07-01 32 F 200 100 100 2 2 5 (80, 81)
134 CS60 P60CS CS 2018-10-01 67 M 200 100 100 14 8 5 (80, 81)
135 CS61 P61CS CS 2019-02-01 52 F 200 100 100 13 12 5 (80, 81)
136 CS62 P62CS CS 2019-04-01 25 F 200 100 100 13 9 5 (80, 81)
2001 T88 TWH088 TWH 2018-03-01 26 M 200 100 100 9 7 5 (80, 81)
2002 T88 TWH088 TWH 2018-03-01 26 M 200 100 100 4 4 5 (80, 81)
2003 T90 TWH090 TWH 2018-04-01 20 M 200 100 100 5 4 5 (80, 81)
2004 T89 TWH089 TWH 2018-03-01 45 F 200 100 100 31 21 5 (80, 81)
2005 T98 TWH098 TWH 2018-09-01 46 F 200 100 100 13 11 5 (80, 81)
2007 T107 TWH107 TWH 2018-12-01 64 F 200 100 100 3 3 5 (80, 81)
2008 T100 TWH100 TWH 2018-10-01 19 F 200 100 100 1 1 5 (80, 81)
2009 T103 TWH103 TWH 2018-11-01 49 M 200 100 100 3 2 5 (80, 81)
2011 T101 TWH101 TWH 2018-10-01 25 F 200 100 100 8 8 5 (80, 81)
The archive's summary gives 59 subjects and 87 files, which the files confirm, and the two published versions hold the same assets. There are 1864 unit rows, one more than the paper's 1863. Eight files have fewer than 100 learning trials (seven with 50 and one with 97). The institution is written "Hunigton Memorial Hospital" in 28 files and "Hunington Memorial Hospital" in 6, and the device both "Neuralynx-cheetah" and "Neuralynx-Cheetah". There is no session 144, the session of the example neuron of Figure 3.
# Units and coding of the variables used: times, responses, labels, categories.
rows = []
for s in sessions:
t = s["trials"]
r = rm.recognition(s)
ev, et = s["events"], s["events_t"]
stim_on = et[ev == 1]
on_idx = np.flatnonzero(ev == 1)
resp_ev = np.array([ev[i + 3] if i + 3 < len(ev) else np.nan for i in on_idx])
sp = np.concatenate(s["spikes"])
rows.append({
"session": s["session"],
"stim_s_median": np.median(t.stim_off_time - t.stim_on_time),
"start_is_stim_on": bool(np.allclose(t.start_time, t.stim_on_time)),
"onsets_match_events": bool(len(stim_on) == len(t) and np.allclose(np.sort(stim_on), np.sort(t.stim_on_time.values))),
"resp_mismatch": int(np.sum(resp_ev != np.where(t.stim_phase == "recog", t.response_value, t.response_value + 20)))
if len(resp_ev) == len(t) else -1,
"unlisted_after_question": sorted({int(x) for x in resp_ev} - set(range(20, 22)) - set(range(31, 37))),
"resp_min": int(r.response.min()), "resp_max": int(r.response.max()),
"labels": tuple(sorted(r.label.unique())),
"spikes_from_s": sp.min() - t.start_time.min(), "spikes_to_s": sp.max() - t.stop_time.max(),
"trial_s_median": np.median(t.stop_time - t.start_time)})
units = pd.DataFrame(rows)
print("Stimulus duration (s), median per file: range", units.stim_s_median.min().round(3), "to", units.stim_s_median.max().round(3))
print("Trial duration, stimulus onset to end of trial (s), median per file:", units.trial_s_median.min().round(2), "to", units.trial_s_median.max().round(2))
print("start_time equals stim_on_time in every file:", units.start_is_stim_on.all())
print("Trial onsets equal the stimulus-onset events (TTL 1) in every file:", units.onsets_match_events.all())
print("Per trial, the event three after stimulus onset (the response the paper's MATLAB code reads) against response_value")
print("(recognition: 31-36; learning: response_value + 20 against 20/21): files with every trial equal:", int((units.resp_mismatch == 0).sum()), "of", len(units))
print(units[units.resp_mismatch != 0][["session", "resp_mismatch", "unlisted_after_question"]].to_string(index=False))
print("Recognition responses (response_value - 30) span", units.resp_min.min(), "to", units.resp_max.max(), "; labels:", sorted({x for l in units.labels for x in l}))
print("First spike relative to first trial onset (s): range", units.spikes_from_s.min().round(1), "to", units.spikes_from_s.max().round(1))
print("Last spike relative to last trial end (s): range", units.spikes_to_s.min().round(1), "to", units.spikes_to_s.max().round(1))
print("Files whose first spike is more than 60 s before the first trial:", len(units[units.spikes_from_s < -60]), units[units.spikes_from_s < -60].session.tolist())
print("\nDescription of new_old_labels_recog in the files:", repr(sessions[0]["trial_descriptions"]["new_old_labels_recog"]))
print("Learning response_value values:", sorted(set(np.concatenate([s["trials"][s["trials"].stim_phase == "learn"].response_value.unique() for s in sessions]))))
Stimulus duration (s), median per file: range 0.998 to 2.016
Trial duration, stimulus onset to end of trial (s), median per file: 2.87 to 5.81
start_time equals stim_on_time in every file: True
Trial onsets equal the stimulus-onset events (TTL 1) in every file: True
Per trial, the event three after stimulus onset (the response the paper's MATLAB code reads) against response_value
(recognition: 31-36; learning: response_value + 20 against 20/21): files with every trial equal: 81 of 87
session resp_mismatch unlisted_after_question
58 1 [23]
123 18 []
127 1 [3]
2001 6 [3]
2002 9 [3]
2003 4 [3]
Recognition responses (response_value - 30) span 1 to 6 ; labels: [np.int64(0), np.int64(1)]
First spike relative to first trial onset (s): range -2462.3 to 17.4
Last spike relative to last trial end (s): range -46.6 to 2055.1
Files whose first spike is more than 60 s before the first trial: 15 [6, 7, 9, 17, 18, 24, 26, 28, 39, 41, 43, 47, 52, 63, 85]
Description of new_old_labels_recog in the files: "The Ground truth Labels for New or Old Stimulus. 0 == Old Stimuli \n (presented during the learning phase), 1 = New Stimuli (not seen )'during learning phase"
Learning response_value values: [np.float64(0.0), np.float64(1.0)]
Times are in seconds on one clock per file: trial start times equal the stimulus-onset events and stimuli last a median of 1.0 to 2.0 s per file (the Methods give no duration). In 81 of 87 files the trials table's response_value equals, trial by trial, the response event the paper's MATLAB code reads; in the six others 1 to 18 trials differ, where extra codes (3 or 23) or a shifted sequence follow the question. The analysis takes responses from response_value minus 30, which spans 1 to 6. In learning trials the table stores 0 and 1 for the events 20 and 21. The column description of new_old_labels_recog says 0 is old; this is tested under new-old-label-coding. In 15 files spikes start more than 60 s before the first trial (up to 2462 s), so the units cover more than the task.
# Recordings deposited more than once: identical spike trains, trial times or event sequences.
pairs = []
for a, b in combinations(sessions, 2):
same_spikes = len(a["spikes"]) == len(b["spikes"]) and all(
len(x) == len(y) and np.array_equal(x, y) for x, y in zip(a["spikes"], b["spikes"]))
ta, tb = a["trials"], b["trials"]
n = min(len(ta), len(tb))
shared_trials = int(np.sum(np.isin(np.round(ta.start_time.values, 4), np.round(tb.start_time.values, 4))))
if same_spikes or shared_trials >= 10:
pairs.append({"a": a["session"], "b": b["session"], "units": len(a["spikes"]), "identical_spike_trains": same_spikes,
"shared_trial_onsets": shared_trials, "trials_a": len(ta), "trials_b": len(tb),
"exp_ids_a": tuple(int(x) for x in np.unique(a["experiment_ids"])),
"exp_ids_b": tuple(int(x) for x in np.unique(b["experiment_ids"])),
"a_trials_s": (round(ta.start_time.min()), round(ta.stop_time.max())),
"b_trials_s": (round(tb.start_time.min()), round(tb.stop_time.max())),
"spikes_s": (round(np.concatenate(a["spikes"]).min()), round(np.concatenate(a["spikes"]).max()))})
pairs = pd.DataFrame(pairs)
print(pairs.to_string(index=False))
DUPLICATE_SECOND = sorted(pairs[pairs.identical_spike_trains].b.tolist())
print("\nSessions whose units repeat another session's (dropped when each recording is counted once):", DUPLICATE_SECOND)
print("Units in them:", sum(len(by_id[k]["spikes"]) for k in DUPLICATE_SECOND))
same_pair = pairs[(pairs.a == 54) & (pairs.b == 55)]
r54, r55 = rm.recognition(by_id[54]), rm.recognition(by_id[55])
l54 = by_id[54]["trials"][by_id[54]["trials"].stim_phase == "learn"]
l55 = by_id[55]["trials"][by_id[55]["trials"].stim_phase == "learn"]
print("Sessions 54 and 55: learning trials identical:", l54.reset_index(drop=True).equals(l55.reset_index(drop=True)),
"; recognition onsets shared:", int(np.isin(r54.start_time.round(4), r55.start_time.round(4)).sum()),
"; recognition images shared:", len(set(r54.external_image_file) & set(r55.external_image_file)))
a b units identical_spike_trains shared_trial_onsets trials_a trials_b exp_ids_a exp_ids_b a_trials_s b_trials_s spikes_s 43 44 23 True 0 200 150 (88, 89) (83, 84) (3292, 4478) (1495, 3035) (1494, 4478) 54 55 32 True 100 200 200 (80, 81) (80, 82) (1611, 3626) (1611, 4525) (1610, 4525) Sessions whose units repeat another session's (dropped when each recording is counted once): [44, 55] Units in them: 55 Sessions 54 and 55: learning trials identical: True ; recognition onsets shared: 0 ; recognition images shared: 0
Two pairs of files hold identical spike trains for every unit. Sessions 43 and 44 (subject H23) are two different task blocks of one continuous recording: the spike trains span both blocks, session 44's trials come first and session 43's later. Sessions 54 and 55 (subject H42) share the same learning block, trial for trial, and differ in their recognition blocks (experiment ids 81 and 82), so session 55 is a second recognition test of the same encoding block, deposited with a copy of the learning trials and the same spike trains. In both pairs the units are the same neurons. The paper counts neurons per session, so its 1863 includes them twice; results are also given with sessions 44 and 55 dropped.
# Clock check: category selectivity of the units in 1 s windows shifted around stimulus onset, with
# the true labels and with the labels of the previous trial. A response to the stimulus can only
# appear after onset and with the true labels; an offset between spikes and trials would move it.
offsets = np.arange(-1.6, 1.61, 0.2)
frac_true, frac_shift = [], []
per_unit = []
for s in sessions:
r = rm.recognition(s)
on, cat = r.start_time.values, r.category.values
for k, sp in enumerate(s["spikes"]):
p_true = [rm.anova_p(rm.window_rates(sp, on, (o, o + 1.0)), cat) for o in offsets]
p_shift = [rm.anova_p(rm.window_rates(sp, on, (o, o + 1.0)), np.roll(cat, 1)) for o in offsets]
per_unit.append({"session": s["session"], "p_true": p_true, "p_shift": p_shift,
"p_pre": rm.anova_p(rm.window_rates(sp, on, (-1.0, 0.0)), cat),
"p_post": rm.anova_p(rm.window_rates(sp, on, rm.WINDOW_METHODS), cat)})
P = np.array([u["p_true"] for u in per_unit]); PS = np.array([u["p_shift"] for u in per_unit])
clock = pd.DataFrame({"window_start_s": offsets.round(1), "frac_p<0.05_true_labels": (P < 0.05).mean(0).round(3),
"frac_p<0.05_previous_trial_labels": (PS < 0.05).mean(0).round(3)})
print(clock.to_string(index=False))
pu = pd.DataFrame(per_unit)
ses = pu.groupby("session").agg(units=("p_post", "size"), sig_pre=("p_pre", lambda p: int((p < 0.05).sum())),
sig_post=("p_post", lambda p: int((p < 0.05).sum())))
ses["binom_p_post_vs_5pct"] = [stats.binomtest(k, n, 0.05, alternative="greater").pvalue for k, n in zip(ses.sig_post, ses.units)]
ses["established"] = (ses.binom_p_post_vs_5pct < 0.05) & (ses.sig_post > ses.sig_pre)
print("\nSessions where the stimulus response (more category-selective units after onset than expected by chance,")
print("and more than before onset) establishes the shared clock:", int(ses.established.sum()), "of", len(ses))
print("Not established (too few units or no excess):", ses[~ses.established].index.tolist())
print("Units in sessions not established:", int(ses[~ses.established].units.sum()))
fig, ax = plt.subplots(figsize=(6, 3))
ax.plot(offsets, (P < 0.05).mean(0), "o-", label="true labels")
ax.plot(offsets, (PS < 0.05).mean(0), "s-", label="previous trial's labels")
ax.axhline(0.05, color="k", lw=0.8)
ax.set_xlabel("window start relative to stimulus onset (s), 1 s windows")
ax.set_ylabel("fraction of units p < 0.05")
ax.legend(); plt.tight_layout(); plt.show()
lo_, hi_ = min((P < 0.05).mean(0).min(), (PS < 0.05).mean(0).min()), max((P < 0.05).mean(0).max(), (PS < 0.05).mean(0).max())
print(f"Plotted fractions range from {lo_:.3f} to {hi_:.3f}; none outside the axes.")
window_start_s frac_p<0.05_true_labels frac_p<0.05_previous_trial_labels
-1.6 0.047 0.055
-1.4 0.044 0.051
-1.2 0.041 0.055
-1.0 0.049 0.060
-0.8 0.056 0.056
-0.6 0.064 0.055
-0.4 0.101 0.048
-0.2 0.127 0.047
-0.0 0.164 0.056
0.2 0.178 0.047
0.4 0.167 0.054
0.6 0.141 0.048
0.8 0.119 0.056
1.0 0.106 0.054
1.2 0.085 0.050
1.4 0.079 0.047
1.6 0.070 0.039
Sessions where the stimulus response (more category-selective units after onset than expected by chance,
and more than before onset) establishes the shared clock: 38 of 87
Not established (too few units or no excess): [5, 6, 9, 17, 18, 20, 21, 24, 26, 27, 38, 39, 47, 50, 52, 55, 56, 61, 63, 64, 66, 67, 68, 78, 85, 92, 96, 97, 101, 111, 113, 114, 116, 117, 118, 120, 122, 125, 127, 128, 133, 135, 136, 2001, 2002, 2003, 2005, 2007, 2008]
Units in sessions not established: 801
Plotted fractions range from 0.039 to 0.178; none outside the axes.
With the true labels, the fraction of category-selective units is 4.1% to 4.9% for windows that end at or before stimulus onset (start at -1.0 s or earlier), rises once the window reaches past 200 ms after onset, peaks at 17.8% for the window starting at 0.2 s and declines after. With the labels of the previous trial it stays between 3.9% and 6.0% at every offset. Spikes and trials therefore share a clock overall. Per session this is shown for 38 of 87 sessions; the 49 others, listed above with 801 units, have too few units or too few selective ones to tell, and for them the shared clock is consistent with the data but assumed.
counts-neurons-subjects¶
The dataset holds 1863 single neurons recorded from the medial temporal lobes of 59 subjects.
n_units = int(info.units.sum())
n_units_once = int(info[~info.session.isin(DUPLICATE_SECOND)].units.sum())
n_subjects = info.subject.nunique()
print(f"(a) every unit row: {n_units} neurons (paper 1863, tolerance 1676.7 to 2049.3; difference {n_units - 1863:+d})")
print(f"(b) duplicated spike trains counted once: {n_units_once} neurons (difference {n_units_once - 1863:+d})")
print(f"subjects: {n_subjects} (paper 59)")
print("units by site:", info.groupby("site").units.sum().to_dict())
print("\nunits per site:", info.groupby("site").units.sum().to_dict())
print("units per session:")
print(info.set_index("session").units.to_frame().T.to_string())
fewest = sorted(((len(sp), s["session"], k) for s in sessions for k, sp in enumerate(s["spikes"])))[:5]
print("five units with the fewest spikes (spikes, session, unit):", fewest)
(a) every unit row: 1864 neurons (paper 1863, tolerance 1676.7 to 2049.3; difference +1)
(b) duplicated spike trains counted once: 1809 neurons (difference -54)
subjects: 59 (paper 59)
units by site: {'CS': 1787, 'TWH': 77}
units per site: {'CS': 1787, 'TWH': 77}
units per session:
session 5 6 7 9 17 18 20 21 23 24 26 27 28 32 38 39 41 43 44 47 48 49 50 52 54 55 56 58 59 60 61 63 64 66 67 68 69 70 73 74 76 77 78 85 92 93 96 97 99 100 101 102 104 105 111 112 113 114 115 116 117 118 119 120 122 123 124 125 126 127 128 130 131 132 133 134 135 136 2001 2002 2003 2004 2005 2007 2008 2009 2011
units 24 19 38 25 23 33 20 14 18 21 33 16 15 43 20 18 15 23 23 17 45 27 14 18 32 32 15 49 21 22 8 14 33 11 15 19 64 33 4 35 53 30 13 6 7 35 13 10 30 42 14 44 42 43 14 12 21 15 35 36 11 13 16 15 27 6 43 46 34 8 7 16 11 8 2 14 13 13 9 4 5 31 13 3 1 3 8
five units with the fewest spikes (spikes, session, unit): [(22, 28, 7), (30, 69, 12), (41, 23, 15), (46, 20, 0), (51, 113, 1)]
Result: reproduced. The files hold 1864 unit rows, one more than the paper's 1863, and 59 subjects, both within tolerance. Counting the 55 units of sessions 44 and 55 once, as their spike trains repeat those of sessions 43 and 54, gives 1809, also within tolerance. The files hold 1787 units in CS sessions and 77 in TWH sessions. The paper gives no count per session or site, so the extra unit cannot be identified; no unit is empty (the fewest spikes is 22).
counts-sessions¶
The patients performed 87 sessions, listed by subject and session id in Online-only Table 1 (the Methods give 89).
table = rm.online_table_1()
files_set = set(zip(info.label.str.replace("CS", "C").str.replace(r"^([A-Z])0*", r"\1", regex=True), info.session))
table_set = set(zip(table.table_subject.str.replace(r"^([A-Z])0*", r"\1", regex=True), table.session))
print("sessions in the files:", len(info), "; in Online-only Table 1:", len(table), "; Methods: 89")
print("(subject, session) pairs only in the files:", sorted(files_set - table_set))
print("(subject, session) pairs only in the table:", sorted(table_set - files_set))
print("Task variant against the stimulus set of the file:")
var = table.set_index("session").variant
print(pd.crosstab(info.session.map(var).rename("variant (table)"),
pd.Series([sorted(s["trials"].category_name.unique())[0] for s in sessions], name="first category name")).to_string())
sessions in the files: 87 ; in Online-only Table 1: 87 ; Methods: 89 (subject, session) pairs only in the files: [] (subject, session) pairs only in the table: [] Task variant against the stimulus set of the file: first category name 1cars fruit houses variant (table) 1 0 0 42 2 0 20 0 3 25 0 0
Result: reproduced. The 87 files match the 87 (subject, session) pairs of Online-only Table 1 exactly, with none missing or extra; the Methods' 89 sessions disagree with the paper's own table. Each table variant corresponds to one stimulus set in the files.
table1-demographics¶
Online-only Table 1 gives the age and sex of every subject; there are 25 female and 34 male subjects.
subj = info.groupby("subject").agg(label=("label", "first"), age=("age", "first"), sex=("sex", "first"),
ages=("age", "nunique"), sexes=("sex", "nunique")).reset_index()
subj["session"] = info.groupby("subject").session.first().values
m = subj.merge(table[["session", "table_subject", "age", "sex", "diagnosis"]], on="session", suffixes=("_file", "_table"))
m["age_file"] = m.age_file.astype(int)
m["match"] = (m.age_file == m.age_table) & (m.sex_file == m.sex_table)
diag = {s["subject_id"]: s["diagnosis"] for s in sessions}
m["diagnosis_file"] = m.subject.map(diag)
print("Subjects with one age and one sex across their files:", int(((subj.ages == 1) & (subj.sexes == 1)).sum()), "of", len(subj))
print("Subjects matching the table in age and sex:", int(m.match.sum()), "of", len(m))
print(m[~m.match][["subject", "table_subject", "age_file", "age_table", "sex_file", "sex_table"]].to_string(index=False))
print("\nFemale / male in the files:", subj.sex.value_counts().to_dict(), "; in the table: F 25, M 34")
print("\nDiagnosis, files against table, where the text differs:")
diag_diff = m[m.diagnosis_file.str.lower() != m.diagnosis.str.lower()]
print("subjects whose diagnosis text differs:", len(diag_diff), "of", len(m))
print(diag_diff[["table_subject", "diagnosis_file", "diagnosis"]].to_string(index=False))
code_list = rm.code_session_list()
print(f"\nThe code's session list (defineNOsessions_release.ini at {rm.CODE_COMMIT[:7]}): {int(code_list.active.sum())} active sections,",
f"commented out: {code_list[~code_list.active].session.tolist()}")
cl = code_list[code_list.active].assign(age=lambda d: d.age.astype(int))
fs = info.merge(cl[["session", "ID", "age", "sex"]], on="session", how="left", suffixes=("_file", "_code"))
print("files with a section in the code's list:", int(fs.ID.notna().sum()), "of", len(fs),
"; with the same age and sex as the file:", int(((fs.age_file.astype(int) == fs.age_code) & (fs.sex_file == fs.sex_code)).sum()))
print("the three subjects that differ from Online-only Table 1, in the code's list:")
print(fs[fs.session.isin(m[~m.match].session)][["session", "label", "ID", "age_file", "age_code", "sex_file", "sex_code"]].to_string(index=False))
Subjects with one age and one sex across their files: 59 of 59
Subjects matching the table in age and sex: 56 of 59
subject table_subject age_file age_table sex_file sex_table
P24CS C24 57 47 F F
P33HMH H33 30 29 M M
P48HMH H48 32 54 F M
Female / male in the files: {'M': 33, 'F': 26} ; in the table: F 25, M 34
Diagnosis, files against table, where the text differs:
subjects whose diagnosis text differs: 19 of 59
table_subject diagnosis_file diagnosis
H10 Left Lateral Frontal Left Frontal
H16 Right Lateral Frontal Right Frontal
H17 Left Lateral Frontal Left Inferior Frontal
H19 Left Lateral Frontal Left Frontal
C25 Bilateral Independent Temporal Bilateral Independent Mesial Temporal
H29 Left Other Left Deep Insula
C32 Not Localized Not Localized (Generalized)
C34 Bilateral Independent Temporal Bilateral Mesial Temporal
C39 Right Other Right Insula
C40 Right Other Right Motor Cortex
C51 Not Localized Not Localized (No Seizures)
H51 Bilateral Independent Frontal Bilateral Frontal and Temporal
C53 Bilateral Independent Temporal Bilateral Independent Mesial Temporal Lobe
C56 Bilateral Independent Temporal Multifocal, Bitemporal + Orbitofrontal
C57 Left Other Left Neocortical Parietal
C58 Right Lateral Frontal Right Frontal Neocortical
C61 Left Lateral Frontal Left Mesial Temporal
T89 Right Lateral Frontal Right Frontal
T90 Bilateral Occipital Cortex Occipital Cortex
The code's session list (defineNOsessions_release.ini at 1d95162): 87 active sections,
commented out: [98]
files with a section in the code's list: 87 of 87 ; with the same age and sex as the file: 87
the three subjects that differ from Online-only Table 1, in the code's list:
session label ID age_file age_code sex_file sex_code
52 H33 H33 30 30 M M
59 CS24 CS24 57 57 F F
99 H48 H48 32 32 F F
Result: partially reproduced. Age and sex match the table for 56 of 59 subjects. C24 is 57 in the files and 47 in the table, H33 is 30 and 29, and H48 is a 32-year-old woman in the files and a 54-year-old man in the table. The files hold 26 female and 33 male subjects, within tolerance of the table's 25 and 34. The diagnosis text differs for 19 of 59 subjects. For eight of them (C25, H29, C32, C39, C40, C51, C53, C57) the files give a broader category, such as Right Other for Right Insula. For six (H10, H16, H17, H19, C58, T89) the files give Lateral Frontal, which adds Lateral to the table's Frontal, except for H17, whose table entry is Left Inferior Frontal. The five others differ in substance: C61 (Left Lateral Frontal against Left Mesial Temporal), C34 (Bilateral Independent Temporal against Bilateral Mesial Temporal), C56 and H51, where the files leave out part of the table's diagnosis (orbitofrontal, temporal), and T90, where they add "Bilateral". The code's session list has a section for each of the 87 files with the same age and sex as the file, so for C24, H33 and H48 the files follow the code and the table differs from both.
task-structure¶
Each session has 100 encoding trials showing 100 novel images and 100 recognition trials, of which 50 show new and 50 old images, and the images come from 5 visual categories.
rows = []
for s in sessions:
t = s["trials"]; learn = t[t.stim_phase == "learn"]; r = rm.recognition(s)
(shape, img_t), = s["stimulus"].values()
rows.append({"session": s["session"], "learn": len(learn), "learn_distinct_images": learn.external_image_file.nunique(),
"recog": len(r), "label_counts": tuple(r.label.value_counts().sort_index()), "categories": t.stimCategory.nunique(),
"category_names": t.category_name.nunique(), "images_stored": shape[0],
"image_times_are_onsets": bool(np.allclose(np.sort(img_t), np.sort(t.start_time.values)))})
task = pd.DataFrame(rows)
ok_learn = (task.learn == 100) & (task.learn_distinct_images == 100)
ok_recog = (task.recog == 100) & (task.label_counts == (50, 50))
ok_cat = (task.categories == 5) & (task.category_names == 5)
print("100 encoding trials with 100 distinct images:", int(ok_learn.sum()), "of", len(task))
print("100 recognition trials, 50 per label:", int(ok_recog.sum()), "of", len(task))
print("5 categories (ids and names):", int(ok_cat.sum()), "of", len(task))
print("One stored image per trial, timestamped at trial onset:", int(((task.images_stored == task.learn + task.recog) & task.image_times_are_onsets).sum()), "of", len(task))
print("\nSessions with other encoding blocks:")
print(task[~ok_learn].to_string(index=False))
print("\nStimulus storage: one ImageSeries per file,", sorted({k for s in sessions for k in s["stimulus"]}),
"(the paper describes stimuli_learn_x and stimuli_recog_x entries)")
100 encoding trials with 100 distinct images: 79 of 87
100 recognition trials, 50 per label: 87 of 87
5 categories (ids and names): 87 of 87
One stored image per trial, timestamped at trial onset: 87 of 87
Sessions with other encoding blocks:
session learn learn_distinct_images recog label_counts categories category_names images_stored image_times_are_onsets
18 50 50 100 (50, 50) 5 5 150 True
20 50 50 100 (50, 50) 5 5 150 True
21 50 50 100 (50, 50) 5 5 150 True
23 50 50 100 (50, 50) 5 5 150 True
26 50 50 100 (50, 50) 5 5 150 True
27 50 50 100 (50, 50) 5 5 150 True
44 50 50 100 (50, 50) 5 5 150 True
123 97 97 100 (50, 50) 5 5 197 True
Stimulus storage: one ImageSeries per file, ['StimulusPresentation'] (the paper describes stimuli_learn_x and stimuli_recog_x entries)
Result: partially reproduced. All 87 sessions have 100 recognition trials with 50 of each label and 5 categories, and one stored image per trial. The encoding block has 100 trials with 100 distinct images in 79 sessions; seven sessions have 50 encoding trials and session 123 has 97. The images are stored as one image series per file, not as the stimuli_learn_x and stimuli_recog_x entries the paper describes. In the eight sessions with shorter encoding blocks, 2% to 48% of the images shown as old in recognition have no encoding trial in the file (shown under new-old-label-coding), so the file does not hold the encoding of every old image.
new-old-label-coding¶
In the trials table, new_old_labels_recog is 0 for old stimuli (seen during encoding) and 1 for new stimuli.
rows = []
for s in sessions:
t = s["trials"]; r = rm.recognition(s)
learned = set(t[t.stim_phase == "learn"].external_image_file)
seen = r.external_image_file.isin(learned)
rows.append({"session": s["session"],
"label0_seen_in_encoding": seen[r.label == 0].mean(), "label1_seen_in_encoding": seen[r.label == 1].mean(),
"label0_old_responses": (r.response[r.label == 0] >= 4).mean(), "label1_old_responses": (r.response[r.label == 1] >= 4).mean()})
lab = pd.DataFrame(rows)
print("Fraction of recognition trials whose image file was shown during encoding, median over sessions:")
print(" label 0:", lab.label0_seen_in_encoding.median(), " label 1:", lab.label1_seen_in_encoding.median())
print("Sessions where every label-1 image and no label-0 image was shown in encoding:",
int(((lab.label1_seen_in_encoding == 1) & (lab.label0_seen_in_encoding == 0)).sum()), "of", len(lab))
print("Sessions where label 0 matches the stated coding (all label-0 images seen, no label-1 image seen):",
int(((lab.label0_seen_in_encoding == 1) & (lab.label1_seen_in_encoding == 0)).sum()))
print("\nFraction of 'old' responses (4-6), median over sessions: label 0", round(lab.label0_old_responses.median(), 3),
" label 1", round(lab.label1_old_responses.median(), 3))
print("Sessions with more 'old' responses to label 1 than to label 0:", int((lab.label1_old_responses > lab.label0_old_responses).sum()), "of", len(lab))
print("Sign test, label 1 above label 0: p =", stats.binomtest(int((lab.label1_old_responses > lab.label0_old_responses).sum()),
int((lab.label1_old_responses != lab.label0_old_responses).sum())).pvalue)
print("Sessions with imperfect image match:")
print(lab[(lab.label1_seen_in_encoding < 1) | (lab.label0_seen_in_encoding > 0)].round(3).to_string(index=False))
Fraction of recognition trials whose image file was shown during encoding, median over sessions:
label 0: 0.0 label 1: 1.0
Sessions where every label-1 image and no label-0 image was shown in encoding: 79 of 87
Sessions where label 0 matches the stated coding (all label-0 images seen, no label-1 image seen): 0
Fraction of 'old' responses (4-6), median over sessions: label 0 0.24 label 1 0.7
Sessions with more 'old' responses to label 1 than to label 0: 86 of 87
Sign test, label 1 above label 0: p = 1.137373342260413e-24
Sessions with imperfect image match:
session label0_seen_in_encoding label1_seen_in_encoding label0_old_responses label1_old_responses
18 0.0 0.58 0.02 0.44
20 0.0 0.58 0.76 0.88
21 0.0 0.52 0.70 0.80
23 0.0 0.52 0.06 0.58
26 0.0 0.58 0.14 0.44
27 0.0 0.52 0.30 0.82
44 0.0 0.52 0.06 0.42
123 0.0 0.98 0.14 0.60
Result: not reproduced. The coding is the reverse of the stated one. In 79 of 87 sessions every label-1 image and no label-0 image was shown during encoding, and in no session does label 0 mark the encoded images. Label-1 trials draw "old" responses more often than label-0 trials in 86 of 87 sessions (median 0.70 against 0.24, sign test p = 1.1e-24). Label 1 is old, as the paper's code reads it; the paper's text and the files' column description say 0. In the eight sessions with shortened encoding blocks only 52% to 98% of the label-1 images are among the stored encoding trials, so encoding trials are missing from those files.
ttl-codes¶
The events stream holds the TTL markers 55, 1, 2, 3, 20 or 21, 31 to 36, 6 and 66 with the meanings of Table 1, and the events and experiment_ids streams have one entry each, in the same order.
LISTED = {55, 1, 2, 3, 20, 21, 31, 32, 33, 34, 35, 36, 6, 66}
rows = []
for s in sessions:
ev, et = s["events"], s["events_t"]
same = len(ev) == len(s["experiment_ids"]) and np.allclose(et, s["experiment_ids_t"])
other = sorted(set(ev.astype(int)) - LISTED)
on = np.flatnonzero(ev == 1)
t = s["trials"]
phase = t.stim_phase.values[np.abs(t.start_time.values[None, :] - et[on][:, None]).argmin(1)]
seq_ok = phase_ok = 0
for i, ph in zip(on, phase):
nxt = ev[i + 1:i + 5]
if len(nxt) == 4 and nxt[0] == 2 and nxt[1] == 3 and (nxt[2] in (20, 21) or 31 <= nxt[2] <= 36) and nxt[3] == 6:
seq_ok += 1
phase_ok += (nxt[2] in (20, 21)) if ph == "learn" else (31 <= nxt[2] <= 36)
rows.append({"session": s["session"], "events": len(ev), "same_length_and_times": same, "unlisted_codes": tuple(other),
"trials": len(on), "trials_in_order": seq_ok, "response_kind_of_phase": phase_ok,
"starts_55": int((ev == 55).sum()), "ends_66": int((ev == 66).sum())})
ttl = pd.DataFrame(rows)
print("events and experiment_ids of equal length and timestamps:", int(ttl.same_length_and_times.sum()), "of", len(ttl))
print("files with codes not in Table 1:", ttl[ttl.unlisted_codes != ()][["session", "unlisted_codes"]].to_string(index=False))
print("trials showing 1, 2, 3, response, 6 in order:", int(ttl.trials_in_order.sum()), "of", int(ttl.trials.sum()))
print("trials in order whose response is of the phase's kind (20 or 21 in learning, 31 to 36 in recognition):",
int(ttl.response_kind_of_phase.sum()), "of", int(ttl.trials_in_order.sum()))
print("(start 55, end 66) markers per file: number of files", ttl.groupby(["starts_55", "ends_66"]).size().to_dict())
print("files without a 55:", ttl[ttl.starts_55 == 0].session.tolist(), "; files with other than two 66:", ttl[ttl.ends_66 != 2].session.tolist())
ttl["all_ok"] = (ttl.same_length_and_times & (ttl.unlisted_codes == ()) & (ttl.trials_in_order == ttl.trials)
& (ttl.response_kind_of_phase == ttl.trials_in_order))
print("files meeting every condition:", int(ttl.all_ok.sum()), "of", len(ttl))
print(ttl[~ttl.all_ok].to_string(index=False))
events and experiment_ids of equal length and timestamps: 87 of 87
files with codes not in Table 1: session unlisted_codes
43 (98, 99)
58 (23,)
93 (99,)
123 (0,)
127 (0,)
128 (4,)
2001 (22, 23)
2002 (23,)
2003 (23,)
trials showing 1, 2, 3, response, 6 in order: 17019 of 17047
trials in order whose response is of the phase's kind (20 or 21 in learning, 31 to 36 in recognition): 17019 of 17019
(start 55, end 66) markers per file: number of files {(0, 2): 7, (1, 2): 1, (2, 2): 78, (2, 3): 1}
files without a 55: [92, 93, 96, 97, 99, 100, 101] ; files with other than two 66: [2001]
files meeting every condition: 78 of 87
session events same_length_and_times unlisted_codes trials trials_in_order response_kind_of_phase starts_55 ends_66 all_ok
43 1006 True (98, 99) 200 200 200 2 2 False
58 1005 True (23,) 200 199 199 2 2 False
93 1004 True (99,) 200 200 200 0 2 False
123 991 True (0,) 197 196 196 1 2 False
127 1005 True (0,) 200 199 199 2 2 False
128 1005 True (4,) 200 199 199 2 2 False
2001 1013 True (22, 23) 200 193 193 2 3 False
2002 1014 True (23,) 200 190 190 2 2 False
2003 1011 True (23,) 200 193 193 2 2 False
Result: partially reproduced. In all 87 files the events and experiment_ids streams have the same length and timestamps, and 17019 of 17047 trials show onset, offset, question, response and end in order. 78 of 87 files meet every condition; the nine others hold codes Table 1 does not list (0, 4, 22, 23, 98 or 99). The start and end markers are not part of the condition: seven files (sessions 92, 93, 96, 97, 99, 100 and 101) have no start-of-experiment code 55, six of which meet every condition, and session 2001 has three end codes 66. In every trial that shows the sequence, the response is of its phase's kind (20 or 21 in learning, 31 to 36 in recognition).
experiment-ids¶
The learning block is labelled with one of 80, 83 or 88 and the recognition block with one of 81, 84 or 89.
rows = []
for s in sessions:
ids = s["experiment_ids"]; ev = s["events"]
first_resp = {int(e): int(ev[(ids == e) & (((ev >= 20) & (ev <= 21)) | ((ev >= 31) & (ev <= 36)))][0]) for e in np.unique(ids)}
learn_id = [e for e, r in first_resp.items() if r in (20, 21)]
recog_id = [e for e, r in first_resp.items() if 31 <= r <= 36]
rows.append({"session": s["session"], "learn_id": learn_id[0] if learn_id else None,
"recog_id": recog_id[0] if recog_id else None, "data_collection": s["data_collection"]})
eid = pd.DataFrame(rows)
eid["ok"] = eid.learn_id.isin([80, 83, 88]) & eid.recog_id.isin([81, 84, 89])
print("Block identified by its responses (20/21 learning, 31-36 recognition).")
print("files whose ids are in the stated sets:", int(eid.ok.sum()), "of", len(eid))
print(eid[~eid.ok].to_string(index=False))
print("\nid pairs:", eid.groupby(["learn_id", "recog_id"]).size().to_dict())
Block identified by its responses (20/21 learning, 31-36 recognition).
files whose ids are in the stated sets: 83 of 87
session learn_id recog_id data_collection ok
55 80 82 learning: 80, recognition: 82 False
77 81 82 learning: 81, recognition: 82 False
100 82 83 learning: 82, recognition: 83 False
105 82 83 learning: 82, recognition: 83 False
id pairs: {(80, 81): 65, (80, 82): 1, (81, 82): 1, (82, 83): 2, (83, 84): 14, (88, 89): 4}
Result: partially reproduced. 83 of 87 files use the ids of Table 2. Session 55 labels its recognition block 82, session 77 uses 81 for learning and 82 for recognition, and sessions 100 and 105 use 82 for learning and 83 for recognition.
session-dates¶
session_start_time keeps only the year and month of the experiment, with the day set to the first of the month for all sessions.
dates = pd.DataFrame({"session": [s["session"] for s in sessions], "start": [s["session_start_time"] for s in sessions]})
dates["day"] = dates.start.str[8:10]; dates["year"] = dates.start.str[:4].astype(int)
print("files on the first of a month:", int((dates.day == "01").sum()), "of", len(dates))
print(dates[dates.day != "01"].to_string(index=False))
print("years:", dates.year.min(), "to", dates.year.max(), "; by year:", dates.year.value_counts().sort_index().to_dict())
files on the first of a month: 86 of 87
session start day year
48 2010-03-13T00:00:00-08:00 13 2010
years: 2000 to 2019 ; by year: {2000: 1, 2006: 4, 2007: 6, 2008: 4, 2009: 6, 2010: 3, 2011: 2, 2012: 6, 2013: 6, 2014: 9, 2015: 4, 2016: 7, 2017: 8, 2018: 19, 2019: 2}
Result: partially reproduced. 86 of 87 files start on the first of a month. Session 48 starts on 2010-03-13, a day that was not set to the first. Session 68 is dated 2000-01-01, outside the span of the other recordings (2006 to 2019).
units-table¶
The units table holds spike_times, waveform_mean_encoding, waveform_mean_recognition (sampled at 100 kHz), origClusterID, SNR, IsolDist and electrodes, with one electrode per neuron.
needed = {"spike_times", "waveform_mean_encoding", "waveform_mean_recognition", "origClusterID", "SNR", "IsolationDist", "electrodes"}
cols_ok = sum(needed <= set(s["unit_columns"]) for s in sessions)
one_elec = sum(bool(np.array_equal(s["unit_electrode_index"], np.arange(1, len(s["spikes"]) + 1))) for s in sessions)
rates = np.unique(np.concatenate([s["waveform_rate"] for s in sessions]))
snr = np.concatenate([s["snr"] for s in sessions]); iso = np.concatenate([s["isolation_distance"] for s in sessions])
print("files with every column (IsolDist is named IsolationDist):", cols_ok, "of", len(sessions))
print("columns in every file:", sorted(set.intersection(*[set(s["unit_columns"]) for s in sessions])))
print("files where every unit links to exactly one electrode:", one_elec, "of", len(sessions))
print("stated waveform sampling rate (Hz):", rates, "; waveform samples:", sorted({s["waveform_shape"][1] for s in sessions}))
print(f"SNR: {np.isfinite(snr).sum()} of {len(snr)} finite; {np.isinf(snr).sum()} infinite; finite range {np.nanmin(snr[np.isfinite(snr)]):.2f} to {np.nanmax(snr[np.isfinite(snr)]):.2f}")
print(f"IsolationDist: {np.isfinite(iso).sum()} of {len(iso)} finite; {np.isnan(iso).sum()} NaN; finite range {np.nanmin(iso):.3g} to {np.nanmax(iso[np.isfinite(iso)]):.3g}; above 1e4: {(iso > 1e4).sum()}")
unsorted = [(s["session"], k) for s in sessions for k, sp in enumerate(s["spikes"]) if np.any(np.diff(sp) < 0)]
print("units whose spike times are not in increasing order:", len(unsorted), "in sessions", sorted({a for a, _ in unsorted}))
for a, k in unsorted:
sp = by_id[a]["spikes"][k]
n_dup = int(np.sum(np.diff(np.sort(sp)) == 0))
if n_dup:
print(f" session {a} unit {k}: {len(sp)} spike times, {n_dup} of them repeat another exactly")
files with every column (IsolDist is named IsolationDist): 87 of 87 columns in every file: ['IsolationDist', 'SNR', 'electrodes', 'electrodes_index', 'id', 'origClusterID', 'spike_times', 'spike_times_index', 'waveform_mean_encoding', 'waveform_mean_recognition', 'waveform_mean_sampling_rate'] files where every unit links to exactly one electrode: 87 of 87 stated waveform sampling rate (Hz): [98400.] ; waveform samples: [256] SNR: 1778 of 1864 finite; 11 infinite; finite range 0.38 to 11.89 IsolationDist: 1371 of 1864 finite; 493 NaN; finite range 4.51 to 8.08e+32; above 1e4: 4
units whose spike times are not in increasing order: 13 in sessions [21, 58, 68, 70, 73, 77, 128] session 58 unit 35: 3268 spike times, 1634 of them repeat another exactly session 58 unit 37: 824 spike times, 412 of them repeat another exactly
Result: partially reproduced. Every file has every column (IsolDist is named IsolationDist), and every unit links to one electrode. The stated waveform sampling rate is 98.4 kHz, within the tolerance of the paper's 100 kHz. The quality metrics are not finite for every unit: SNR is finite for 1778 of 1864 units, 11 being infinite, and the isolation distance is NaN for 493 units, with 4 values above 1e4 (up to 8.08e+32). The spike times of 13 units in 7 sessions are not in increasing order, and two units of session 58 hold every spike time twice.
electrodes-table¶
The electrodes table gives each electrode's brain area, MNI coordinates, impedance, the filter applied before spike sorting (300 to 3000 Hz) and the original hardware channel.
E = pd.concat([s["electrodes"].assign(session=s["session"]) for s in sessions], ignore_index=True)
print("electrodes:", len(E), "; columns in every file:", sorted(set.intersection(*[set(s["electrodes"].columns) for s in sessions])))
for c in ["location", "x", "y", "z", "imp", "filtering", "origChannel"]:
v = E[c]
empty = v.isna() | (v.astype(str).str.strip() == "")
print(f"{c:12s} empty for {int(empty.sum())} of {len(E)}; files with any empty: {E[empty].session.nunique()}")
print("filtering values:", E.filtering.value_counts().to_dict())
print("sessions with missing coordinates:", sorted(E[E.x.isna()].session.unique()))
print("coordinate ranges (mm): x", E.x.min(), "to", E.x.max(), "; y", E.y.min(), "to", E.y.max(), "; z", E.z.min(), "to", E.z.max())
side = E.dropna(subset=["x"])
print("left-named electrodes with x < 0:", int((side.location.str.startswith("Left") & (side.x < 0)).sum()), "of", int(side.location.str.startswith("Left").sum()),
"; right-named with x > 0:", int((side.location.str.startswith("Right") & (side.x > 0)).sum()), "of", int(side.location.str.startswith("Right").sum()))
print("imp: dtype", E.imp.dtype, "; distinct stored values:", [repr(v) for v in pd.unique(E.imp)][:10])
electrodes: 887 ; columns in every file: ['filtering', 'group_name', 'id', 'imp', 'location', 'origChannel', 'x', 'y', 'z']
location empty for 0 of 887; files with any empty: 0
x empty for 99 of 887; files with any empty: 8
y empty for 99 of 887; files with any empty: 8
z empty for 99 of 887; files with any empty: 8
imp empty for 887 of 887; files with any empty: 87
filtering empty for 0 of 887; files with any empty: 0
origChannel empty for 0 of 887; files with any empty: 0
filtering values: {'300-3000Hz': 887}
sessions with missing coordinates: [np.int64(7), np.int64(20), np.int64(21), np.int64(23), np.int64(24), np.int64(50), np.int64(52), np.int64(2004)]
coordinate ranges (mm): x -33.0 to 34.97 ; y -26.5 to 6.88 ; z -35.9736 to -7.74
left-named electrodes with x < 0: 364 of 364 ; right-named with x > 0: 424 of 424
imp: dtype float64 ; distinct stored values: ['np.float64(nan)']
Result: not reproduced. Location, filter (300-3000Hz) and original channel are filled for all 887 electrodes, and the coordinates match the hemisphere of the location wherever they are given. The impedance is stored as NaN (float64) for all 887 electrodes, and the coordinates are missing for 99 electrodes in 8 sessions.
isi-criterion¶
Clusters were accepted as units only when fewer than 3% of their interspike intervals were shorter than 3 ms.
isi = pd.DataFrame([{"session": s["session"], "unit": k, "spikes": len(sp), "isi_sorted": rm.isi_fraction(sp),
"isi_as_stored": rm.isi_fraction(sp, sort=False)} for s in sessions for k, sp in enumerate(s["spikes"])])
print("units:", len(isi))
for c in ["isi_sorted", "isi_as_stored"]:
print(f"{c}: below 3% {int((isi[c] < 0.03).sum())} ({100 * (isi[c] < 0.03).mean():.1f}%); median {100 * isi[c].median():.2f}%; max {100 * isi[c].max():.1f}%")
for thr in (0.03, 0.05, 0.10):
print(f"units at or above {thr:.0%} (sorted): {int((isi.isi_sorted >= thr).sum())}")
print(isi[isi.isi_sorted >= 0.03].round(4).to_string(index=False))
print("by site, fraction below 3%:", isi.assign(site=isi.session.map({s['session']: s['site'] for s in sessions})).groupby("site").isi_sorted.apply(lambda v: round((v < 0.03).mean(), 3)).to_dict())
fig, ax = plt.subplots(figsize=(5, 3))
ax.hist(100 * isi.isi_sorted.clip(upper=0.2), bins=50)
ax.axvline(3, color="k"); ax.set_xlabel("% of ISIs < 3 ms (clipped at 20%)"); ax.set_ylabel("units")
plt.tight_layout(); plt.show()
print("units above 20% (clipped into the last bin):", int((isi.isi_sorted > 0.2).sum()))
units: 1864
isi_sorted: below 3% 1858 (99.7%); median 0.19%; max 50.1%
isi_as_stored: below 3% 1860 (99.8%); median 0.19%; max 9.0%
units at or above 3% (sorted): 6
units at or above 5% (sorted): 4
units at or above 10% (sorted): 2
session unit spikes isi_sorted isi_as_stored
58 35 3268 0.5002 0.0003
58 37 824 0.5006 0.0012
66 2 1176 0.0647 0.0647
124 2 4511 0.0302 0.0302
126 6 5530 0.0901 0.0901
126 7 2018 0.0377 0.0377
by site, fraction below 3%: {'CS': 0.997, 'TWH': 1.0}
units above 20% (clipped into the last bin): 2
Result: partially reproduced. 1858 of 1864 units (99.7%) have fewer than 3% of their interspike intervals below 3 ms, above the 95% the criterion sets for a partial result. Six units are at or above 3%, two of them above 20%. With the spike times as stored (unsorted in 13 units), 1860 units pass.
fig5a-mtl-locations¶
All neurons were recorded from the medial temporal lobe, in the amygdala and the hippocampus.
loc = pd.Series([s["electrodes"].location.values[e] for s in sessions for e in s["unit_electrode"]])
print("unit locations:", loc.value_counts().to_dict())
print("units in amygdala or hippocampus:", int(loc.str.contains("Amygdala|Hippocampus").sum()), "of", len(loc))
unit locations:
{'Right Amygdala': 594, 'Left Amygdala': 498, 'Right Hippocampus': 407, 'Left Hippocampus': 365}
units in amygdala or hippocampus: 1864 of 1864
Result: reproduced. All 1864 units are on electrodes located in the left or right amygdala or hippocampus.
fig5c-auc-all¶
Subjects distinguished new from old images; the behavioural AUC averaged over all sessions is 0.74 ± 0.10.
beh = []
for s in sessions:
r = rm.recognition(s)
lab_, resp = r.label.values, r.response.values
hi_f, lo_f = rm.confidence_accuracy(lab_, resp, "fixed")
hi_d, lo_d = rm.confidence_accuracy(lab_, resp, "dynamic")
beh.append({"session": s["session"], "site": s["site"], "auc": rm.auc(lab_, resp), "zroc_slope": rm.zroc_slope(lab_, resp), "zroc_degenerate": rm.zroc_degenerate(lab_, resp),
"acc_high_fixed": hi_f, "acc_low_fixed": lo_f, "acc_high_dynamic": hi_d, "acc_low_dynamic": lo_d,
"hit_rate": np.mean(resp[lab_ == rm.OLD] >= 4), "fa_rate": np.mean(resp[lab_ == rm.NEW] >= 4)})
beh = pd.DataFrame(beh)
print(f"AUC over {len(beh)} sessions: mean {beh.auc.mean():.3f}, SD {beh.auc.std(ddof=1):.3f} (paper 0.74 ± 0.10)")
print(f"range {beh.auc.min():.3f} to {beh.auc.max():.3f}; sessions below 0.6: {int((beh.auc < 0.6).sum())}; at or below 0.5: {int((beh.auc <= 0.5).sum())}")
print(f"chance AUC is 0.5; one-sample t-test of AUC against 0.5: t = {stats.ttest_1samp(beh.auc, 0.5).statistic:.1f}, p = {stats.ttest_1samp(beh.auc, 0.5).pvalue:.2g}")
print(f"hit rate {beh.hit_rate.mean():.3f}, false alarm rate {beh.fa_rate.mean():.3f} (means over sessions)")
once = beh[~beh.session.isin(DUPLICATE_SECOND)]
print(f"variant, sessions 44 and 55 dropped: mean {once.auc.mean():.3f}, SD {once.auc.std(ddof=1):.3f}")
beh["auc_stated_coding"] = [rm.auc(1 - rm.recognition(s).label.values, rm.recognition(s).response.values) for s in sessions]
print(f"variant, label coding as the paper states (0 = old), recomputed: mean AUC {beh.auc_stated_coding.mean():.3f}, "
f"range {beh.auc_stated_coding.min():.3f} to {beh.auc_stated_coding.max():.3f}")
fig, ax = plt.subplots(figsize=(5, 3))
ax.hist(beh.auc, bins=20); ax.set_xlabel("AUC"); ax.set_ylabel("sessions"); plt.tight_layout(); plt.show()
AUC over 87 sessions: mean 0.753, SD 0.094 (paper 0.74 ± 0.10)
range 0.445 to 0.919; sessions below 0.6: 6; at or below 0.5: 1 chance AUC is 0.5; one-sample t-test of AUC against 0.5: t = 25.1, p = 2.2e-41 hit rate 0.693, false alarm rate 0.269 (means over sessions) variant, sessions 44 and 55 dropped: mean 0.754, SD 0.095
variant, label coding as the paper states (0 = old), recomputed: mean AUC 0.247, range 0.081 to 0.555
Result: reproduced. The AUC over 87 sessions is 0.753 with a standard deviation of 0.094, both within tolerance of 0.74 ± 0.10, and well above the chance level of 0.5 (p = 2.2e-41). Recomputed with the label coding the paper states, the mean AUC is 0.247 (0.081 to 0.555), below chance.
fig5c-auc-sites¶
Performance was similar at the two study sites, with an average AUC of 0.74 ± 0.09 for CS sessions and 0.69 ± 0.13 for TWH sessions.
g = beh.groupby("site").auc.agg(["size", "mean", lambda v: v.std(ddof=1)]).rename(columns={"<lambda_0>": "sd"})
print(g.round(3).to_string())
print("paper: CS 0.74 ± 0.09, TWH 0.69 ± 0.13")
w = stats.ttest_ind(beh[beh.site == "CS"].auc, beh[beh.site == "TWH"].auc, equal_var=False)
print(f"CS against TWH, Welch's t-test: t = {w.statistic:.2f}, p = {w.pvalue:.3f}; difference of means {g.loc['CS', 'mean'] - g.loc['TWH', 'mean']:.3f}")
print("TWH sessions:", beh[beh.site == "TWH"][["session", "auc"]].round(3).to_dict("records"))
size mean sd
site
CS 78 0.760 0.085
TWH 9 0.688 0.138
paper: CS 0.74 ± 0.09, TWH 0.69 ± 0.13
CS against TWH, Welch's t-test: t = 1.53, p = 0.161; difference of means 0.072
TWH sessions: [{'session': 2001, 'auc': 0.798}, {'session': 2002, 'auc': 0.787}, {'session': 2003, 'auc': 0.628}, {'session': 2004, 'auc': 0.544}, {'session': 2005, 'auc': 0.445}, {'session': 2007, 'auc': 0.774}, {'session': 2008, 'auc': 0.738}, {'session': 2009, 'auc': 0.616}, {'session': 2011, 'auc': 0.865}]
Result: reproduced. CS sessions have an AUC of 0.760 ± 0.085 (78 sessions) and TWH sessions 0.688 ± 0.138 (9 sessions), all four values within tolerance. The sites do not differ significantly (Welch's t-test p = 0.161).
fig5b-roc-asymmetric¶
The ROC curve is asymmetric (p < 0.05), as expected for declarative memory.
# Sessions whose five interior z(FA) or z(H) points are all equal have no real z-ROC slope: with
# equal z(FA) the regression has no unique fit (it can return a negative slope), with equal z(H)
# it returns 0. They are left out of the main result and kept in a variant.
syn_lab = np.r_[np.ones(50), np.zeros(50)].astype(int)
syn_resp = np.ones(100, int); syn_resp[:36] = 6; syn_resp[50:54] = 6
print(f"synthetic session answering only 1 and 6 (36 of 50 old, 4 of 50 new at 6): slope {rm.zroc_slope(syn_lab, syn_resp):.4f}, "
f"degenerate: {rm.zroc_degenerate(syn_lab, syn_resp)}")
print("sessions with a degenerate z-ROC (left out):")
print(beh[beh.zroc_degenerate][["session", "site", "auc", "zroc_slope", "hit_rate", "fa_rate"]].round(3).to_string(index=False))
sl = beh[~beh.zroc_degenerate].zroc_slope
t1 = stats.ttest_1samp(sl, 1.0)
wx = stats.wilcoxon(sl - 1.0)
print(f"\nmain, z-ROC slope over {len(sl)} sessions: mean {sl.mean():.3f}, median {sl.median():.3f}, SD {sl.std(ddof=1):.3f}, range {sl.min():.2f} to {sl.max():.2f}")
print(f"one-sample t-test against 1: t = {t1.statistic:.2f}, p = {t1.pvalue:.4g}; sessions with slope below 1: {int((sl < 1).sum())} of {len(sl)}")
print(f"variant, Wilcoxon signed-rank against 1: W = {wx.statistic:.0f}, p = {wx.pvalue:.4g}")
sl_all = beh.zroc_slope
print(f"variant, all {len(sl_all)} sessions with the degenerate ones: mean {sl_all.mean():.3f}, below 1 in {int((sl_all < 1).sum())}, "
f"t-test p = {stats.ttest_1samp(sl_all, 1.0).pvalue:.4g}, Wilcoxon p = {stats.wilcoxon(sl_all - 1.0).pvalue:.4g}")
pooled_lab = np.concatenate([rm.recognition(s).label.values for s in sessions])
pooled_resp = np.concatenate([rm.recognition(s).response.values for s in sessions])
print(f"variant, slope of the pooled ROC (all trials of all sessions): {rm.zroc_slope(pooled_lab, pooled_resp):.3f}")
once = beh[~beh.session.isin(DUPLICATE_SECOND) & ~beh.zroc_degenerate].zroc_slope
print(f"variant, sessions 44 and 55 also dropped: {len(once)} sessions, mean {once.mean():.3f}, p = {stats.ttest_1samp(once, 1.0).pvalue:.2g}")
print("\nper session: AUC, z-ROC slope, hit and false alarm rates")
print(beh[["session", "site", "auc", "zroc_slope", "zroc_degenerate", "hit_rate", "fa_rate"]].round(3).to_string(index=False))
synthetic session answering only 1 and 6 (36 of 50 old, 4 of 50 new at 6): slope -0.2074, degenerate: True
sessions with a degenerate z-ROC (left out):
session site auc zroc_slope hit_rate fa_rate
59 CS 0.845 -0.000 0.88 0.20
60 CS 0.764 -0.247 0.70 0.16
93 CS 0.510 0.415 0.40 0.38
99 CS 0.817 -0.207 0.72 0.08
119 CS 0.845 -0.535 0.86 0.16
2008 TWH 0.738 -0.413 0.72 0.24
main, z-ROC slope over 81 sessions: mean 0.792, median 0.795, SD 0.289, range 0.04 to 1.67
one-sample t-test against 1: t = -6.49, p = 6.587e-09; sessions with slope below 1: 64 of 81
variant, Wilcoxon signed-rank against 1: W = 487, p = 3.292e-08
variant, all 87 sessions with the degenerate ones: mean 0.726, below 1 in 70, t-test p = 1.631e-09, Wilcoxon p = 1.586e-09
variant, slope of the pooled ROC (all trials of all sessions): 0.816
variant, sessions 44 and 55 also dropped: 79 sessions, mean 0.795, p = 2.1e-08
per session: AUC, z-ROC slope, hit and false alarm rates
session site auc zroc_slope zroc_degenerate hit_rate fa_rate
5 CS 0.767 0.775 False 0.70 0.26
6 CS 0.720 0.629 False 0.72 0.32
7 CS 0.658 0.952 False 0.66 0.46
9 CS 0.800 0.882 False 0.70 0.24
17 CS 0.729 1.065 False 0.66 0.30
18 CS 0.812 0.915 False 0.44 0.02
20 CS 0.604 0.505 False 0.88 0.76
21 CS 0.571 1.144 False 0.80 0.70
23 CS 0.768 0.102 False 0.58 0.06
24 CS 0.676 1.669 False 0.56 0.20
26 CS 0.681 1.080 False 0.44 0.14
27 CS 0.830 0.895 False 0.82 0.30
28 CS 0.749 0.642 False 0.80 0.48
32 CS 0.794 0.795 False 0.70 0.24
38 CS 0.736 0.736 False 0.92 0.60
39 CS 0.798 0.816 False 0.84 0.46
41 CS 0.721 0.922 False 0.80 0.36
43 CS 0.789 0.536 False 0.60 0.10
44 CS 0.683 0.585 False 0.42 0.06
47 CS 0.734 0.822 False 0.68 0.34
48 CS 0.779 0.959 False 0.62 0.18
49 CS 0.858 0.689 False 0.66 0.16
50 CS 0.703 1.297 False 0.70 0.38
52 CS 0.890 0.959 False 0.86 0.26
54 CS 0.852 0.630 False 0.70 0.14
55 CS 0.741 0.712 False 0.70 0.32
56 CS 0.697 0.831 False 0.58 0.10
58 CS 0.886 1.074 False 0.80 0.16
59 CS 0.845 -0.000 True 0.88 0.20
60 CS 0.764 -0.247 True 0.70 0.16
61 CS 0.805 1.014 False 0.82 0.28
63 CS 0.681 0.787 False 0.54 0.22
64 CS 0.669 0.533 False 0.70 0.36
66 CS 0.654 1.360 False 0.80 0.50
67 CS 0.574 0.621 False 0.50 0.34
68 CS 0.631 0.953 False 0.78 0.58
69 CS 0.794 1.110 False 0.80 0.32
70 CS 0.785 0.626 False 0.70 0.24
73 CS 0.760 0.208 False 0.58 0.02
74 CS 0.785 0.558 False 0.68 0.28
76 CS 0.792 0.545 False 0.58 0.06
77 CS 0.754 0.796 False 0.52 0.16
78 CS 0.864 0.653 False 0.80 0.22
85 CS 0.624 1.083 False 0.52 0.36
92 CS 0.885 0.569 False 0.86 0.20
93 CS 0.510 0.415 True 0.40 0.38
96 CS 0.809 0.920 False 0.68 0.20
97 CS 0.856 0.719 False 0.72 0.22
99 CS 0.817 -0.207 True 0.72 0.08
100 CS 0.712 0.042 False 0.46 0.02
101 CS 0.823 0.573 False 0.68 0.18
102 CS 0.836 1.385 False 0.98 0.40
104 CS 0.849 0.640 False 0.92 0.40
105 CS 0.753 0.833 False 0.84 0.48
111 CS 0.838 0.560 False 0.64 0.08
112 CS 0.783 0.635 False 0.74 0.24
113 CS 0.768 0.560 False 0.48 0.08
114 CS 0.769 0.983 False 0.64 0.22
115 CS 0.919 0.806 False 0.94 0.18
116 CS 0.876 0.778 False 0.84 0.26
117 CS 0.797 0.894 False 0.64 0.16
118 CS 0.872 0.430 False 0.84 0.30
119 CS 0.845 -0.535 True 0.86 0.16
120 CS 0.772 0.471 False 0.68 0.16
122 CS 0.828 1.048 False 0.78 0.26
123 CS 0.797 0.732 False 0.60 0.14
124 CS 0.853 0.609 False 0.64 0.04
125 CS 0.757 0.660 False 0.56 0.10
126 CS 0.740 0.821 False 0.66 0.18
127 CS 0.637 1.055 False 0.82 0.48
128 CS 0.597 1.411 False 0.78 0.58
130 CS 0.658 1.197 False 0.90 0.64
131 CS 0.633 0.567 False 0.78 0.54
132 CS 0.825 0.755 False 0.62 0.08
133 CS 0.744 0.650 False 0.50 0.14
134 CS 0.730 0.289 False 0.68 0.18
135 CS 0.741 0.294 False 0.56 0.02
136 CS 0.852 0.880 False 0.74 0.24
2001 TWH 0.798 1.257 False 0.80 0.32
2002 TWH 0.787 0.822 False 0.64 0.24
2003 TWH 0.628 0.804 False 0.26 0.08
2004 TWH 0.544 0.485 False 0.18 0.08
2005 TWH 0.445 0.870 False 0.68 0.72
2007 TWH 0.774 0.741 False 0.90 0.44
2008 TWH 0.738 -0.413 True 0.72 0.24
2009 TWH 0.616 1.110 False 0.86 0.68
2011 TWH 0.865 0.799 False 0.86 0.14
Result: reproduced. Six sessions (59, 60, 93, 99, 119 and 2008) answer with too few confidence levels for a z-ROC: their five interior z(FA) or z(H) points are all equal, so the fitted slope is not an estimate (it is negative in four of them and 0 in one). Without them, the z-ROC slope is below 1 in 64 of 81 sessions, with a mean of 0.792 (t-test against 1, p = 6.6e-09; Wilcoxon p = 3.3e-08). With all 87 sessions the mean is 0.726, below 1 in 70 (t-test p = 1.631e-09, Wilcoxon p = 1.586e-09). The pooled ROC has a slope of 0.816.
fig5d-confidence-accuracy¶
Accuracy is significantly higher in high than in low confidence trials (P < 0.001, paired t test) across all sessions.
for split in ("fixed", "dynamic"):
h, l = beh[f"acc_high_{split}"], beh[f"acc_low_{split}"]
ok = h.notna() & l.notna()
tr = stats.ttest_rel(h[ok], l[ok])
tu = stats.ttest_ind(h[ok], l[ok])
print(f"({'a' if split == 'fixed' else 'b'}) {split} split: {int(ok.sum())} sessions, {int((~ok).sum())} left out (no responses in a group): "
f"{beh[~ok].session.tolist()}")
print(f" high {h[ok].mean():.1f}% against low {l[ok].mean():.1f}% (means); high > low in {int((h[ok] > l[ok]).sum())} sessions")
print(f" paired t-test: t = {tr.statistic:.2f}, p = {tr.pvalue:.2g}; variant, unpaired (the code's ttest2): p = {tu.pvalue:.2g}")
print(f" mean difference {np.mean(h[ok] - l[ok]):.1f} points, 2.5th to 97.5th percentile of the per-session differences {np.percentile(h[ok] - l[ok], [2.5, 97.5]).round(1)}")
(a) fixed split: 84 sessions, 3 left out (no responses in a group): [93, 99, 2008]
high 80.1% against low 60.9% (means); high > low in 73 sessions
paired t-test: t = 10.16, p = 3.2e-16; variant, unpaired (the code's ttest2): p = 3.4e-17
mean difference 19.2 points, 2.5th to 97.5th percentile of the per-session differences [-15.1 47.2]
(b) dynamic split: 84 sessions, 3 left out (no responses in a group): [93, 99, 2008]
high 78.3% against low 60.4% (means); high > low in 69 sessions
paired t-test: t = 8.87, p = 1.2e-13; variant, unpaired (the code's ttest2): p = 1.3e-15
mean difference 17.9 points, 2.5th to 97.5th percentile of the per-session differences [-20.3 47.2]
Result: reproduced. Under the fixed split, accuracy is 80.1% for high and 60.9% for low confidence over 84 sessions (paired t-test p = 3.2e-16); under the dynamic split 78.3% and 60.4% (p = 1.2e-13). Three sessions without responses in one group are left out. The unpaired test the code runs also gives p below 0.001.
Single-unit selectivity¶
Every unit is tested on the recognition trials of its file. The firing rate in the window is the spike count divided by the window length. Results for VS, MS and dual cells come from this one computation; each claim's section prints its own numbers.
rng_vs = np.random.default_rng(20200304)
rng_ms = np.random.default_rng(78)
rng_ms2 = np.random.default_rng(79) # a second seed, to show how much the bootstrap's noise moves the counts
rng_null = np.random.default_rng(415)
rng_null_code = np.random.default_rng(416) # null of the code's choices, kept apart so the other nulls do not move
N_NULL = 10
rows = []
started = time.time()
for s in sessions:
r = rm.recognition(s)
on, cat, lab_, resp = r.start_time.values, r.category.values, r.label.values, r.response.values
correct = ((resp >= 4) & (lab_ == rm.OLD)) | ((resp <= 3) & (lab_ == rm.NEW))
names = r.groupby("category").category_name.first()
for k, sp in enumerate(s["spikes"]):
row = {"session": s["session"], "site": s["site"], "unit": k,
"channel": int(s["electrodes"].origChannel.values[s["unit_electrode"][k]]),
"electrode_row": int(s["unit_electrode"][k]),
"location": s["electrodes"].location.values[s["unit_electrode"][k]]}
for wname, w in (("methods", rm.WINDOW_METHODS), ("code", rm.WINDOW_CODE)):
x = rm.window_rates(sp, on, w)
row[f"p_vs_{wname}"] = rm.anova_p(x, cat)
row[f"p_ms_{wname}_all"] = rm.bootstrap_p(x[lab_ == rm.OLD], x[lab_ == rm.NEW], rng_ms)
row[f"p_ms_{wname}_correct"] = rm.bootstrap_p(x[(lab_ == rm.OLD) & correct], x[(lab_ == rm.NEW) & correct], rng_ms)
row[f"p_ms_{wname}_all_seed2"] = rm.bootstrap_p(x[lab_ == rm.OLD], x[lab_ == rm.NEW], rng_ms2)
row[f"p_ms_{wname}_correct_seed2"] = rm.bootstrap_p(x[(lab_ == rm.OLD) & correct], x[(lab_ == rm.NEW) & correct], rng_ms2)
row[f"best_{wname}"] = names[pd.Series(x).groupby(cat).mean().idxmax()]
x = rm.window_rates(sp, on, rm.WINDOW_METHODS)
row["null_vs"] = np.mean([rm.anova_p(x, rng_null.permutation(cat)) < 0.05 for _ in range(N_NULL)])
shuffled = [rng_null.permutation(lab_) for _ in range(N_NULL)]
row["null_ms"] = np.mean([rm.bootstrap_p(x[L == rm.OLD], x[L == rm.NEW], rng_null) < 0.05 for L in shuffled])
# null of the code's choices: labels shuffled among the correct trials, code's window
xc, lc = rm.window_rates(sp, on, rm.WINDOW_CODE)[correct], lab_[correct]
row["null_ms_code_correct"] = np.mean([rm.bootstrap_p(xc[L == rm.OLD], xc[L == rm.NEW], rng_null_code) < 0.05
for L in (rng_null_code.permutation(lc) for _ in range(N_NULL))])
row["rate_hz"] = len(sp) / (s["trials"].stop_time.max() - s["trials"].start_time.min())
rows.append(row)
cells = pd.DataFrame(rows)
cells["vs"] = cells.p_vs_methods < 0.05
cells["ms"] = cells.p_ms_methods_all < 0.05
cells["once"] = ~cells.session.isin(DUPLICATE_SECOND)
print(f"{len(cells)} units tested in {time.time() - started:.0f} s; none dropped. Recognition trials per unit: 100 in every file.")
print(f"units with no spike in any recognition window (Methods' window): {int((cells.p_vs_methods == 1).sum())}")
print("correct trials per session (old, new):", sorted({(int((((rm.recognition(s).response >= 4) & (rm.recognition(s).label == 1))).sum()),
int((((rm.recognition(s).response <= 3) & (rm.recognition(s).label == 0))).sum())) for s in sessions})[:5], "... (first five)")
1864 units tested in 21 s; none dropped. Recognition trials per unit: 100 in every file. units with no spike in any recognition window (Methods' window): 6
correct trials per session (old, new): [(9, 46), (13, 46), (20, 31), (21, 47), (22, 43)] ... (first five)
vs-cells-proportion¶
343 of the 1863 cells (18.4%) are visually selective, by a one-way ANOVA (P < 0.05) of the firing rate in recognition trials, in a 1 s window starting 200 ms after stimulus onset, across the 5 visual categories.
def share(mask, base=None):
base = cells if base is None else base
return f"{int(mask.sum())} of {len(base)} ({100 * mask.mean():.1f}%)"
print("main (Methods' window, every unit):", share(cells.vs), " paper: 343 of 1863 (18.4%)")
print("variant, code's window 200-1700 ms:", share(cells.p_vs_code < 0.05))
print("variant, duplicated spike trains counted once:", share(cells[cells.once].vs, cells[cells.once]))
print(f"null, categories shuffled within session ({N_NULL} shuffles per unit): {100 * cells.null_vs.mean():.1f}% (expected 5%)")
for thr in (0.01, 0.05, 0.10):
print(f"threshold p < {thr}: {100 * (cells.p_vs_methods < thr).mean():.1f}%")
print("\nby session (main), sessions with at least 20 units:")
bs = cells.groupby("session").agg(units=("vs", "size"), vs=("vs", "sum"))
print(bs[bs.units >= 20].assign(pct=lambda d: (100 * d.vs / d.units).round(1)).T.to_string())
print("range of per-session percentages (sessions with at least 20 units):", (100 * bs[bs.units >= 20].vs / bs[bs.units >= 20].units).min().round(1),
"to", (100 * bs[bs.units >= 20].vs / bs[bs.units >= 20].units).max().round(1))
print("by area:", cells.groupby("location").vs.apply(lambda v: f"{int(v.sum())}/{len(v)}").to_dict())
main (Methods' window, every unit): 332 of 1864 (17.8%) paper: 343 of 1863 (18.4%)
variant, code's window 200-1700 ms: 343 of 1864 (18.4%)
variant, duplicated spike trains counted once: 326 of 1809 (18.0%)
null, categories shuffled within session (10 shuffles per unit): 4.7% (expected 5%)
threshold p < 0.01: 8.6%
threshold p < 0.05: 17.8%
threshold p < 0.1: 25.6%
by session (main), sessions with at least 20 units:
session 5 7 9 17 18 20 24 26 32 38 43 44 48 49 54 55 58 59 60 64 69 70 74 76 77 93 99 100 102 104 105 113 115 116 122 124 125 126 2004
units 24.0 38.0 25.0 23.0 33.0 20.0 21.0 33.0 43.0 20.0 23.0 23.0 45.0 27.0 32.0 32.0 49.0 21.0 22.0 33.0 64.0 33.0 35.0 53.0 30.0 35.0 30.0 42.0 44.0 42.0 43.0 21.0 35.0 36.0 27.0 43.0 46.0 34.0 31.0
vs 2.0 8.0 3.0 2.0 3.0 0.0 3.0 0.0 13.0 0.0 7.0 5.0 8.0 8.0 6.0 1.0 17.0 5.0 5.0 1.0 9.0 5.0 10.0 11.0 8.0 9.0 8.0 12.0 8.0 15.0 10.0 2.0 10.0 3.0 2.0 7.0 3.0 6.0 6.0
pct 8.3 21.1 12.0 8.7 9.1 0.0 14.3 0.0 30.2 0.0 30.4 21.7 17.8 29.6 18.8 3.1 34.7 23.8 22.7 3.0 14.1 15.2 28.6 20.8 26.7 25.7 26.7 28.6 18.2 35.7 23.3 9.5 28.6 8.3 7.4 16.3 6.5 17.6 19.4
range of per-session percentages (sessions with at least 20 units): 0.0 to 35.7
by area: {'Left Amygdala': '81/498', 'Left Hippocampus': '60/365', 'Right Amygdala': '116/594', 'Right Hippocampus': '75/407'}
Result: reproduced. With the Methods' window, 332 of 1864 units (17.8%) are visually selective, within tolerance of 343 (18.4%), against 4.7% with shuffled categories. The code's window gives 343 of 1864 (18.4%), the paper's numbers. Counting duplicated spike trains once gives 18.0%.
ms-cells-proportion¶
146 of the 1863 cells (7.8%) are memory selective, by a two-tailed bootstrap comparison of mean firing rates (1,000 runs, P < 0.05) between the 50 novel and 50 familiar recognition trials in a 1 s window starting 200 ms after stimulus onset.
print("main (Methods' window, all 50 and 50 trials):", share(cells.ms), " paper: 146 of 1863 (7.8%)")
print("variant, Methods' window, correct trials only:", share(cells.p_ms_methods_correct < 0.05))
print("variant, code's window, all trials:", share(cells.p_ms_code_all < 0.05))
print("variant, code's choices (code's window, correct trials only):", share(cells.p_ms_code_correct < 0.05))
print("variant, duplicated spike trains counted once:", share(cells[cells.once].ms, cells[cells.once]))
print(f"null, labels shuffled within session ({N_NULL} shuffles per unit): {100 * cells.null_ms.mean():.1f}% (expected 5%)")
for thr in (0.01, 0.05, 0.10):
print(f"threshold p < {thr}: {100 * (cells.p_ms_methods_all < thr).mean():.1f}%")
print("by site:", cells.groupby("site").ms.apply(lambda v: f"{int(v.sum())}/{len(v)}").to_dict())
print("by area:", cells.groupby("location").ms.apply(lambda v: f"{int(v.sum())}/{len(v)}").to_dict())
print("\nsecond bootstrap seed:")
for col, name in (("p_ms_methods_all", "main"), ("p_ms_methods_correct", "Methods' window, correct trials"),
("p_ms_code_all", "code's window, all trials"), ("p_ms_code_correct", "code's choices")):
a, b = cells[col] < 0.05, cells[col + "_seed2"] < 0.05
print(f" {name}: {int(a.sum())} and {int(b.sum())}; units selected under one seed only: {int((a != b).sum())}")
n, k, f0 = len(cells), int(cells.ms.sum()), cells.null_ms.mean()
lo0, hi0 = stats.binomtest(int(round(f0 * n * N_NULL)), n * N_NULL).proportion_ci()
print(f"\nnull fraction {100 * f0:.2f}% from {n * N_NULL} shuffled tests, 95% CI {100 * lo0:.2f}% to {100 * hi0:.2f}%")
print(f"main count {k} against the null: expected {n * f0:.1f}, binomial p (one-sided) = {stats.binomtest(k, n, f0, alternative='greater').pvalue:.3g}; "
f"against 5%: expected {n * 0.05:.1f}, p = {stats.binomtest(k, n, 0.05, alternative='greater').pvalue:.3g}")
k2 = int((cells.p_ms_code_correct < 0.05).sum())
print(f"code's choices count {k2} against 5%: p = {stats.binomtest(k2, n, 0.05, alternative='greater').pvalue:.3g}")
f2 = cells.null_ms_code_correct.mean()
lo2, hi2 = stats.binomtest(int(round(f2 * n * N_NULL)), n * N_NULL).proportion_ci()
print(f"null of the code's choices (labels shuffled among correct trials, code's window, {N_NULL} shuffles per unit): "
f"{100 * f2:.2f}%, 95% CI {100 * lo2:.2f}% to {100 * hi2:.2f}%")
print(f"code's choices count {k2} against this null: expected {n * f2:.1f}, binomial p (one-sided) = "
f"{stats.binomtest(k2, n, f2, alternative='greater').pvalue:.3g}")
ms_cols = ["p_ms_methods_all", "p_ms_methods_all_seed2", "p_ms_methods_correct", "p_ms_methods_correct_seed2",
"p_ms_code_all", "p_ms_code_all_seed2", "p_ms_code_correct", "p_ms_code_correct_seed2"]
any_ms = (cells[ms_cols] < 0.05).any(axis=1)
print(f"\nper-unit p-values of every unit memory selective under some reading or seed ({int(any_ms.sum())} units):")
print(cells[any_ms][["session", "unit", "p_vs_methods", "p_vs_code"] + ms_cols].round(3).to_string(index=False))
main (Methods' window, all 50 and 50 trials): 110 of 1864 (5.9%) paper: 146 of 1863 (7.8%)
variant, Methods' window, correct trials only: 143 of 1864 (7.7%)
variant, code's window, all trials: 108 of 1864 (5.8%)
variant, code's choices (code's window, correct trials only): 147 of 1864 (7.9%)
variant, duplicated spike trains counted once: 105 of 1809 (5.8%)
null, labels shuffled within session (10 shuffles per unit): 5.4% (expected 5%)
threshold p < 0.01: 1.1%
threshold p < 0.05: 5.9%
threshold p < 0.1: 11.6%
by site: {'CS': '103/1787', 'TWH': '7/77'}
by area: {'Left Amygdala': '25/498', 'Left Hippocampus': '26/365', 'Right Amygdala': '36/594', 'Right Hippocampus': '23/407'}
second bootstrap seed:
main: 110 and 118; units selected under one seed only: 22
Methods' window, correct trials: 143 and 140; units selected under one seed only: 17
code's window, all trials: 108 and 109; units selected under one seed only: 15
code's choices: 147 and 149; units selected under one seed only: 10
null fraction 5.39% from 18640 shuffled tests, 95% CI 5.07% to 5.73%
main count 110 against the null: expected 100.5, binomial p (one-sided) = 0.177; against 5%: expected 93.2, p = 0.0443
code's choices count 147 against 5%: p = 7.12e-08
null of the code's choices (labels shuffled among correct trials, code's window, 10 shuffles per unit): 5.58%, 95% CI 5.26% to 5.92%
code's choices count 147 against this null: expected 104.1, binomial p (one-sided) = 2.49e-05
per-unit p-values of every unit memory selective under some reading or seed (288 units):
session unit p_vs_methods p_vs_code p_ms_methods_all p_ms_methods_all_seed2 p_ms_methods_correct p_ms_methods_correct_seed2 p_ms_code_all p_ms_code_all_seed2 p_ms_code_correct p_ms_code_correct_seed2
5 6 0.817 0.860 0.047 0.045 0.287 0.272 0.569 0.557 0.857 0.841
5 12 0.475 0.046 0.031 0.031 0.033 0.043 0.036 0.042 0.022 0.027
5 14 0.392 0.333 0.043 0.050 0.374 0.356 0.112 0.129 0.488 0.480
5 17 0.515 0.658 0.027 0.021 0.198 0.202 0.038 0.028 0.271 0.292
6 5 0.206 0.552 0.142 0.153 0.030 0.027 0.023 0.029 0.003 0.005
6 7 0.444 0.725 0.017 0.015 0.061 0.046 0.101 0.088 0.211 0.217
6 10 0.178 0.716 0.047 0.054 0.001 0.001 0.126 0.112 0.020 0.023
6 14 0.314 0.370 0.123 0.112 0.043 0.045 0.541 0.531 0.309 0.313
7 1 0.955 0.960 0.020 0.012 0.111 0.110 0.026 0.032 0.127 0.141
7 13 0.672 0.842 0.010 0.004 0.030 0.028 0.004 0.005 0.048 0.047
7 16 0.985 0.886 0.003 0.001 0.031 0.018 0.069 0.067 0.164 0.180
7 35 0.818 0.925 0.335 0.332 0.029 0.027 0.651 0.651 0.214 0.197
7 37 0.000 0.000 0.018 0.013 0.060 0.068 0.165 0.182 0.533 0.532
9 0 0.462 0.452 0.156 0.152 0.041 0.036 0.444 0.436 0.402 0.420
9 3 0.641 0.273 0.472 0.451 0.048 0.041 0.568 0.571 0.056 0.062
9 21 0.444 0.160 0.027 0.031 0.090 0.067 0.007 0.010 0.039 0.030
17 15 0.341 0.446 0.030 0.020 0.044 0.054 0.160 0.159 0.110 0.127
17 16 0.721 0.561 0.038 0.033 0.227 0.239 0.109 0.092 0.217 0.229
17 21 0.491 0.124 0.539 0.553 0.050 0.043 0.598 0.608 0.109 0.088
18 3 0.673 0.297 0.072 0.067 0.084 0.091 0.070 0.070 0.039 0.032
18 7 0.564 0.917 0.000 0.001 0.000 0.000 0.119 0.129 0.145 0.147
18 8 0.451 0.190 0.058 0.071 0.005 0.007 0.121 0.143 0.004 0.011
18 9 0.256 0.038 0.593 0.614 0.029 0.024 0.192 0.210 0.028 0.028
18 11 0.092 0.190 0.365 0.386 0.042 0.047 0.363 0.335 0.922 0.924
18 12 0.000 0.010 0.140 0.159 0.001 0.001 0.346 0.337 0.034 0.026
18 15 0.306 0.106 0.145 0.133 0.044 0.048 0.022 0.025 0.020 0.016
18 20 0.083 0.039 0.728 0.727 0.022 0.021 0.533 0.526 0.000 0.002
18 21 0.669 0.041 0.650 0.578 0.181 0.166 0.123 0.122 0.005 0.005
18 23 0.510 0.842 0.531 0.544 0.226 0.257 0.210 0.234 0.034 0.049
18 25 0.163 0.073 0.067 0.060 0.039 0.058 0.009 0.012 0.000 0.002
18 27 0.102 0.307 0.316 0.323 0.025 0.031 0.115 0.109 0.007 0.007
18 29 0.810 0.555 0.002 0.000 0.019 0.023 0.000 0.002 0.007 0.013
18 30 0.294 0.050 0.452 0.434 0.158 0.182 0.083 0.070 0.045 0.047
18 31 0.553 0.313 0.014 0.018 0.007 0.005 0.006 0.002 0.006 0.008
20 2 0.478 0.521 0.073 0.088 0.075 0.083 0.052 0.054 0.015 0.015
20 11 0.584 0.721 0.451 0.444 0.531 0.540 0.677 0.669 0.035 0.046
20 14 0.560 0.460 0.015 0.018 0.513 0.492 0.031 0.024 0.924 0.920
21 0 0.535 0.845 0.152 0.155 0.194 0.190 0.062 0.035 0.051 0.055
21 6 0.654 0.721 0.009 0.011 0.353 0.345 0.007 0.010 0.132 0.136
23 1 0.436 0.231 0.197 0.187 0.125 0.100 0.111 0.101 0.027 0.021
23 4 0.019 0.072 0.117 0.126 0.081 0.092 0.010 0.013 0.017 0.020
23 11 0.423 0.215 0.178 0.151 0.118 0.125 0.029 0.032 0.063 0.062
24 5 0.149 0.618 0.089 0.112 0.019 0.027 0.272 0.278 0.064 0.071
24 9 0.825 0.850 0.212 0.220 0.312 0.335 0.024 0.021 0.078 0.061
26 9 0.379 0.805 0.010 0.007 0.062 0.069 0.019 0.011 0.069 0.057
26 18 0.716 0.974 0.053 0.044 0.119 0.115 0.270 0.226 0.168 0.159
26 25 0.672 0.544 0.065 0.065 0.036 0.035 0.273 0.267 0.163 0.164
26 28 0.157 0.486 0.002 0.001 0.000 0.000 0.332 0.325 0.035 0.030
26 32 0.220 0.023 0.520 0.536 0.112 0.098 0.463 0.455 0.000 0.000
27 8 0.821 0.639 0.024 0.025 0.047 0.053 0.144 0.173 0.313 0.316
27 9 0.876 0.897 0.081 0.063 0.355 0.385 0.044 0.047 0.259 0.281
32 1 0.608 0.897 0.176 0.189 0.022 0.025 0.098 0.091 0.015 0.007
32 11 0.471 0.515 0.019 0.017 0.024 0.024 0.114 0.109 0.172 0.172
32 18 0.968 0.779 0.592 0.630 0.171 0.202 0.152 0.128 0.023 0.025
32 33 0.689 0.947 0.166 0.186 0.115 0.114 0.111 0.106 0.052 0.045
32 37 0.099 0.115 0.222 0.254 0.146 0.124 0.049 0.055 0.028 0.028
38 1 0.475 0.673 0.144 0.142 0.342 0.356 0.010 0.019 0.049 0.048
38 5 0.560 0.560 0.165 0.177 0.044 0.046 0.238 0.235 0.186 0.189
38 8 0.736 0.909 0.070 0.076 0.030 0.030 0.284 0.309 0.835 0.836
38 10 0.689 0.753 0.018 0.023 0.038 0.032 0.037 0.041 0.039 0.044
38 17 0.123 0.312 0.313 0.293 0.372 0.369 0.008 0.009 0.023 0.030
39 8 0.328 0.121 0.014 0.015 0.013 0.016 0.022 0.023 0.019 0.013
39 9 0.628 0.649 0.086 0.068 0.117 0.087 0.048 0.036 0.109 0.103
41 1 0.000 0.000 0.103 0.090 0.025 0.032 0.160 0.170 0.059 0.067
41 2 0.369 0.507 0.078 0.060 0.192 0.220 0.030 0.034 0.159 0.181
41 10 0.202 0.833 0.412 0.426 0.040 0.031 0.223 0.244 0.019 0.021
41 11 0.562 0.626 0.765 0.723 0.738 0.751 0.020 0.023 0.004 0.003
41 13 0.919 0.868 0.039 0.024 0.006 0.006 0.117 0.112 0.022 0.018
43 1 0.288 0.471 0.019 0.014 0.109 0.115 0.046 0.040 0.100 0.098
43 10 0.455 0.293 0.055 0.062 0.077 0.066 0.042 0.035 0.036 0.035
43 12 0.668 0.641 0.362 0.366 0.105 0.085 0.048 0.057 0.017 0.046
44 0 0.009 0.218 0.335 0.325 0.334 0.323 0.092 0.109 0.040 0.046
44 3 0.051 0.128 0.082 0.097 0.305 0.305 0.023 0.020 0.041 0.031
44 10 0.072 0.286 0.414 0.419 0.109 0.094 0.060 0.035 0.035 0.048
44 13 0.254 0.404 0.146 0.131 0.054 0.037 0.055 0.065 0.061 0.055
44 14 0.295 0.061 0.158 0.149 0.170 0.189 0.123 0.132 0.043 0.037
47 0 0.672 0.333 0.042 0.039 0.098 0.137 0.368 0.371 0.596 0.599
47 2 0.209 0.254 0.269 0.261 0.006 0.013 0.214 0.215 0.069 0.065
47 5 0.645 0.422 0.030 0.028 0.217 0.259 0.269 0.244 0.974 0.967
47 6 0.221 0.096 0.016 0.013 0.000 0.000 0.175 0.170 0.053 0.043
47 12 0.526 0.185 0.087 0.100 0.067 0.063 0.087 0.090 0.052 0.039
48 2 0.304 0.351 0.767 0.776 0.228 0.247 0.479 0.491 0.021 0.032
48 15 0.611 0.374 0.053 0.044 0.028 0.029 0.061 0.059 0.053 0.041
48 17 0.088 0.127 0.461 0.420 0.240 0.258 0.082 0.090 0.022 0.023
48 26 0.470 0.352 0.180 0.167 0.229 0.210 0.047 0.062 0.112 0.086
48 30 0.064 0.022 0.063 0.052 0.047 0.041 0.933 0.935 0.796 0.807
48 33 0.056 0.226 0.167 0.180 0.396 0.423 0.036 0.038 0.118 0.122
48 38 0.125 0.558 0.561 0.529 0.121 0.112 0.543 0.550 0.034 0.037
49 16 0.066 0.197 0.042 0.042 0.077 0.072 0.151 0.167 0.336 0.305
49 18 0.246 0.560 0.049 0.050 0.116 0.111 0.138 0.133 0.175 0.189
52 1 0.781 0.776 0.214 0.276 0.120 0.116 0.173 0.141 0.040 0.029
52 4 0.540 0.582 0.369 0.384 0.007 0.011 0.138 0.168 0.000 0.001
52 5 0.676 0.598 0.071 0.083 0.027 0.025 0.021 0.025 0.005 0.009
52 13 0.483 0.346 0.072 0.073 0.038 0.032 0.170 0.211 0.169 0.164
52 17 0.741 0.244 0.053 0.043 0.046 0.060 0.011 0.008 0.043 0.037
54 0 0.012 0.072 0.084 0.075 0.082 0.085 0.058 0.057 0.040 0.043
54 7 0.002 0.003 0.032 0.032 0.000 0.001 0.145 0.132 0.000 0.003
54 8 0.071 0.020 0.046 0.055 0.039 0.047 0.309 0.298 0.271 0.266
54 12 0.002 0.006 0.413 0.422 0.028 0.027 0.680 0.666 0.089 0.090
54 15 0.468 0.485 0.039 0.035 0.067 0.071 0.476 0.491 0.529 0.536
54 16 0.779 0.560 0.022 0.019 0.059 0.058 0.016 0.018 0.022 0.021
55 3 0.729 0.696 0.202 0.208 0.039 0.053 0.055 0.076 0.013 0.008
55 6 0.027 0.303 0.026 0.028 0.016 0.008 0.039 0.039 0.054 0.067
55 19 0.165 0.432 0.039 0.032 0.064 0.067 0.073 0.069 0.088 0.086
55 20 0.821 0.966 0.036 0.032 0.024 0.023 0.039 0.026 0.044 0.053
55 21 0.468 0.185 0.031 0.037 0.092 0.079 1.000 1.000 0.513 0.558
55 24 0.846 0.897 0.039 0.049 0.040 0.043 0.027 0.038 0.057 0.053
55 29 0.900 0.935 0.057 0.054 0.075 0.083 0.035 0.030 0.074 0.063
56 4 0.002 0.021 0.826 0.828 0.031 0.032 0.846 0.834 0.058 0.070
58 9 0.758 0.827 0.232 0.249 0.007 0.005 0.492 0.491 0.234 0.236
58 21 0.394 0.659 0.006 0.003 0.002 0.006 0.002 0.000 0.001 0.001
58 24 0.444 0.741 0.047 0.045 0.121 0.123 0.129 0.138 0.445 0.459
58 28 0.000 0.000 0.215 0.218 0.047 0.049 0.214 0.213 0.146 0.131
58 30 0.527 0.424 0.022 0.029 0.020 0.014 0.127 0.126 0.164 0.138
59 17 0.110 0.031 0.114 0.089 0.029 0.035 0.092 0.095 0.023 0.034
60 0 0.263 0.272 0.040 0.038 0.062 0.069 0.062 0.054 0.078 0.085
60 3 0.512 0.829 0.051 0.049 0.147 0.136 0.025 0.019 0.080 0.065
60 11 0.163 0.029 0.084 0.123 0.047 0.060 0.116 0.122 0.043 0.033
60 15 0.169 0.091 0.528 0.515 0.916 0.921 0.037 0.046 0.166 0.155
61 2 0.101 0.215 0.035 0.040 0.081 0.086 0.043 0.042 0.053 0.062
61 3 0.327 0.851 0.083 0.106 0.075 0.054 0.020 0.016 0.022 0.015
63 2 0.764 0.883 0.263 0.244 0.071 0.065 0.059 0.055 0.010 0.008
63 9 0.149 0.175 0.046 0.041 0.187 0.179 0.068 0.069 0.155 0.151
63 11 0.999 0.761 0.020 0.017 0.011 0.010 0.062 0.052 0.066 0.078
64 10 0.686 0.501 0.868 0.838 0.134 0.132 0.473 0.502 0.028 0.026
64 19 0.036 0.218 0.065 0.048 0.249 0.260 0.186 0.155 0.634 0.619
64 21 0.877 0.836 0.007 0.007 0.066 0.050 0.013 0.013 0.094 0.087
64 32 0.126 0.135 0.047 0.045 0.247 0.241 0.060 0.072 0.279 0.298
66 2 0.798 0.462 0.016 0.012 0.044 0.040 0.004 0.004 0.114 0.111
67 13 0.663 0.422 0.035 0.060 0.004 0.006 0.179 0.170 0.007 0.010
67 14 0.645 0.169 0.061 0.049 0.446 0.457 0.312 0.294 0.891 0.879
68 6 0.445 0.335 0.756 0.776 0.739 0.743 0.045 0.049 0.091 0.094
68 13 0.284 0.539 0.104 0.091 0.003 0.007 0.255 0.258 0.043 0.036
69 4 0.007 0.009 0.156 0.178 0.079 0.101 0.064 0.088 0.040 0.047
69 14 0.524 0.940 0.841 0.850 0.043 0.049 0.714 0.705 0.109 0.095
69 15 0.991 0.887 0.088 0.081 0.043 0.037 0.100 0.100 0.016 0.010
69 22 0.516 0.360 0.051 0.042 0.201 0.192 0.110 0.121 0.388 0.366
69 30 0.462 0.396 0.030 0.035 0.058 0.055 0.147 0.145 0.356 0.351
69 51 0.567 0.277 0.020 0.014 0.013 0.005 0.003 0.003 0.004 0.005
69 56 0.197 0.212 0.317 0.320 0.125 0.108 0.032 0.025 0.028 0.030
69 61 0.833 0.207 0.569 0.583 0.029 0.033 0.825 0.829 0.536 0.515
70 12 0.041 0.129 0.195 0.204 0.056 0.052 0.369 0.350 0.048 0.046
70 16 0.320 0.182 0.025 0.035 0.055 0.069 0.019 0.024 0.031 0.034
70 26 0.150 0.310 0.469 0.449 0.033 0.033 0.219 0.226 0.008 0.014
74 4 0.131 0.168 0.092 0.084 0.108 0.146 0.042 0.057 0.311 0.336
74 6 0.414 0.567 0.138 0.123 0.106 0.120 0.029 0.033 0.022 0.019
74 22 0.020 0.075 0.078 0.066 0.009 0.008 0.092 0.086 0.023 0.018
74 28 0.226 0.037 0.017 0.022 0.103 0.079 0.201 0.201 0.111 0.100
74 34 0.433 0.644 0.104 0.103 0.055 0.063 0.055 0.072 0.045 0.033
76 3 0.030 0.022 0.230 0.210 0.036 0.025 0.186 0.173 0.009 0.016
76 5 0.120 0.144 0.338 0.371 0.063 0.052 0.057 0.057 0.007 0.003
76 12 0.035 0.093 0.075 0.077 0.145 0.130 0.020 0.011 0.021 0.024
76 15 0.074 0.046 0.141 0.163 0.063 0.065 0.090 0.104 0.022 0.025
76 24 0.025 0.032 0.247 0.248 0.500 0.488 0.050 0.045 0.110 0.085
76 26 0.524 0.133 0.227 0.238 0.010 0.023 0.321 0.297 0.085 0.089
76 30 0.093 0.292 0.061 0.064 0.000 0.000 0.099 0.093 0.007 0.005
76 31 0.309 0.247 0.044 0.039 0.154 0.146 0.198 0.194 0.376 0.375
76 36 0.001 0.004 0.478 0.471 0.027 0.024 0.468 0.478 0.039 0.027
76 37 0.056 0.099 0.664 0.666 0.059 0.075 0.205 0.172 0.000 0.000
76 39 0.081 0.190 1.000 1.000 0.045 0.048 0.863 0.879 0.076 0.062
76 44 0.496 0.600 0.302 0.330 0.044 0.049 0.074 0.061 0.027 0.020
77 2 0.006 0.000 0.800 0.807 0.004 0.002 0.655 0.635 0.002 0.005
77 3 0.666 0.808 0.438 0.437 0.274 0.288 0.088 0.090 0.024 0.020
77 6 0.353 0.145 0.024 0.024 0.071 0.080 0.016 0.034 0.167 0.153
77 7 0.468 0.039 0.037 0.030 0.057 0.061 0.073 0.082 0.155 0.153
77 25 0.185 0.295 0.459 0.469 0.045 0.038 0.195 0.209 0.019 0.030
78 2 0.002 0.000 0.377 0.402 0.145 0.118 0.104 0.091 0.010 0.007
78 10 0.160 0.058 0.367 0.393 0.192 0.192 0.334 0.343 0.047 0.043
78 11 0.222 0.122 0.024 0.024 0.029 0.027 0.084 0.069 0.117 0.133
85 3 0.124 0.119 0.236 0.234 0.110 0.116 0.192 0.194 0.040 0.054
92 6 0.360 0.495 0.036 0.033 0.045 0.046 0.036 0.024 0.037 0.029
93 3 0.429 0.684 0.165 0.190 0.065 0.053 0.394 0.405 0.035 0.037
93 5 0.892 0.871 0.203 0.206 0.038 0.039 0.096 0.073 0.005 0.006
93 8 0.622 0.776 0.006 0.003 0.028 0.036 0.024 0.021 0.186 0.174
93 10 0.038 0.018 0.261 0.239 0.485 0.480 0.029 0.020 0.481 0.488
93 21 0.030 0.098 0.011 0.006 0.055 0.049 0.118 0.136 0.068 0.059
96 7 0.000 0.000 0.006 0.011 0.005 0.002 0.025 0.015 0.014 0.020
99 5 0.353 0.317 0.014 0.019 0.080 0.085 0.012 0.006 0.043 0.051
99 6 0.614 0.442 0.014 0.017 0.037 0.034 0.010 0.007 0.032 0.027
99 14 0.004 0.001 0.012 0.011 0.028 0.039 0.030 0.029 0.132 0.129
99 20 0.073 0.069 0.011 0.014 0.001 0.003 0.032 0.019 0.023 0.018
99 21 0.368 0.305 0.094 0.103 0.021 0.021 0.173 0.194 0.041 0.038
99 22 0.002 0.009 0.002 0.002 0.005 0.007 0.017 0.019 0.020 0.037
100 1 0.823 0.408 0.021 0.028 0.001 0.004 0.043 0.039 0.080 0.087
100 2 0.058 0.074 0.101 0.105 0.013 0.010 0.032 0.026 0.000 0.000
100 10 0.013 0.011 0.794 0.782 0.047 0.061 0.971 0.963 0.083 0.077
100 18 0.137 0.069 0.354 0.321 0.390 0.404 0.018 0.023 0.022 0.025
100 40 0.064 0.033 0.021 0.028 0.012 0.021 0.184 0.190 0.126 0.141
100 41 0.137 0.339 0.382 0.414 0.242 0.218 0.029 0.019 0.088 0.083
101 6 0.725 0.927 0.104 0.102 0.046 0.034 0.009 0.014 0.009 0.009
101 12 0.344 0.180 0.001 0.005 0.001 0.001 0.000 0.003 0.001 0.002
102 3 0.623 0.540 0.076 0.071 0.035 0.045 0.033 0.028 0.030 0.044
102 4 0.126 0.287 0.280 0.296 0.135 0.154 0.058 0.069 0.017 0.012
102 22 0.642 0.315 0.351 0.363 0.664 0.652 0.369 0.340 0.023 0.030
102 38 0.320 0.167 0.096 0.089 0.015 0.015 0.052 0.038 0.007 0.009
102 40 0.171 0.326 0.013 0.006 0.066 0.061 0.025 0.021 0.160 0.144
102 41 0.354 0.665 0.061 0.055 0.186 0.190 0.047 0.046 0.097 0.069
104 2 0.036 0.122 0.081 0.065 0.005 0.005 0.272 0.254 0.020 0.020
104 3 0.085 0.048 0.374 0.375 0.328 0.341 0.028 0.030 0.081 0.078
104 15 0.079 0.009 0.060 0.038 0.126 0.128 0.057 0.035 0.106 0.099
104 16 0.612 0.415 0.169 0.186 0.163 0.174 0.033 0.026 0.034 0.038
104 20 0.105 0.103 0.633 0.641 0.030 0.037 0.562 0.569 0.033 0.032
104 22 0.006 0.007 0.041 0.045 0.066 0.048 0.006 0.007 0.018 0.021
104 23 0.348 0.271 0.074 0.088 0.177 0.172 0.026 0.019 0.033 0.028
104 24 0.661 0.190 0.032 0.042 0.120 0.132 0.765 0.778 0.771 0.756
104 36 0.001 0.002 0.043 0.043 0.060 0.060 0.086 0.090 0.189 0.166
105 7 0.000 0.001 0.057 0.071 0.003 0.001 0.063 0.087 0.000 0.001
105 8 0.025 0.048 0.219 0.202 0.124 0.131 0.144 0.156 0.048 0.047
105 9 0.000 0.002 0.086 0.093 0.061 0.055 0.080 0.073 0.028 0.023
105 10 0.758 0.471 0.028 0.025 0.602 0.590 0.042 0.046 0.904 0.910
105 12 0.976 0.861 0.007 0.010 0.026 0.033 0.007 0.004 0.009 0.012
105 18 0.966 0.996 0.013 0.011 0.028 0.027 0.133 0.139 0.168 0.162
105 19 0.487 0.883 0.674 0.701 0.032 0.042 0.863 0.864 0.282 0.274
105 25 0.044 0.162 0.000 0.002 0.018 0.007 0.000 0.002 0.003 0.005
105 30 0.677 0.296 0.219 0.187 0.070 0.066 0.199 0.252 0.044 0.041
105 37 0.246 0.450 0.048 0.051 0.082 0.079 0.049 0.050 0.181 0.178
105 41 0.170 0.382 0.017 0.011 0.042 0.048 0.066 0.068 0.225 0.224
111 3 0.518 0.674 0.058 0.049 0.218 0.226 0.076 0.079 0.499 0.504
111 9 0.685 0.130 0.005 0.004 0.000 0.001 0.030 0.029 0.014 0.026
111 12 0.539 0.966 0.078 0.075 0.501 0.506 0.018 0.022 0.364 0.347
112 6 0.779 0.794 0.345 0.354 0.372 0.359 0.014 0.021 0.023 0.036
113 5 0.879 0.974 0.167 0.149 0.268 0.272 0.070 0.061 0.022 0.018
113 10 0.634 0.674 0.119 0.126 0.200 0.178 0.035 0.041 0.155 0.134
114 0 0.000 0.000 0.080 0.079 0.009 0.015 0.154 0.133 0.026 0.019
114 5 0.082 0.009 0.386 0.335 0.016 0.019 0.189 0.192 0.045 0.046
114 6 0.671 0.448 0.627 0.623 0.106 0.093 0.137 0.150 0.005 0.007
115 5 0.374 0.132 0.225 0.220 0.156 0.155 0.053 0.049 0.021 0.022
115 17 0.997 0.660 0.077 0.085 0.056 0.042 0.070 0.086 0.035 0.037
115 20 0.071 0.171 0.027 0.026 0.016 0.021 0.090 0.082 0.070 0.073
115 22 0.097 0.323 0.005 0.006 0.010 0.018 0.009 0.006 0.007 0.004
115 32 0.042 0.079 0.002 0.006 0.005 0.006 0.007 0.004 0.007 0.013
116 3 0.787 0.135 0.156 0.150 0.046 0.041 0.022 0.024 0.003 0.011
116 8 0.778 0.438 0.013 0.020 0.138 0.143 0.014 0.019 0.096 0.093
116 14 0.523 0.229 0.004 0.004 0.027 0.023 0.423 0.431 0.784 0.782
116 16 0.777 0.357 0.505 0.522 0.170 0.155 0.119 0.107 0.021 0.030
116 28 0.544 0.584 0.059 0.043 0.049 0.064 0.230 0.221 0.283 0.317
116 29 0.156 0.407 0.060 0.040 0.177 0.168 0.183 0.208 0.404 0.423
117 2 0.233 0.219 0.015 0.012 0.001 0.000 0.006 0.003 0.000 0.000
117 3 0.237 0.124 0.010 0.019 0.004 0.002 0.003 0.006 0.001 0.000
117 8 0.428 0.131 0.008 0.017 0.001 0.000 0.004 0.003 0.000 0.000
117 10 0.056 0.052 0.169 0.179 0.107 0.106 0.017 0.022 0.036 0.043
118 5 0.389 0.541 0.063 0.049 0.003 0.001 0.675 0.686 0.353 0.352
118 10 0.741 0.530 0.351 0.359 0.095 0.118 0.055 0.062 0.039 0.039
119 8 0.507 0.236 0.026 0.024 0.024 0.014 0.033 0.024 0.035 0.041
119 9 0.087 0.105 0.032 0.049 0.078 0.099 0.147 0.143 0.230 0.236
122 26 0.716 0.955 0.186 0.207 0.388 0.421 0.057 0.048 0.186 0.188
123 3 0.540 0.764 0.045 0.053 0.382 0.375 0.029 0.034 0.462 0.472
124 1 0.291 0.181 0.140 0.147 0.042 0.045 0.221 0.236 0.336 0.327
124 12 0.159 0.027 0.010 0.016 0.037 0.028 0.041 0.037 0.040 0.043
124 21 0.215 0.379 0.035 0.033 0.007 0.008 0.390 0.389 0.295 0.241
124 23 0.518 0.533 0.515 0.491 0.893 0.908 0.047 0.054 0.137 0.111
124 32 0.131 0.013 0.285 0.328 0.039 0.049 0.203 0.201 0.035 0.030
125 2 0.472 0.444 0.072 0.044 0.163 0.182 0.049 0.059 0.066 0.049
125 9 0.657 0.571 0.010 0.015 0.020 0.024 0.015 0.014 0.029 0.038
125 20 0.668 0.775 0.069 0.069 0.001 0.005 0.224 0.242 0.066 0.048
125 41 0.301 0.184 0.297 0.334 0.010 0.008 0.313 0.306 0.047 0.052
125 43 0.403 0.508 0.912 0.899 0.021 0.038 0.702 0.679 0.252 0.223
126 6 0.139 0.036 0.016 0.015 0.176 0.170 0.027 0.025 0.221 0.260
126 8 0.038 0.019 0.016 0.009 0.020 0.023 0.126 0.137 0.182 0.170
126 10 0.346 0.559 0.247 0.253 0.060 0.063 0.131 0.115 0.037 0.037
126 11 0.269 0.719 0.039 0.035 0.108 0.105 0.023 0.028 0.085 0.072
126 29 0.546 0.406 0.027 0.045 0.018 0.014 0.107 0.085 0.089 0.090
126 31 0.359 0.665 0.018 0.019 0.035 0.046 0.258 0.276 0.483 0.516
126 32 0.969 0.649 0.257 0.248 0.011 0.023 0.185 0.192 0.067 0.065
127 5 0.955 0.979 0.000 0.002 0.041 0.029 0.071 0.076 0.358 0.371
128 0 0.018 0.129 0.222 0.206 0.005 0.007 0.135 0.127 0.015 0.029
130 5 0.733 0.326 0.099 0.081 0.056 0.057 0.059 0.064 0.028 0.040
130 10 0.163 0.044 0.471 0.474 0.021 0.018 0.753 0.750 0.322 0.314
131 5 0.032 0.401 0.164 0.142 0.027 0.032 0.154 0.143 0.023 0.026
133 1 0.487 0.589 0.021 0.021 0.036 0.027 0.045 0.042 0.020 0.030
134 5 0.018 0.048 0.087 0.085 0.218 0.257 0.042 0.045 0.108 0.085
134 11 0.133 0.605 0.439 0.449 0.445 0.436 0.055 0.039 0.085 0.073
136 1 0.524 0.809 0.591 0.599 0.041 0.040 0.642 0.615 0.107 0.093
2001 2 0.668 0.935 0.040 0.047 0.038 0.032 0.112 0.114 0.084 0.066
2001 6 0.457 0.333 0.050 0.044 0.199 0.187 0.311 0.281 0.525 0.488
2001 7 0.622 0.655 0.099 0.082 0.015 0.014 0.856 0.860 0.248 0.288
2003 4 0.933 0.719 1.000 1.000 0.046 0.059 0.395 0.377 0.021 0.012
2004 2 0.307 0.329 0.041 0.032 0.586 0.584 0.117 0.128 0.496 0.482
2004 4 0.006 0.008 0.070 0.036 0.040 0.055 0.218 0.202 0.145 0.134
2004 11 0.318 0.154 0.160 0.138 0.019 0.015 0.008 0.009 0.227 0.192
2004 12 0.427 0.653 0.009 0.008 0.000 0.002 0.002 0.002 0.000 0.000
2004 13 0.063 0.090 0.030 0.033 0.000 0.000 0.015 0.018 0.000 0.000
2004 14 0.794 0.787 0.011 0.004 0.000 0.000 0.001 0.000 0.000 0.000
2004 17 0.403 0.043 0.791 0.801 0.051 0.036 0.712 0.696 0.017 0.021
2004 19 0.613 0.774 0.031 0.033 0.000 0.000 0.000 0.000 0.000 0.000
2004 21 0.016 0.016 0.117 0.134 0.397 0.401 0.044 0.045 0.287 0.326
2004 30 0.056 0.205 0.200 0.180 0.004 0.006 0.136 0.132 0.005 0.010
2005 2 0.099 0.088 0.011 0.016 0.036 0.035 0.000 0.001 0.008 0.009
Result: not reproduced. The test the Methods describe, all 50 new and 50 old trials in a 1 s window, finds 110 of 1864 units (5.9%), outside the tolerance of 146 (7.8%). This is not distinguishable from the 5.4% found with shuffled labels (one-sided binomial p = 0.18) and only just above 5% (p = 0.044). A second bootstrap seed gives 118 (6.3%), also outside the tolerance. The code's choices, correct trials only in a 1.5 s window, give 147 and 149 (7.9% and 8.0%) under the two seeds, and correct trials in the Methods' window 143 and 140, all matching the paper. The code's choices also exceed their own null, labels shuffled among the correct trials in the code's window (5.58%, 147 against 104.1 expected, one-sided binomial p = 2.5e-05). The outcome comes from the Methods disagreeing with the paper's code on the trials used, not from the data: the paper's number follows its code.
dual-cells-proportion¶
26 cells (1.4%) are both visually and memory selective.
dual = cells.vs & cells.ms
print("main:", share(dual), " paper: 26 of 1863 (1.4%)")
print("expected if VS and MS were independent:", f"{len(cells) * cells.vs.mean() * cells.ms.mean():.1f} units")
print("variant, code's choices:", share((cells.p_vs_code < 0.05) & (cells.p_ms_code_correct < 0.05)))
print("variant, duplicated spike trains counted once:", share(dual[cells.once], cells[cells.once]))
print("seed 2, main:", share(cells.vs & (cells.p_ms_methods_all_seed2 < 0.05)),
"; code's choices:", share((cells.p_vs_code < 0.05) & (cells.p_ms_code_correct_seed2 < 0.05)))
main: 12 of 1864 (0.6%) paper: 26 of 1863 (1.4%) expected if VS and MS were independent: 19.6 units variant, code's choices: 26 of 1864 (1.4%) variant, duplicated spike trains counted once: 11 of 1809 (0.6%) seed 2, main: 14 of 1864 (0.8%) ; code's choices: 26 of 1864 (1.4%)
Result: not reproduced. Under the Methods' tests 12 units (0.6%) are both VS and MS (14 with the second bootstrap seed), outside the tolerance of 26 (1.4%) and below the 19.6 expected if the two were independent. The code's choices give 26 (1.4%) under both seeds, the paper's numbers. As for ms-cells-proportion, the outcome comes from the Methods disagreeing with the code, not from the data.
fig5f-site-proportions¶
The percentages of VS, MS and dual cells are comparable between CS sessions (18.4%, 7.7% and 1.4%) and the new TWH sessions (19.4%, 10.4% and 1.3%).
cells["dual"] = cells.vs & cells.ms
PAPER = {("vs", "CS"): 18.4, ("ms", "CS"): 7.7, ("dual", "CS"): 1.4, ("vs", "TWH"): 19.4, ("ms", "TWH"): 10.4, ("dual", "TWH"): 1.3}
within = 0
for kind in ("vs", "ms", "dual"):
a = cells[cells.site == "CS"][kind]; b = cells[cells.site == "TWH"][kind]
tab2 = [[int(a.sum()), int((~a).sum())], [int(b.sum()), int((~b).sum())]]
p = stats.fisher_exact(tab2).pvalue
for site, v in (("CS", a), ("TWH", b)):
ok = abs(100 * v.mean() - PAPER[(kind, site)]) <= 0.1 * PAPER[(kind, site)]
within += ok
print(f"{kind:4s} {site:3s}: {int(v.sum())} of {len(v)} = {100 * v.mean():.1f}% (paper {PAPER[(kind, site)]}%, within 10%: {ok})")
print(f" Fisher's exact test CS against TWH: p = {p:.3f}")
print("values within tolerance:", within, "of 6")
code_sel = {"vs": cells.p_vs_code < 0.05, "ms": cells.p_ms_code_correct < 0.05}
code_sel["dual"] = code_sel["vs"] & code_sel["ms"]
for kind, sel in code_sel.items():
print(f"variant, code's choices, {kind}:", cells.assign(x=sel).groupby("site").x.apply(lambda v: f"{int(v.sum())} of {len(v)} = {100 * v.mean():.1f}%").to_dict())
print("\n95% Clopper-Pearson intervals of the main percentages:")
for kind in ("vs", "ms", "dual"):
for site in ("CS", "TWH"):
v = cells[cells.site == site][kind]
ci = stats.binomtest(int(v.sum()), len(v)).proportion_ci()
print(f" {kind:4s} {site:3s}: {100 * v.mean():.1f}% ({100 * ci.low:.1f} to {100 * ci.high:.1f})")
vs CS : 315 of 1787 = 17.6% (paper 18.4%, within 10%: True)
vs TWH: 17 of 77 = 22.1% (paper 19.4%, within 10%: False)
Fisher's exact test CS against TWH: p = 0.360
ms CS : 103 of 1787 = 5.8% (paper 7.7%, within 10%: False)
ms TWH: 7 of 77 = 9.1% (paper 10.4%, within 10%: False)
Fisher's exact test CS against TWH: p = 0.215
dual CS : 12 of 1787 = 0.7% (paper 1.4%, within 10%: False)
dual TWH: 0 of 77 = 0.0% (paper 1.3%, within 10%: False)
Fisher's exact test CS against TWH: p = 1.000
values within tolerance: 1 of 6
variant, code's choices, vs: {'CS': '328 of 1787 = 18.4%', 'TWH': '15 of 77 = 19.5%'}
variant, code's choices, ms: {'CS': '139 of 1787 = 7.8%', 'TWH': '8 of 77 = 10.4%'}
variant, code's choices, dual: {'CS': '25 of 1787 = 1.4%', 'TWH': '1 of 77 = 1.3%'}
95% Clopper-Pearson intervals of the main percentages:
vs CS : 17.6% (15.9 to 19.5)
vs TWH: 22.1% (13.4 to 33.0)
ms CS : 5.8% (4.7 to 6.9)
ms TWH: 9.1% (3.7 to 17.8)
dual CS : 0.7% (0.3 to 1.2)
dual TWH: 0.0% (0.0 to 4.7)
Result: partially reproduced. The sites do not differ by Fisher's exact test (VS p = 0.360, MS p = 0.215, dual p = 1.000), but only 1 of the 6 site values is within tolerance under the Methods' tests (CS VS 17.6%). The code's choices give CS 18.4%, 7.8% and 1.4% and TWH 19.5%, 10.4% and 1.3%, the values of Figure 5f to within 0.1 point, so the partial outcome comes from the Methods disagreeing with the code. TWH has 77 units, so its percentages rest on 17, 7 and 0 cells under the main tests, with 95% intervals of 13.4% to 33.0%, 3.7% to 17.8% and 0.0% to 4.7%; the tests have little power, and "no difference" carries little weight.
twh-sessions¶
The dataset includes 9 sessions from a new institution, Toronto Western Hospital.
twh = info[info.site == "TWH"]
print("TWH sessions:", len(twh), twh.session.tolist(), "; subjects:", twh.subject.nunique(), "; units:", int(twh.units.sum()))
print("institution of these files:", twh.institution.unique().tolist())
TWH sessions: 9 [2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2011] ; subjects: 8 ; units: 77 institution of these files: ['Toronto Western Hospital']
Result: reproduced. Nine sessions (2001 to 2011) of 8 subjects come from Toronto Western Hospital, with 77 units.
fig4-example-vs-neuron¶
Neuron 132-177-4-RA (session 132, channel 177, cell 4, right amygdala) is visually selective, responding most to images of animals.
ex = cells[(cells.session == 132) & (cells.channel == 177)]
print(ex[["session", "unit", "channel", "electrode_row", "location", "p_vs_methods", "p_vs_code", "best_methods", "best_code"]].to_string(index=False))
s = by_id[132]; r = rm.recognition(s); k = int(ex.unit.iloc[0])
for wname, w in (("Methods' window", rm.WINDOW_METHODS), ("code's window", rm.WINDOW_CODE)):
x = rm.window_rates(s["spikes"][k], r.start_time.values, w)
print(wname, "mean rate (Hz) by category:", pd.Series(x).groupby(r.category_name.values).mean().round(2).to_dict())
print("units on channel 177 in session 132:", len(ex))
print("units of session 132 (0-based unit index, electrode row, channel):",
cells[cells.session == 132][["unit", "electrode_row", "channel"]].values.tolist())
session unit channel electrode_row location p_vs_methods p_vs_code best_methods best_code
132 4 177 4 Right Amygdala 1.197068e-22 7.885374e-19 5animals 5animals
Methods' window mean rate (Hz) by category: {'1cars': 0.45, '2food': 0.5, '3people': 0.85, '4spatial': 0.25, '5animals': 6.75}
code's window mean rate (Hz) by category: {'1cars': 0.4, '2food': 0.37, '3people': 0.7, '4spatial': 0.23, '5animals': 5.6}
units on channel 177 in session 132: 1
units of session 132 (0-based unit index, electrode row, channel): [[0, 0, 146], [1, 1, 148], [2, 2, 149], [3, 3, 150], [4, 4, 177], [5, 5, 181], [6, 6, 182], [7, 7, 183]]
Result: reproduced. The single unit on channel 177 of session 132 sits in the right amygdala at electrode row 4, and it is the fifth of the file's eight units (0-based index 4), so the label's cell number equals both its electrode row and its 0-based unit index. It is category selective (ANOVA p = 1.2e-22 in the Methods' window, 7.9e-19 in the code's) and fires most to animals (6.75 Hz against 0.25 to 0.85 Hz for the other categories).
previous-release-link¶
The sessions of the earlier release (Faraut et al. 2018) are found in this dandiset under the same subject, session id and task variant.
The earlier release's sessions come from Table 1 of Faraut et al. 2018 (doi:10.1038/sdata.2018.10), copied into the helper module. The task variant of a file is read from its stimulus set, through the variant Online-only Table 1 gives each stimulus set (shown under counts-sessions).
norm = lambda v: v.str.replace("CS", "C").str.replace(r"^([A-Z])0*", r"\1", regex=True)
early = rm.faraut_2018_table_1()
early["subject_norm"] = norm(early.table_subject)
first_cat = pd.Series({s["session"]: sorted(s["trials"].category_name.unique())[0] for s in sessions})
cat_to_variant = pd.crosstab(first_cat.rename("cat"), pd.Series(first_cat.index.map(var), index=first_cat.index, name="variant")).idxmax(axis=1)
files = info[["session", "label", "age", "sex"]].assign(subject_norm=lambda d: norm(d.label),
variant_file=lambda d: d.session.map(first_cat).map(cat_to_variant).values)
lk = early.merge(files, on="session", how="left", suffixes=("_2018", "_file"))
lk["found"] = lk.label.notna()
lk["agrees"] = lk.found & (lk.subject_norm_2018 == lk.subject_norm_file) & (lk.variant == lk.variant_file)
print("sessions in Faraut et al. 2018 Table 1:", len(early), "; subjects:", early.table_subject.nunique())
print("found in the dandiset by session id:", int(lk.found.sum()), "of", len(lk), f"({100 * lk.found.mean():.1f}%)")
print("found and agreeing on subject and task variant:", int(lk.agrees.sum()), "of", len(lk), f"({100 * lk.agrees.mean():.1f}%)")
print("\nnot found or not agreeing:")
print(lk[~lk.agrees][["table_subject", "session", "variant", "label", "variant_file"]].to_string(index=False))
subj18 = lk[lk.found].groupby("table_subject").agg(age_2018=("age_2018", "first"), sex_2018=("sex_2018", "first"),
age_file=("age_file", "first"), sex_file=("sex_file", "first"))
subj18["age_file"] = subj18.age_file.astype(int)
same = (subj18.age_2018 == subj18.age_file) & (subj18.sex_2018 == subj18.sex_file)
print("\nmatched subjects with the same age and sex in the files as in the 2018 table:", int(same.sum()), "of", len(subj18))
print(subj18[~same].to_string())
print("\nsession 98 in the code's session list:")
print(code_list[code_list.session == 98].to_string(index=False))
sessions in Faraut et al. 2018 Table 1: 65 ; subjects: 42
found in the dandiset by session id: 64 of 65 (98.5%)
found and agreeing on subject and task variant: 64 of 65 (98.5%)
not found or not agreeing:
table_subject session variant label variant_file
H48 98 2 NaN NaN
matched subjects with the same age and sex in the files as in the 2018 table: 39 of 42
age_2018 sex_2018 age_file sex_file
table_subject
C24 47 F 57 F
H33 29 M 30 M
H48 54 M 32 F
session 98 in the code's session list:
session active name ID age sex filename
98 False P48HMH_NOID98 H48 32 F P48HMH_NOID98.nwb
Result: partially reproduced. 64 of the 65 sessions of Faraut et al. 2018 (98.5%) are in the dandiset under the same subject and task variant. Session 98 of subject H48 is not; its section in the code's session list is commented out. Age and sex agree for 39 of the 42 matched subjects; C24, H33 and H48 differ, the same three subjects that differ from Online-only Table 1, and in each the two papers' tables agree with each other.
previous-release-additions¶
Relative to the earlier release of this task's data (Faraut et al. 2018), this release adds 23 sessions, 17 subjects and 288 neurons.
new_ses = info[~info.session.isin(early.session)]
old_subj = set(norm(info[info.session.isin(early.session)].label))
new_subj = sorted(set(norm(new_ses.label)) - old_subj)
print("new sessions:", len(new_ses), new_ses.session.tolist(), " paper: 23")
print("new subjects:", len(new_subj), new_subj, " paper: 17")
print("units in the new sessions:", int(new_ses.units.sum()), " paper: 288")
print(" of which CS:", int(new_ses[new_ses.site == "CS"].units.sum()), "in", int((new_ses.site == "CS").sum()), "sessions;",
"TWH:", int(new_ses[new_ses.site == "TWH"].units.sum()), "in", int((new_ses.site == "TWH").sum()), "sessions")
print("subjects of the new sessions that also have earlier sessions:", sorted(set(norm(new_ses.label)) & old_subj))
print("\nvariant, differences of the totals the two papers state (2020 minus 2018): sessions 87 - 65 =", 87 - 65,
"; subjects 59 - 42 =", 59 - 42, "; neurons 1863 - 1576 =", 1863 - 1576)
print("units in the dandiset's sessions that are in the 2018 table:", int(info[info.session.isin(early.session)].units.sum()),
" (Faraut et al. 2018: 1576 neurons in 65 sessions)")
print("Faraut et al. 2018 neurons plus the stated additions: 1576 + 288 =", 1576 + 288, "; unit rows in the dandiset:", int(info.units.sum()))
new sessions: 23 [122, 123, 124, 125, 126, 127, 128, 130, 131, 132, 133, 134, 135, 136, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2011] paper: 23 new subjects: 17 ['C53', 'C54', 'C55', 'C56', 'C57', 'C58', 'C60', 'C61', 'C62', 'T100', 'T101', 'T103', 'T107', 'T88', 'T89', 'T90', 'T98'] paper: 17 units in the new sessions: 325 paper: 288 of which CS: 248 in 14 sessions; TWH: 77 in 9 sessions subjects of the new sessions that also have earlier sessions: [] variant, differences of the totals the two papers state (2020 minus 2018): sessions 87 - 65 = 22 ; subjects 59 - 42 = 17 ; neurons 1863 - 1576 = 287 units in the dandiset's sessions that are in the 2018 table: 1539 (Faraut et al. 2018: 1576 neurons in 65 sessions) Faraut et al. 2018 neurons plus the stated additions: 1576 + 288 = 1864 ; unit rows in the dandiset: 1864
Result: partially reproduced. Against the session list of Faraut et al. 2018, the dandiset has 23 new sessions (14 CS, 9 TWH) from 17 new subjects, the paper's numbers. The new sessions hold 325 units, outside the tolerance of 288; the sessions also in the earlier release hold 1539, where Faraut et al. 2018 gives 1576 neurons for its 65 sessions. The paper's 288 equals 1864 minus 1576, which suggests it is a difference of totals, not a count of the new sessions. The missing session 98 (previous-release-link) does not change the new sessions or subjects; it may account for part of the 37 units fewer in the earlier sessions.
fig3-example-vs-neuron (blocked)¶
Neuron 144-140-3-RH (session 144, channel 140, cell 3, right hippocampus) is visually selective for phones.
Session 144 is not in the dandiset (data check) or in Online-only Table 1, so the claim is blocked.
As a weak check of whether the neuron is in the dandiset under another session id, every unit on
original channel 140 in the right hippocampus is listed with its category ANOVA and preferred
category. This does not decide the claim. The paper's code is read for the same purpose, not run: the
lines of its demo script python/export/demo/export.py that name session 144, and the code's
session list for subject T103, are printed. The cell number of the label is tested against the
Figure 4 neuron, whose label 132-177-4-RA has cell number 4.
cand = cells[(cells.channel == 140) & (cells.location == "Right Hippocampus")]
print("units on channel 140 in the right hippocampus:", len(cand))
cand = cand.assign(cluster=[int(by_id[a]["cluster_id"][k]) for a, k in zip(cand.session, cand.unit)])
print(cand[["session", "unit", "electrode_row", "cluster", "p_vs_methods", "best_methods", "p_vs_code", "best_code"]].to_string(index=False))
print("of these, VS (Methods' window) and preferring a phone category:",
int((cand.vs & cand.best_methods.str.contains("hone", case=False)).sum()))
print("category names containing 'phone':", sorted({n for s in sessions for n in s["trials"].category_name.unique() if "hone" in n.lower()}))
print("subject of session 2009:", by_id[2009]["label"], "; sessions of this subject:", info[info.label == by_id[2009]["label"]].session.tolist())
print("\nexport.py at the pinned commit, lines naming the session:",
[l for l in rm.code_file("python/export/demo/export.py").splitlines() if "144" in l or "TWH103" in l])
print("the code's session list, sections of subject T103:")
print(code_list[code_list.ID == "T103"].to_string(index=False))
print("\ncell number of the label against the Figure 4 neuron (label cell number 4):")
f4 = cells[(cells.session == 132) & (cells.channel == 177)].iloc[0]
print(f" Figure 4 unit: 0-based unit index {f4.unit}, 1-based {f4.unit + 1}, electrode row {f4.electrode_row}")
for _, c in cand.iterrows():
print(f" session {c.session} candidate: 0-based unit index {c.unit}, 1-based {c.unit + 1}, electrode row {c.electrode_row}")
print("units of session 2009 (0-based unit index, electrode row, channel):",
cells[cells.session == 2009][["unit", "electrode_row", "channel"]].values.tolist())
units on channel 140 in the right hippocampus: 2
session unit electrode_row cluster p_vs_methods best_methods p_vs_code best_code
2009 1 1 352 2.668321e-02 smallAnimal 4.351638e-03 smallAnimal
2009 2 1 414 1.344250e-09 phones 5.022522e-08 phones
of these, VS (Methods' window) and preferring a phone category: 1
category names containing 'phone': ['phones']
subject of session 2009: T103 ; sessions of this subject: [2009]
export.py at the pinned commit, lines naming the session: ["#session: 'TWH103_111918'", 'NOID = 144', "io = NWBHDF5IO('TWH103_111918.nwb', mode='w')"]
the code's session list, sections of subject T103:
session active name ID age sex filename
2009 True TWH103_NOID2009 T103 49 M TWH103_NOID2009.nwb
cell number of the label against the Figure 4 neuron (label cell number 4):
Figure 4 unit: 0-based unit index 4, 1-based 5, electrode row 4
session 2009 candidate: 0-based unit index 1, 1-based 2, electrode row 1
session 2009 candidate: 0-based unit index 2, 1-based 3, electrode row 1
units of session 2009 (0-based unit index, electrode row, channel): [[0, 0, 137], [1, 1, 140], [2, 1, 140]]
Result: blocked (metadata insufficient). No file has session 144. The paper's demo script sets NOID 144 for the session it names TWH103_111918, and the code's session list gives subject T103 one section, session 2009. In session 2009 two units are on channel 140 in the right hippocampus, both at electrode row 1; unit 2 is visually selective (p = 1.3e-09) and fires most to phones. The Figure 4 neuron sets the convention for the cell number: its cell number 4 is its electrode row and its 0-based unit index, and its 1-based index is 5. Under that convention the phone-selective unit is cell 2 (unit index) or 1 (electrode row), not 3; only its 1-based index is 3. The candidate matches the label's channel, area and category, but neither its cell number nor a recorded mapping of 144 to 2009 identifies it as the neuron of Figure 3.
Summary¶
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.
| Claim | Outcome |
|---|---|
| counts-neurons-subjects | reproduced |
| counts-sessions | reproduced |
| table1-demographics | partially reproduced |
| task-structure | partially reproduced |
| new-old-label-coding | not reproduced |
| ttl-codes | partially reproduced |
| experiment-ids | partially reproduced |
| session-dates | partially reproduced |
| units-table | partially reproduced |
| electrodes-table | not reproduced |
| isi-criterion | partially reproduced |
| fig5a-mtl-locations | reproduced |
| fig5c-auc-all | reproduced |
| fig5c-auc-sites | reproduced |
| fig5b-roc-asymmetric | reproduced |
| fig5d-confidence-accuracy | reproduced |
| vs-cells-proportion | reproduced |
| ms-cells-proportion | not reproduced |
| dual-cells-proportion | not reproduced |
| fig5f-site-proportions | partially reproduced |
| twh-sessions | reproduced |
| fig4-example-vs-neuron | reproduced |
| previous-release-link | partially reproduced (with outside input) |
| previous-release-additions | partially reproduced (with outside input) |
| fig3-example-vs-neuron | blocked: session 144 is not in the dandiset, and nothing maps it to a session there |
How the analysis differs from the paper's.
- Selectivity uses the Methods' window (200 to 1200 ms) and, for memory selectivity, all 50 new and 50 old trials. The paper's code uses 200 to 1700 ms and correct trials only; with these choices the counts are 343 VS, 147 MS and 26 dual cells, against the paper's 343, 146 and 26. The MS, dual and Figure 5f outcomes rest on the Methods' description, and come from its disagreement with the code.
- The z-ROC slope is averaged over the 81 sessions whose interior points allow a fit; the six sessions left out are shown as a variant.
- The bootstrap is the code's algorithm with a fixed seed; a second seed moves the main MS count from 110 to 118 and the code's choices from 147 to 149.
- The paper names no test for the ROC asymmetry or for "comparable" proportions; a t-test of the z-ROC slope against 1 and Fisher's exact test were chosen.
- Accuracy by confidence uses a paired test, as the Results say; the code runs an unpaired one, shown as a variant.
- Units are counted per file, as in the paper; counts with the duplicated spike trains of sessions 44 and 55 dropped are shown.
- The earlier release's sessions are taken from Table 1 of Faraut et al. 2018, not from its Dryad archive, which a script could not fetch.
What was found about the dandiset.
- The new/old label is coded 1 = old, the reverse of the paper's text and the files' own column description. A user following the description would compute an AUC of 0.25.
- Sessions 43 and 44, and 54 and 55, hold identical spike trains: the same neurons are deposited under two sessions, so 55 of the 1864 units are repeats. Session 55 also repeats session 54's learning block.
- Seven sessions have 50 encoding trials and one has 97, where the paper describes 100; part of the images later shown as old are missing from their encoding blocks.
- Electrode impedance is empty for every electrode, coordinates are missing for 99 electrodes, the isolation distance is NaN for 493 units, and SNR is not finite for 86.
- Spike times are unsorted in 13 units, and two units of session 58 hold each spike time twice.
- Age or sex differ from Online-only Table 1 for three subjects. One session date keeps its day, and another is set to 2000-01-01.
- Event codes outside Table 1 occur in nine files and experiment ids outside Table 2 in four.
- The Methods give 89 sessions where the paper's table and the files have 87, and the unit count is 1864 where the paper gives 1863.
- The paper's Figure 3 example neuron is from session 144, which is not in the dandiset. The paper's code names NOID 144 as a recording of subject T103, whose one session here is 2009; one unit of session 2009 on channel 140 in the right hippocampus prefers phones, but no record maps 144 to 2009.
- Session 98 of the earlier release (subject H48) is not in the dandiset; its section in the code's session list is commented out. The new sessions hold 325 units, not the 288 the paper gives.