Reproducing Nair et al. (2018) from CRCNS data set bf-1ΒΆ

Paper. Nair J, Klaassen A-L, Arato J, Vyssotski AL, Harvey M, Rainer G (2018). Basal forebrain contributes to default mode network regulation. PNAS 115:1352-1357. doi:10.1073/pnas.1712431115. The claims file was written from the version of record and its supplement (SI Results, SI Materials and Methods, legends of Figs. S1 to S7).

Data set. CRCNS bf-1, "Basal forebrain LFP recordings from rats in home cage and during arena exploration", doi:10.6080/K0MK6B2Q, read from the public bucket crcnsarchive under bf-1/live/. Version: unversioned. CRCNS data sets have no versions; the paper cites the data set's DOI only, and the bucket holds one copy (live, no old), so that copy is the one read. It is pinned by the MD5 sum of its single data file, from the data set's checksums.md5: basal_forebrain_lfp.tar.gz a8acbf0400260f8395257778ed04dd8c (22,301,364 bytes); the notebook checks the sum of what it streams against it. The data descriptor is version 0.5 (Jan 23, 2018).

What the data set holds. BF LFP at 400 Hz from six rats, as 2-s segments with artifact segments removed, one movement-sensor value per segment, and two home-cage and two arena sessions per rat. It holds no ACC or VC recordings, no single units, no object sessions, no behaviour scoring or video, no day labels, no identification of bipolar electrodes and no recording across the transfer between arena and home cage.

Claim Tested here
fig3a-bf-gamma-reduction tested
fig3a-each-hemisphere-significant tested
fig3b-arena-gamma-unrelated-to-movement tested
fig3b-home-cage-gamma-increases-with-movement tested
fig3b-matched-movement tested
fig3c-home-cage-above-both-sessions tested in part (day factor dropped, as the criterion says; the session order is not in the data set)
fig3d-gamma-rises-with-time-in-arena tested
figs2-bf-low-frequency-bands tested
figs3-unimodal-states tested
figs5-arena-excursions tested
bipolar-gamma-reduction blocked: metadata_insufficient (no rat is identified as bipolar)
fig3c-consistent-over-days blocked: metadata_insufficient (no day labels)
fig3d-rapid-switch-to-home-cage blocked: data_missing (no recording across the transfer)
behavior-arena-time-budget, behavior-home-cage-less-exploration, behavior-movement-speed, fig3f-grooming-quiet-arena, figs6-object-more-stationary blocked: data_missing (no video or behaviour scoring)
fig4-object-exploration-suppressed blocked: data_missing (no object sessions)
fig5a-phase-locked-neurons, fig5cd-pli-max-at-gamma, spike-width-phase-locked blocked: data_missing (no single units)
fig6a-acc-gamma-home-cage, fig6a-vc-gamma-unchanged, fig6b-coherence, fig6c-granger-bf-to-acc, fig6d-trivariate-granger, figs2-acc-vc-low-frequency-bands blocked: data_missing (no ACC or VC)

Method details common to the tested claims are in the docstring of bflfp.py, with the passage of the paper or the claims file each comes from.

InΒ [1]:
import time
import warnings

import diptest
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
from statsmodels.stats.anova import AnovaRM
from statsmodels.stats.multicomp import pairwise_tukeyhsd

import bflfp

pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 200)
warnings.filterwarnings("ignore", category=RuntimeWarning)

started = time.time()
sums = bflfp.checksums()
recordings, archive = bflfp.load_recordings()
print(f"data set {bflfp.DATASET_ID} ({bflfp.DATASET_VERSION}, doi:{bflfp.DATASET_DOI}): "
      f"{len(recordings)} files streamed in {time.time() - started:.0f} s")
print("checksums.md5 lists:", sums)
print("MD5 of the streamed archive:", archive["md5"], f"({archive['bytes']} bytes);",
      "matches checksums.md5:", archive["md5"] == sums[bflfp.ARCHIVE])
data set bf-1 (unversioned, doi:10.6080/K0MK6B2Q): 24 files streamed in 3 s
checksums.md5 lists: {'basal_forebrain_lfp.tar.gz': 'a8acbf0400260f8395257778ed04dd8c', 'docs/crcns_bf-1_data_description.docx': '3c5ed5f3c32b0104e0800708399e0da2', 'docs/crcns_bf-1_data_description.pdf': '958e4e1c07db4af4b9a8308581d5d332', 'filelist.txt': 'f0d111ec1369a817591aacf2ebcc8e46'}
MD5 of the streamed archive: a8acbf0400260f8395257778ed04dd8c (22301364 bytes); matches checksums.md5: True

Data checkΒΆ

InΒ [2]:
rows = []
for r in recordings:
    starts = r["t"][0]
    dt = np.diff(r["t"], axis=0)
    span = starts[-1] + 2.0
    rows.append({"rat": r["rat"], "condition": r["condition"], "session": r["session"],
                 "variables": ",".join(r["variables"]), "samples_per_segment": r["lfp"].shape[0],
                 "segments": r["lfp"].shape[1], "minutes_kept": r["lfp"].shape[1] * 2 / 60,
                 "first_start_s": starts[0], "last_end_s": span,
                 "removed_pct": 100 * (1 - r["lfp"].shape[1] * 2 / span),
                 "sample_dt_ms": f"{1000 * dt.min():.4f}-{1000 * dt.max():.4f}",
                 "segment_steps_s": ",".join(f"{x:g}" for x in np.unique(np.round(np.diff(starts), 3))[:5]),
                 "within_segment_contiguous": bool(np.allclose(r["t"][-1] - r["t"][0], 799 / 400)),
                 "lfp_min": r["lfp"].min(), "lfp_max": r["lfp"].max(), "lfp_sd": r["lfp"].std(),
                 "mov_min": r["mov"].min(), "mov_median": np.median(r["mov"]), "mov_max": r["mov"].max(),
                 "md5": r["md5"][:8]})
info = pd.DataFrame(rows)
print("Files by rat and condition:")
print(pd.crosstab(info.rat, info.condition).to_string())
print("\nVariables in every file:", info.variables.unique().tolist())
print("Condition labels in the file names:", sorted(info.condition.unique()))
print(info.drop(columns=["variables"]).round(2).to_string(index=False))
print("\nRemoved segments, %% of each file's span: %.1f to %.1f (SI: 1-4%% of epochs rejected)"
      % (info.removed_pct.min(), info.removed_pct.max()))
Files by rat and condition:
condition  Arena  HCage
rat                    
1              2      2
2              2      2
3              2      2
4              2      2
5              2      2
6              2      2

Variables in every file: ['LFP_data,T_data,mov_data']
Condition labels in the file names: ['Arena', 'HCage']
 rat condition  session  samples_per_segment  segments  minutes_kept  first_start_s  last_end_s  removed_pct  sample_dt_ms segment_steps_s  within_segment_contiguous  lfp_min  lfp_max  lfp_sd  mov_min  mov_median  mov_max      md5
   1     Arena        1                  800       283          9.43            0.0       620.0         8.71 2.5000-2.5000      2,4,6,8,10                       True  -350.07   315.89   69.09    19.54      441.42   887.43 3884e20b
   1     Arena        2                  800       281          9.37            0.0       620.0         9.35 2.5000-2.5000           2,4,6                       True  -301.28   282.71   66.73   190.96      558.23   945.24 950fdd71
   1     HCage        1                  800       948         31.60            0.0      2000.0         5.20 2.5000-2.5000      2,4,6,8,10                       True  -566.13   992.54   90.42    16.82      220.02   848.53 7d43c330
   1     HCage        2                  800       354         11.80            0.0       794.0        10.83 2.5000-2.5000      2,4,6,8,10                       True  -397.00   347.20   81.17    18.36      327.37   835.87 5a959f9d
   2     Arena        1                  800       302         10.07            0.0       620.0         2.58 2.5000-2.5000           2,4,8                       True  -346.23   372.96   64.61    24.68      402.51   941.77 0bce913e
   2     Arena        2                  800       255          8.50            0.0       526.0         3.04 2.5000-2.5000           2,4,8                       True  -479.75   430.21   63.14    48.48      499.75   995.71 018d34c7
   2     HCage        1                  800       623         20.77            0.0      1400.0        11.00 2.5000-2.5000      2,4,6,8,10                       True  -376.99   335.59   77.42    18.26      327.58   667.31 70a3cf3c
   2     HCage        2                  800       317         10.57            0.0       700.0         9.43 2.5000-2.5000         2,4,6,8                       True  -366.81   326.60   71.43   107.69      285.76   769.97 327145d6
   3     Arena        1                  800       332         11.07            0.0       688.0         3.49 2.5000-2.5000           2,4,6                       True  -352.93   342.38   78.04   103.32      370.09   911.24 b6ca8558
   3     Arena        2                  800       301         10.03            4.0       698.0        13.75 2.5000-2.5000         2,4,6,8                       True  -311.88   303.27   77.30    25.50      416.09   796.76 c2acda89
   3     HCage        1                  800       449         14.97            2.0      1000.0        10.20 2.5000-2.5000      2,4,6,8,10                       True  -370.54   352.23   84.04    19.63      223.57   677.81 104c6fb3
   3     HCage        2                  800       291          9.70            6.0       650.0        10.46 2.5000-2.5000     2,4,8,12,14                       True  -380.24   401.03   84.38    19.63      200.24   677.81 ebab2252
   4     Arena        1                  800       354         11.80            0.0       758.0         6.60 2.5000-2.5000        2,4,6,10                       True  -239.91   242.51   62.18    15.26       91.48   318.56 afa67f08
   4     Arena        2                  800       335         11.17            0.0       694.0         3.46 2.5000-2.5000           2,4,8                       True  -245.80   291.40   60.22    19.94      111.91   307.82 5648f61a
   4     HCage        1                  800       359         11.97            0.0       758.0         5.28 2.5000-2.5000         2,4,6,8                       True  -245.80   279.63   67.68     3.94       60.07   204.94 a8f68eae
   4     HCage        2                  800       289          9.63            0.0       700.0        17.43 2.5000-2.5000      2,4,6,8,14                       True  -214.48   271.77   68.53     5.27       47.24   198.02 54a177ac
   5     Arena        1                  800       312         10.40            2.0       704.0        11.36 2.5000-2.5000        2,4,6,10                       True  -391.54   358.53   88.39     6.98       84.54   223.36 0c270ea7
   5     Arena        2                  800       323         10.77            0.0       694.0         6.92 2.5000-2.5000         2,4,6,8                       True  -450.07   352.72   86.12    18.86      103.21   382.62 39de92d5
   5     HCage        1                  800       290          9.67            0.0       758.0        23.48 2.5000-2.5000      2,4,6,8,10                       True  -487.20   493.32  107.05     3.03       61.46   210.53 633e0bab
   5     HCage        2                  800       286          9.53            0.0       650.0        12.00 2.5000-2.5000      2,4,6,8,12                       True  -514.48   522.58  115.25     5.24       74.42   230.08 41742613
   6     Arena        1                  800       291          9.70            0.0       660.0        11.82 2.5000-2.5000      2,4,6,8,10                       True  -277.33   275.38   67.15    20.51      314.32   599.66 396870a9
   6     Arena        2                  800       350         11.67            0.0       780.0        10.26 2.5000-2.5000     2,4,6,10,22                       True  -281.30   281.30   67.09    41.09      346.79   693.43 e240c424
   6     HCage        1                  800       370         12.33            0.0       898.0        17.59 2.5000-2.5000      2,4,6,8,12                       True  -310.50   298.79   70.88    17.01      187.56   803.56 4ab28b37
   6     HCage        2                  800       261          8.70            0.0       600.0        13.00 2.5000-2.5000     2,4,6,10,14                       True  -314.53   326.17   76.95    16.37      304.30   776.14 ab2e1dbd

Removed segments, % of each file's span: 2.6 to 23.5 (SI: 1-4% of epochs rejected)

Every file holds LFP_data, T_data and mov_data with 800 samples per segment, and sample intervals of exactly 2.5 ms (400 Hz), as the descriptor says. Segment starts step by multiples of 2 s, so segments are consecutive 2-s blocks with some removed, as the descriptor says. Each file's clock starts at or near 0, so no two files share a clock: an arena file cannot be placed in time relative to a home-cage file. The descriptor names the home-cage condition Hcage; the files use HCage. Arena files span 8.8 to 13 min and keep 8.5 to 11.8 min of segments; home-cage files keep 8.7 to 31.6 min. The share of removed segments (2.6% to 23.5% of a file's span) is larger than the SI's 1-4% of rejected epochs, so the data set's cleaning differs from the paper's rule or removed more. The LFP has no stated unit; its range (a few hundred, standard deviations of 60 to 115) is consistent with microvolts, the unit of the paper's Figure 4 axis. The movement values have no unit (the descriptor says dimensionless); in rats 4 and 5 they are about a quarter of those in the other rats, so they are only compared within a rat.

