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).

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

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

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

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

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