Buckets:

redmingwei's picture
download
raw
6.76 kB
#!/usr/bin/env python3
"""exp61 — 截断到相同时长后的 tau。
exp60 的问题:
tau 与记录时长的 ρ = 0.287(越长越慢)
猕猴片段中位 16 min,人类 2 min
人类放电率 [0.16, 0.30] 与猕猴 [0.35, 0.63] 几乎不重叠,无法匹配
所以物种差异可能只是"人类片段太短,估不出慢成分"。
本脚本把两边的片段都截到相同长度,重算平台模型的 tau:
3000 bin (60 s) —— 人类中位 2 min,多数够
1500 bin (30 s) —— 更严格
全部可用长度 —— 对照
若截断后物种差异消失 → 差异是时长的产物。
若仍在 → 差异与记录时长无关(但仍与记录技术混淆)。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
from scipy.stats import mannwhitneyu, spearmanr
V3 = Path("/domains/counts-v3")
OUT = Path("/domains/runs/trunc")
OUT.mkdir(parents=True, exist_ok=True)
DT = 20.0
MAX_LAG = 500
N_NULL = 2
SEED = 0
LENS = [1500, 3000, 6000, None] # None = 全段
def n2(n):
k = 1
while k < n:
k <<= 1
return k
def rho_curve(x, max_lag):
T, d = x.shape
xc = x - x.mean(axis=0, keepdims=True)
N = n2(2 * T)
F = np.fft.rfft(xc, n=N, axis=0)
Fs = F.sum(axis=1)
tot = np.fft.irfft(Fs * np.conj(Fs), n=N)[:max_lag + 1]
dia = np.fft.irfft((np.abs(F) ** 2).sum(axis=1), n=N)[:max_lag + 1]
nrm = np.arange(T, T - max_lag - 1, -1)
tot = tot / np.maximum(nrm, 1)
dia = dia / np.maximum(nrm, 1)
t0 = max(tot[0], 1e-12)
return (tot - dia) / t0
def null_noise(x, ml, rng, n=N_NULL):
prof = []
for _ in range(n):
xs = np.empty_like(x)
for j in range(x.shape[1]):
xs[:, j] = np.roll(x[:, j], int(rng.integers(1, x.shape[0])))
prof.append(rho_curve(xs, ml))
return float(np.sqrt((np.array(prof)[:, 2:] ** 2).mean()))
def m_plateau(t, A, tau, c):
return A * np.exp(-t / np.maximum(tau, 1e-6)) + c
def fit_tau(t, y):
try:
p, _ = curve_fit(m_plateau, t, y, p0=[float(y[0]), 200.0, max(float(y[-1]), 0.0)],
bounds=([0.0, 2.0, -1.0], [10.0, 1e5, 1.0]), maxfev=60000)
r = y - m_plateau(t, *p)
return float(p[1]), float(p[0]), float(p[2]), float(1 - np.var(r) / max(np.var(y), 1e-20))
except Exception:
return None, None, None, None
def task_of(source, fname):
if source == "human_intracortical":
return "H1_reach_grasp" if fname.startswith("h1") else "H2_handwriting"
if "churchland" in fname:
return "reach"
if "dmfc" in fname:
return "timing"
if "area2" in fname:
return "bump"
return "?"
def main():
t0 = time.time()
W = np.load(V3 / "counts.npy", mmap_mode="r")
idx = pd.read_parquet(V3 / "counts_index.parquet")
rng = np.random.default_rng(SEED)
rows = []
for n, (_, r) in enumerate(idx.iterrows()):
x = np.asarray(W[r.bin_start:r.bin_end, :r.n_units], dtype=np.float32)
rec = {"source": r.source, "task": task_of(r.source, r.file),
"file": r.file, "n_units": int(x.shape[1]),
"n_bins_max": int(x.shape[0]), "rate": float(x.mean())}
got = False
for L in LENS:
key = "full" if L is None else str(L)
xx = x if L is None else x[:L]
if xx.shape[0] < 1200:
rec[f"tau_{key}"] = None
continue
ml = min(MAX_LAG, xx.shape[0] - 2)
noise = null_noise(xx, ml, rng)
rho = rho_curve(xx, ml)
snr = rho[1] / max(noise, 1e-12)
usable = np.flatnonzero((rho > 2 * noise) & np.isfinite(rho))
usable = usable[(usable >= 2) & (usable <= ml)]
if snr < 3.0 or len(usable) < 10:
rec[f"tau_{key}"] = None
continue
tv, A, c, r2 = fit_tau(usable.astype(float) * DT, rho[usable])
rec[f"tau_{key}"] = round(tv, 1) if tv else None
rec[f"A_{key}"] = round(A, 3) if A else None
rec[f"c_{key}"] = round(c, 4) if c is not None else None
rec[f"snr_{key}"] = round(snr, 1)
got = got or tv is not None
rows.append(rec)
if n % 20 == 0:
print(f" [{n+1}/{len(idx)}] {r.file} tau_full={rec.get('tau_full')} "
f"tau_3000={rec.get('tau_3000')} [{time.time()-t0:.0f}s]", flush=True)
del x
d = pd.DataFrame(rows)
d.to_csv(OUT / "per_segment.csv", index=False)
rep = {"task": "duration_truncation", "dt_ms": DT, "n": int(len(d))}
for L in LENS:
key = "full" if L is None else str(L)
col = f"tau_{key}"
mac = d[d.source == "macaque_nlb"][col].dropna()
hum = d[d.source == "human_intracortical"][col].dropna()
entry = {"n_macaque": int(len(mac)), "n_human": int(len(hum)),
"hours": "full" if L is None else round(L * DT / 3.6e6, 3)}
if len(mac) > 3 and len(hum) > 3:
entry.update({
"macaque_median": round(float(mac.median()), 1),
"human_median": round(float(hum.median()), 1),
"ratio": round(float(mac.median() / max(hum.median(), 1e-9)), 2),
"p": float(mannwhitneyu(mac, hum).pvalue)})
mac_m1 = d[(d.source == "macaque_nlb") & (d.task == "reach")][col].dropna()
hum_h2 = d[d.task == "H2_handwriting"][col].dropna()
if len(mac_m1) > 3 and len(hum_h2) > 3:
entry["reach_vs_handwriting"] = {
"macaque": round(float(mac_m1.median()), 1),
"human": round(float(hum_h2.median()), 1),
"ratio": round(float(mac_m1.median() / max(hum_h2.median(), 1e-9)), 2),
"p": float(mannwhitneyu(mac_m1, hum_h2).pvalue)}
rep[f"len_{key}"] = entry
# tau 与时长(在同一物种内)
rep["tau_vs_duration_within"] = {}
for src, g in d.groupby("source"):
m = g[["tau_full", "n_bins_max"]].dropna()
if len(m) > 10:
r_ = spearmanr(m.tau_full, m.n_bins_max)
rep["tau_vs_duration_within"][src] = {
"rho": round(float(r_[0]), 3), "p": float(r_[1]), "n": int(len(m))}
m = d[["tau_full", "n_bins_max"]].dropna()
r_ = spearmanr(m.tau_full, m.n_bins_max)
rep["tau_vs_duration_pooled"] = {"rho": round(float(r_[0]), 3), "p": float(r_[1])}
(OUT / "report.json").write_text(json.dumps(rep, indent=2, ensure_ascii=False),
encoding="utf-8")
print("\n" + json.dumps(rep, indent=2, ensure_ascii=False), flush=True)
if __name__ == "__main__":
main()

Xet Storage Details

Size:
6.76 kB
·
Xet hash:
f7d9a36868347803c03a99f14601d99a15c0c02f7b2a8d9c3c9acacd7d575d32

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.