InΒ [3]:
shared = bflfp.shared_segments(recordings)
print("Pairs of files that share identical LFP segments:")
print(shared.to_string(index=False) if len(shared) else "none")
by_key = {bflfp.key(r): r for r in recordings}
for _, row in shared.iterrows():
    a, b = by_key[row["first"]], by_key[row["second"]]
    pos = {a["lfp"][:, j].tobytes(): j for j in range(a["lfp"].shape[1])}
    pairs = [(pos.get(b["lfp"][:, j].tobytes()), j) for j in range(b["lfp"].shape[1])]
    matched = [(i, j) for i, j in pairs if i is not None]
    same_mov = np.mean([a["mov"][i] == b["mov"][j] for i, j in matched])
    print(f"{row['second']}: {len(matched)} of {b['lfp'].shape[1]} segments are segments of {row['first']} "
          f"({a['lfp'].shape[1]} segments), shifted by {row['time_offsets_s']} s; they cover "
          f"{a['t'][0, matched[0][0]]:.0f} to {a['t'][0, matched[-1][0]] + 2:.0f} s of it; "
          f"movement values equal on {100 * same_mov:.0f}% of the shared segments")
    lag = [np.mean([a["mov"][i + k] == b["mov"][j] for i, j in matched if 0 <= i + k < len(a["mov"])]) for k in (-1, 1)]
    print(f"  movement values equal at a shift of -1 segment: {100 * lag[0]:.0f}%, +1 segment: {100 * lag[1]:.0f}%")
DUPLICATES = {row["second"] for _, row in shared.iterrows()}
print("Left out as a second deposit of the same recording:", DUPLICATES)
Pairs of files that share identical LFP segments:
        first        second  shared_segments time_offsets_s
(3, HCage, 1) (3, HCage, 2)              287        [100.0]
(3, 'HCage', 2): 287 of 291 segments are segments of (3, 'HCage', 1) (449 segments), shifted by [np.float64(100.0)] s; they cover 106 to 750 s of it; movement values equal on 100% of the shared segments
  movement values equal at a shift of -1 segment: 0%, +1 segment: 0%
Left out as a second deposit of the same recording: {(3, 'HCage', 2)}

Rat 3's home-cage session 2 is not a separate recording: all but 4 of its 291 segments are segments of rat 3's home-cage session 1, 100 s later on that file's clock, with the same LFP. It is counted once: the analysis uses session 1 and leaves session 2 out. Rat 3 so has one home-cage recording. Where a test pairs sessions, the pair of rat 3's arena session 2 is dropped (11 pairs), and the result with the files as delivered (12 pairs) is shown as a variant.

InΒ [4]:
# Spectra: line noise at 50 Hz (mains in Switzerland) lies inside the 30-80 Hz gamma band.
rows = []
for r in recordings:
    f, p = bflfp.spectrum(r["lfp"])
    mean = p.mean(axis=1)
    near = (f >= 45) & (f <= 55) & (f != 50)
    band = (f >= 20) & (f <= 100)
    rows.append({"rat": r["rat"], "condition": r["condition"], "session": r["session"],
                 "peak_20_100_Hz": f[band][np.argmax(mean[band])],
                 "power_50Hz_over_45_55": mean[f == 50][0] / mean[near].mean()})
lines = pd.DataFrame(rows)
print(lines.round(2).to_string(index=False))
print("Frequency resolution of a 2-s epoch: %.2f Hz; highest frequency %.0f Hz" % (f[1] - f[0], f[-1]))
 rat condition  session  peak_20_100_Hz  power_50Hz_over_45_55
   1     Arena        1            57.5                   0.80
   1     Arena        2            20.0                   1.06
   1     HCage        1            50.0                   1.59
   1     HCage        2            51.5                   1.18
   2     Arena        1            59.5                   1.88
   2     Arena        2            50.0                   3.13
   2     HCage        1            50.0                  26.05
   2     HCage        2            57.5                   0.79
   3     Arena        1            20.0                   1.10
   3     Arena        2            20.0                   1.03
   3     HCage        1            48.5                   1.02
   3     HCage        2            51.0                   0.94
   4     Arena        1            50.0                   1.16
   4     Arena        2            50.0                   2.12
   4     HCage        1            46.5                   1.18
   4     HCage        2            48.0                   1.05
   5     Arena        1            57.0                   0.99
   5     Arena        2            61.0                   1.00
   5     HCage        1            51.0                   1.22
   5     HCage        2            53.5                   1.07
   6     Arena        1            29.5                   1.01
   6     Arena        2            25.0                   1.52
   6     HCage        1            50.0                   1.26
   6     HCage        2            51.5                   1.01
Frequency resolution of a 2-s epoch: 0.50 Hz; highest frequency 200 Hz

The spectral peak between 20 and 100 Hz lies at 46.5 to 57.5 Hz in every home-cage file, the BF gamma the paper describes; in arena files it lies at 50 to 61 Hz in half and at 20 to 30 Hz in the others. One file, rat 2 home cage session 1, has a narrow line at 50 Hz, 26 times the power of its neighbours; a few others have a smaller one (up to 3 times). The paper does not mention line noise; the 30-80 Hz band includes 50 Hz, and the effect of leaving out 49-51 Hz is shown under fig3a-bf-gamma-reduction.

InΒ [5]:
# Do the movement values share the clock of the LFP? Head movement adds broadband muscle and motion
# artefact to an LFP, seen best above the LFP bands (100-200 Hz here). Within each file the
# correlation of that power with the movement values should peak at zero shift of the movement
# values (shifts in segments, by position in the file); theta power (6-10 Hz) is shown alongside.
shifts = range(-5, 6)
rows = []
for r in recordings:
    f, p = bflfp.spectrum(r["lfp"])
    for name, (lo, hi) in (("100-200 Hz", (100, 200)), ("theta", (6, 10))):
        x, mv = np.log10(p[(f >= lo) & (f <= hi)].mean(axis=0)), r["mov"]
        rho = {k: (stats.spearmanr(x[k:], mv[:len(mv) - k])[0] if k >= 0 else stats.spearmanr(x[:k], mv[-k:])[0])
               for k in shifts}
        rows.append({"measure": name, "rat": r["rat"], "condition": r["condition"], "session": r["session"],
                     **{f"s{k:+d}": v for k, v in rho.items()}, "best_shift": max(rho, key=rho.get)})
sync = pd.DataFrame(rows)
print("Spearman correlation of log band power with movement; s+k pairs the LFP of segment i+k with the movement of segment i:")
print(sync.round(2).to_string(index=False))
for name, g in sync.groupby("measure"):
    print(f"\n{name}: best shift per file {g.best_shift.value_counts().sort_index().to_dict()}; median correlation "
          f"at 0 {g['s+0'].median():.2f}, at +1 {g['s+1'].median():.2f}, at -1 {g['s-1'].median():.2f}, "
          f"at +-5 {np.median(np.r_[g['s+5'], g['s-5']]):.2f}")
Spearman correlation of log band power with movement; s+k pairs the LFP of segment i+k with the movement of segment i:
   measure  rat condition  session   s-5   s-4   s-3   s-2   s-1   s+0   s+1   s+2   s+3   s+4   s+5  best_shift
100-200 Hz    1     Arena        1  0.31  0.34  0.35  0.39  0.42  0.60  0.57  0.42  0.38  0.36  0.32           0
     theta    1     Arena        1  0.36  0.30  0.22  0.16  0.31  0.34  0.27  0.19  0.21  0.26  0.28          -5
100-200 Hz    1     Arena        2  0.30  0.31  0.35  0.33  0.33  0.44  0.48  0.37  0.28  0.30  0.39           1
     theta    1     Arena        2  0.11  0.06  0.18  0.18  0.24  0.33  0.21  0.15  0.15  0.19  0.25           0
100-200 Hz    1     HCage        1  0.04  0.05  0.07  0.11  0.17  0.29  0.28  0.18  0.14  0.13  0.11           0
     theta    1     HCage        1  0.32  0.32  0.32  0.31  0.32  0.37  0.34  0.26  0.24  0.23  0.20           0
100-200 Hz    1     HCage        2  0.15  0.18  0.25  0.32  0.47  0.68  0.66  0.46  0.32  0.22  0.18           0
     theta    1     HCage        2 -0.05 -0.03  0.01  0.02  0.01 -0.01 -0.05 -0.06 -0.07 -0.06 -0.05          -2
100-200 Hz    2     Arena        1  0.33  0.33  0.30  0.29  0.38  0.63  0.66  0.42  0.31  0.28  0.27           1
     theta    2     Arena        1 -0.11 -0.15 -0.13 -0.14 -0.12 -0.01 -0.06 -0.20 -0.19 -0.10 -0.07           0
100-200 Hz    2     Arena        2  0.21  0.26  0.24  0.29  0.33  0.50  0.53  0.31  0.19  0.14  0.11           1
     theta    2     Arena        2  0.21  0.16  0.15  0.24  0.26  0.26  0.19  0.11  0.12  0.20  0.17           0
100-200 Hz    2     HCage        1  0.10  0.10  0.09  0.09  0.15  0.25  0.21  0.16  0.18  0.18  0.18           0
     theta    2     HCage        1  0.12  0.16  0.18  0.17  0.23  0.32  0.25  0.11  0.10  0.10  0.11           0
100-200 Hz    2     HCage        2 -0.33 -0.35 -0.33 -0.28 -0.21 -0.09 -0.03 -0.05 -0.04 -0.05 -0.12           1
     theta    2     HCage        2  0.14  0.14  0.14  0.18  0.23  0.25  0.20  0.19  0.23  0.24  0.22           0
100-200 Hz    3     Arena        1  0.09  0.12  0.11  0.07  0.13  0.28  0.28  0.14  0.08 -0.00  0.00           0
     theta    3     Arena        1 -0.12 -0.13 -0.13 -0.16 -0.12 -0.12 -0.19 -0.16 -0.11 -0.09 -0.12           4
100-200 Hz    3     Arena        2  0.10  0.08  0.09  0.07  0.06  0.26  0.30  0.17  0.14  0.10  0.15           1
     theta    3     Arena        2 -0.00 -0.07 -0.10 -0.13 -0.05  0.04 -0.06 -0.07 -0.11 -0.05 -0.07           0
100-200 Hz    3     HCage        1 -0.06 -0.04 -0.03 -0.01  0.02  0.11  0.13  0.01 -0.05 -0.09 -0.11           1
     theta    3     HCage        1 -0.09 -0.10 -0.10 -0.06 -0.04 -0.06 -0.06 -0.04 -0.03 -0.04 -0.09           3
100-200 Hz    3     HCage        2  0.02 -0.00 -0.01 -0.00 -0.01  0.08  0.08 -0.01 -0.04 -0.08 -0.09           1
     theta    3     HCage        2  0.03  0.03 -0.01  0.06  0.12  0.12  0.12  0.11  0.12  0.06  0.00           0
100-200 Hz    4     Arena        1  0.21  0.20  0.22  0.22  0.24  0.43  0.44  0.30  0.18  0.15  0.20           1
     theta    4     Arena        1 -0.05 -0.09 -0.08 -0.07 -0.06  0.08  0.03 -0.10 -0.02  0.04 -0.04           0
100-200 Hz    4     Arena        2  0.05  0.08  0.13  0.19  0.19  0.30  0.46  0.29  0.21  0.20  0.17           1
     theta    4     Arena        2  0.07  0.05  0.01  0.01  0.04  0.10  0.02 -0.02  0.10  0.17  0.14           4
100-200 Hz    4     HCage        1  0.10  0.08  0.08  0.16  0.27  0.45  0.53  0.35  0.25  0.19  0.13           1
     theta    4     HCage        1  0.03  0.03  0.03  0.00  0.01  0.08  0.03 -0.00 -0.05 -0.04  0.01           0
100-200 Hz    4     HCage        2  0.23  0.26  0.27  0.26  0.31  0.44  0.46  0.36  0.26  0.20  0.24           1
     theta    4     HCage        2  0.09  0.09  0.09  0.13  0.12  0.14  0.16  0.16  0.17  0.16  0.17           3
100-200 Hz    5     Arena        1  0.25  0.30  0.30  0.28  0.31  0.49  0.45  0.34  0.30  0.26  0.20           0
     theta    5     Arena        1  0.10  0.13  0.16  0.16  0.17  0.26  0.31  0.14  0.04  0.04  0.08           1
100-200 Hz    5     Arena        2  0.04  0.10  0.06  0.06  0.05  0.22  0.32  0.11 -0.05  0.04  0.10           1
     theta    5     Arena        2 -0.02 -0.03 -0.07 -0.06 -0.06 -0.03 -0.04  0.02  0.02 -0.00 -0.00           2
100-200 Hz    5     HCage        1  0.17  0.20  0.24  0.28  0.29  0.47  0.51  0.34  0.17  0.04  0.01           1
     theta    5     HCage        1  0.10  0.05  0.00 -0.06 -0.06 -0.06 -0.03 -0.04 -0.02 -0.03 -0.08          -5
