Sit & Goard (2023) on dandiset 000483: head rotation and remapping (Figures 2 and 3)¶
Paper. Sit KK, Goard MJ (2023), Nature Communications 14:1992, doi:10.1038/s41467-023-37704-5; claims file written from the version of record without its supplement.
Dataset. DANDI:000483, version
0.230421.2321, the only published version (the paper cites none). See single_cue.ipynb for the
data check of all files.
| Claim | Here |
|---|---|
| fig2f-head-chamber-same-preference | tested |
| fig3c-coherent-remapping | tested |
Activity is inferred spikes computed from the raw fluorescence (no neuropil correction; see
rsc.py). Cells are selected by the heading-selection test with the centred fit, the reading
adopted in single_cue.ipynb (the code's uncentred fit only passes cells whose peak lies near
one heading, so preferred headings cannot be compared across conditions with it).
import os
for _v in ["OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"]:
os.environ[_v] = "1" # one BLAS thread, so that results do not depend on the number of cores
import time
import warnings
from multiprocessing import Pool
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import rsc
warnings.filterwarnings("ignore", category=RuntimeWarning)
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 30)
started = time.time()
cat = rsc.catalog(full=False)
sub = cat[cat.experiment.isin(["head_rotation", "chamber_rotation", "remapping"])].reset_index(drop=True)
recs = rsc.load_many(sub.url.tolist())
with Pool(4) as pool:
for r in recs:
r["dff"] = rsc.dff(r["F"])
r["sp"] = rsc.spikes(r["dff"], pool)
del r["F"]
print(f"dandiset {rsc.DANDISET_ID} version {rsc.DANDISET_VERSION}: {len(sub)} files loaded and deconvolved "
f"in {time.time() - started:.0f} s")
dandiset 000483 version 0.230421.2321: 38 files loaded and deconvolved in 196 s
rows = []
for m, r in zip(sub.itertuples(), recs):
h = rsc.clean_heading(r["heading"])
step = rsc.wrap180(np.diff(h))
rows.append(dict(experiment=m.experiment, subject=m.subject, identifier=m.identifier[:6] + " " + m.identifier.split("_")[-1],
frames=m.n_frames, rois=m.n_rois, heading_nan=int(np.isnan(r["heading"]).sum()),
heading_range=f"{np.nanmin(r['heading']):.0f}..{np.nanmax(r['heading']):.0f}",
median_step=round(float(np.median(step)), 2), direction=int(np.sign(np.median(step))),
trials=len(rsc.trial_segments(r["heading"])),
spikes_per_s=round(float(r["sp"].sum(0).mean() / (m.n_frames / 10)), 3)))
info = pd.DataFrame(rows)
print(info.to_string(index=False))
experiment subject identifier frames rois heading_nan heading_range median_step direction trials spikes_per_s
chamber_rotation SKKS089 210429 SKKS088-PlatformRotation-002 2600 118 23 -179..177 1.94 1 13 0.070
chamber_rotation SKKS090 210429 SKKS090-PlatformRotation-002 2600 167 24 -179..177 1.97 1 13 0.084
chamber_rotation SKKS091 210427 SKKS091-PlatformRotation-002 2550 135 25 -179..177 1.94 1 13 0.109
chamber_rotation SKKS091 210526 SKKS091-PlatformRotation-002 5100 128 53 -177..179 -2.01 -1 26 0.063
chamber_rotation SKKS112 210526 SKKS112-PlatformRotation-002 5100 384 24 -177..179 -0.12 -1 10 0.114
chamber_rotation SKKS112 210526 SKKS112-PlatformRotation-004 5100 293 53 -180..179 -2.02 -1 26 0.071
chamber_rotation SKKS116 210526 SKKS116-PlatformRotation-002 5100 211 50 -177..179 -1.96 -1 27 0.065
head_rotation SKKS089 210429 SKKS088-HeadRotation-001 2600 118 0 -180..180 0.68 1 5 0.214
head_rotation SKKS090 210429 SKKS090-HeadRotation-001 2600 167 0 -180..180 -1.05 -1 9 0.166
head_rotation SKKS091 210427 SKKS091-HeadRotation-001 2600 135 0 -180..180 1.12 1 9 0.164
head_rotation SKKS091 210526 SKKS091-HeadRotation-001 5100 128 0 -180..180 1.28 1 17 0.095
head_rotation SKKS112 210526 SKKS112-HeadRotation-001 5100 384 0 -180..180 1.26 1 16 0.890
head_rotation SKKS112 210526 SKKS112-HeadRotation-003 5100 293 0 -180..180 1.21 1 15 3.188
head_rotation SKKS116 210526 SKKS116-HeadRotation-001 5100 211 0 -180..180 1.22 1 16 0.106
remapping SKKS088 210405 SKKS088-Remapping-Stars-002 7550 346 78 -179..179 1.96 1 39 0.046
remapping SKKS088 210405 SKKS088-Remapping-Stripes-001 7550 346 74 -179..177 1.97 1 39 0.054
remapping SKKS088 210504 SKKS088-Remapping-1-001 7550 81 79 -179..177 2.03 1 40 0.086
remapping SKKS088 210504 SKKS088-Remapping-2-002 7550 81 80 -179..177 2.05 1 41 0.050
remapping SKKS088 210518 SKKS088-Remapping-1-001 7550 322 80 -179..180 2.02 1 40 0.064
remapping SKKS088 210518 SKKS088-Remapping-2-002 7550 322 75 -179..177 2.00 1 39 0.047
remapping SKKS089 210405 SKKS089-Remapping-Stars-001 7550 303 79 -179..177 1.97 1 39 0.113
remapping SKKS089 210405 SKKS089-Remapping-Stripes-002 7550 303 77 -179..177 1.97 1 40 0.084
remapping SKKS089 210504 SKKS089-Remapping-1-001 7550 187 83 -179..177 2.07 1 41 0.054
remapping SKKS089 210504 SKKS089-Remapping-2-002 7550 187 80 -179..177 2.03 1 42 0.071
remapping SKKS089 210518 SKKS088-Remapping-1-001 7550 240 42 -179..177 1.57 1 21 0.093
remapping SKKS089 210518 SKKS088-Remapping-2-002 7550 240 79 -179..177 2.03 1 39 0.068
remapping SKKS090 210405 SKKS090-Remapping-Stars-003 7550 431 78 -179..179 1.94 1 40 0.084
remapping SKKS090 210405 SKKS090-Remapping-Stripes-001 7550 431 79 -179..177 1.94 1 38 0.070
remapping SKKS090 210504 SKKS090-Remapping-1-001 7550 229 80 -180..179 2.04 1 41 0.114
remapping SKKS090 210504 SKKS090-Remapping-2-002 7550 229 76 -179..177 2.01 1 41 0.098
remapping SKKS090 210518 SKKS090-Remapping-1-001 7550 124 70 -179..177 2.01 1 39 0.092
remapping SKKS090 210518 SKKS090-Remapping-2-002 7550 124 76 -179..177 1.99 1 38 0.124
remapping SKKS091 210405 SKKS091-Remapping-Stars-001 7550 189 59 -179..177 1.82 1 29 0.071
remapping SKKS091 210405 SKKS091-Remapping-Stripes-002 7550 189 79 -179..177 1.96 1 38 0.046
remapping SKKS091 210504 SKKS091-Remapping-1-001 7550 197 81 -179..177 2.06 1 37 0.088
remapping SKKS091 210504 SKKS091-Remapping-1-002 7550 197 79 -179..177 2.06 1 36 0.080
remapping SKKS091 210518 SKKS091-Remapping-1-001 7550 300 77 -179..177 1.99 1 38 0.091
remapping SKKS091 210518 SKKS091-Remapping-2-002 7550 300 80 -180..177 2.00 1 40 0.070
Head-rotation (HR) and chamber-rotation (CR) files come in pairs with the same number of ROIs (a single set of ROIs, Results), as do the two contexts of each remapping pair. In the head-rotation files the heading steps 0.7 to 1.3 degrees per frame (7 to 13 degrees/s; the Methods give 2 rpm, 12 degrees/s). In 5 of the 7 HR/CR pairs the stored heading runs in opposite directions in the two files; in two pairs (210427 SKKS091 and 210429 SKKS089) it runs the same way. The paper reports 5 pairs over 4 mice; the 5 opposite-direction pairs are 5 over 4 mice.
Clock check. These files rotate one way at constant speed, where a time offset between heading
and activity only rotates every tuning curve and cannot be seen within a file; the check of
single_cue.ipynb on the files that rotate both ways is the only one, and the clock of these
files is not established. An offset common to the two files of a pair would cancel in the
differences tested below; an offset that differs between them would not.
trials = [rsc.trial_tuning(r["sp"], r["heading"]) for r in recs]
t0 = time.time()
with Pool(4) as pool:
sel = rsc.selection_many(trials, "single", 201, pool, centre=True)
info["selective"] = [s.is_hd.sum() for s in sel]
info["trials_used"] = [t.shape[2] for t in trials]
print(f"selection test in {time.time() - t0:.0f} s")
print(info[["experiment", "subject", "identifier", "rois", "trials_used", "selective"]].to_string(index=False))
selection test in 282 s
experiment subject identifier rois trials_used selective
chamber_rotation SKKS089 210429 SKKS088-PlatformRotation-002 118 13 38
chamber_rotation SKKS090 210429 SKKS090-PlatformRotation-002 167 13 13
chamber_rotation SKKS091 210427 SKKS091-PlatformRotation-002 135 13 38
chamber_rotation SKKS091 210526 SKKS091-PlatformRotation-002 128 26 45
chamber_rotation SKKS112 210526 SKKS112-PlatformRotation-002 384 10 74
chamber_rotation SKKS112 210526 SKKS112-PlatformRotation-004 293 26 45
chamber_rotation SKKS116 210526 SKKS116-PlatformRotation-002 211 27 138
head_rotation SKKS089 210429 SKKS088-HeadRotation-001 118 5 61
head_rotation SKKS090 210429 SKKS090-HeadRotation-001 167 9 139
head_rotation SKKS091 210427 SKKS091-HeadRotation-001 135 9 98
head_rotation SKKS091 210526 SKKS091-HeadRotation-001 128 17 70
head_rotation SKKS112 210526 SKKS112-HeadRotation-001 384 16 247
head_rotation SKKS112 210526 SKKS112-HeadRotation-003 293 15 193
head_rotation SKKS116 210526 SKKS116-HeadRotation-001 211 16 175
remapping SKKS088 210405 SKKS088-Remapping-Stars-002 346 39 51
remapping SKKS088 210405 SKKS088-Remapping-Stripes-001 346 39 46
remapping SKKS088 210504 SKKS088-Remapping-1-001 81 40 32
remapping SKKS088 210504 SKKS088-Remapping-2-002 81 41 10
remapping SKKS088 210518 SKKS088-Remapping-1-001 322 40 55
remapping SKKS088 210518 SKKS088-Remapping-2-002 322 39 64
remapping SKKS089 210405 SKKS089-Remapping-Stars-001 303 39 85
remapping SKKS089 210405 SKKS089-Remapping-Stripes-002 303 40 29
remapping SKKS089 210504 SKKS089-Remapping-1-001 187 41 33
remapping SKKS089 210504 SKKS089-Remapping-2-002 187 42 19
remapping SKKS089 210518 SKKS088-Remapping-1-001 240 21 28
remapping SKKS089 210518 SKKS088-Remapping-2-002 240 39 15
remapping SKKS090 210405 SKKS090-Remapping-Stars-003 431 40 33
remapping SKKS090 210405 SKKS090-Remapping-Stripes-001 431 38 34
remapping SKKS090 210504 SKKS090-Remapping-1-001 229 41 66
remapping SKKS090 210504 SKKS090-Remapping-2-002 229 41 25
remapping SKKS090 210518 SKKS090-Remapping-1-001 124 39 10
remapping SKKS090 210518 SKKS090-Remapping-2-002 124 38 18
remapping SKKS091 210405 SKKS091-Remapping-Stars-001 189 29 16
remapping SKKS091 210405 SKKS091-Remapping-Stripes-002 189 38 61
remapping SKKS091 210504 SKKS091-Remapping-1-001 197 37 26
remapping SKKS091 210504 SKKS091-Remapping-1-002 197 36 35
remapping SKKS091 210518 SKKS091-Remapping-1-001 300 38 56
remapping SKKS091 210518 SKKS091-Remapping-2-002 300 40 72
fig2f-head-chamber-same-preference¶
Matched ROIs have on average the same preferred heading relative to the wall pattern whether the head is rotated in a fixed chamber or the chamber is rotated around a fixed head (V-test against 0 degrees, V = 14.1, p = 2.5e-4), though not all cells keep their preference.
Per ROI, preferred heading in HR minus that in CR, wrapped to +-180, V-test against 0. Readings of the claims file: cells (a) selective in both, (b) in either, (c) in CR; preferred heading (i) peak of the trial-mean curve, (ii) the code's 'fit' method. Two further details are open and both are run: the pairs, all 7 or the 5 whose heading runs in opposite directions; and the sign of the heading. The paper says that in both conditions "the visual cue card [moves] across the visual field in the same direction" (Figure 2B legend), while the stored headings of the 5 pairs run in opposite directions, so one of the two must be mirrored to put both in the frame of the wall pattern; the reading "CR mirrored" (heading -h in the CR file) does this, "as stored" does not.
pairs = []
for i in sub.index[sub.experiment == "head_rotation"]:
n = int(sub.identifier[i][-3:])
j = sub.index[(sub.experiment == "chamber_rotation") & (sub.date == sub.date[i]) & (sub.subject == sub.subject[i])
& (sub.identifier.str[-3:].astype(int) == n + 1)][0]
pairs.append((i, j, info.direction[i] != info.direction[j]))
print("pairs (HR, CR, opposite directions):", [(info.identifier[i], info.identifier[j], o) for i, j, o in pairs])
def preference(i, method, mirror=False):
tc = trials[i].mean(2)
if mirror:
tc = tc[:, ::-1]
return rsc.peak_heading(tc) if method == "peak" else rsc.fit_preferred(tc, "single")
res = []
diffs = {}
for method in ["peak", "fit"]:
for mirror in [True, False]:
for cells in ["both", "either", "CR"]:
for pset in ["5 pairs", "7 pairs"]:
d_all = []
for i, j, opp in pairs:
if pset == "5 pairs" and not opp:
continue
h1, h2 = sel[i].is_hd.values, sel[j].is_hd.values
m = {"both": h1 & h2, "either": h1 | h2, "CR": h2}[cells]
d_all.append(rsc.wrap180(preference(i, method) - preference(j, method, mirror))[m])
d = np.concatenate(d_all)
V, p, n = rsc.vtest(d)
diffs[(method, mirror, cells, pset)] = d
res.append(dict(preference=method, sign="CR mirrored" if mirror else "as stored", cells=cells, pairs=pset,
n=n, V=round(V, 1), p=float(f"{p:.3g}"),
within_30deg=round(float(np.mean(np.abs(d) <= 30)), 3)))
res = pd.DataFrame(res)
print(res.to_string(index=False))
print("\nReadings with p < 0.05 and V > 0:", int(((res.p < 0.05) & (res.V > 0)).sum()), "of", len(res))
print("Closest n to the paper's 32:", res.iloc[(res.n - 32).abs().argsort()[:3]][["preference", "sign", "cells", "pairs", "n", "V", "p"]].to_string(index=False))
pairs (HR, CR, opposite directions): [('210429 SKKS088-HeadRotation-001', '210429 SKKS088-PlatformRotation-002', np.False_), ('210429 SKKS090-HeadRotation-001', '210429 SKKS090-PlatformRotation-002', np.True_), ('210427 SKKS091-HeadRotation-001', '210427 SKKS091-PlatformRotation-002', np.False_), ('210526 SKKS091-HeadRotation-001', '210526 SKKS091-PlatformRotation-002', np.True_), ('210526 SKKS112-HeadRotation-001', '210526 SKKS112-PlatformRotation-002', np.True_), ('210526 SKKS112-HeadRotation-003', '210526 SKKS112-PlatformRotation-004', np.True_), ('210526 SKKS116-HeadRotation-001', '210526 SKKS116-PlatformRotation-002', np.True_)]
preference sign cells pairs n V p within_30deg
peak CR mirrored both 5 pairs 228 34.6 0.000590 0.263
peak CR mirrored both 7 pairs 272 13.1 0.130000 0.228
peak CR mirrored either 5 pairs 911 21.3 0.160000 0.202
peak CR mirrored either 7 pairs 1102 -28.9 0.891000 0.181
peak CR mirrored CR 5 pairs 315 47.7 0.000073 0.244
peak CR mirrored CR 7 pairs 391 18.3 0.095300 0.212
peak as stored both 5 pairs 228 -43.6 1.000000 0.123
peak as stored both 7 pairs 272 -57.0 1.000000 0.121
peak as stored either 5 pairs 911 -74.8 1.000000 0.176
peak as stored either 7 pairs 1102 -109.2 1.000000 0.176
peak as stored CR 5 pairs 315 -45.2 1.000000 0.143
peak as stored CR 7 pairs 391 -72.2 1.000000 0.133
fit CR mirrored both 5 pairs 228 36.0 0.000380 0.241
fit CR mirrored both 7 pairs 272 13.1 0.131000 0.210
fit CR mirrored either 5 pairs 911 1.4 0.474000 0.169
fit CR mirrored either 7 pairs 1102 -60.3 0.995000 0.151
fit CR mirrored CR 5 pairs 315 47.6 0.000074 0.238
fit CR mirrored CR 7 pairs 391 15.8 0.130000 0.205
fit as stored both 5 pairs 228 -34.1 0.999000 0.088
fit as stored both 7 pairs 272 -47.7 1.000000 0.092
fit as stored either 5 pairs 911 -72.3 1.000000 0.131
fit as stored either 7 pairs 1102 -112.6 1.000000 0.127
fit as stored CR 5 pairs 315 -35.2 0.997000 0.108
fit as stored CR 7 pairs 391 -60.4 1.000000 0.102
Readings with p < 0.05 and V > 0: 4 of 24
Closest n to the paper's 32: preference sign cells pairs n V p
peak CR mirrored both 5 pairs 228 34.6 0.00059
peak as stored both 5 pairs 228 -43.6 1.00000
fit CR mirrored both 5 pairs 228 36.0 0.00038
print("Per pair, peak preference, CR mirrored, cells selective in both:")
for i, j, opp in pairs:
m = sel[i].is_hd.values & sel[j].is_hd.values
d = rsc.wrap180(preference(i, "peak") - preference(j, "peak", True))[m]
V, p, n = rsc.vtest(d) if len(d) else (np.nan, np.nan, 0)
print(f" {info.identifier[i]} / {info.identifier[j]} opposite={opp}: n={n}, V={V:.1f}, p={p:.2g}, "
f"median |diff|={np.median(np.abs(d)) if len(d) else np.nan:.0f} deg")
# Null for the V statistic: ROIs paired at random within each pair of files (the paper's shuffle)
rng = np.random.default_rng(202)
null = []
for _ in range(200):
d_all = []
for i, j, opp in pairs:
if not opp:
continue
m = np.flatnonzero(sel[i].is_hd.values & sel[j].is_hd.values)
d_all.append(rsc.wrap180(preference(i, "peak")[m] - preference(j, "peak", True)[rng.permutation(m)]))
null.append(rsc.vtest(np.concatenate(d_all))[0])
print(f"Random pairing of ROIs (5 pairs, both, peak, CR mirrored): V mean {np.mean(null):.1f}, 95th percentile {np.percentile(null, 95):.1f}")
d = diffs[("peak", True, "both", "5 pairs")]
fig, ax = plt.subplots(figsize=(4, 3))
ax.hist(d, bins=36, range=(-180, 180), color="0.4"); ax.set_xlabel("HR minus CR preferred heading (deg)"); ax.set_ylabel("ROIs")
plt.tight_layout(); plt.show()
print("All differences lie in -180 to 180.")
Per pair, peak preference, CR mirrored, cells selective in both: 210429 SKKS088-HeadRotation-001 / 210429 SKKS088-PlatformRotation-002 opposite=False: n=20, V=-12.3, p=1, median |diff|=156 deg 210429 SKKS090-HeadRotation-001 / 210429 SKKS090-PlatformRotation-002 opposite=True: n=11, V=-6.3, p=1, median |diff|=126 deg 210427 SKKS091-HeadRotation-001 / 210427 SKKS091-PlatformRotation-002 opposite=False: n=24, V=-9.2, p=1, median |diff|=123 deg 210526 SKKS091-HeadRotation-001 / 210526 SKKS091-PlatformRotation-002 opposite=True: n=25, V=-3.6, p=0.85, median |diff|=108 deg 210526 SKKS112-HeadRotation-001 / 210526 SKKS112-PlatformRotation-002 opposite=True: n=50, V=-14.5, p=1, median |diff|=111 deg 210526 SKKS112-HeadRotation-003 / 210526 SKKS112-PlatformRotation-004 opposite=True: n=27, V=0.3, p=0.47, median |diff|=90 deg 210526 SKKS116-HeadRotation-001 / 210526 SKKS116-PlatformRotation-002 opposite=True: n=115, V=58.9, p=4.2e-15, median |diff|=42 deg
Random pairing of ROIs (5 pairs, both, peak, CR mirrored): V mean 46.2, 95th percentile 57.6
All differences lie in -180 to 180.
Further checks asked for in verification: the random-pairing null per pair (1000 draws), the
activity and the share of selective cells in the two files of each pair (heading-locked
artefacts of derotation would make the head-rotation files look more selective), and how far a
clock offset could move the preferred headings. In single_cue.ipynb the lag of the peak
correlation is 4 to 6 frames in the files that rotate both ways, with one file at -6; an offset
of k frames moves every preferred heading of a file by k times the heading step per frame.
pref_hr = {i: preference(i, "peak") for i, _, _ in pairs}
pref_cr = {j: preference(j, "peak", True) for _, j, _ in pairs}
rng = np.random.default_rng(204)
rows2 = []
for i, j, opp in pairs:
m = np.flatnonzero(sel[i].is_hd.values & sel[j].is_hd.values)
if len(m) == 0:
continue
V = rsc.vtest(rsc.wrap180(pref_hr[i][m] - pref_cr[j][m]))[0]
nullp = np.array([rsc.vtest(rsc.wrap180(pref_hr[i][m] - pref_cr[j][rng.permutation(m)]))[0] for _ in range(1000)])
rows2.append(dict(pair=f"{info.identifier[i]} / {info.identifier[j].split(' ')[-1]}", opposite=opp, n=len(m), V=round(V, 1),
null_mean=round(float(nullp.mean()), 1), null_p95=round(float(np.percentile(nullp, 95)), 1),
p_random=round(float(np.mean(nullp >= V)), 3),
HR_selective=f"{sel[i].is_hd.sum()}/{len(sel[i])} ({100 * sel[i].is_hd.mean():.0f}%)",
CR_selective=f"{sel[j].is_hd.sum()}/{len(sel[j])} ({100 * sel[j].is_hd.mean():.0f}%)",
HR_spikes_s=info.spikes_per_s[i], CR_spikes_s=info.spikes_per_s[j],
HR_step=abs(info.median_step[i]), CR_step=abs(info.median_step[j]),
shift_6_frames_deg=f"{6 * abs(info.median_step[i]):.0f} / {6 * abs(info.median_step[j]):.0f}"))
print(pd.DataFrame(rows2).to_string(index=False))
print("p_random: share of 1000 random pairings of the same ROIs with V at least the real one (peak preference, CR mirrored, cells selective in both)")
pair opposite n V null_mean null_p95 p_random HR_selective CR_selective HR_spikes_s CR_spikes_s HR_step CR_step shift_6_frames_deg 210429 SKKS088-HeadRotation-001 / SKKS088-PlatformRotation-002 False 20 -12.3 -11.4 -10.4 0.851 61/118 (52%) 38/118 (32%) 0.214 0.070 0.68 1.94 4 / 12 210429 SKKS090-HeadRotation-001 / SKKS090-PlatformRotation-002 True 11 -6.3 -6.1 -4.7 0.609 139/167 (83%) 13/167 (8%) 0.166 0.084 1.05 1.97 6 / 12 210427 SKKS091-HeadRotation-001 / SKKS091-PlatformRotation-002 False 24 -9.2 -9.6 -7.0 0.413 98/135 (73%) 38/135 (28%) 0.164 0.109 1.12 1.94 7 / 12 210526 SKKS091-HeadRotation-001 / SKKS091-PlatformRotation-002 True 25 -3.6 -1.1 3.5 0.849 70/128 (55%) 45/128 (35%) 0.095 0.063 1.28 2.01 8 / 12 210526 SKKS112-HeadRotation-001 / SKKS112-PlatformRotation-002 True 50 -14.5 -2.3 5.1 0.998 247/384 (64%) 74/384 (19%) 0.890 0.114 1.26 0.12 8 / 1 210526 SKKS112-HeadRotation-003 / SKKS112-PlatformRotation-004 True 27 0.3 -1.4 2.0 0.222 193/293 (66%) 45/293 (15%) 3.188 0.071 1.21 2.02 7 / 12 210526 SKKS116-HeadRotation-001 / SKKS116-PlatformRotation-002 True 115 58.9 56.8 61.9 0.258 175/211 (83%) 138/211 (65%) 0.106 0.065 1.22 1.96 7 / 12 p_random: share of 1000 random pairings of the same ROIs with V at least the real one (peak preference, CR mirrored, cells selective in both)
Result: partially reproduced, resting on assumptions (the CR heading mirrored, the 5 opposite-direction pairs, the centred fit). With the chamber-rotation heading mirrored, cells selective in both conditions in the 5 pairs give V = 34.6, n = 228, p = 5.9e-04 (peak; 'fit' V = 36.0, p = 3.8e-04), so the criterion's test passes; cells selective in CR give V = 47.7, p = 7.3e-05. With all 7 pairs no reading has p < 0.05, and with the headings as stored every V is negative: 4 of 24 readings pass. Neither the mirroring nor the choice of 5 pairs can be checked against the data. The V is not the paper's 14.1 (n = 228 against 32). Matched ROIs do not keep their preference better than ROIs paired at random: over the 5 pairs random pairing gives a larger V (mean 46.2, 95th percentile 57.6), and in every pair the real V lies within its own random-pairing null (share of random pairings at or above it 0.222 to 0.998; SKKS116, V = 58.9 against a null mean of 56.8). The V-test passes because preferred headings are not uniform in either file. Head-rotation files have more selective cells than their chamber-rotation partners (52 to 83% against 8 to 65%) and more activity (0.095 to 3.19 spikes/s against 0.063 to 0.114), so heading-locked artefacts of derotation are not excluded. A clock offset of 6 frames would move preferred headings by 4 to 8 degrees in a head-rotation file and by 12 degrees in six of the chamber-rotation files. The seventh, 210526 SKKS112-PlatformRotation-002, barely rotates: its median step is 0.12 degree per frame (1 degree in 6 frames) against about 2 in the others, and it yields 10 trials against 26 in the other 5100-frame files.
fig3c-coherent-remapping¶
When all the wall cues are replaced, heading cells remap coherently, each shifting its preferred heading by about the same angle as the rest of the population in its recording (V-test of the Delta phase offsets against 0 degrees, V = 25.6, p = 4.9e-6).
Pairs: the two remapping files of a mouse on a day (same ROIs), in the order recorded. Per neuron, Delta phase offset = (pref A - pref B) minus the expected offset of the other neurons: (i) the median of their differences, (ii) the median of their pref A minus the median of their pref B. Medians of angles are circular medians (the angle minimising the summed absolute circular distance, a standard choice). Cells (a) selective in both contexts, (b) in either; preference, peak or 'fit'. Null: neurons paired at random within each recording (Methods).
rm = sub[sub.experiment == "remapping"]
rpairs = [tuple(g.sort_values("identifier").index) for _, g in rm.groupby(["date", "subject"])]
print("remapping pairs (subject, file A, file B):", [(sub.subject[i], info.identifier[i], info.identifier[j]) for i, j in rpairs])
grid = np.arange(-180, 180, 1.0)
def circmed(x):
return grid[np.argmin(np.abs(rsc.wrap180(np.asarray(x)[None] - grid[:, None])).sum(1))]
def delta_offsets(P, cells, expected, rng=None):
out, rec = [], []
for k, (i, j) in enumerate(rpairs):
m = {"both": sel[i].is_hd.values & sel[j].is_hd.values, "either": sel[i].is_hd.values | sel[j].is_hd.values}[cells]
idx = np.flatnonzero(m)
if len(idx) < 3:
continue
pa, pb = P[i][idx], P[j][idx]
if rng is not None:
pb = pb[rng.permutation(len(pb))]
diff = rsc.wrap180(pa - pb)
for n in range(len(idx)):
o = np.delete(np.arange(len(idx)), n)
e = circmed(diff[o]) if expected == "i" else rsc.wrap180(circmed(pa[o]) - circmed(pb[o]))
out.append(rsc.wrap180(diff[n] - e)); rec.append(k)
return np.array(out), np.array(rec)
rows = []
for method in ["peak", "fit"]:
P = {i: (rsc.peak_heading(trials[i].mean(2)) if method == "peak" else rsc.fit_preferred(trials[i].mean(2))) for p in rpairs for i in p}
for cells in ["both", "either"]:
for expected in ["i", "ii"]:
d, rec = delta_offsets(P, cells, expected)
V, p, n = rsc.vtest(d)
rows.append(dict(preference=method, cells=cells, expected=expected, n=n, recordings=len(np.unique(rec)),
V=round(V, 1), p=float(f"{p:.3g}"), within_30deg=round(float(np.mean(np.abs(d) <= 30)), 3)))
if (method, cells, expected) == ("peak", "both", "i"):
d_main, rec_main, P_main = d, rec, P
rows = pd.DataFrame(rows)
print(rows.to_string(index=False))
print("\nReadings with p < 0.05 and V > 0:", int(((rows.p < 0.05) & (rows.V > 0)).sum()), "of", len(rows))
rng = np.random.default_rng(203)
null = [rsc.vtest(delta_offsets(P_main, "both", "i", rng)[0])[0] for _ in range(1000)]
print(f"Random pairing of neurons (peak, both, (i)), 1000 draws: V mean {np.mean(null):.1f}, 95th percentile {np.percentile(null, 95):.1f}, "
f"share at or above the real V {np.mean(np.array(null) >= rows.V[0]):.3f}")
print("Per recording pair (peak, both, (i)):")
for k, (i, j) in enumerate(rpairs):
dk = d_main[rec_main == k]
if len(dk):
print(f" {sub.subject[i]} {info.identifier[i]}: n={len(dk)}, V={rsc.vtest(dk)[0]:.1f}, median |offset|={np.median(np.abs(dk)):.0f} deg")
else:
print(f" {sub.subject[i]} {info.identifier[i]}: fewer than 3 cells selective in both")
fig, ax = plt.subplots(figsize=(4, 3))
ax.hist(d_main, bins=36, range=(-180, 180), color="0.4"); ax.set_xlabel("Delta phase offset (deg)"); ax.set_ylabel("neurons")
plt.tight_layout(); plt.show()
print("All offsets lie in -180 to 180.")
remapping pairs (subject, file A, file B): [('SKKS088', '210405 SKKS088-Remapping-Stars-002', '210405 SKKS088-Remapping-Stripes-001'), ('SKKS089', '210405 SKKS089-Remapping-Stars-001', '210405 SKKS089-Remapping-Stripes-002'), ('SKKS090', '210405 SKKS090-Remapping-Stars-003', '210405 SKKS090-Remapping-Stripes-001'), ('SKKS091', '210405 SKKS091-Remapping-Stars-001', '210405 SKKS091-Remapping-Stripes-002'), ('SKKS088', '210504 SKKS088-Remapping-1-001', '210504 SKKS088-Remapping-2-002'), ('SKKS089', '210504 SKKS089-Remapping-1-001', '210504 SKKS089-Remapping-2-002'), ('SKKS090', '210504 SKKS090-Remapping-1-001', '210504 SKKS090-Remapping-2-002'), ('SKKS091', '210504 SKKS091-Remapping-1-001', '210504 SKKS091-Remapping-1-002'), ('SKKS088', '210518 SKKS088-Remapping-1-001', '210518 SKKS088-Remapping-2-002'), ('SKKS089', '210518 SKKS088-Remapping-1-001', '210518 SKKS088-Remapping-2-002'), ('SKKS090', '210518 SKKS090-Remapping-1-001', '210518 SKKS090-Remapping-2-002'), ('SKKS091', '210518 SKKS091-Remapping-1-001', '210518 SKKS091-Remapping-2-002')]
preference cells expected n recordings V p within_30deg
peak both i 85 9 47.0 2.760000e-13 0.506
peak both ii 85 9 38.4 2.000000e-09 0.471
peak either i 830 12 208.9 0.000000e+00 0.325
peak either ii 830 12 193.2 0.000000e+00 0.327
fit both i 85 9 51.9 8.880000e-16 0.459
fit both ii 85 9 50.2 6.550000e-15 0.553
fit either i 830 12 233.5 0.000000e+00 0.329
fit either ii 830 12 215.8 0.000000e+00 0.306
Readings with p < 0.05 and V > 0: 8 of 8
Random pairing of neurons (peak, both, (i)), 1000 draws: V mean 36.2, 95th percentile 45.7, share at or above the real V 0.026 Per recording pair (peak, both, (i)): SKKS088 210405 SKKS088-Remapping-Stars-002: n=13, V=9.7, median |offset|=30 deg SKKS089 210405 SKKS089-Remapping-Stars-001: n=4, V=0.8, median |offset|=75 deg SKKS090 210405 SKKS090-Remapping-Stars-003: n=8, V=7.8, median |offset|=9 deg SKKS091 210405 SKKS091-Remapping-Stars-001: n=5, V=3.8, median |offset|=36 deg SKKS088 210504 SKKS088-Remapping-1-001: n=5, V=2.7, median |offset|=24 deg SKKS089 210504 SKKS089-Remapping-1-001: n=4, V=-0.4, median |offset|=90 deg SKKS090 210504 SKKS090-Remapping-1-001: n=7, V=3.7, median |offset|=30 deg SKKS091 210504 SKKS091-Remapping-1-001: fewer than 3 cells selective in both SKKS088 210518 SKKS088-Remapping-1-001: n=21, V=12.6, median |offset|=24 deg SKKS089 210518 SKKS088-Remapping-1-001: fewer than 3 cells selective in both SKKS090 210518 SKKS090-Remapping-1-001: fewer than 3 cells selective in both SKKS091 210518 SKKS091-Remapping-1-001: n=18, V=6.3, median |offset|=54 deg
All offsets lie in -180 to 180.
Result: partially reproduced, under the centred fit (an assumption). All 8 readings give V > 0 with p < 0.05. For cells selective in both contexts, peak preference and the median of the other cells' differences: V = 47.0, n = 85 over 9 recording pairs, p = 2.8e-13, against 25.6 with n = 66 in the paper, outside the tolerance of 2.56; no reading gives a V within it (38.4 to 233.5). Pairing neurons at random gives V of 36.2 on average (95th percentile 45.7, 1000 draws); 2.6% of random pairings reach the real V, so the real V lies only just above the null. 8 of the 9 pairs with 3 or more cells have V > 0.
Summary¶
| Claim | Outcome |
|---|---|
| fig2f-head-chamber-same-preference | partially reproduced, assumed (CR heading mirrored, 5 pairs, centred fit): the V-test passes, but random pairing gives as large a V in every pair |
| fig3c-coherent-remapping | partially reproduced, assumed (centred fit): every reading passes; V outside the tolerance and just above the random-pairing null |
A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.
How the analysis differs from the paper's: inferred spikes from raw fluorescence without neuropil correction; cells selected with the centred fit. For Figure 2, the sign of the chamber-rotation heading and the set of pairs are not stated and were chosen as said above. Circular medians stand in for the medians of the Methods.
What was found about the dataset: 7 head-rotation pairs over 5 mice where the paper reports 5 over 4. The stored headings of 5 pairs run in opposite directions in the two conditions, and the files do not say how either heading is defined. Remapping files are named "1" and "2" or "Stars" and "Stripes"; one pair of SKKS091 is named "1-001" and "1-002", so it may not be a remapping pair; it enters only the readings with cells selective in either context, as it has fewer than 3 cells selective in both.