100-200 Hz    5     HCage        2 -0.17 -0.22 -0.28 -0.26 -0.26 -0.19 -0.20 -0.29 -0.37 -0.45 -0.43          -5
     theta    5     HCage        2 -0.17 -0.18 -0.18 -0.09 -0.04 -0.10 -0.16 -0.19 -0.19 -0.23 -0.20          -1
100-200 Hz    6     Arena        1  0.07  0.10  0.10  0.17  0.35  0.62  0.57  0.32  0.14  0.12  0.14           0
     theta    6     Arena        1  0.08  0.14  0.16  0.16  0.14  0.22  0.24  0.08  0.10  0.12  0.14           1
100-200 Hz    6     Arena        2  0.21  0.24  0.22  0.28  0.38  0.55  0.54  0.36  0.27  0.29  0.31           0
     theta    6     Arena        2  0.15  0.16  0.18  0.16  0.19  0.29  0.21  0.04  0.06  0.12  0.13           0
100-200 Hz    6     HCage        1  0.33  0.30  0.32  0.37  0.47  0.57  0.56  0.46  0.39  0.38  0.38           0
     theta    6     HCage        1  0.14  0.16  0.18  0.20  0.23  0.36  0.35  0.21  0.09  0.07  0.07           0
100-200 Hz    6     HCage        2  0.13  0.20  0.24  0.29  0.39  0.60  0.56  0.40  0.27  0.17  0.08           0
     theta    6     HCage        2 -0.12 -0.12 -0.13 -0.12 -0.04 -0.03 -0.10 -0.10 -0.11 -0.10 -0.11           0

100-200 Hz: best shift per file {-5: 1, 0: 10, 1: 13}; median correlation at 0 0.44, at +1 0.46, at -1 0.28, at +-5 0.13

theta: best shift per file {-5: 2, -2: 1, -1: 1, 0: 13, 1: 2, 2: 1, 3: 2, 4: 2}; median correlation at 0 0.11, at +1 0.07, at -1 0.08, at +-5 0.07
InΒ [6]:
# Which way does movement go with gamma and theta power, by condition (epochs, per file)?
tab_all = bflfp.epoch_table(recordings, 2, "asis")
rows = []
for (rat, cond, sess), g in tab_all.groupby(["rat", "condition", "session"]):
    rows.append({"rat": rat, "condition": cond, "session": sess,
                 "rho_mov_gamma": stats.spearmanr(g.mov, g.gamma)[0],
                 "rho_mov_theta": stats.spearmanr(g.mov, g.theta)[0]})
print(pd.DataFrame(rows).round(2).to_string(index=False))
print("Median movement value by condition, per rat:")
print(tab_all.groupby(["rat", "condition"]).mov.median().unstack().round(0).to_string())
 rat condition  session  rho_mov_gamma  rho_mov_theta
   1     Arena        1           0.01           0.34
   1     Arena        2           0.13           0.33
   1     HCage        1           0.42           0.37
   1     HCage        2           0.49          -0.01
   2     Arena        1           0.43          -0.01
   2     Arena        2           0.06           0.26
   2     HCage        1           0.45           0.32
   2     HCage        2           0.50           0.25
   3     Arena        1           0.27          -0.12
   3     Arena        2           0.07           0.04
   3     HCage        1           0.42          -0.06
   3     HCage        2           0.45           0.12
   4     Arena        1          -0.06           0.08
   4     Arena        2          -0.11           0.10
   4     HCage        1           0.30           0.08
   4     HCage        2           0.47           0.14
   5     Arena        1          -0.13           0.26
   5     Arena        2           0.05          -0.03
   5     HCage        1           0.35          -0.06
   5     HCage        2           0.27          -0.10
   6     Arena        1           0.01           0.22
   6     Arena        2           0.21           0.29
   6     HCage        1           0.56           0.36
   6     HCage        2           0.35          -0.03
Median movement value by condition, per rat:
condition  Arena  HCage
rat                    
1          496.0  248.0
2          440.0  316.0
3          394.0  213.0
4          102.0   55.0
5           95.0   68.0
6          329.0  238.0

In 23 of 24 files the correlation of 100-200 Hz power with the movement values is largest at a shift of 0 or +1 segment and falls off at larger shifts (printed above), so the movement values are aligned with the LFP segments to within one segment (2 s). In 13 files the peak is at +1: the movement value of a segment goes best with the LFP of the next segment, so the movement stream may lead the LFP by up to one segment; this is consistent with the data and not shown to be an offset. Theta power gives a weaker and less regular relation. Movement is higher in the arena than in the home cage in every rat, as the conditions imply. This is consistent with the labels and the movement stream, and does not by itself prove each file's label. No two files share a clock, so alignment across files cannot be checked.

Units of analysis and readings shared by the claimsΒΆ

Gamma power and the other band powers are computed per 2-s segment (bflfp.epoch_table). The claims file's header names readings that apply to several claims, and each is run:

  • epoch length: 2-s segments (main) or 10-s epochs of five contiguous segments;
  • artifacts: segments as delivered (main) or the SI's peak-to-peak rule applied again;
  • unit of pairing: per rat, the two sessions of a condition averaged (rat 3's home cage is session 1 alone), n = 6; or session pairs (session 1 with session 1), n = 11 without the duplicate, and 12 with the files as delivered.
InΒ [7]:
def drop_duplicates(tab):
    keys = list(zip(tab.rat, tab.condition, tab.session))
    return tab[[k not in DUPLICATES for k in keys]].copy()


def unit_means(tab, unit, measure="gamma"):
    # Per recording, the mean over its epochs; then per rat (sessions averaged) or per session pair.
    m = tab.groupby(["rat", "condition", "session"])[measure].mean().reset_index()
    if unit != "session_asis":
        m = m[[(a, b, c) not in DUPLICATES for a, b, c in zip(m.rat, m.condition, m.session)]]
    if unit == "rat":
        return m.groupby(["rat", "condition"])[measure].mean().unstack()
    return m.set_index(["rat", "session", "condition"])[measure].unstack().dropna()


tables = {(ep, art): bflfp.epoch_table(recordings, ep, art) for ep in (2, 10) for art in ("asis", "ptp")}
for (ep, art), t in tables.items():
    kept = t.groupby(["rat", "condition", "session"]).size()
    print(f"{ep}-s epochs, artifacts {art}: {len(t)} epochs, per file {kept.min()} to {kept.max()}")
n2 = sum(r["lfp"].shape[1] for r in recordings)
print(f"The peak-to-peak rule drops {n2 - len(tables[(2, 'ptp')])} of {n2} segments "
      f"({100 * (1 - len(tables[(2, 'ptp')]) / n2):.1f}%); the SI reports 1-4%")
main = drop_duplicates(tables[(2, "asis")])
2-s epochs, artifacts asis: 8556 epochs, per file 255 to 948
2-s epochs, artifacts ptp: 7118 epochs, per file 221 to 808
10-s epochs, artifacts asis: 1476 epochs, per file 41 to 180
10-s epochs, artifacts ptp: 1261 epochs, per file 34 to 154
The peak-to-peak rule drops 1438 of 8556 segments (16.8%); the SI reports 1-4%

fig3a-bf-gamma-reductionΒΆ

BF gamma power was reduced by 35 +/- 3% when rats moved from the home cage to the novel arena, a highly significant reduction. (Expected 35 +/- 3%; criterion: reduction within tolerance under reading (a) mean over recordings of 100 x (1 - arena / home cage) or (b) from the group means, and a paired t test over rats at p < 0.05 with home cage higher; partially reproduced when the test holds and no reading is within tolerance.)

InΒ [8]:
rows = []
for (ep, art), t in tables.items():
    for unit in ("rat", "session", "session_asis"):
        w = unit_means(t, unit)
        h, a = w["HCage"], w["Arena"]
        test = stats.ttest_rel(h, a)
        rows.append({"epoch_s": ep, "artifacts": art, "unit": unit, "n": len(w),
                     "reading_a_pct": (100 * (1 - a / h)).mean(), "reading_b_pct": 100 * (1 - a.mean() / h.mean()),
                     "sem_a": stats.sem(100 * (1 - a / h)), "t": test.statistic, "p": test.pvalue,
                     "home_higher_in": f"{int((h > a).sum())}/{len(w)}"})
grid = pd.DataFrame(rows)
print(grid.round(4).to_string(index=False))
print("\nReadings with the reduction within 32-38%:",
      int(((grid.reading_a_pct.between(32, 38)) | (grid.reading_b_pct.between(32, 38))).sum()), "of", len(grid))
w = unit_means(main, "rat")
print("\nPer rat (main reading: 2-s, as delivered), gamma power (uV^2/Hz if the LFP is in uV):")
print(w.assign(reduction_pct=100 * (1 - w.Arena / w.HCage)).round(1).to_string())
 epoch_s artifacts         unit  n  reading_a_pct  reading_b_pct  sem_a      t      p home_higher_in
       2      asis          rat  6        48.0473        48.0196 3.5832 3.8443 0.0121            6/6
       2      asis      session 11        47.7245        47.9953 3.4179 5.0958 0.0005          11/11
       2      asis session_asis 12        47.7780        48.0134 3.1206 5.2753 0.0003          12/12
       2       ptp          rat  6        47.2548        47.5060 3.5077 3.6860 0.0142            6/6
       2       ptp      session 11        46.8431        47.4976 3.5070 4.8875 0.0006          11/11
       2       ptp session_asis 12        46.9023        47.5004 3.2020 5.0768 0.0004          12/12
      10      asis          rat  6        48.1002        48.0612 3.6526 3.8799 0.0116            6/6
      10      asis      session 11        47.6699        48.0389 3.6382 5.1140 0.0005          11/11
      10      asis session_asis 12        47.7852        48.0891 3.3232 5.3084 0.0002          12/12
      10       ptp          rat  6        48.0663        48.0006 3.4964 3.9241 0.0111            6/6
      10       ptp      session 11        47.6082        47.9685 3.5607 5.2056 0.0004          11/11
      10       ptp session_asis 12        47.7115        48.0129 3.2521 5.4115 0.0002          12/12

Readings with the reduction within 32-38%: 0 of 12

Per rat (main reading: 2-s, as delivered), gamma power (uV^2/Hz if the LFP is in uV):
condition  Arena  HCage  reduction_pct
rat                                   
1           20.2   56.0           63.9
2           31.3   60.2           48.1
3           18.7   36.7           49.0
4           23.3   42.2           44.9
5           79.4  145.0           45.2
6           22.6   36.0           37.3
InΒ [9]:
# Variants not named by the criterion: 49-51 Hz left out of the band (line noise), and the amplitude
# spectrum |FFT| in place of power, which the data descriptor's example uses (abs(fft(...))).
rows = []
for r in recordings:
    if bflfp.key(r) in DUPLICATES:
        continue
    f, p = bflfp.spectrum(r["lfp"])
    band = (f >= 30) & (f <= 80)
    notch = band & ~((f >= 49) & (f <= 51))
    rows.append({"rat": r["rat"], "condition": r["condition"], "session": r["session"],
                 "power": p[band].mean(), "power_no_50": p[notch].mean(), "amplitude": np.sqrt(p[band]).mean()})
var = pd.DataFrame(rows)
for measure in ("power", "power_no_50", "amplitude"):
    w = var.groupby(["rat", "condition"])[measure].mean().unstack()
    h, a = w["HCage"], w["Arena"]
    print(f"{measure:12s} reduction (a) {(100 * (1 - a / h)).mean():.1f}%  (b) {100 * (1 - a.mean() / h.mean()):.1f}%  "
          f"paired t p = {stats.ttest_rel(h, a).pvalue:.3g}, n = {len(w)}")
power        reduction (a) 48.0%  (b) 48.0%  paired t p = 0.0121, n = 6
power_no_50  reduction (a) 44.7%  (b) 44.5%  paired t p = 0.0121, n = 6
amplitude    reduction (a) 24.0%  (b) 23.9%  paired t p = 0.000619, n = 6
InΒ [10]:
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))
for r in recordings:
    if bflfp.key(r) in DUPLICATES:
        continue
    f, p = bflfp.spectrum(r["lfp"])
    keep = (f >= 1) & (f <= 100)
    ax[0].semilogy(f[keep], p[keep].mean(axis=1), color="C0" if r["condition"] == "HCage" else "C3", lw=0.7)
ax[0].set(xlabel="frequency (Hz)", ylabel="power (LFP unit^2/Hz)", title="Mean spectrum per file (blue home cage, red arena)")
w = unit_means(main, "rat")
for rat, row in w.iterrows():
    ax[1].plot([0, 1], [row.HCage, row.Arena], "o-", color="0.4")
ax[1].set(xticks=[0, 1], xticklabels=["home cage", "arena"], ylabel="gamma power", title="Per rat")
plt.tight_layout()
plt.show()
print("Plotted gamma power per rat ranges from %.1f to %.1f" % (w.values.min(), w.values.max()))
No description has been provided for this image
Plotted gamma power per rat ranges from 18.7 to 145.0

Result: partially reproduced. Home-cage gamma exceeds arena gamma in every rat and every session pair, and the paired t test gives p = 0.012 over 6 rats (p = 0.00047 over 11 session pairs). The reduction is 48% under both percentage readings and every epoch, artifact and pairing reading (46.8% to 48.1%), outside 35 +/- 3%. Leaving out 49-51 Hz does not change this; the amplitude spectrum, which the paper does not use, gives a smaller reduction (printed above).

fig3a-each-hemisphere-significantΒΆ

The BF gamma reduction in the arena was significant in every one of the 22 hemispheres analyzed individually. (Criterion: per recording, a rat's arena session against its home-cage session of the same number, unpaired t test of epoch gamma, every recording p < 0.01 with home cage higher.)

InΒ [11]:
rows = []
for ep_art, t in (("2-s, as delivered", tables[(2, "asis")]), ("2-s, peak-to-peak rule", tables[(2, "ptp")]),
                  ("10-s, as delivered", tables[(10, "asis")])):
    for (rat, sess), g in t[t.condition == "Arena"].groupby(["rat", "session"]):
        for home_session, label in ((sess, "same number"), (1, "home session 1")):
            if label == "home session 1" and not (rat == 3 and sess == 2):
                continue
            h = t[(t.rat == rat) & (t.condition == "HCage") & (t.session == home_session)]
            test = stats.ttest_ind(h.gamma, g.gamma)
            rows.append({"epochs": ep_art, "rat": rat, "session": sess, "home_cage_file": f"session {home_session}",
                         "n_home": len(h), "n_arena": len(g), "home_mean": h.gamma.mean(), "arena_mean": g.gamma.mean(),
                         "t": test.statistic, "p": test.pvalue})
each = pd.DataFrame(rows)
print(each.to_string(index=False, float_format=lambda x: f"{x:.3g}"))
for ep_art, g in each.groupby("epochs", sort=False):
    ok = (g.p < 0.01) & (g.home_mean > g.arena_mean)
    print(f"{ep_art}: {int(ok.sum())} of {len(g)} comparisons p < 0.01 with home cage higher; largest p = {g.p.max():.2g}")
                epochs  rat  session home_cage_file  n_home  n_arena  home_mean  arena_mean    t         p
     2-s, as delivered    1        1      session 1     948      283       57.7          24 17.8  3.51e-63
     2-s, as delivered    1        2      session 2     354      281       54.3        16.5 23.8  1.08e-89
     2-s, as delivered    2        1      session 1     623      302       68.4        34.5 27.5 2.43e-122
     2-s, as delivered    2        2      session 2     317      255         52        28.1 16.4  7.13e-50
     2-s, as delivered    3        1      session 1     449      332       36.7        18.6 29.6 2.08e-129
     2-s, as delivered    3        2      session 2     291      301       36.6        18.9 26.9 9.89e-105
     2-s, as delivered    3        2      session 1     449      301       36.7        18.9 28.2 6.68e-120
     2-s, as delivered    4        1      session 1     359      354       45.1        26.8 19.8  7.34e-70
     2-s, as delivered    4        2      session 2     289      335       39.4        19.7 21.1  6.67e-75
     2-s, as delivered    5        1      session 1     290      312        135        84.8 20.4   2.1e-70
     2-s, as delivered    5        2      session 2     286      323        155        74.1 28.5 2.91e-114
     2-s, as delivered    6        1      session 1     370      291       31.7        23.5   12  2.75e-30
     2-s, as delivered    6        2      session 2     261      350       40.3        21.6 20.6  6.84e-72
2-s, peak-to-peak rule    1        1      session 1     808      226       52.5        22.6 15.3  4.92e-48
2-s, peak-to-peak rule    1        2      session 2     303      230       49.2        16.1 21.2  1.71e-72
2-s, peak-to-peak rule    2        1      session 1     528      252         65          33 26.6 3.44e-111
2-s, peak-to-peak rule    2        2      session 2     260      221       46.5        26.7 15.4  8.52e-44
2-s, peak-to-peak rule    3        1      session 1     365      277       35.5        18.3 26.6 8.44e-106
2-s, peak-to-peak rule    3        2      session 2     230      234       35.5        18.6 23.3  7.07e-80
2-s, peak-to-peak rule    3        2      session 1     365      234       35.5        18.6 24.6  4.72e-93
2-s, peak-to-peak rule    4        1      session 1     307      285       43.2        25.7 18.2  6.67e-59
2-s, peak-to-peak rule    4        2      session 2     240      275       38.6        18.4 21.5  9.16e-74
2-s, peak-to-peak rule    5        1      session 1     251      248        129        80.2 19.6  6.62e-64
2-s, peak-to-peak rule    5        2      session 2     243      271        147        69.4 28.9 1.63e-109
2-s, peak-to-peak rule    6        1      session 1     299      244       29.5          23 9.65  1.92e-20
2-s, peak-to-peak rule    6        2      session 2     230      291       38.5        21.1 18.5  5.25e-59
    10-s, as delivered    1        1      session 1     180       51       57.5        23.3 10.1  5.45e-20
    10-s, as delivered    1        2      session 2      61       47       53.1        16.4   14  6.44e-26
    10-s, as delivered    2        1      session 1     101       58       67.5          34 18.5  3.06e-41
    10-s, as delivered    2        2      session 2      52       48         51        27.8 11.2  3.68e-19
    10-s, as delivered    3        1      session 1      79       62       36.3        18.3 18.7  7.27e-40
    10-s, as delivered    3        2      session 2      53       45       36.7        18.7 16.6  6.21e-30
    10-s, as delivered    3        2      session 1      79       45       36.3        18.7 16.5  6.77e-33
    10-s, as delivered    4        1      session 1      65       63         45        26.7 13.7  1.28e-26
    10-s, as delivered    4        2      session 2      43       63       40.1        19.4   14  1.11e-25
    10-s, as delivered    5        1      session 1      46       49        132          83 11.6  7.67e-20
    10-s, as delivered    5        2      session 2      47       58        153          73 16.7  5.58e-31
    10-s, as delivered    6        1      session 1      54       50       30.4        23.6 5.71  1.11e-07
    10-s, as delivered    6        2      session 2      41       60       40.6        21.3 12.8  1.06e-22
2-s, as delivered: 13 of 13 comparisons p < 0.01 with home cage higher; largest p = 2.7e-30
2-s, peak-to-peak rule: 13 of 13 comparisons p < 0.01 with home cage higher; largest p = 1.9e-20
10-s, as delivered: 13 of 13 comparisons p < 0.01 with home cage higher; largest p = 1.1e-07

Result: reproduced. Each of the 12 arena recordings has lower gamma than its home-cage recording at p < 0.01 by the unpaired t test over epochs, under every epoch reading, including rat 3's arena session 2 against either of its home-cage files (largest p printed above; for that recording p < 1e-100 with 2-s epochs as delivered, 7.1e-80 and 4.7e-93 under the peak-to-peak rule, 6.2e-30 and 6.8e-33 with 10-s epochs). The unit is a rat and session, not a hemisphere.

fig3b-arena-gamma-unrelated-to-movementΒΆ

In the arena, BF gamma power did not vary with movement-sensor quartile. (Criterion, absence: 2-s epochs in quartiles of the movement value within each condition, within each recording or over a rat's pooled sessions; one-way ANOVA over quartiles on per-rat quartile means; reproduced when p > 0.1, not reproduced when p < 0.05. Variants: the ANOVA over epochs, and a repeated-measures ANOVA.)

InΒ [12]:
def quartile_means(tab, scope):
    # Quartiles of the movement value within each recording, or within a rat's pooled sessions,
    # for each condition separately (criterion). Returns per-rat quartile means and the epoch table.
    grp = ["rat", "condition", "session"] if scope == "recording" else ["rat", "condition"]
    t = tab.copy()
    t["q"] = t.groupby(grp)["mov"].transform(lambda x: pd.qcut(x, 4, labels=False) + 1)
    return t.groupby(["rat", "condition", "q"])[["gamma", "mov"]].mean().reset_index(), t


def quartile_tests(tab, cond):
    rows = []
    for scope in ("recording", "rat"):
        qm, t = quartile_means(tab, scope)
        d = qm[qm.condition == cond]
        oneway = stats.f_oneway(*[d[d.q == q].gamma for q in range(1, 5)])
        epochs = stats.f_oneway(*[t[(t.condition == cond) & (t.q == q)].gamma for q in range(1, 5)])
        rm = AnovaRM(d, "gamma", "rat", within=["q"]).fit().anova_table
        # variant: each rat's quartile means divided by that rat's mean over quartiles
        norm = d.gamma / d.groupby("rat").gamma.transform("mean")
        oneway_norm = stats.f_oneway(*[norm[d.q == q] for q in range(1, 5)])
        means = d.groupby("q").gamma.mean()
        # effect size: eta squared of the one-way ANOVA on per-rat means
        grand = d.gamma.mean()
        eta2 = sum(len(d[d.q == q]) * (means[q] - grand) ** 2 for q in range(1, 5)) / ((d.gamma - grand) ** 2).sum()
        rows.append({"quartiles_within": scope, "Q1": means[1], "Q2": means[2], "Q3": means[3], "Q4": means[4],
                     "Q4_over_Q1": means[4] / means[1], "rats_Q4_gt_Q1": int((d[d.q == 4].gamma.values > d[d.q == 1].gamma.values).sum()),
                     "oneway_F": oneway.statistic, "oneway_p": oneway.pvalue, "eta2": eta2,
                     "oneway_normalized_p": oneway_norm.pvalue,
                     "epochs_F": epochs.statistic, "epochs_p": epochs.pvalue,
                     "rm_F": rm["F Value"].iloc[0], "rm_p": rm["Pr > F"].iloc[0]})
    return pd.DataFrame(rows)


print("Arena, 2-s epochs as delivered:")
print(quartile_tests(main, "Arena").to_string(index=False, float_format=lambda x: f"{x:.3g}"))
print("\nArena, other epoch and artifact readings:")
for (ep, art), t in tables.items():
    if (ep, art) == (2, "asis"):
        continue
    r = quartile_tests(drop_duplicates(t), "Arena")
    print(f"  {ep}-s, {art}: one-way p = {r.oneway_p.round(3).tolist()}, Q4/Q1 = {r.Q4_over_Q1.round(3).tolist()}")
qm, _ = quartile_means(main, "recording")
print("\nPer-rat quartile means, arena (quartiles within each recording):")
print(qm[qm.condition == "Arena"].pivot(index="rat", columns="q", values="gamma").round(1).to_string())
Arena, 2-s epochs as delivered:
quartiles_within   Q1   Q2   Q3   Q4  Q4_over_Q1  rats_Q4_gt_Q1  oneway_F  oneway_p    eta2  oneway_normalized_p  epochs_F  epochs_p  rm_F  rm_p
       recording 32.3 32.7   32 33.5        1.04              4    0.0048         1 0.00072                0.117     0.628     0.597   1.1 0.378
             rat 33.8 33.1 31.6   32       0.949              3    0.0105     0.998 0.00157                0.325      1.61     0.185  1.39 0.283

Arena, other epoch and artifact readings:
  2-s, ptp: one-way p = [1.0, 0.998], Q4/Q1 = [1.041, 0.941]
  10-s, asis: one-way p = [1.0, 0.992], Q4/Q1 = [1.003, 0.922]
  10-s, ptp: one-way p = [1.0, 0.993], Q4/Q1 = [1.001, 0.914]

Per-rat quartile means, arena (quartiles within each recording):
q       1     2     3     4
rat                        
1    19.9  20.4  20.3  20.4
2    29.4  30.7  30.6  35.4
3    18.3  17.9  18.1  20.7
4    24.8  22.7  22.8  23.3
5    79.8  81.8  77.0  78.5
6    21.5  22.9  23.0  22.6

Result: reproduced. In the arena the per-rat quartile means of gamma differ by a few percent (Q4/Q1 near 1), and the one-way ANOVA over quartiles gives p > 0.1 with quartiles formed within each recording or within each rat, under every epoch and artifact reading. The epoch-level and repeated-measures variants also give p > 0.1 (epochs 0.60 and 0.19, repeated measures 0.38 and 0.28), as does the one-way ANOVA on quartile means divided by each rat's mean (0.12 and 0.33). The criterion's test cannot see a within-rat effect against the between-rat spread (it misses the home-cage rise below), so these variants are the informative ones here.

fig3b-home-cage-gamma-increases-with-movementΒΆ

In the home cage, BF gamma power increased with movement-sensor quartile. (Criterion: per-rat quartile means as for the arena claim; reproduced when the one-way ANOVA over quartiles gives p < 0.05 and the fourth quartile mean exceeds the first.)

InΒ [13]:
print("Home cage, 2-s epochs as delivered:")
print(quartile_tests(main, "HCage").to_string(index=False, float_format=lambda x: f"{x:.3g}"))
print("\nHome cage, other epoch and artifact readings:")
for (ep, art), t in tables.items():
    if (ep, art) == (2, "asis"):
        continue
    r = quartile_tests(drop_duplicates(t), "HCage")
    print(f"  {ep}-s, {art}: one-way p = {r.oneway_p.round(3).tolist()}, rm p = {r.rm_p.round(6).tolist()}, "
          f"Q4/Q1 = {r.Q4_over_Q1.round(2).tolist()}")
print("\nPer-rat quartile means, home cage (quartiles within each recording):")
print(qm[qm.condition == "HCage"].pivot(index="rat", columns="q", values="gamma").round(1).to_string())
print("\nBetween-rat spread of home-cage gamma (per-rat means): %.1f to %.1f"
      % (unit_means(main, "rat").HCage.min(), unit_means(main, "rat").HCage.max()))
Home cage, 2-s epochs as delivered:
quartiles_within   Q1   Q2   Q3   Q4  Q4_over_Q1  rats_Q4_gt_Q1  oneway_F  oneway_p   eta2  oneway_normalized_p  epochs_F  epochs_p  rm_F     rm_p
       recording 50.3 62.1 68.4   72        1.43              6     0.311     0.817 0.0446             1.56e-09        73  3.89e-46  19.4    2e-05
             rat 49.5 60.9 70.1 72.3        1.46              6     0.371     0.774 0.0528             2.48e-10      81.7  1.71e-51  21.1 1.23e-05

Home cage, other epoch and artifact readings:
  2-s, ptp: one-way p = [0.836, 0.803], rm p = [1.1e-05, 5e-06], Q4/Q1 = [1.42, 1.43]
  10-s, asis: one-way p = [0.892, 0.839], rm p = [0.00089, 8e-06], Q4/Q1 = [1.33, 1.36]
  10-s, ptp: one-way p = [0.885, 0.835], rm p = [5.2e-05, 2.1e-05], Q4/Q1 = [1.34, 1.35]

Per-rat quartile means, home cage (quartiles within each recording):
q        1      2      3      4
rat                            
1     40.7   50.9   61.8   73.6
2     47.0   62.5   69.7   72.4
3     30.9   36.4   38.7   41.0
4     35.3   40.3   44.3   50.5
5    122.2  146.4  157.0  154.2
6     25.9   35.9   38.7   40.6

Between-rat spread of home-cage gamma (per-rat means): 36.0 to 145.0
InΒ [14]:
# Variant: the data check found the movement value of a segment may go with the LFP of the next
# segment. Pair each LFP segment with the movement value of the segment before it in its file.
shifted = main.copy()
shifted["mov"] = shifted.groupby(["rat", "condition", "session"]).mov.shift(1)
shifted = shifted.dropna(subset=["mov"])
for cond in ("Arena", "HCage"):
    r = quartile_tests(shifted, cond)
    print(f"{cond}, movement shifted by one segment: one-way p = {r.oneway_p.round(3).tolist()}, "
          f"rm p = {r.rm_p.round(6).tolist()}, Q4/Q1 = {r.Q4_over_Q1.round(2).tolist()}")
Arena, movement shifted by one segment: one-way p = [0.999, 0.998], rm p = [0.136259, 0.227404], Q4/Q1 = [1.05, 0.97]
HCage, movement shifted by one segment: one-way p = [0.79, 0.747], rm p = [2.2e-05, 1.4e-05], Q4/Q1 = [1.46, 1.49]
InΒ [15]:
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5), sharey=True)
for i, cond in enumerate(("HCage", "Arena")):
    d = qm[qm.condition == cond]
    for rat, g in d.groupby("rat"):
        ax[i].plot(g.q, g.gamma, "o-", color="0.5", lw=0.8)
    ax[i].plot(range(1, 5), d.groupby("q").gamma.mean(), "k-", lw=2)
    ax[i].set(title=cond, xlabel="movement quartile", xticks=range(1, 5))
ax[0].set_ylabel("gamma power")
plt.tight_layout()
plt.show()
print("Plotted values range from %.1f to %.1f" % (qm.gamma.min(), qm.gamma.max()))
No description has been provided for this image
Plotted values range from 17.9 to 157.0

Result: not reproduced. Home-cage gamma rises from the first to the fourth movement quartile in every rat (Q4/Q1 1.43 and 1.46), but the criterion's test, a one-way ANOVA over quartiles on the per-rat quartile means, gives p = 0.82 and 0.77, because per-rat home-cage gamma ranges from 36 to 145 (eta squared 0.045). The variants that take the rat into account find the rise: the repeated-measures ANOVA gives p = 2.0e-05, the ANOVA over epochs p = 3.9e-46, and the one-way ANOVA on quartile means divided by each rat's mean p = 1.6e-09. Pairing each segment with the movement value of the segment before it, a variant, gives one-way p of 0.75 to 0.79. Whether the claims file's per-rat one-way ANOVA is the paper's test is asked of a reader (questions/crcns-bf-1.yaml).

fig3b-matched-movementΒΆ

Gamma suppression in the arena remained highly significant when epochs matched for movement were compared, for example the fourth home-cage quartile against the third arena quartile. (Criterion: reading (a) that pair; (b) the pair of quartiles, one per condition, whose mean movement values are closest; one-way ANOVA of the per-rat gamma means of the two groups, p < 0.05 with the home cage higher.)

InΒ [16]:
rows = []
for scope in ("recording", "rat"):
    qm_s, _ = quartile_means(main, scope)
    mov = qm_s.groupby(["condition", "q"]).mov.mean().unstack()
    diff = pd.DataFrame([(h, a, abs(mov.loc["HCage", h] - mov.loc["Arena", a])) for h in range(1, 5) for a in range(1, 5)],
                        columns=["home_q", "arena_q", "mov_diff"])
    closest = diff.sort_values("mov_diff").iloc[0]
    for reading, (hq, aq) in (("(a) home Q4 vs arena Q3", (4, 3)),
                              ("(b) closest movement", (int(closest.home_q), int(closest.arena_q)))):
        h = qm_s[(qm_s.condition == "HCage") & (qm_s.q == hq)].set_index("rat")
        a = qm_s[(qm_s.condition == "Arena") & (qm_s.q == aq)].set_index("rat")
        oneway = stats.f_oneway(h.gamma, a.gamma)
        paired = stats.ttest_rel(h.gamma, a.gamma)
        # variant: each rat's two values divided by their mean
        scale = (h.gamma + a.gamma) / 2
        oneway_norm = stats.f_oneway(h.gamma / scale, a.gamma / scale)
        rows.append({"quartiles_within": scope, "reading": reading, "home_q": hq, "arena_q": aq,
                     "home_mov": h.mov.mean(), "arena_mov": a.mov.mean(), "home_gamma": h.gamma.mean(),
                     "arena_gamma": a.gamma.mean(), "rats_home_higher": int((h.gamma > a.gamma).sum()),
                     "oneway_p": oneway.pvalue, "paired_t_p_variant": paired.pvalue,
                     "oneway_normalized_p_variant": oneway_norm.pvalue})
    if scope == "recording":
        print("Mean movement value per quartile (mean over rats), quartiles within each recording:")
        print(mov.round(0).to_string())
matched = pd.DataFrame(rows)
print(matched.to_string(index=False, float_format=lambda x: f"{x:.3g}"))

# Variant: movement matched within each rat, the home-cage and arena quartiles closest in that rat.
rows = []
qm_r, _ = quartile_means(main, "recording")
for rat, d in qm_r.groupby("rat"):
    h, a = d[d.condition == "HCage"].set_index("q"), d[d.condition == "Arena"].set_index("q")
    hq, aq = min(((x, y) for x in range(1, 5) for y in range(1, 5)), key=lambda p: abs(h.mov[p[0]] - a.mov[p[1]]))
    rows.append({"rat": rat, "home_q": hq, "arena_q": aq, "home_mov": h.mov[hq], "arena_mov": a.mov[aq],
                 "home_gamma": h.gamma[hq], "arena_gamma": a.gamma[aq]})
within = pd.DataFrame(rows)
print("\nVariant, quartiles matched within each rat:")
print(within.round(1).to_string(index=False))
print("one-way p = %.3g, paired t p = %.3g" % (stats.f_oneway(within.home_gamma, within.arena_gamma).pvalue,
                                            stats.ttest_rel(within.home_gamma, within.arena_gamma).pvalue))
Mean movement value per quartile (mean over rats), quartiles within each recording:
q              1      2      3      4
condition                            
Arena      202.0  287.0  333.0  421.0
HCage       77.0  161.0  228.0  356.0
quartiles_within                 reading  home_q  arena_q  home_mov  arena_mov  home_gamma  arena_gamma  rats_home_higher  oneway_p  paired_t_p_variant  oneway_normalized_p_variant
       recording (a) home Q4 vs arena Q3       4        3       356        333          72           32                 6      0.07             0.00714                     7.45e-08
       recording    (b) closest movement       4        3       356        333          72           32                 6      0.07             0.00714                     7.45e-08
             rat (a) home Q4 vs arena Q3       4        3       360        337        72.3         31.6                 6    0.0703             0.00692                      5.7e-08
             rat    (b) closest movement       4        3       360        337        72.3         31.6                 6    0.0703             0.00692                      5.7e-08

Variant, quartiles matched within each rat:
 rat  home_q  arena_q  home_mov  arena_mov  home_gamma  arena_gamma
   1       3        1     297.6      324.8        61.8         19.9
   2       4        3     478.7      473.9        72.4         30.6
   3       3        1     279.8      268.5        38.7         18.3
   4       4        3     107.5      107.4        50.5         22.8
   5       4        4     124.4      123.4       154.2         78.5
   6       2        1     196.8      191.4        35.9         21.5
one-way p = 0.0985, paired t p = 0.00914

Result: not reproduced. Both readings pick the home-cage fourth and arena third quartiles, and the movement-matched home-cage gamma (72.0) exceeds the arena gamma (32.0) in 6 of 6 rats, but the criterion's one-way ANOVA of the per-rat means of the two groups gives p = 0.070, above 0.05. A paired t test of the same values, a variant, gives p = 0.0071, and the one-way ANOVA on values divided by each rat's mean gives p = 7.5e-08. As for the home-cage rise, the choice of test is asked of a reader.

fig3c-home-cage-above-both-sessionsΒΆ

BF gamma was larger in the home cage than in either of the two daily arena sessions, and slightly larger (5.62 +/- 1.2%) in the second arena session than in the first, with no interaction between day and session. (Criterion: levels home cage, arena 1, arena 2; Tukey's HSD, with Bonferroni-corrected paired t tests as the variant; increase as 100 x (arena 2 / arena 1 - 1) averaged over recordings, or from the group means. Without day labels the two arena sessions are compared within rat by a paired t test and the day factor is dropped.)

InΒ [17]:
# The descriptor calls sessions 1 and 2 "two separate examples" of each session type; nothing in the
# file names, variables or documents gives a day or the order of trials within a day. The arena 2
# against arena 1 comparison is printed but cannot test the claim's second part.
for label, t in (("2-s, as delivered", main), ("2-s, peak-to-peak rule", drop_duplicates(tables[(2, "ptp")])),
                 ("10-s, as delivered", drop_duplicates(tables[(10, "asis")]))):
    m = t.groupby(["rat", "condition", "session"]).gamma.mean()
    home = m.xs("HCage", level="condition").groupby("rat").mean()
    a1 = m.xs("Arena", level="condition").xs(1, level="session")
    a2 = m.xs("Arena", level="condition").xs(2, level="session")
    levels = pd.DataFrame({"home": home, "arena1": a1, "arena2": a2})
    print(f"--- {label}")
    if label.startswith("2-s, as"):
        print(levels.round(1).to_string())
    tukey = pairwise_tukeyhsd(np.r_[home, a1, a2], ["home"] * 6 + ["arena1"] * 6 + ["arena2"] * 6)
    print(tukey.summary())
    for x, y, name in ((home, a1, "home vs arena1"), (home, a2, "home vs arena2"), (a2, a1, "arena2 vs arena1")):
        p = stats.ttest_rel(x, y).pvalue
        print(f"paired t {name}: mean difference {np.mean(x - y):.2f}, p = {p:.3g}, Bonferroni (x3) p = {min(1, 3 * p):.3g}")
    inc = 100 * (a2 / a1 - 1)
    print("increase arena2 over arena1: (a) mean over rats %.2f%% (per rat %s); (b) group means %.2f%%"
          % (inc.mean(), inc.round(1).tolist(), 100 * (a2.mean() / a1.mean() - 1)))
--- 2-s, as delivered
      home  arena1  arena2
rat                       
1     56.0    24.0    16.5
2     60.2    34.5    28.1
3     36.7    18.6    18.9
4     42.2    26.8    19.7
5    145.0    84.8    74.1
6     36.0    23.5    21.6
 Multiple Comparison of Means - Tukey HSD, FWER=0.05 
=====================================================
group1 group2 meandiff p-adj   lower    upper  reject
-----------------------------------------------------
arena1 arena2  -5.5423 0.9476 -51.5418 40.4572  False
arena1   home  27.3301 0.2998 -18.6694 73.3296  False
arena2   home  32.8724 0.1857 -13.1271 78.8719  False
-----------------------------------------------------
paired t home vs arena1: mean difference 27.33, p = 0.0126, Bonferroni (x3) p = 0.0379
paired t home vs arena2: mean difference 32.87, p = 0.0118, Bonferroni (x3) p = 0.0354
paired t arena2 vs arena1: mean difference -5.54, p = 0.0201, Bonferroni (x3) p = 0.0604
increase arena2 over arena1: (a) mean over rats -15.86% (per rat [-31.3, -18.5, 1.9, -26.6, -12.6, -8.1]); (b) group means -15.68%
--- 2-s, peak-to-peak rule
 Multiple Comparison of Means - Tukey HSD, FWER=0.05 
=====================================================
group1 group2 meandiff p-adj   lower    upper  reject
-----------------------------------------------------
arena1 arena2  -5.4244 0.9443 -49.0225 38.1737  False
arena1   home  25.4338  0.312 -18.1643 69.0319  False
arena2   home  30.8582 0.1911 -12.7399 74.4562  False
-----------------------------------------------------
paired t home vs arena1: mean difference 25.43, p = 0.0151, Bonferroni (x3) p = 0.0452
paired t home vs arena2: mean difference 30.86, p = 0.0137, Bonferroni (x3) p = 0.0412
paired t arena2 vs arena1: mean difference -5.42, p = 0.0211, Bonferroni (x3) p = 0.0633
increase arena2 over arena1: (a) mean over rats -16.05% (per rat [-28.8, -19.2, 1.8, -28.4, -13.5, -8.2]); (b) group means -16.04%
--- 10-s, as delivered
 Multiple Comparison of Means - Tukey HSD, FWER=0.05 
=====================================================
group1 group2 meandiff p-adj   lower    upper  reject
-----------------------------------------------------
arena1 arena2  -5.3891 0.9484 -50.4534 39.6752  False
arena1   home  27.0522 0.2929 -18.0121 72.1166  False
arena2   home  32.4414 0.1816 -12.6229 77.5057  False
-----------------------------------------------------
paired t home vs arena1: mean difference 27.05, p = 0.0125, Bonferroni (x3) p = 0.0375
paired t home vs arena2: mean difference 32.44, p = 0.0111, Bonferroni (x3) p = 0.0334
paired t arena2 vs arena1: mean difference -5.39, p = 0.0169, Bonferroni (x3) p = 0.0507
increase arena2 over arena1: (a) mean over rats -15.84% (per rat [-29.8, -18.1, 2.0, -27.1, -12.0, -9.8]); (b) group means -15.47%

Result: partially reproduced. The home cage exceeds both arena sessions by Bonferroni-corrected paired t tests (p = 0.038 and 0.035), but not by Tukey's HSD on the per-rat values (p = 0.30 and 0.19), which treats the 18 values as independent groups and ignores the pairing within rat. The criterion names Tukey as the standard reading and the paired tests as a variant; the outcome rests on the variant, chosen because the rats are paired across conditions. The second part of the claim, a rise from the first to the second daily arena session, cannot be tested: the descriptor calls sessions 1 and 2 "two separate examples" of each session type, and nothing in the data set gives a day or the order of trials. Arena session 2 has 15.9% lower gamma than session 1 (paired t p = 0.020), against the paper's +5.62%; this is reported, not judged, as session 2 need not be the second trial of a day.

fig3d-gamma-rises-with-time-in-arenaΒΆ

Within arena sessions BF gamma stayed suppressed but rose with time spent in the arena (r = 0.72, n = 22). (Expected r = 0.72 +/- 0.072; criterion: reading (a) Pearson r between time and gamma averaged across recordings in 1-min bins over the 10-min session, with 2-s epochs as the variant; (b) the mean over recordings of each recording's r; (c) r over all epochs pooled; reproduced when under one reading r is within tolerance and positive at p < 0.05.)

InΒ [18]:
arena = main[main.condition == "Arena"].copy()
arena["minute"] = (arena.t // 60).astype(int)
rows = []
for window, a in (("first 10 min", arena[arena.t < 600]), ("whole file", arena)):
    per = a.groupby(["rat", "session", "minute"]).gamma.mean().reset_index()
    curve = per.groupby("minute").gamma.mean()
    r_a, p_a = stats.pearsonr(curve.index, curve.values)
    per_epoch = a.groupby("t").gamma.mean()
    r_a2, p_a2 = stats.pearsonr(per_epoch.index, per_epoch.values)
    rs = np.array([stats.pearsonr(g.t, g.gamma)[0] for _, g in a.groupby(["rat", "session"])])
    t_b = stats.ttest_1samp(rs, 0)
    r_c, p_c = stats.pearsonr(a.t, a.gamma)
    rows += [{"window": window, "reading": "(a) 1-min bins, mean over recordings", "r": r_a, "p": p_a, "n": len(curve)},
             {"window": window, "reading": "(a) variant, 2-s epochs, mean over recordings", "r": r_a2, "p": p_a2, "n": len(per_epoch)},
             {"window": window, "reading": "(b) mean of per-recording r", "r": rs.mean(), "p": t_b.pvalue, "n": len(rs)},
             {"window": window, "reading": "(c) all epochs pooled", "r": r_c, "p": p_c, "n": len(a)}]
    if window == "first 10 min":
        print("Gamma per minute, mean over the 12 arena recordings:", curve.round(1).tolist())
        print("Per-recording r:", np.round(rs, 2).tolist())
        print("Recordings contributing to each minute:", per.groupby("minute").size().tolist())
    else:
        print("Whole file, recordings contributing to each minute:", per.groupby("minute").size().tolist())
corr = pd.DataFrame(rows)
print(corr.to_string(index=False, float_format=lambda x: f"{x:.3g}"))
print("Readings within 0.648-0.792 and positive at p < 0.05:",
      corr[(corr.r.between(0.648, 0.792)) & (corr.p < 0.05)][["window", "reading"]].values.tolist())
Gamma per minute, mean over the 12 arena recordings: [29.3, 29.1, 32.2, 32.2, 31.6, 33.1, 33.9, 33.1, 32.7, 34.4]
Per-recording r: [-0.02, -0.27, 0.06, 0.03, 0.28, 0.36, 0.36, 0.16, 0.07, 0.4, 0.24, -0.13]
Recordings contributing to each minute: [12, 12, 12, 12, 12, 12, 12, 12, 12, 11]
Whole file, recordings contributing to each minute: [12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 7, 2]
      window                                       reading      r        p    n
first 10 min          (a) 1-min bins, mean over recordings  0.859  0.00147   10
first 10 min (a) variant, 2-s epochs, mean over recordings  0.197  0.00061  300
first 10 min                   (b) mean of per-recording r  0.129   0.0562   12
first 10 min                         (c) all epochs pooled 0.0451  0.00944 3312
  whole file          (a) 1-min bins, mean over recordings  0.285    0.345   13
  whole file (a) variant, 2-s epochs, mean over recordings    0.1   0.0481  388
  whole file                   (b) mean of per-recording r  0.165    0.011   12
  whole file                         (c) all epochs pooled 0.0742 5.99e-06 3719
Readings within 0.648-0.792 and positive at p < 0.05: []
InΒ [19]:
# Where is arena entry? Each file's clock starts at 0 and files span 526 to 780 s, longer than the
# SI's 10-min trial. Fig. 3D's time axis runs from -10 to 10 min with the transfer to the home cage at
# 0, so the 10-min window is also anchored at the file's end. Reading (b) is the same under any shift
# of a file's clock; (a) and (c) are not. Bin width is shown, not judged (the criterion names 1 min).
arena["from_end"] = arena.t - (arena.groupby(["rat", "session"]).t.transform("max") + 2.0)
windows = {"first 10 min (t = 0 is entry)": (arena[arena.t < 600], "t"),
           "last 10 min (file end is transfer)": (arena[arena.from_end >= -600], "from_end")}
rows = []
for window, (a, col) in windows.items():
    for width in (20, 30, 60, 120):
        b = a.assign(bin=(a[col] // width).astype(int))
        curve = b.groupby(["rat", "session", "bin"]).gamma.mean().groupby("bin").mean()
        r, p = stats.pearsonr(curve.index, curve.values)
        rows.append({"window": window, "reading": f"(a) {width}-s bins", "r": r, "p": p, "n": len(curve)})
    rs = np.array([stats.pearsonr(g[col], g.gamma)[0] for _, g in a.groupby(["rat", "session"])])
    rows.append({"window": window, "reading": "(b) mean of per-recording r", "r": rs.mean(),
                 "p": stats.ttest_1samp(rs, 0).pvalue, "n": len(rs)})
    r, p = stats.pearsonr(a[col], a.gamma)
    rows.append({"window": window, "reading": "(c) all epochs pooled", "r": r, "p": p, "n": len(a)})
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda x: f"{x:.3g}"))

# Movement and gamma in the first minute, the middle and the last minute of each arena file: a
# transfer or handling at a file's edge would show as a change of the movement value there.
rows = []
for (rat, s), g in arena.groupby(["rat", "session"]):
    first, last = g[g.t < 60], g[g.from_end >= -60]
    mid = g[(g.t >= 60) & (g.from_end < -60)]
    rows.append({"rat": rat, "session": s, "span_s": -g.from_end.min() + g.t.min(),
                 "mov_first_min": first.mov.mean(), "mov_middle": mid.mov.mean(), "mov_last_min": last.mov.mean(),
                 "gamma_first_min": first.gamma.mean(), "gamma_middle": mid.gamma.mean(), "gamma_last_min": last.gamma.mean()})
edges = pd.DataFrame(rows)
print(edges.round(1).to_string(index=False))
print("Files whose first-minute movement exceeds the middle: %d of %d; last minute below the middle: %d of %d"
      % ((edges.mov_first_min > edges.mov_middle).sum(), len(edges), (edges.mov_last_min < edges.mov_middle).sum(), len(edges)))
                            window                     reading      r        p    n
     first 10 min (t = 0 is entry)               (a) 20-s bins  0.645 0.000119   30
     first 10 min (t = 0 is entry)               (a) 30-s bins  0.744 0.000168   20
     first 10 min (t = 0 is entry)               (a) 60-s bins  0.859  0.00147   10
     first 10 min (t = 0 is entry)              (a) 120-s bins   0.86   0.0618    5
     first 10 min (t = 0 is entry) (b) mean of per-recording r  0.129   0.0562   12
     first 10 min (t = 0 is entry)       (c) all epochs pooled 0.0451  0.00944 3312
last 10 min (file end is transfer)               (a) 20-s bins  0.662 6.86e-05   30
last 10 min (file end is transfer)               (a) 30-s bins  0.747 0.000152   20
last 10 min (file end is transfer)               (a) 60-s bins  0.885 0.000664   10
last 10 min (file end is transfer)              (a) 120-s bins  0.969  0.00646    5
last 10 min (file end is transfer) (b) mean of per-recording r   0.13   0.0214   12
last 10 min (file end is transfer)       (c) all epochs pooled 0.0429   0.0137 3296
 rat  session  span_s  mov_first_min  mov_middle  mov_last_min  gamma_first_min  gamma_middle  gamma_last_min
   1        1   620.0          511.2       447.8         137.4             22.6          24.1            24.3
   1        2   620.0          587.3       557.7         494.6             16.2          16.7            15.0
   2        1   620.0          419.1       398.7         652.4             31.3          33.5            45.3
   2        2   526.0          482.9       503.6         428.7             28.1          28.6            24.0
   3        1   688.0          403.9       389.2         372.4             17.4          18.6            19.1
   3        2   698.0          402.9       420.4         354.3             17.2          18.8            21.4
   4        1   758.0          103.2        95.2          80.7             23.2          27.3            25.3
   4        2   694.0          114.7       113.6          84.5             19.0          19.7            20.6
   5        1   704.0          106.7        75.9          78.5             76.5          84.8            94.1
   5        2   694.0           92.8       106.6          81.0             61.5          74.7            81.3
   6        1   660.0          361.0       313.2         257.5             17.4          24.2            23.6
   6        2   780.0          390.0       337.6         275.7             21.1          21.6            23.0
Files whose first-minute movement exceeds the middle: 9 of 12; last minute below the middle: 10 of 12
InΒ [20]:
fig, ax = plt.subplots(figsize=(5, 3.5))
per = arena[arena.t < 600].groupby(["rat", "session", "minute"]).gamma.mean().reset_index()
for _, g in per.groupby(["rat", "session"]):
    ax.plot(g.minute + 0.5, g.gamma / g.gamma.mean(), color="0.7", lw=0.8)
ax.plot(np.arange(10) + 0.5, per.groupby("minute").gamma.mean() / per.gamma.mean(), "k-", lw=2)
ax.set(xlabel="time in arena (min)", ylabel="gamma / recording mean", title="Arena gamma by minute")
plt.show()
print("Plotted ratios range from %.2f to %.2f" % ((per.gamma / per.groupby(["rat", "session"]).gamma.transform("mean")).min(),
                                                 (per.gamma / per.groupby(["rat", "session"]).gamma.transform("mean")).max()))
No description has been provided for this image
Plotted ratios range from 0.72 to 1.45

Result: partially reproduced. Gamma rises with time in the arena. Under reading (a), 1-min bins over the first 10 min, r = 0.859 (p = 0.0015), above the tolerance of 0.72 +/- 0.072; reading (b) gives a mean per-recording r of 0.129 (p = 0.056) and reading (c) r = 0.045 (p = 0.0094). No reading the criterion names puts r within tolerance, while the positive correlation holds at p < 0.05 under (a) and (c).

Whether a file's t = 0 is arena entry is not documented. With the 10-min window anchored at the file's end, which Fig. 3D's axis makes the transfer, 1-min bins give r = 0.885 (p = 0.00066) and reading (b) r = 0.130 (p = 0.021). Reading (b) does not depend on where a file's clock starts, and over whole files it gives r = 0.165 (p = 0.011). In 9 of 12 arena files the first minute has more movement than the middle, consistent with entry near t = 0 but not proof of it. Several of these differences are small. The outcome rests on the criterion's 1-min bins. Other bin widths would put r within tolerance: 30-s bins give r = 0.744 and 0.747 under the two anchors, and 20-s bins with the end anchor give 0.662 (p = 6.9e-5); 20-s bins from the start give 0.645, just below, and 2-min bins 0.860 and 0.969 (shown, not judged).

figs2-bf-low-frequency-bandsΒΆ

In the BF, delta and beta power, like gamma, were higher in the home cage than in the arena, while theta power was strongly suppressed in the home cage. (Criterion: band power per rat and condition, paired t test per band; reproduced when all four bands differ at p < 0.05 in the stated directions; partially when theta and gamma do and one of delta and beta does not.)

InΒ [21]:
expected_sign = {"delta": 1, "theta": -1, "beta": 1, "gamma": 1}
rows = []
for (ep, art), t in tables.items():
    for unit in ("rat", "session", "session_asis"):
        for band, sign in expected_sign.items():
            w = unit_means(t, unit, band)
            test = stats.ttest_rel(w.HCage, w.Arena)
            rows.append({"epoch_s": ep, "artifacts": art, "unit": unit, "band": band, "n": len(w),
                         "home": w.HCage.mean(), "arena": w.Arena.mean(), "home_over_arena": (w.HCage / w.Arena).mean(),
                         "t": test.statistic, "p": test.pvalue,
                         "holds": bool(test.pvalue < 0.05 and np.sign(test.statistic) == sign)})
bands = pd.DataFrame(rows)
print(bands[(bands.epoch_s == 2) & (bands.artifacts == "asis")].to_string(index=False, float_format=lambda x: f"{x:.3g}"))
summary = bands.groupby(["epoch_s", "artifacts", "unit"]).holds.agg(lambda x: "".join("Y" if v else "n" for v in x))
print("\nBands holding (delta, theta, beta, gamma) under every reading:")
print(summary.to_string())
 epoch_s artifacts         unit  band  n  home  arena  home_over_arena     t        p  holds
       2      asis          rat delta  6   334    223             1.51  3.01   0.0299   True
       2      asis          rat theta  6   161    252            0.639 -8.17 0.000448   True
       2      asis          rat  beta  6  46.7     35             1.33   3.2   0.0239   True
       2      asis          rat gamma  6  62.7   32.6             1.98  3.84   0.0121   True
       2      asis      session delta 11   314    202             1.55  3.89  0.00301   True
       2      asis      session theta 11   157    247            0.641 -9.31 3.04e-06   True
       2      asis      session  beta 11  47.1   35.2             1.34  4.04  0.00237   True
       2      asis      session gamma 11    65   33.8             2.01   5.1 0.000467   True
       2      asis session_asis delta 12   333    223             1.52  4.17  0.00157   True
       2      asis session_asis theta 12   162    252            0.645 -10.2 5.85e-07   True
       2      asis session_asis  beta 12  46.6     35             1.33  4.32  0.00122   True
       2      asis session_asis gamma 12  62.7   32.6                2  5.28 0.000262   True

Bands holding (delta, theta, beta, gamma) under every reading:
epoch_s  artifacts  unit        
2        asis       rat             YYYY
                    session         YYYY
                    session_asis    YYYY
         ptp        rat             YYYY
                    session         YYYY
                    session_asis    YYYY
10       asis       rat             YYYY
                    session         YYYY
                    session_asis    YYYY
         ptp        rat             YYYY
                    session         YYYY
                    session_asis    YYYY

Result: reproduced. Over the six rats, delta, beta and gamma power are higher in the home cage and theta lower, each at p < 0.05 by the paired t test (theta p < 0.001), and all four hold under every epoch, artifact and pairing reading (printed above).

figs3-unimodal-statesΒΆ

Within each condition, the distributions of BF theta and gamma power over 2-s epochs were unimodal, with no evidence of substates, in each of six rats. (Criterion, absence: Hartigan dip test per rat, condition and band; reproduced when every test gives p > 0.1, not reproduced when any gives p < 0.05; readings (a) a rat's two sessions of a condition pooled, (b) each session alone.)

InΒ [22]:
rows = []
for reading in ("(a) sessions pooled", "(b) each session"):
    groups = main.groupby(["rat", "condition"]) if reading.startswith("(a)") else main.groupby(["rat", "condition", "session"])
    for k, g in groups:
        for band in ("theta", "gamma"):
            for scale, x in (("raw", g[band].values), ("log10", np.log10(g[band].values))):
                dip, p = diptest.diptest(x)
                rows.append({"reading": reading, "scale": scale, "group": k, "band": band, "n_epochs": len(g),
                             "minutes": len(g) * 2 / 60, "dip": dip, "p": p})
dips = pd.DataFrame(rows)
print(dips[dips.scale == "raw"].to_string(index=False, float_format=lambda x: f"{x:.3g}"))
for (reading, scale), g in dips.groupby(["reading", "scale"]):
    print(f"{reading}, {scale} power: {len(g)} tests, smallest p = {g.p.min():.3f}, tests with p <= 0.1: {int((g.p <= 0.1).sum())}, "
          f"with p < 0.05: {int((g.p < 0.05).sum())}")
# Variant: the duplicate file kept (rat 3 home cage session 2).
dup = tables[(2, "asis")]
for k in DUPLICATES:
    g = dup[(dup.rat == k[0]) & (dup.condition == k[1]) & (dup.session == k[2])]
    print("Variant, the left-out file", k, {b: round(diptest.diptest(g[b].values)[1], 3) for b in ("theta", "gamma")})
            reading scale         group  band  n_epochs  minutes     dip     p
(a) sessions pooled   raw    (1, Arena) theta       564     18.8   0.011 0.959
(a) sessions pooled   raw    (1, Arena) gamma       564     18.8  0.0103 0.984
(a) sessions pooled   raw    (1, HCage) theta      1302     43.4  0.0065 0.991
(a) sessions pooled   raw    (1, HCage) gamma      1302     43.4 0.00714 0.971
(a) sessions pooled   raw    (2, Arena) theta       557     18.6  0.0152 0.554
(a) sessions pooled   raw    (2, Arena) gamma       557     18.6 0.00995 0.991
(a) sessions pooled   raw    (2, HCage) theta       940     31.3 0.00801 0.985
(a) sessions pooled   raw    (2, HCage) gamma       940     31.3 0.00736 0.992
(a) sessions pooled   raw    (3, Arena) theta       633     21.1  0.0118 0.854
(a) sessions pooled   raw    (3, Arena) gamma       633     21.1  0.0117 0.866
(a) sessions pooled   raw    (3, HCage) theta       449       15  0.0116 0.983
(a) sessions pooled   raw    (3, HCage) gamma       449       15  0.0117  0.98
(a) sessions pooled   raw    (4, Arena) theta       689       23 0.00743 0.996
(a) sessions pooled   raw    (4, Arena) gamma       689       23 0.00898  0.99
(a) sessions pooled   raw    (4, HCage) theta       648     21.6 0.00896 0.992
(a) sessions pooled   raw    (4, HCage) gamma       648     21.6  0.0126 0.745
(a) sessions pooled   raw    (5, Arena) theta       635     21.2 0.00813 0.995
(a) sessions pooled   raw    (5, Arena) gamma       635     21.2  0.0117 0.859
(a) sessions pooled   raw    (5, HCage) theta       576     19.2 0.00856 0.995
(a) sessions pooled   raw    (5, HCage) gamma       576     19.2  0.0143 0.627
(a) sessions pooled   raw    (6, Arena) theta       641     21.4 0.00926 0.991
(a) sessions pooled   raw    (6, Arena) gamma       641     21.4  0.0112 0.905
(a) sessions pooled   raw    (6, HCage) theta       631       21 0.00881 0.992
(a) sessions pooled   raw    (6, HCage) gamma       631       21  0.0105 0.958
   (b) each session   raw (1, Arena, 1) theta       283     9.43  0.0179 0.816
   (b) each session   raw (1, Arena, 1) gamma       283     9.43  0.0178 0.826
   (b) each session   raw (1, Arena, 2) theta       281     9.37  0.0166 0.911
   (b) each session   raw (1, Arena, 2) gamma       281     9.37  0.0154 0.958
   (b) each session   raw (1, HCage, 1) theta       948     31.6 0.00743 0.992
   (b) each session   raw (1, HCage, 1) gamma       948     31.6 0.00738 0.992
   (b) each session   raw (1, HCage, 2) theta       354     11.8  0.0144  0.93
   (b) each session   raw (1, HCage, 2) gamma       354     11.8  0.0186 0.581
   (b) each session   raw (2, Arena, 1) theta       302     10.1  0.0194 0.639
   (b) each session   raw (2, Arena, 1) gamma       302     10.1  0.0135  0.99
   (b) each session   raw (2, Arena, 2) theta       255      8.5   0.021 0.649
   (b) each session   raw (2, Arena, 2) gamma       255      8.5  0.0181 0.868
   (b) each session   raw (2, HCage, 1) theta       623     20.8  0.0107 0.952
   (b) each session   raw (2, HCage, 1) gamma       623     20.8 0.00917 0.991
   (b) each session   raw (2, HCage, 2) theta       317     10.6  0.0109 0.996
   (b) each session   raw (2, HCage, 2) gamma       317     10.6  0.0143 0.965
   (b) each session   raw (3, Arena, 1) theta       332     11.1  0.0144 0.952
   (b) each session   raw (3, Arena, 1) gamma       332     11.1  0.0165 0.822
   (b) each session   raw (3, Arena, 2) theta       301       10  0.0141 0.981
   (b) each session   raw (3, Arena, 2) gamma       301       10  0.0165 0.884
   (b) each session   raw (3, HCage, 1) theta       449       15  0.0116 0.983
   (b) each session   raw (3, HCage, 1) gamma       449       15  0.0117  0.98
   (b) each session   raw (4, Arena, 1) theta       354     11.8  0.0123 0.991
   (b) each session   raw (4, Arena, 1) gamma       354     11.8   0.012 0.991
   (b) each session   raw (4, Arena, 2) theta       335     11.2  0.0147 0.935
   (b) each session   raw (4, Arena, 2) gamma       335     11.2  0.0131 0.986
   (b) each session   raw (4, HCage, 1) theta       359       12  0.0174 0.692
   (b) each session   raw (4, HCage, 1) gamma       359       12  0.0155 0.855
   (b) each session   raw (4, HCage, 2) theta       289     9.63  0.0138  0.99
   (b) each session   raw (4, HCage, 2) gamma       289     9.63  0.0139 0.988
   (b) each session   raw (5, Arena, 1) theta       312     10.4  0.0136 0.986
   (b) each session   raw (5, Arena, 1) gamma       312     10.4  0.0172  0.81
   (b) each session   raw (5, Arena, 2) theta       323     10.8  0.0176 0.751
   (b) each session   raw (5, Arena, 2) gamma       323     10.8  0.0124 0.992
   (b) each session   raw (5, HCage, 1) theta       290     9.67  0.0213 0.501
   (b) each session   raw (5, HCage, 1) gamma       290     9.67  0.0283 0.102
   (b) each session   raw (5, HCage, 2) theta       286     9.53  0.0126 0.993
   (b) each session   raw (5, HCage, 2) gamma       286     9.53  0.0198 0.654
   (b) each session   raw (6, Arena, 1) theta       291      9.7  0.0154  0.95
   (b) each session   raw (6, Arena, 1) gamma       291      9.7   0.015 0.962
   (b) each session   raw (6, Arena, 2) theta       350     11.7  0.0116 0.993
   (b) each session   raw (6, Arena, 2) gamma       350     11.7  0.0139 0.956
   (b) each session   raw (6, HCage, 1) theta       370     12.3  0.0114 0.992
   (b) each session   raw (6, HCage, 1) gamma       370     12.3  0.0138 0.949
   (b) each session   raw (6, HCage, 2) theta       261      8.7  0.0163 0.948
   (b) each session   raw (6, HCage, 2) gamma       261      8.7  0.0159 0.959
(a) sessions pooled, log10 power: 24 tests, smallest p = 0.462, tests with p <= 0.1: 0, with p < 0.05: 0
(a) sessions pooled, raw power: 24 tests, smallest p = 0.554, tests with p <= 0.1: 0, with p < 0.05: 0
(b) each session, log10 power: 46 tests, smallest p = 0.035, tests with p <= 0.1: 1, with p < 0.05: 1
(b) each session, raw power: 46 tests, smallest p = 0.102, tests with p <= 0.1: 0, with p < 0.05: 0
Variant, the left-out file (3, 'HCage', 2) {'theta': 0.993, 'gamma': 0.956}

Result: reproduced. Every Hartigan dip test of theta and gamma power over 2-s epochs, per rat and condition, gives p > 0.1, with sessions pooled (24 tests, smallest p = 0.554) or taken alone (46 tests, smallest p = 0.102; rat 3 has one home-cage file). The p-values come from the diptest package's tabulated null distribution. On log10 power, a variant the criterion does not name, the pooled reading also gives p > 0.1 (smallest 0.462), and each session alone gives one test below 0.05: rat 5's home-cage session 1, gamma, p = 0.035, the same test that gives the smallest p on raw power.

figs5-arena-excursionsΒΆ

During about 20 min of arena exploration, BF gamma made on average eight brief excursions into the home-cage range, lasting 8 to 44 s (16 +/- 8 s), with a clear rise in gamma power (0.56 +/- 0.27) and no change in theta power (-0.01 +/- 0.82) relative to 4 to 8 s before the peak. (Criterion: boxcar of 3, threshold at the intersection of the home-cage and arena gamma histograms, readings for the bins (20 equal bins; kernel density), the power scale (raw, log10, normalized to the rat's mean) and the arena data (two sessions together; each alone, count doubled). Reproduced when, under one reading, count and mean duration are within tolerance, durations within 8 to 44 s widened by 10%, the gamma rise is significant (p < 0.05) and the theta change is not (p > 0.1); partially reproduced when the gamma and theta tests hold and the count or duration does not.)

InΒ [23]:
def scaled(x, scale, ref):
    return {"raw": x, "log10": np.log10(x), "normalized": x / ref}[scale]


def run_excursions(scale, method, data):
    per_rat, events = [], []
    for rat in range(1, 7):
        t = main[main.rat == rat]
        ref_g, ref_t = t.gamma.mean(), t.theta.mean()
        home, ar = t[t.condition == "HCage"], t[t.condition == "Arena"]
        if data == "together":
            sets = [(home, ar, 1)]
        else:
            sets = [(home[home.session == s] if (home.session == s).any() else home, ar[ar.session == s], 2) for s in (1, 2)]
        for h, a, factor in sets:
            find = bflfp.histogram_intersection if method == "20 bins" else bflfp.kde_intersection
            thr = find(scaled(h.gamma.values, scale, ref_g), scaled(a.gamma.values, scale, ref_g))
            ev = []
            for s, g in a.groupby("session"):  # files do not share a clock: one series per file
                ev += bflfp.excursions(scaled(g.gamma.values, scale, ref_g), scaled(g.theta.values, scale, ref_t),
                                       g.t.values, thr)
            per_rat.append({"rat": rat, "threshold": thr, "count_20min": factor * len(ev),
                            "arena_minutes": len(a) * 2 / 60})
            events += [dict(e, rat=rat) for e in ev]
    per_rat, events = pd.DataFrame(per_rat), pd.DataFrame(events)
    ok = events.dropna(subset=["gamma_before"])
    by_rat = ok.groupby("rat")[["gamma_peak", "gamma_before", "theta_peak", "theta_before"]].mean()
    g_test = stats.ttest_rel(by_rat.gamma_peak, by_rat.gamma_before)
    t_test = stats.ttest_rel(by_rat.theta_peak, by_rat.theta_before)
    return per_rat, events, {
        "scale": scale, "bins": method, "arena_data": data,
        "threshold_range": f"{per_rat.threshold.min():.3g}-{per_rat.threshold.max():.3g}",
        "count_per_20min": per_rat.groupby("rat").count_20min.mean().mean(),
        "mean_duration_s": events.duration_s.mean(), "sd_duration_s": events.duration_s.std(),
        "min_duration_s": events.duration_s.min(), "max_duration_s": events.duration_s.max(),
        "n_events": len(events), "events_without_before": len(events) - len(ok),
        "gamma_rise": (by_rat.gamma_peak - by_rat.gamma_before).mean(), "gamma_p": g_test.pvalue,
        "theta_change": (by_rat.theta_peak - by_rat.theta_before).mean(), "theta_p": t_test.pvalue,
        # scale-free comparison with the paper's 0.56 / 2.6: the rise over the mean threshold, shown not judged
        "rise_over_threshold": (by_rat.gamma_peak - by_rat.gamma_before).mean() / per_rat.threshold.mean()}


results = []
for scale in ("raw", "log10", "normalized"):
    for method in ("20 bins", "kernel density"):
        for data in ("together", "each alone"):
            results.append(run_excursions(scale, method, data)[2])
exc = pd.DataFrame(results)
exc["count_ok"] = exc.count_per_20min.between(7.2, 8.8)
exc["duration_ok"] = exc.mean_duration_s.between(8, 24) & (exc.min_duration_s >= 7.2) & (exc.max_duration_s <= 48.4)
exc["gamma_ok"] = exc.gamma_p < 0.05
exc["theta_ok"] = exc.theta_p > 0.1
print(exc.to_string(index=False, float_format=lambda x: f"{x:.3g}"))
print("Paper's rise over threshold: 0.56 / 2.6 = %.3f; here, raw and normalized scales: %s"
      % (0.56 / 2.6, exc[exc.scale != "log10"].rise_over_threshold.round(3).tolist()))
     scale           bins arena_data threshold_range  count_per_20min  mean_duration_s  sd_duration_s  min_duration_s  max_duration_s  n_events  events_without_before  gamma_rise  gamma_p  theta_change  theta_p  rise_over_threshold  count_ok  duration_ok  gamma_ok  theta_ok
       raw        20 bins   together        25.3-106             14.5             5.45           4.89               2              28        87                      2        18.9   0.0317          11.5   0.0282                0.427     False        False      True     False
       raw        20 bins each alone        24.9-109             12.3             5.59           5.06               2              28        74                      3        18.9     0.04           -16    0.415                0.429     False        False      True      True
       raw kernel density   together        24.8-108             13.8             5.35           5.17               2              30        83                      2        18.6   0.0269          11.1    0.244                0.417     False        False      True      True
       raw kernel density each alone        24.5-108             10.8             5.42           5.39               2              28        65                      2        19.2   0.0284         -8.26    0.526                0.438     False        False      True      True
     log10        20 bins   together       1.39-2.03             13.7             5.12           5.08               2              28        82                      2       0.144 0.000417        0.0346    0.193               0.0913     False        False      True      True
     log10        20 bins each alone       1.38-2.03               13             5.56           5.44               2              28        78                      3       0.164 0.000787      -0.00473    0.894                0.105     False        False      True      True
     log10 kernel density   together       1.39-2.03             13.7              5.1           4.98               2              30        82                      2       0.146 0.000488        0.0315    0.346                0.092     False        False      True      True
     log10 kernel density each alone       1.38-2.03             10.7             5.38           5.83               2              28        64                      3        0.17  0.00052        -0.028    0.505                0.108     False        False      True      True
normalized        20 bins   together     0.699-0.992             14.5             5.45           4.89               2              28        87                      2       0.349 0.000852        0.0593     0.04                0.387     False        False      True     False
normalized        20 bins each alone      0.551-1.11             12.3             5.59           5.06               2              28        74                      3       0.347  0.00287       -0.0805    0.398                0.391     False        False      True      True
normalized kernel density   together     0.674-0.998             13.8             5.35           5.17               2              30        83                      2       0.352 0.000112        0.0558    0.256                 0.39     False        False      True      True
normalized kernel density each alone      0.541-1.11             10.8             5.42           5.39               2              28        65                      2       0.366  0.00116       -0.0428    0.532                0.411     False        False      True      True
Paper's rise over threshold: 0.56 / 2.6 = 0.215; here, raw and normalized scales: [0.427, 0.429, 0.417, 0.438, 0.387, 0.391, 0.39, 0.411]
InΒ [24]:
per_rat, events, _ = run_excursions("log10", "20 bins", "together")
print("log10 scale, 20 bins, sessions together: per rat")
print(per_rat.round(3).to_string(index=False))
print("Durations (s), counts:", events.duration_s.value_counts().sort_index().to_dict())
print("Excursions lasting 8 s or more: %d of %d; their mean count per rat %.1f"
      % ((events.duration_s >= 8).sum(), len(events), (events.duration_s >= 8).groupby(events.rat).sum().mean()))
fig, ax = plt.subplots(figsize=(9, 3))
t = main[(main.rat == 1) & (main.condition == "Arena") & (main.session == 1)]
ax.plot(t.t / 60, bflfp.boxcar(np.log10(t.gamma.values)), lw=0.8)
ax.axhline(per_rat[per_rat.rat == 1].threshold.iloc[0], color="k", ls=":")
ax.set(xlabel="time (min)", ylabel="log10 gamma, boxcar 3", title="Rat 1, arena session 1")
plt.show()
print("Plotted range: %.2f to %.2f" % (bflfp.boxcar(np.log10(t.gamma.values)).min(), bflfp.boxcar(np.log10(t.gamma.values)).max()))
log10 scale, 20 bins, sessions together: per rat
 rat  threshold  count_20min  arena_minutes
   1      1.481           11         18.800
   2      1.628           11         18.567
   3      1.392            8         21.100
   4      1.520           16         22.967
   5      2.029           16         21.167
   6      1.445           20         21.367
Durations (s), counts: {2.0: 37, 4.0: 17, 6.0: 15, 8.0: 2, 10.0: 4, 12.0: 2, 14.0: 1, 18.0: 1, 22.0: 1, 26.0: 1, 28.0: 1}
Excursions lasting 8 s or more: 13 of 82; their mean count per rat 2.2
No description has been provided for this image
Plotted range: 1.15 to 1.60

Result: partially reproduced. Gamma rises at the excursion peak under all 12 readings (paired t test over rats, p from 0.0001 to 0.04), and the theta change is not significant (p > 0.1) under 10 of them, among them every log10 reading; under the raw and normalized scales with 20 bins and the sessions taken together the theta test gives p = 0.028 and 0.040. No reading gives the paper's count or durations: 10.7 to 14.5 excursions per 20 min in place of 8, mean durations of 5.1 to 5.6 s in place of 16, and durations from 2 s, below the paper's shortest of 8 s. No reading puts the threshold near 2.6, so the 0.56 and -0.01 values are reported and not judged. As a scale-free comparison, the gamma rise over the mean threshold is 0.39 to 0.44 on the raw and normalized scales, against 0.56 / 2.6 = 0.215 in the paper (shown, not judged).

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
fig3a-bf-gamma-reduction partially reproduced (48% reduction, p = 0.012 over 6 rats)
fig3a-each-hemisphere-significant reproduced (12 of 12 recordings p < 0.01)
fig3b-arena-gamma-unrelated-to-movement reproduced
fig3b-home-cage-gamma-increases-with-movement not reproduced by the criterion's test (rise in every rat; repeated-measures p < 0.001)
fig3b-matched-movement not reproduced by the criterion's test (home higher in every rat; one-way p about 0.07)
fig3c-home-cage-above-both-sessions partially reproduced (home cage above both arena sessions by Bonferroni-corrected paired t tests, not by Tukey's HSD; session order not in the data set)
fig3d-gamma-rises-with-time-in-arena partially reproduced (r = 0.86 in 1-min bins; within tolerance in 30-s bins), rests on the criterion's bin width
figs2-bf-low-frequency-bands reproduced
figs3-unimodal-states reproduced
figs5-arena-excursions partially reproduced (more and shorter excursions)
bipolar-gamma-reduction, fig3c-consistent-over-days blocked, metadata insufficient
fig3d-rapid-switch-to-home-cage and 15 claims on behaviour, objects, units, ACC and VC blocked, data missing

How the analysis differs from the paper's: the data set gives one BF signal per rat for six rats, so tests run over rats (n = 6) or session pairs, where the paper used 22 hemispheres; the segments come already cleaned and down-sampled; the gamma measure is a plain periodogram mean over 30-80 Hz; the data set has no day labels and no order of sessions within a day.

What was found about the data set: rat 3's home-cage session 2 is a second deposit of 287 segments of its session 1; the files hold more removed segments (2.6% to 23.5%) than the SI's 1-4%; each file has its own clock, so no recording spans the transfer between arena and home cage; the LFP unit is not stated; movement values differ in scale between rats; the descriptor names Hcage where files say HCage, and calls the archive a file to unpack where it is a gzipped tar; the data set holds only the BF LFP of six rats, not the ACC, VC, unit, object or behaviour data of the paper.