feat(matlab): add Satterthwaite DF + honest random-slope test to LME reports
Every fitlme-based report (lme_*, paper_*, phase_*, and the variations' analyze.m) now shows, per effect: residual-DF p, Satterthwaite-DF p, and -- for the interaction -- an HONEST test from a per-animal random-SLOPE model (day|rat), whose Satterthwaite DF collapses toward the animal count. New: tdcs_random_slope_interaction.m (shared helper). Wired into tdcs_lme, tdcs_paper_lme, tdcs_phase_lme, variation_analyze; SUMMARY.csv gains interaction_p_satt / interaction_p_rs. Regenerated all results/, variations/, matched-effort outputs. Key point this surfaces: Satterthwaite ~= residual on the random-INTERCEPT model (the slope's error is at session level), so it does NOT fix pseudoreplication; the random-slope model does. Effect: full-range mergeA2 interaction 0.009 -> 0.75 (collapses); unmerge_d0_5 0.015 -> 0.13 (n.s.); the pooled-control early windows survive honestly (naive_a2_d0_5 0.001 -> 0.028; naive_boxa_d0_5 0.003 -> 0.036). Suite 42/42. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
# Experiments Database — Backup
|
||||||
|
|
||||||
|
## Location
|
||||||
|
```
|
||||||
|
/home/sam/synology/Backups/Experiments-DB-Backup/
|
||||||
|
├── daily/ ← up to 5 change-triggered daily backups
|
||||||
|
├── weekly/ ← up to 2 unconditional weekly backups
|
||||||
|
├── .last_backup_state ← DB fingerprint from last backup run
|
||||||
|
└── backup.log ← all run output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
```
|
||||||
|
scripts/
|
||||||
|
├── backup.sh ← main backup script
|
||||||
|
└── setup_cron.sh ← installs / updates cron entries (idempotent)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schedule
|
||||||
|
| Cron | Mode | Behaviour |
|
||||||
|
|------|------|-----------|
|
||||||
|
| `0 2 * * *` (daily 02:00) | `daily` | Compares DB fingerprint; skips if nothing changed |
|
||||||
|
| `0 3 * * 0` (Sunday 03:00) | `weekly` | Always dumps; ignores fingerprint |
|
||||||
|
|
||||||
|
To re-install cron entries after a path change:
|
||||||
|
```bash
|
||||||
|
./scripts/setup_cron.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Format
|
||||||
|
Files are `pg_dump --format=custom --compress=9` (`.pgdump`).
|
||||||
|
|
||||||
|
**Restore a backup:**
|
||||||
|
```bash
|
||||||
|
# Restore into a running container
|
||||||
|
docker exec -i experiments_postgres pg_restore \
|
||||||
|
-U expuser -d experiments_db --clean \
|
||||||
|
< /home/sam/synology/Backups/Experiments-DB-Backup/daily/<file>.pgdump
|
||||||
|
|
||||||
|
# Or restore to a fresh database
|
||||||
|
docker exec -i experiments_postgres createdb -U expuser experiments_db_restore
|
||||||
|
docker exec -i experiments_postgres pg_restore \
|
||||||
|
-U expuser -d experiments_db_restore \
|
||||||
|
< /path/to/file.pgdump
|
||||||
|
```
|
||||||
|
|
||||||
|
## Change Detection
|
||||||
|
The daily run fingerprints the database as:
|
||||||
|
```
|
||||||
|
<experiments count>,<animals count>,<daily_statuses count>,
|
||||||
|
<daily_analyses count>,<session_files count>,<latest audit_log timestamp>
|
||||||
|
```
|
||||||
|
Any insert, update, or delete on any table shifts this string and triggers a backup.
|
||||||
|
|
||||||
|
## Retention Policy
|
||||||
|
| Type | Keep |
|
||||||
|
|------|------|
|
||||||
|
| Daily | 5 most recent |
|
||||||
|
| Weekly | 2 most recent |
|
||||||
|
|
||||||
|
Older files are automatically deleted after each successful dump.
|
||||||
|
|
||||||
|
## Manual Run
|
||||||
|
```bash
|
||||||
|
# Force a backup right now (ignores change detection)
|
||||||
|
./scripts/backup.sh weekly
|
||||||
|
|
||||||
|
# Run change-detect check manually
|
||||||
|
./scripts/backup.sh daily
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify Cron Is Installed
|
||||||
|
```bash
|
||||||
|
crontab -l | grep ExpDB
|
||||||
|
```
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
# Experiments Database — n8n Workflows
|
||||||
|
|
||||||
|
n8n is running at **http://localhost:5678**
|
||||||
|
|
||||||
|
The Postgres credential **"Experiments DB (PostgreSQL)"** (id: `9iJuyA9iR5KUzmj5`) connects to `experiments_postgres:5432 / experiments_db` via the shared `llm-net` Docker network.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Index
|
||||||
|
|
||||||
|
| ID | Workflow | Entity |
|
||||||
|
|----|----------|--------|
|
||||||
|
| `jjmtecjuHJPKZ4T7` | ExpDB · Experiments | `experiments` table |
|
||||||
|
| `HYnlIscuKwMW7blG` | ExpDB · Animals | `animals` table |
|
||||||
|
| `9emayzMpnhIZQxqd` | ExpDB · Daily Statuses | `daily_statuses` table |
|
||||||
|
| `hso5z2W1uO44bTP6` | ExpDB · Daily Analyses | `daily_analyses` table |
|
||||||
|
| `2s02OX1yibfqp6jK` | ExpDB · Session Files | `session_files` table |
|
||||||
|
| `T76NAYiFcCxHDKQB` | ExpDB · Audit Logs | `audit_logs` table |
|
||||||
|
| `lq3R3YqeHgxixrbZ` | ExpDB · Reports | cross-table queries |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Call a Workflow
|
||||||
|
|
||||||
|
All workflows use an **Execute Workflow Trigger** — call them with an **Execute Workflow** node from any parent workflow. Pass a JSON object with `action` + the required fields.
|
||||||
|
|
||||||
|
```
|
||||||
|
Execute Workflow node
|
||||||
|
→ Workflow: <select workflow by name>
|
||||||
|
→ Input: { "action": "...", ...fields }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Experiments
|
||||||
|
|
||||||
|
### `list`
|
||||||
|
Returns all experiments ordered by creation date.
|
||||||
|
```json
|
||||||
|
{ "action": "list" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get`
|
||||||
|
```json
|
||||||
|
{ "action": "get", "id": "<experiment_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `create`
|
||||||
|
```json
|
||||||
|
{ "action": "create", "title": "My Experiment" }
|
||||||
|
```
|
||||||
|
Creates with empty `template` and `subject_info_template` (`[]`). UUID auto-generated.
|
||||||
|
|
||||||
|
### `update`
|
||||||
|
```json
|
||||||
|
{ "action": "update", "id": "<experiment_uuid>", "title": "New Title" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `delete`
|
||||||
|
```json
|
||||||
|
{ "action": "delete", "id": "<experiment_uuid>" }
|
||||||
|
```
|
||||||
|
Returns `{ id }` of deleted row.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Animals
|
||||||
|
|
||||||
|
### `list`
|
||||||
|
Returns all animals in an experiment, with `daily_status_count`.
|
||||||
|
```json
|
||||||
|
{ "action": "list", "experiment_id": "<experiment_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get`
|
||||||
|
Returns animal + `experiment_title`.
|
||||||
|
```json
|
||||||
|
{ "action": "get", "id": "<animal_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `create`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "create",
|
||||||
|
"experiment_id": "<experiment_uuid>",
|
||||||
|
"animal_id_string": "M001",
|
||||||
|
"animal_name": "Mouse 1",
|
||||||
|
"subject_info": { "<fieldId>": "value" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `update`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "update",
|
||||||
|
"id": "<animal_uuid>",
|
||||||
|
"animal_id_string": "M001",
|
||||||
|
"animal_name": "Mouse 1",
|
||||||
|
"subject_info": { "<fieldId>": "value" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `delete`
|
||||||
|
```json
|
||||||
|
{ "action": "delete", "id": "<animal_uuid>" }
|
||||||
|
```
|
||||||
|
Cascades to `daily_statuses`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Daily Statuses
|
||||||
|
|
||||||
|
### `list`
|
||||||
|
Returns statuses for an animal ordered by date descending.
|
||||||
|
```json
|
||||||
|
{ "action": "list", "animal_id": "<animal_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get`
|
||||||
|
Returns status + animal + experiment context.
|
||||||
|
```json
|
||||||
|
{ "action": "get", "id": "<status_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `create`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "create",
|
||||||
|
"animal_id": "<animal_uuid>",
|
||||||
|
"date": "2025-04-25",
|
||||||
|
"experiment_description": "...",
|
||||||
|
"vitals": "...",
|
||||||
|
"treatment": "...",
|
||||||
|
"notes": "...",
|
||||||
|
"custom_fields": { "<fieldId>": "value" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
All fields except `animal_id` and `date` are optional (default `null`).
|
||||||
|
|
||||||
|
### `update`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "update",
|
||||||
|
"id": "<status_uuid>",
|
||||||
|
"experiment_description": "...",
|
||||||
|
"vitals": "...",
|
||||||
|
"treatment": "...",
|
||||||
|
"notes": "...",
|
||||||
|
"custom_fields": { "<fieldId>": "value" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `delete`
|
||||||
|
```json
|
||||||
|
{ "action": "delete", "id": "<status_uuid>" }
|
||||||
|
```
|
||||||
|
Cascades to `daily_analyses` and `session_files`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Daily Analyses
|
||||||
|
|
||||||
|
### `list`
|
||||||
|
Returns analysis headers (no heavy JSON blobs) for a daily status.
|
||||||
|
```json
|
||||||
|
{ "action": "list", "daily_status_id": "<status_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get`
|
||||||
|
Returns full analysis including `sequence`, `category_counts`, `consecutive_stats`, `runs`.
|
||||||
|
```json
|
||||||
|
{ "action": "get", "id": "<analysis_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `push_summary`
|
||||||
|
Writes an `analysis_summary` blob back to a daily status (same as the "Save metrics" button in the UI).
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "push_summary",
|
||||||
|
"daily_status_id": "<status_uuid>",
|
||||||
|
"analysis_summary": {
|
||||||
|
"counts": { "Success": 12, "Failure": 3 },
|
||||||
|
"total": 15,
|
||||||
|
"success_rate": 0.8,
|
||||||
|
"computed_at": "2025-04-25T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Session Files
|
||||||
|
|
||||||
|
### `list`
|
||||||
|
```json
|
||||||
|
{ "action": "list", "daily_status_id": "<status_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get`
|
||||||
|
```json
|
||||||
|
{ "action": "get", "id": "<file_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Audit Logs
|
||||||
|
|
||||||
|
### `list_by_record`
|
||||||
|
All changes to a specific record (e.g. one animal, one daily status).
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "list_by_record",
|
||||||
|
"table_name": "daily_statuses",
|
||||||
|
"record_id": "<status_uuid>",
|
||||||
|
"limit": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Valid `table_name` values: `experiments`, `animals`, `daily_statuses`.
|
||||||
|
|
||||||
|
### `list_by_table`
|
||||||
|
Recent changes across an entire table.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "list_by_table",
|
||||||
|
"table_name": "animals",
|
||||||
|
"limit": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ExpDB · Reports
|
||||||
|
|
||||||
|
### `subjects_summary`
|
||||||
|
Per-subject stats for an experiment: total days logged, last entry date, days with saved metrics.
|
||||||
|
```json
|
||||||
|
{ "action": "subjects_summary", "experiment_id": "<experiment_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `daily_summary`
|
||||||
|
All days × subjects where `analysis_summary` exists — useful for CSV export or charting outside the app.
|
||||||
|
```json
|
||||||
|
{ "action": "daily_summary", "experiment_id": "<experiment_uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `recent_activity`
|
||||||
|
Latest audit log entries across all tables.
|
||||||
|
```json
|
||||||
|
{ "action": "recent_activity", "limit": 50 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Schema Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
experiments
|
||||||
|
id (uuid PK) title created_at template (json) subject_info_template (json)
|
||||||
|
|
||||||
|
animals
|
||||||
|
id (uuid PK) experiment_id (FK→experiments)
|
||||||
|
animal_id_string animal_name subject_info (json?)
|
||||||
|
|
||||||
|
daily_statuses
|
||||||
|
id (uuid PK) animal_id (FK→animals) date
|
||||||
|
experiment_description? vitals? treatment? notes?
|
||||||
|
custom_fields (json?) analysis_summary (json?)
|
||||||
|
|
||||||
|
daily_analyses
|
||||||
|
id (uuid PK) daily_status_id (FK→daily_statuses) created_at
|
||||||
|
file_name? timestamp_col note_col total_note_rows session_end_ts?
|
||||||
|
classifications (json) sequence (json) category_counts (json)
|
||||||
|
consecutive_stats (json) runs (json)
|
||||||
|
|
||||||
|
session_files
|
||||||
|
id (uuid PK) daily_status_id (FK→daily_statuses)
|
||||||
|
filename file_size file_type? last_modified?
|
||||||
|
matched_identifier? animal_id_string_snap? animal_name_snap?
|
||||||
|
notes? created_at
|
||||||
|
|
||||||
|
audit_logs
|
||||||
|
id (uuid PK) table_name record_id action changes (json) timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
`custom_fields` and `subject_info` are keyed by `fieldId` (UUID), matching the `template` / `subject_info_template` arrays on the experiment. Field keys can be renamed without data loss; only the UUID is used for storage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure Notes
|
||||||
|
|
||||||
|
- `experiments_postgres` is connected to both `experiments-database_default` and `llm-net` networks, making it reachable from n8n as hostname `experiments_postgres`.
|
||||||
|
- The n8n API key `n8n_api_ca07bd874c628ed0f52d9a2e65073960f9f617c5` was created for the account `quocsam93@gmail.com` and is stored in the `user_api_keys` table of the n8n Postgres.
|
||||||
|
- Workflows were generated by `n8n_create_workflows.py` in this repo root.
|
||||||
@@ -74,7 +74,8 @@ for gi = 1:size(groupings, 1)
|
|||||||
try
|
try
|
||||||
r = localRun(fullfile(folder, 'analyze.m'));
|
r = localRun(fullfile(folder, 'analyze.m'));
|
||||||
rows(end + 1, :) = {vname, gname, wname, r.nRats, r.nObs, ...
|
rows(end + 1, :) = {vname, gname, wname, r.nRats, r.nObs, ...
|
||||||
r.covEqual, r.interP, r.interEst, r.stimP, r.dayP}; %#ok<AGROW>
|
r.covEqual, r.interP, r.interPsatt, r.interPrs, r.interEst, ...
|
||||||
|
r.stimP, r.dayP}; %#ok<AGROW>
|
||||||
fprintf(' %-16s N=%2d obs=%4d interaction p=%.4g (%+.2f) %s\n', ...
|
fprintf(' %-16s N=%2d obs=%4d interaction p=%.4g (%+.2f) %s\n', ...
|
||||||
vname, r.nRats, r.nObs, r.interP, r.interEst, ...
|
vname, r.nRats, r.nObs, r.interP, r.interEst, ...
|
||||||
localTern(r.covEqual, 'equal-cov', 'UNEQUAL-cov'));
|
localTern(r.covEqual, 'equal-cov', 'UNEQUAL-cov'));
|
||||||
@@ -87,8 +88,8 @@ end
|
|||||||
|
|
||||||
% Top-level index of all variations.
|
% Top-level index of all variations.
|
||||||
S = cell2table(rows, 'VariableNames', {'variation', 'grouping', 'window', ...
|
S = cell2table(rows, 'VariableNames', {'variation', 'grouping', 'window', ...
|
||||||
'nRats', 'nObs', 'covEqual', 'interaction_p', 'interaction_est', ...
|
'nRats', 'nObs', 'covEqual', 'interaction_p', 'interaction_p_satt', ...
|
||||||
'stim_p', 'day_p'});
|
'interaction_p_rs', 'interaction_est', 'stim_p', 'day_p'});
|
||||||
writetable(S, fullfile(root, 'SUMMARY.csv'));
|
writetable(S, fullfile(root, 'SUMMARY.csv'));
|
||||||
fprintf('\nWrote %d variations under %s\n(index: variations/SUMMARY.csv)\n', ...
|
fprintf('\nWrote %d variations under %s\n(index: variations/SUMMARY.csv)\n', ...
|
||||||
size(rows, 1), root);
|
size(rows, 1), root);
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 13.04 11.2 15.183
|
{'Res Std'} 13.04 11.2 15.183
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(79)= 1.51 F(1)= 2.292 p=0.134
|
days x tDCS (interaction) t(79)= 1.51 F(1)= 2.292 p=0.134 p=0.1338 (df=83)
|
||||||
days (learning) t(79)= 12.43 F(1)= 154.593 p=2.759e-20
|
days (learning) t(79)= 12.43 F(1)=154.593 p=2.759e-20 p=1.192e-20 (df=83)
|
||||||
tDCS (main, at Day 1) t(79)= 0.80 F(1)= 0.635 p=0.428
|
tDCS (main, at Day 1) t(79)= 0.80 F(1)= 0.635 p=0.428 p=0.4278 (df=83)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,20.3)=1.35, p=0.258
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.1340, slope diff=1.38) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.1340, slope diff=1.38) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 14.123 12.278 16.245
|
{'Res Std'} 14.123 12.278 16.245
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(94)= 1.36 F(1)= 1.844 p=0.1777
|
days x tDCS (interaction) t(94)= 1.36 F(1)= 1.844 p=0.1777 p=0.1776 (df=98)
|
||||||
days (learning) t(94)= 13.14 F(1)= 172.537 p=5.403e-23
|
days (learning) t(94)= 13.14 F(1)=172.537 p=5.403e-23 p=2.463e-23 (df=98)
|
||||||
tDCS (main, at Day 1) t(94)= 1.12 F(1)= 1.264 p=0.2637
|
tDCS (main, at Day 1) t(94)= 1.12 F(1)= 1.264 p=0.2637 p=0.2636 (df=98)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,16.2)=1.37, p=0.2593
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.1777, slope diff=1.00) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.1777, slope diff=1.00) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -57,11 +57,17 @@ Group: Error
|
|||||||
{'Res Std'} 18.954 16.597 21.644
|
{'Res Std'} 18.954 16.597 21.644
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(105)= -2.66 F(1)= 7.094 p=0.008953
|
days x tDCS (interaction) t(105)= -2.66 F(1)= 7.094 p=0.008953 p=0.008906 (df=109)
|
||||||
days (learning) t(105)= 9.82 F(1)= 96.388 p=1.573e-16
|
days (learning) t(105)= 9.82 F(1)= 96.388 p=1.573e-16 p=1.113e-16 (df=109)
|
||||||
tDCS (main, at Day 1) t(105)= 3.43 F(1)= 11.778 p=0.0008585
|
tDCS (main, at Day 1) t(105)= 3.43 F(1)= 11.778 p=0.0008585 p=0.000848 (df=109)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,9.7)=0.11, p=0.746
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: SIGNIFICANT (p=0.0090, slope diff=-2.18) -> the tDCS (Box-B2) group improves SLOWER -- groups CONVERGE (Box-B2 is ahead early, the gap narrows).
|
- days x tDCS interaction: SIGNIFICANT (p=0.0090, slope diff=-2.18) -> the tDCS (Box-B2) group improves SLOWER -- groups CONVERGE (Box-B2 is ahead early, the gap narrows).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 11.955 10.184 14.034
|
{'Res Std'} 11.955 10.184 14.034
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(79)= 1.91 F(1)= 3.632 p=0.06032
|
days x tDCS (interaction) t(79)= 1.91 F(1)= 3.632 p=0.06032 p=0.06034 (df=79)
|
||||||
days (learning) t(79)= 10.55 F(1)= 111.334 p=9.547e-17
|
days (learning) t(79)= 10.55 F(1)=111.334 p=9.547e-17 p=8.241e-17 (df=80)
|
||||||
tDCS (main, at Day 1) t(79)= 0.07 F(1)= 0.004 p=0.9478
|
tDCS (main, at Day 1) t(79)= 0.07 F(1)= 0.004 p=0.9478 p=0.9482 (df=21)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,30.6)=3.00, p=0.09319
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.0603, slope diff=1.74) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.0603, slope diff=1.74) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 13.148 11.362 15.215
|
{'Res Std'} 13.148 11.362 15.215
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(94)= 1.95 F(1)= 3.812 p=0.05385
|
days x tDCS (interaction) t(94)= 1.95 F(1)= 3.812 p=0.05385 p=0.05377 (df=97)
|
||||||
days (learning) t(94)= 9.91 F(1)= 98.208 p=2.855e-16
|
days (learning) t(94)= 9.91 F(1)= 98.208 p=2.855e-16 p=1.981e-16 (df=98)
|
||||||
tDCS (main, at Day 1) t(94)= 0.29 F(1)= 0.082 p=0.7757
|
tDCS (main, at Day 1) t(94)= 0.29 F(1)= 0.082 p=0.7757 p=0.7776 (df=24)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,29.7)=3.25, p=0.08138
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.0538, slope diff=1.54) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.0538, slope diff=1.54) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -57,11 +57,17 @@ Group: Error
|
|||||||
{'Res Std'} 19.354 16.948 22.101
|
{'Res Std'} 19.354 16.948 22.101
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(105)= -1.43 F(1)= 2.043 p=0.1559
|
days x tDCS (interaction) t(105)= -1.43 F(1)= 2.043 p=0.1559 p=0.1558 (df=109)
|
||||||
days (learning) t(105)= 6.89 F(1)= 47.494 p=4.234e-10
|
days (learning) t(105)= 6.89 F(1)= 47.494 p=4.234e-10 p=3.734e-10 (df=109)
|
||||||
tDCS (main, at Day 1) t(105)= 2.45 F(1)= 6.021 p=0.01578
|
tDCS (main, at Day 1) t(105)= 2.45 F(1)= 6.021 p=0.01578 p=0.01572 (df=109)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,11.8)=0.02, p=0.9027
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.1559, slope diff=-1.47) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.1559, slope diff=-1.47) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 12.508 10.37 15.086
|
{'Res Std'} 12.508 10.37 15.086
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(57)= 1.49 F(1)= 2.208 p=0.1428
|
days x tDCS (interaction) t(57)= 1.49 F(1)= 2.208 p=0.1428 p=0.1428 (df=57)
|
||||||
days (learning) t(57)= 10.08 F(1)= 101.556 p=2.832e-14
|
days (learning) t(57)= 10.08 F(1)=101.556 p=2.832e-14 p=1.931e-14 (df=59)
|
||||||
tDCS (main, at Day 1) t(57)= 0.68 F(1)= 0.466 p=0.4974
|
tDCS (main, at Day 1) t(57)= 0.68 F(1)= 0.466 p=0.4974 p=0.5037 (df=17)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,13.0)=1.46, p=0.2479
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.1428, slope diff=1.56) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.1428, slope diff=1.56) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 14.676 12.436 17.32
|
{'Res Std'} 14.676 12.436 17.32
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038
|
days x tDCS (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038 p=0.1035 (df=70)
|
||||||
days (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13
|
days (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13 p=1.823e-13 (df=70)
|
||||||
tDCS (main, at Day 1) t(66)= 0.95 F(1)= 0.910 p=0.3436
|
tDCS (main, at Day 1) t(66)= 0.95 F(1)= 0.910 p=0.3436 p=0.3434 (df=70)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,12.8)=1.64, p=0.2227
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.1038, slope diff=1.55) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.1038, slope diff=1.55) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -54,11 +54,17 @@ Group: Error
|
|||||||
{'Res Std'} 14.163 11.93 16.813
|
{'Res Std'} 14.163 11.93 16.813
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
----------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
days x tDCS (interaction) t(67)= 1.02 F(1)= 1.041 p=0.3112
|
days x tDCS (interaction) t(67)= 1.02 F(1)= 1.041 p=0.3112 p=0.3111 (df=70)
|
||||||
days (learning) t(67)= 9.20 F(1)= 84.665 p=1.671e-13
|
days (learning) t(67)= 9.20 F(1)= 84.665 p=1.671e-13 p=1.04e-13 (df=71)
|
||||||
tDCS (main, at Day 1) t(67)= 1.04 F(1)= 1.086 p=0.3012
|
tDCS (main, at Day 1) t(67)= 1.04 F(1)= 1.086 p=0.3012 p=0.3116 (df=18)
|
||||||
|
|
||||||
|
HONEST LME -- per-animal random slope (day|subject): interaction F(1,8.8)=0.61, p=0.456
|
||||||
|
Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual
|
||||||
|
(the slope's error is at the session level), so it does NOT fix pseudoreplication.
|
||||||
|
Letting each animal have its OWN slope collapses the interaction DF toward the animal
|
||||||
|
count -- this, and the per-animal slope test, are the honest learning-rate inference.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- days x tDCS interaction: n.s. (p=0.3112, slope diff=0.94) -> slopes are parallel (no differential change over training).
|
- days x tDCS interaction: n.s. (p=0.3112, slope diff=0.94) -> slopes are parallel (no differential change over training).
|
||||||
|
|||||||
@@ -57,13 +57,18 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(105)= -2.66 F(1)= 7.094 p=0.008953
|
stim x day (interaction) t(105)= -2.66 F(1)= 7.094 p=0.008953 p=0.008906 (df=109)
|
||||||
day (learning) t(105)= 9.82 F(1)= 96.388 p=1.573e-16
|
day (learning) t(105)= 9.82 F(1)= 96.388 p=1.573e-16 p=1.113e-16 (df=109)
|
||||||
stim (main, Day 1) t(105)= 3.43 F(1)= 11.778 p=0.0008585
|
stim (main, Day 1) t(105)= 3.43 F(1)= 11.778 p=0.0008585 p=0.000848 (df=109)
|
||||||
interaction 95% CI: [-3.80, -0.56]
|
interaction 95% CI: [-3.80, -0.56]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,9.7)=0.11, p=0.746
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- stim x day interaction: SIGNIFICANT (p=0.0090, slope diff=-2.18) -> tDCS (Box-B2) improves SLOWER -- groups converge.
|
- stim x day interaction: SIGNIFICANT (p=0.0090, slope diff=-2.18) -> tDCS (Box-B2) improves SLOWER -- groups converge.
|
||||||
- stim main effect on Day 1 (our day 0): SIGNIFICANT (p=0.0009) -> groups already DIFFER on Day 1.
|
- stim main effect on Day 1 (our day 0): SIGNIFICANT (p=0.0009) -> groups already DIFFER on Day 1.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
PAPER LME REPLICATION -- mergeB2
|
PAPER LME REPLICATION -- mergeB2 (full range)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
||||||
|
behavior = successful reaches (COUNT per session)
|
||||||
N = 8 rats, 109 sessions (day raw; day 0 = paper "Day 1")
|
N = 8 rats, 109 sessions (day raw; day 0 = paper "Day 1")
|
||||||
day coverage: stim(B2) 0..22, control(A2) 0..13
|
day coverage: stim(B2) 0..22, control(A2) 0..13
|
||||||
** WARNING: unequal day coverage -- the full-range stim:day interaction
|
** WARNING: unequal day coverage -- the full-range stim:day interaction
|
||||||
@@ -56,12 +57,21 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(105)= -1.43 F(1)= 2.043 p=0.1559
|
stim x day (interaction) t(105)= -1.43 F(1)= 2.043 p=0.1559 p=0.1558 (df=109)
|
||||||
day (learning) t(105)= 6.89 F(1)= 47.494 p=4.234e-10
|
day (learning) t(105)= 6.89 F(1)= 47.494 p=4.234e-10 p=3.734e-10 (df=109)
|
||||||
stim (main, Day 1) t(105)= 2.45 F(1)= 6.021 p=0.01578
|
stim (main, Day 1) t(105)= 2.45 F(1)= 6.021 p=0.01578 p=0.01572 (df=109)
|
||||||
interaction 95% CI: [-3.50, +0.57]
|
interaction 95% CI: [-3.50, +0.57]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,11.8)=0.02, p=0.9027
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
|
INTERPRETATION
|
||||||
|
- stim x day interaction: n.s. (p=0.1559, slope diff=-1.47) -> slopes parallel -- no differential learning rate over this window.
|
||||||
|
- stim main effect on Day 1 (our day 0): SIGNIFICANT (p=0.0158) -> groups already DIFFER on Day 1.
|
||||||
|
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
||||||
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
PAPER LME REPLICATION -- mergeNaive
|
PAPER LME REPLICATION -- mergeNaive (full range)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
||||||
|
behavior = successful reaches (COUNT per session)
|
||||||
N = 11 rats, 170 sessions (day raw; day 0 = paper "Day 1")
|
N = 11 rats, 170 sessions (day raw; day 0 = paper "Day 1")
|
||||||
day coverage: stim(B2) 0..22, control(A2) 0..26
|
day coverage: stim(B2) 0..22, control(A2) 0..26
|
||||||
** WARNING: unequal day coverage -- the full-range stim:day interaction
|
** WARNING: unequal day coverage -- the full-range stim:day interaction
|
||||||
@@ -56,12 +57,21 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(166)= 2.70 F(1)= 7.299 p=0.007614
|
stim x day (interaction) t(166)= 2.70 F(1)= 7.299 p=0.007614 p=0.007598 (df=170)
|
||||||
day (learning) t(166)= 10.58 F(1)= 111.990 p=2.508e-20
|
day (learning) t(166)= 10.58 F(1)=111.990 p=2.508e-20 p=2.011e-20 (df=170)
|
||||||
stim (main, Day 1) t(166)= 1.27 F(1)= 1.611 p=0.2062
|
stim (main, Day 1) t(166)= 1.27 F(1)= 1.611 p=0.2062 p=0.217 (df=23)
|
||||||
interaction 95% CI: [+0.43, +2.74]
|
interaction 95% CI: [+0.43, +2.74]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,11.4)=0.92, p=0.3565
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
|
INTERPRETATION
|
||||||
|
- stim x day interaction: SIGNIFICANT (p=0.0076, slope diff=+1.58) -> tDCS (Box-B2) improves FASTER -- benefit accumulates over training.
|
||||||
|
- stim main effect on Day 1 (our day 0): n.s. (p=0.2062) -> groups are comparable on Day 1.
|
||||||
|
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
||||||
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
||||||
|
|||||||
@@ -56,13 +56,18 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(111)= 1.97 F(1)= 3.880 p=0.05134
|
stim x day (interaction) t(111)= 1.97 F(1)= 3.880 p=0.05134 p=0.05149 (df=105)
|
||||||
day (learning) t(111)= 15.57 F(1)= 242.567 p=1.081e-29
|
day (learning) t(111)= 15.57 F(1)=242.567 p=1.081e-29 p=3.188e-29 (df=107)
|
||||||
stim (main, Day 1) t(111)= 1.32 F(1)= 1.742 p=0.1895
|
stim (main, Day 1) t(111)= 1.32 F(1)= 1.742 p=0.1895 p=0.2003 (df=22)
|
||||||
interaction 95% CI: [-0.01, +3.25]
|
interaction 95% CI: [-0.01, +3.25]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,10.1)=2.00, p=0.1875
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- stim x day interaction: n.s. (p=0.0513, slope diff=+1.62) -> slopes parallel -- no differential learning rate over this window.
|
- stim x day interaction: n.s. (p=0.0513, slope diff=+1.62) -> slopes parallel -- no differential learning rate over this window.
|
||||||
- stim main effect on Day 1 (our day 0): n.s. (p=0.1895) -> groups are comparable on Day 1.
|
- stim main effect on Day 1 (our day 0): n.s. (p=0.1895) -> groups are comparable on Day 1.
|
||||||
|
|||||||
@@ -56,13 +56,18 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(134)= 2.66 F(1)= 7.068 p=0.008801
|
stim x day (interaction) t(134)= 2.66 F(1)= 7.068 p=0.008801 p=0.008831 (df=130)
|
||||||
day (learning) t(134)= 15.01 F(1)= 225.182 p=1.766e-30
|
day (learning) t(134)= 15.01 F(1)=225.182 p=1.766e-30 p=2.418e-30 (df=132)
|
||||||
stim (main, Day 1) t(134)= 1.28 F(1)= 1.629 p=0.204
|
stim (main, Day 1) t(134)= 1.28 F(1)= 1.629 p=0.204 p=0.2142 (df=24)
|
||||||
interaction 95% CI: [+0.46, +3.14]
|
interaction 95% CI: [+0.46, +3.14]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,54.8)=6.46, p=0.01387
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
INTERPRETATION
|
INTERPRETATION
|
||||||
- stim x day interaction: SIGNIFICANT (p=0.0088, slope diff=+1.80) -> tDCS (Box-B2) improves FASTER -- benefit accumulates over training.
|
- stim x day interaction: SIGNIFICANT (p=0.0088, slope diff=+1.80) -> tDCS (Box-B2) improves FASTER -- benefit accumulates over training.
|
||||||
- stim main effect on Day 1 (our day 0): n.s. (p=0.2040) -> groups are comparable on Day 1.
|
- stim main effect on Day 1 (our day 0): n.s. (p=0.2040) -> groups are comparable on Day 1.
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
PAPER LME REPLICATION -- unmerged
|
PAPER LME REPLICATION -- unmerged (full range)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
model: behavior ~ stim + day + stim:day + (1|rat) [stim: Electrode-Box-B2=1 vs Electrode-Box-A2=0]
|
||||||
|
behavior = successful reaches (COUNT per session)
|
||||||
N = 6 rats, 71 sessions (day raw; day 0 = paper "Day 1")
|
N = 6 rats, 71 sessions (day raw; day 0 = paper "Day 1")
|
||||||
day coverage: stim(B2) 0..14, control(A2) 0..13
|
day coverage: stim(B2) 0..14, control(A2) 0..13
|
||||||
|
(Equal day coverage -- the stim:day interaction over this window is NOT
|
||||||
|
confounded by the full-range coverage imbalance.)
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
FULL MODEL SUMMARY -- fitlme: behavior ~ stim + day + stim:day + (1|rat)
|
FULL MODEL SUMMARY -- fitlme: behavior ~ stim + day + stim:day + (1|rat)
|
||||||
@@ -53,12 +56,21 @@ Group: Error
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(67)= 1.02 F(1)= 1.041 p=0.3112
|
stim x day (interaction) t(67)= 1.02 F(1)= 1.041 p=0.3112 p=0.3111 (df=70)
|
||||||
day (learning) t(67)= 9.20 F(1)= 84.665 p=1.671e-13
|
day (learning) t(67)= 9.20 F(1)= 84.665 p=1.671e-13 p=1.04e-13 (df=71)
|
||||||
stim (main, Day 1) t(67)= 1.04 F(1)= 1.086 p=0.3012
|
stim (main, Day 1) t(67)= 1.04 F(1)= 1.086 p=0.3012 p=0.3116 (df=18)
|
||||||
interaction 95% CI: [-0.90, +2.77]
|
interaction 95% CI: [-0.90, +2.77]
|
||||||
|
|
||||||
|
HONEST LME -- per-rat random slope (day|rat): interaction F(1,8.8)=0.61, p=0.456
|
||||||
|
Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the
|
||||||
|
slope's error is at session level), so it does NOT fix pseudoreplication. A per-animal
|
||||||
|
random slope collapses the interaction DF toward the animal count -- the honest test.
|
||||||
|
|
||||||
|
INTERPRETATION
|
||||||
|
- stim x day interaction: n.s. (p=0.3112, slope diff=+0.94) -> slopes parallel -- no differential learning rate over this window.
|
||||||
|
- stim main effect on Day 1 (our day 0): n.s. (p=0.3012) -> groups are comparable on Day 1.
|
||||||
|
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; day t(227)=9.64,
|
||||||
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ PHASED days x tDCS LME -- mergeA2 (Box-B2 vs Box-A2)
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
||||||
|
|
||||||
phase N A2slope B2slope day p tDCS(lvl) p interaction p slopeDiff [95% CI]
|
phase N A2slp B2slp day p tDCS p intP(res) intP(Satt) intP(RS) slopeDiff [95% CI]
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------
|
||||||
0-5 8 10.54 15.16 1.5e-10 0.693 0.014 +4.62 [+1.00, +8.24]
|
0-5 8 10.54 15.16 1.5e-10 0.693 0.014 0.014 0.089 +4.62 [+1.00,+8.24]
|
||||||
6-10 7 3.17 4.83 0.035 0.127 0.390 +1.66 [-2.22, +5.53]
|
6-10 7 3.17 4.83 0.035 0.127 0.390 0.390 0.466 +1.66 [-2.22,+5.53]
|
||||||
6-13 7 2.92 4.47 0.00032 0.125 0.118 +1.55 [-0.41, +3.52]
|
6-13 7 2.92 4.47 0.00032 0.125 0.118 0.118 0.303 +1.55 [-0.41,+3.52]
|
||||||
|
|
||||||
Note: the interaction p (and CI) use fitlme observation-level DF and are
|
Columns: intP(res)=observation-level DF (anticonservative); intP(Satt)=Satterthwaite
|
||||||
ANTICONSERVATIVE at these small subject counts (see tdcs_power_sim). The early
|
DF (~= residual on this random-intercept model); intP(RS)=per-animal random-slope
|
||||||
phase carries the Box-B2 faster-acquisition signal; late phases converge.
|
(dayp|subject) model, the honest test (DF collapses toward the animal count; 'n/a' if it
|
||||||
|
did not converge). The early phase carries the Box-B2 faster-acquisition signal; late
|
||||||
|
phases converge.
|
||||||
==============================================================================
|
==============================================================================
|
||||||
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ PHASED days x tDCS LME -- mergeB2 (Box-B2 vs Box-A2)
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
||||||
|
|
||||||
phase N A2slope B2slope day p tDCS(lvl) p interaction p slopeDiff [95% CI]
|
phase N A2slp B2slp day p tDCS p intP(res) intP(Satt) intP(RS) slopeDiff [95% CI]
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------
|
||||||
0-5 8 9.86 14.65 2.9e-08 0.399 0.013 +4.79 [+1.06, +8.53]
|
0-5 8 9.86 14.65 2.9e-08 0.399 0.013 0.013 0.088 +4.79 [+1.06,+8.53]
|
||||||
6-10 7 2.40 4.80 0.18 0.142 0.253 +2.40 [-1.80, +6.60]
|
6-10 7 2.40 4.80 0.18 0.142 0.253 0.254 0.328 +2.40 [-1.80,+6.60]
|
||||||
6-13 7 1.71 4.42 0.083 0.127 0.018 +2.71 [+0.50, +4.93]
|
6-13 7 1.71 4.42 0.083 0.127 0.018 0.018 0.024 +2.71 [+0.50,+4.93]
|
||||||
|
|
||||||
Note: the interaction p (and CI) use fitlme observation-level DF and are
|
Columns: intP(res)=observation-level DF (anticonservative); intP(Satt)=Satterthwaite
|
||||||
ANTICONSERVATIVE at these small subject counts (see tdcs_power_sim). The early
|
DF (~= residual on this random-intercept model); intP(RS)=per-animal random-slope
|
||||||
phase carries the Box-B2 faster-acquisition signal; late phases converge.
|
(dayp|subject) model, the honest test (DF collapses toward the animal count; 'n/a' if it
|
||||||
|
did not converge). The early phase carries the Box-B2 faster-acquisition signal; late
|
||||||
|
phases converge.
|
||||||
==============================================================================
|
==============================================================================
|
||||||
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ PHASED days x tDCS LME -- mergeNaive (Box-B2 vs Box-A2)
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
||||||
|
|
||||||
phase N A2slope B2slope day p tDCS(lvl) p interaction p slopeDiff [95% CI]
|
phase N A2slp B2slp day p tDCS p intP(res) intP(Satt) intP(RS) slopeDiff [95% CI]
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------
|
||||||
0-5 11 8.26 15.16 1.6e-10 0.852 0.000 +6.90 [+3.40, +10.41]
|
0-5 11 8.26 15.16 1.6e-10 0.852 0.000 0.000 0.013 +6.90 [+3.40,+10.41]
|
||||||
6-10 10 5.97 4.83 3.9e-05 0.007 0.584 -1.14 [-5.31, +3.03]
|
6-10 10 5.97 4.83 3.9e-05 0.007 0.584 0.585 0.643 -1.14 [-5.31,+3.03]
|
||||||
6-13 10 2.83 4.47 0.00071 0.012 0.196 +1.65 [-0.87, +4.17]
|
6-13 10 2.83 4.47 0.00071 0.012 0.196 0.196 0.333 +1.65 [-0.87,+4.17]
|
||||||
|
|
||||||
Note: the interaction p (and CI) use fitlme observation-level DF and are
|
Columns: intP(res)=observation-level DF (anticonservative); intP(Satt)=Satterthwaite
|
||||||
ANTICONSERVATIVE at these small subject counts (see tdcs_power_sim). The early
|
DF (~= residual on this random-intercept model); intP(RS)=per-animal random-slope
|
||||||
phase carries the Box-B2 faster-acquisition signal; late phases converge.
|
(dayp|subject) model, the honest test (DF collapses toward the animal count; 'n/a' if it
|
||||||
|
did not converge). The early phase carries the Box-B2 faster-acquisition signal; late
|
||||||
|
phases converge.
|
||||||
==============================================================================
|
==============================================================================
|
||||||
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ PHASED days x tDCS LME -- unmerged (Box-B2 vs Box-A2)
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]
|
||||||
|
|
||||||
phase N A2slope B2slope day p tDCS(lvl) p interaction p slopeDiff [95% CI]
|
phase N A2slp B2slp day p tDCS p intP(res) intP(Satt) intP(RS) slopeDiff [95% CI]
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------
|
||||||
0-5 6 9.86 15.22 1.6e-07 0.689 0.015 +5.36 [+1.11, +9.62]
|
0-5 6 9.86 15.22 1.6e-07 0.689 0.015 0.015 0.133 +5.36 [+1.11,+9.62]
|
||||||
6-10 5 2.40 4.17 0.23 0.098 0.486 +1.77 [-3.41, +6.95]
|
6-10 5 2.40 4.17 0.23 0.098 0.486 0.486 0.555 +1.77 [-3.41,+6.95]
|
||||||
6-13 5 1.71 3.98 0.12 0.101 0.097 +2.27 [-0.44, +4.97]
|
6-13 5 1.71 3.98 0.12 0.101 0.097 0.097 0.138 +2.27 [-0.44,+4.97]
|
||||||
|
|
||||||
Note: the interaction p (and CI) use fitlme observation-level DF and are
|
Columns: intP(res)=observation-level DF (anticonservative); intP(Satt)=Satterthwaite
|
||||||
ANTICONSERVATIVE at these small subject counts (see tdcs_power_sim). The early
|
DF (~= residual on this random-intercept model); intP(RS)=per-animal random-slope
|
||||||
phase carries the Box-B2 faster-acquisition signal; late phases converge.
|
(dayp|subject) model, the honest test (DF collapses toward the animal count; 'n/a' if it
|
||||||
|
did not converge). The early phase carries the Box-B2 faster-acquisition signal; late
|
||||||
|
phases converge.
|
||||||
==============================================================================
|
==============================================================================
|
||||||
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
FULL MODEL SUMMARY -- phase 0-5: success ~ dayp*tDCS + (1|subject)
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -32,24 +32,32 @@ T.tDCS = double(T.group == cfg.anchorHigh); % Box-B2 = 1, Box-A2 = 0
|
|||||||
lme = fitlme(T, 'success ~ day*tDCS + (1|subject)');
|
lme = fitlme(T, 'success ~ day*tDCS + (1|subject)');
|
||||||
C = lme.Coefficients;
|
C = lme.Coefficients;
|
||||||
A = anova(lme);
|
A = anova(lme);
|
||||||
|
As = anova(lme, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
L.lme = lme;
|
L.lme = lme;
|
||||||
L.nSubjects = numel(unique(T.subject));
|
L.nSubjects = numel(unique(T.subject));
|
||||||
L.nObs = height(T);
|
L.nObs = height(T);
|
||||||
L.maxDayA = max(T.day(T.group == cfg.anchorLow)); % last day Box-A2 has data
|
L.maxDayA = max(T.day(T.group == cfg.anchorLow)); % last day Box-A2 has data
|
||||||
L.maxDayB = max(T.day(T.group == cfg.anchorHigh)); % last day Box-B2 has data
|
L.maxDayB = max(T.day(T.group == cfg.anchorHigh)); % last day Box-B2 has data
|
||||||
L.interaction = localTerm(C, A, 'day:tDCS');
|
L.interaction = localTerm(C, A, As, 'day:tDCS');
|
||||||
L.day = localTerm(C, A, 'day');
|
L.day = localTerm(C, A, As, 'day');
|
||||||
L.tDCS = localTerm(C, A, 'tDCS');
|
L.tDCS = localTerm(C, A, As, 'tDCS');
|
||||||
|
% Honest test: refit with a per-subject random SLOPE (see
|
||||||
|
% tdcs_random_slope_interaction) so the interaction DF collapses toward n.
|
||||||
|
L.interRS = tdcs_random_slope_interaction(T, ...
|
||||||
|
'success ~ day*tDCS + (day|subject)', 'day:tDCS');
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function e = localTerm(C, A, name)
|
function e = localTerm(C, A, As, name)
|
||||||
%LOCALTERM Pull one term's coefficient (t/df/p) and ANOVA (F/df/p) stats.
|
%LOCALTERM Pull one term's coefficient (t/df/p), residual-DF ANOVA (F/df/p),
|
||||||
|
% and Satterthwaite denominator DF + p (.dfSatt, .pSatt).
|
||||||
ci = strcmp(C.Name, name);
|
ci = strcmp(C.Name, name);
|
||||||
ai = strcmp(A.Term, name);
|
ai = strcmp(A.Term, name);
|
||||||
|
si = strcmp(As.Term, name);
|
||||||
e = struct( ...
|
e = struct( ...
|
||||||
'estimate', C.Estimate(ci), 'se', C.SE(ci), ...
|
'estimate', C.Estimate(ci), 'se', C.SE(ci), ...
|
||||||
't', C.tStat(ci), 'df', C.DF(ci), 'p', C.pValue(ci), ...
|
't', C.tStat(ci), 'df', C.DF(ci), 'p', C.pValue(ci), ...
|
||||||
'F', A.FStat(ai), 'df1', A.DF1(ai), 'df2', A.DF2(ai), 'Fp', A.pValue(ai));
|
'F', A.FStat(ai), 'df1', A.DF1(ai), 'df2', A.DF2(ai), 'Fp', A.pValue(ai), ...
|
||||||
|
'dfSatt', As.DF2(si), 'pSatt', As.pValue(si));
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ s = [s sprintf('\n')];
|
|||||||
|
|
||||||
s = [s tdcs_model_summary(L.lme, 'fitlme: success ~ day*tDCS + (1|subject)') sprintf('\n')];
|
s = [s tdcs_model_summary(L.lme, 'fitlme: success ~ day*tDCS + (1|subject)') sprintf('\n')];
|
||||||
|
|
||||||
s = [s sprintf('%-27s %-14s %-13s %s\n', 'effect', 't (df)', 'F (df1)', 'p')];
|
s = [s sprintf('%-27s %-20s %-12s %s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)')];
|
||||||
s = [s sprintf('%s\n', repmat('-', 1, 70))];
|
s = [s sprintf('%s\n', repmat('-', 1, 78))];
|
||||||
s = [s localRow('days x tDCS (interaction)', L.interaction)];
|
s = [s localRow('days x tDCS (interaction)', L.interaction)];
|
||||||
s = [s localRow('days (learning)', L.day)];
|
s = [s localRow('days (learning)', L.day)];
|
||||||
s = [s localRow('tDCS (main, at Day 1)', L.tDCS)];
|
s = [s localRow('tDCS (main, at Day 1)', L.tDCS)];
|
||||||
|
s = [s localHonest(L.interRS)];
|
||||||
|
|
||||||
s = [s sprintf('\nINTERPRETATION\n')];
|
s = [s sprintf('\nINTERPRETATION\n')];
|
||||||
if L.interaction.p >= 0.05
|
if L.interaction.p >= 0.05
|
||||||
@@ -66,7 +67,24 @@ fprintf(fid, '%s', s);
|
|||||||
end
|
end
|
||||||
|
|
||||||
function r = localRow(name, e)
|
function r = localRow(name, e)
|
||||||
r = sprintf('%-27s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', name, e.df, e.t, e.df1, e.F, e.p);
|
r = sprintf('%-27s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', ...
|
||||||
|
name, e.df, e.t, e.df1, e.F, e.p, e.pSatt, e.dfSatt);
|
||||||
|
end
|
||||||
|
|
||||||
|
function s = localHonest(rs)
|
||||||
|
%LOCALHONEST Report the random-slope interaction (the honest LME test) + why
|
||||||
|
% Satterthwaite on the random-intercept model above barely changes the DF.
|
||||||
|
if rs.ok
|
||||||
|
s = sprintf(['\nHONEST LME -- per-animal random slope (day|subject): ' ...
|
||||||
|
'interaction F(%d,%.1f)=%.2f, p=%.4g\n'], rs.df1, rs.df2, rs.F, rs.p);
|
||||||
|
else
|
||||||
|
s = sprintf(['\nHONEST LME -- per-animal random slope (day|subject): ' ...
|
||||||
|
'model did not converge for this window.\n']);
|
||||||
|
end
|
||||||
|
s = [s sprintf([' Note: Satterthwaite DF on the random-INTERCEPT model above stays ~= residual\n' ...
|
||||||
|
' (the slope''s error is at the session level), so it does NOT fix pseudoreplication.\n' ...
|
||||||
|
' Letting each animal have its OWN slope collapses the interaction DF toward the animal\n' ...
|
||||||
|
' count -- this, and the per-animal slope test, are the honest learning-rate inference.\n'])];
|
||||||
end
|
end
|
||||||
|
|
||||||
function t = localSig(p)
|
function t = localSig(p)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ tbl.rat = A.subject;
|
|||||||
|
|
||||||
model = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
model = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = model.Coefficients; An = anova(model); ci = coefCI(model);
|
C = model.Coefficients; An = anova(model); ci = coefCI(model);
|
||||||
|
Ans = anova(model, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
L.model = model;
|
L.model = model;
|
||||||
L.windowKey = windowKey;
|
L.windowKey = windowKey;
|
||||||
@@ -51,24 +52,30 @@ L.nRats = numel(unique(tbl.rat));
|
|||||||
L.nObs = height(tbl);
|
L.nObs = height(tbl);
|
||||||
L.maxDayCtrl = max(tbl.day(tbl.stim == 0));
|
L.maxDayCtrl = max(tbl.day(tbl.stim == 0));
|
||||||
L.maxDayStim = max(tbl.day(tbl.stim == 1));
|
L.maxDayStim = max(tbl.day(tbl.stim == 1));
|
||||||
L.stim = localTerm(C, An, ci, 'stim');
|
L.stim = localTerm(C, An, Ans, ci, 'stim');
|
||||||
L.day = localTerm(C, An, ci, 'day');
|
L.day = localTerm(C, An, Ans, ci, 'day');
|
||||||
L.interaction = localTerm(C, An, ci, 'day:stim'); % MATLAB canonicalizes stim:day -> day:stim
|
L.interaction = localTerm(C, An, Ans, ci, 'day:stim'); % MATLAB canonicalizes stim:day -> day:stim
|
||||||
|
% Honest test: refit with a per-rat random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (see tdcs_random_slope_interaction).
|
||||||
|
L.interRS = tdcs_random_slope_interaction(tbl, ...
|
||||||
|
'behavior ~ stim + day + stim:day + (day|rat)', 'day:stim');
|
||||||
|
|
||||||
localReport(L, mergeKey, cfg);
|
localReport(L, mergeKey, cfg);
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function e = localTerm(C, An, ci, name)
|
function e = localTerm(C, An, Ans, ci, name)
|
||||||
i = strcmp(C.Name, name);
|
i = strcmp(C.Name, name);
|
||||||
if ~any(i)
|
if ~any(i)
|
||||||
error('tdcs_paper_lme:missingTerm', 'No "%s" coefficient (have: %s).', ...
|
error('tdcs_paper_lme:missingTerm', 'No "%s" coefficient (have: %s).', ...
|
||||||
name, strjoin(C.Name, ', '));
|
name, strjoin(C.Name, ', '));
|
||||||
end
|
end
|
||||||
ai = strcmp(An.Term, name);
|
ai = strcmp(An.Term, name);
|
||||||
|
si = strcmp(Ans.Term, name);
|
||||||
e = struct('estimate', C.Estimate(i), 'se', C.SE(i), 't', C.tStat(i), ...
|
e = struct('estimate', C.Estimate(i), 'se', C.SE(i), 't', C.tStat(i), ...
|
||||||
'df', C.DF(i), 'p', C.pValue(i), 'F', An.FStat(ai), 'df1', An.DF1(ai), ...
|
'df', C.DF(i), 'p', C.pValue(i), 'F', An.FStat(ai), 'df1', An.DF1(ai), ...
|
||||||
'df2', An.DF2(ai), 'Fp', An.pValue(ai), 'ci', ci(i, :));
|
'df2', An.DF2(ai), 'Fp', An.pValue(ai), 'ci', ci(i, :), ...
|
||||||
|
'dfSatt', Ans.DF2(si), 'pSatt', Ans.pValue(si));
|
||||||
end
|
end
|
||||||
|
|
||||||
function localReport(L, mergeKey, cfg)
|
function localReport(L, mergeKey, cfg)
|
||||||
@@ -101,12 +108,13 @@ else
|
|||||||
end
|
end
|
||||||
s = [s sprintf('\n')];
|
s = [s sprintf('\n')];
|
||||||
s = [s tdcs_model_summary(L.model, 'fitlme: behavior ~ stim + day + stim:day + (1|rat)') sprintf('\n')];
|
s = [s tdcs_model_summary(L.model, 'fitlme: behavior ~ stim + day + stim:day + (1|rat)') sprintf('\n')];
|
||||||
s = [s sprintf('\n%-26s %-13s %-13s %s\n', 'effect', 't (df)', 'F (df1)', 'p')];
|
s = [s sprintf('\n%-26s %-18s %-12s %s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)')];
|
||||||
s = [s sprintf('%s\n', repmat('-', 1, 66))];
|
s = [s sprintf('%s\n', repmat('-', 1, 76))];
|
||||||
s = [s localRow('stim x day (interaction)', L.interaction)];
|
s = [s localRow('stim x day (interaction)', L.interaction)];
|
||||||
s = [s localRow('day (learning)', L.day)];
|
s = [s localRow('day (learning)', L.day)];
|
||||||
s = [s localRow('stim (main, Day 1)', L.stim)];
|
s = [s localRow('stim (main, Day 1)', L.stim)];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', L.interaction.ci(1), L.interaction.ci(2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', L.interaction.ci(1), L.interaction.ci(2))];
|
||||||
|
s = [s localHonest(L.interRS)];
|
||||||
s = [s sprintf('\nINTERPRETATION\n')];
|
s = [s sprintf('\nINTERPRETATION\n')];
|
||||||
if L.interaction.p >= 0.05
|
if L.interaction.p >= 0.05
|
||||||
interTxt = 'slopes parallel -- no differential learning rate over this window';
|
interTxt = 'slopes parallel -- no differential learning rate over this window';
|
||||||
@@ -133,7 +141,21 @@ fprintf(fid, '%s', s);
|
|||||||
end
|
end
|
||||||
|
|
||||||
function r = localRow(name, e)
|
function r = localRow(name, e)
|
||||||
r = sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', name, e.df, e.t, e.df1, e.F, e.p);
|
r = sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', ...
|
||||||
|
name, e.df, e.t, e.df1, e.F, e.p, e.pSatt, e.dfSatt);
|
||||||
|
end
|
||||||
|
|
||||||
|
function s = localHonest(rs)
|
||||||
|
if rs.ok
|
||||||
|
s = sprintf(['\nHONEST LME -- per-rat random slope (day|rat): ' ...
|
||||||
|
'interaction F(%d,%.1f)=%.2f, p=%.4g\n'], rs.df1, rs.df2, rs.F, rs.p);
|
||||||
|
else
|
||||||
|
s = sprintf(['\nHONEST LME -- per-rat random slope (day|rat): ' ...
|
||||||
|
'model did not converge for this window.\n']);
|
||||||
|
end
|
||||||
|
s = [s sprintf([' Satterthwaite DF on the random-INTERCEPT model above stays ~= residual (the\n' ...
|
||||||
|
' slope''s error is at session level), so it does NOT fix pseudoreplication. A per-animal\n' ...
|
||||||
|
' random slope collapses the interaction DF toward the animal count -- the honest test.\n'])];
|
||||||
end
|
end
|
||||||
|
|
||||||
function t = localSigTxt(p)
|
function t = localSigTxt(p)
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ s = sprintf('%s\n', bar);
|
|||||||
s = [s sprintf('PHASED days x tDCS LME -- %s (Box-B2 vs Box-A2)\n', mergeKey)];
|
s = [s sprintf('PHASED days x tDCS LME -- %s (Box-B2 vs Box-A2)\n', mergeKey)];
|
||||||
s = [s sprintf('%s\n', bar)];
|
s = [s sprintf('%s\n', bar)];
|
||||||
s = [s sprintf('model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]\n\n')];
|
s = [s sprintf('model per phase: success ~ dayp*tDCS + (1|subject) [dayp = day - phaseStart]\n\n')];
|
||||||
s = [s sprintf('%-9s N A2slope B2slope day p tDCS(lvl) p interaction p slopeDiff [95%% CI]\n', 'phase')];
|
s = [s sprintf('%-8s N A2slp B2slp day p tDCS p intP(res) intP(Satt) intP(RS) slopeDiff [95%% CI]\n', 'phase')];
|
||||||
s = [s sprintf('%s\n', repmat('-', 1, 92))];
|
s = [s sprintf('%s\n', repmat('-', 1, 104))];
|
||||||
|
|
||||||
rawSummaries = '';
|
rawSummaries = '';
|
||||||
for k = 1:numel(phases)
|
for k = 1:numel(phases)
|
||||||
@@ -42,6 +42,9 @@ for k = 1:numel(phases)
|
|||||||
|
|
||||||
lme = fitlme(Tp, 'success ~ dayp*tDCS + (1|subject)');
|
lme = fitlme(Tp, 'success ~ dayp*tDCS + (1|subject)');
|
||||||
C = lme.Coefficients; A = anova(lme); ci = coefCI(lme);
|
C = lme.Coefficients; A = anova(lme); ci = coefCI(lme);
|
||||||
|
As = anova(lme, 'DFMethod', 'satterthwaite');
|
||||||
|
rs = tdcs_random_slope_interaction(Tp, ...
|
||||||
|
'success ~ dayp*tDCS + (dayp|subject)', 'dayp:tDCS');
|
||||||
ii = strcmp(C.Name, 'dayp:tDCS');
|
ii = strcmp(C.Name, 'dayp:tDCS');
|
||||||
di = strcmp(C.Name, 'dayp');
|
di = strcmp(C.Name, 'dayp');
|
||||||
|
|
||||||
@@ -53,22 +56,27 @@ for k = 1:numel(phases)
|
|||||||
e.dayP = A.pValue(strcmp(A.Term, 'dayp'));
|
e.dayP = A.pValue(strcmp(A.Term, 'dayp'));
|
||||||
e.tDCSlevelP = A.pValue(strcmp(A.Term, 'tDCS'));
|
e.tDCSlevelP = A.pValue(strcmp(A.Term, 'tDCS'));
|
||||||
e.interP = A.pValue(strcmp(A.Term, 'dayp:tDCS'));
|
e.interP = A.pValue(strcmp(A.Term, 'dayp:tDCS'));
|
||||||
|
e.interPsatt = As.pValue(strcmp(As.Term, 'dayp:tDCS')); % Satterthwaite DF
|
||||||
|
e.interPrs = rs.p; e.rsOk = rs.ok; % honest random-slope
|
||||||
e.interEst = C.Estimate(ii);
|
e.interEst = C.Estimate(ii);
|
||||||
e.interCI = ci(ii, :);
|
e.interCI = ci(ii, :);
|
||||||
P.phases{k} = e;
|
P.phases{k} = e;
|
||||||
|
|
||||||
s = [s sprintf('%d-%-6d %d %6.2f %6.2f %-9.2g %-11.3f %-13.3f %+.2f [%+.2f, %+.2f]\n', ...
|
if rs.ok; rsStr = sprintf('%.3f', rs.p); else; rsStr = 'n/a'; end
|
||||||
|
s = [s sprintf('%d-%-5d %d %5.2f %5.2f %-8.2g %-8.3f %-9.3f %-10.3f %-9s %+.2f [%+.2f,%+.2f]\n', ...
|
||||||
ph(1), ph(2), e.nSub, e.a2Slope, e.b2Slope, e.dayP, e.tDCSlevelP, ...
|
ph(1), ph(2), e.nSub, e.a2Slope, e.b2Slope, e.dayP, e.tDCSlevelP, ...
|
||||||
e.interP, e.interEst, e.interCI(1), e.interCI(2))]; %#ok<AGROW>
|
e.interP, e.interPsatt, rsStr, e.interEst, e.interCI(1), e.interCI(2))]; %#ok<AGROW>
|
||||||
|
|
||||||
rawSummaries = [rawSummaries tdcs_model_summary(lme, ...
|
rawSummaries = [rawSummaries tdcs_model_summary(lme, ...
|
||||||
sprintf('phase %d-%d: success ~ dayp*tDCS + (1|subject)', ph(1), ph(2))) ...
|
sprintf('phase %d-%d: success ~ dayp*tDCS + (1|subject)', ph(1), ph(2))) ...
|
||||||
sprintf('\n')]; %#ok<AGROW>
|
sprintf('\n')]; %#ok<AGROW>
|
||||||
end
|
end
|
||||||
|
|
||||||
s = [s sprintf(['\nNote: the interaction p (and CI) use fitlme observation-level DF and are\n' ...
|
s = [s sprintf(['\nColumns: intP(res)=observation-level DF (anticonservative); intP(Satt)=Satterthwaite\n' ...
|
||||||
'ANTICONSERVATIVE at these small subject counts (see tdcs_power_sim). The early\n' ...
|
'DF (~= residual on this random-intercept model); intP(RS)=per-animal random-slope\n' ...
|
||||||
'phase carries the Box-B2 faster-acquisition signal; late phases converge.\n'])];
|
'(dayp|subject) model, the honest test (DF collapses toward the animal count; ''n/a'' if it\n' ...
|
||||||
|
'did not converge). The early phase carries the Box-B2 faster-acquisition signal; late\n' ...
|
||||||
|
'phases converge.\n'])];
|
||||||
|
|
||||||
s = [s rawSummaries];
|
s = [s rawSummaries];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
function rs = tdcs_random_slope_interaction(T, formula, term)
|
||||||
|
%TDCS_RANDOM_SLOPE_INTERACTION Honest LME test of a slope-interaction TERM.
|
||||||
|
% RS = TDCS_RANDOM_SLOPE_INTERACTION(T, FORMULA, TERM) refits the linear mixed
|
||||||
|
% model FORMULA (which must include a per-subject random SLOPE, e.g.
|
||||||
|
% 'success ~ day*tDCS + (day|subject)') on table T and returns the
|
||||||
|
% Satterthwaite-DF marginal F-test of TERM (e.g. 'day:tDCS').
|
||||||
|
%
|
||||||
|
% Why this is the honest test. The paper's model has only a random INTERCEPT,
|
||||||
|
% so it assumes every animal shares one true slope and estimates the slope /
|
||||||
|
% interaction with session-level precision -- Satterthwaite DF on that model
|
||||||
|
% stays ~= residual DF and does NOT fix the pseudoreplication. Giving each
|
||||||
|
% animal its OWN slope (a random slope) lets the between-animal slope variance
|
||||||
|
% enter the standard error, and the Satterthwaite denominator DF then collapses
|
||||||
|
% toward the number of animals -- matching the per-animal (cluster-honest)
|
||||||
|
% test. RS fields: .ok (false if the richer model failed to fit), .F, .df1,
|
||||||
|
% .df2 (Satterthwaite), .p.
|
||||||
|
|
||||||
|
rs = struct('ok', false, 'F', NaN, 'df1', NaN, 'df2', NaN, 'p', NaN);
|
||||||
|
w = warning('off', 'all'); cleanup = onCleanup(@() warning(w)); %#ok<NASGU>
|
||||||
|
try
|
||||||
|
lme = fitlme(T, formula);
|
||||||
|
A = anova(lme, 'DFMethod', 'satterthwaite');
|
||||||
|
i = strcmp(A.Term, term);
|
||||||
|
if any(i)
|
||||||
|
rs = struct('ok', true, 'F', A.FStat(i), 'df1', A.DF1(i), ...
|
||||||
|
'df2', A.DF2(i), 'p', A.pValue(i));
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
% Random-slope model unidentifiable / non-convergent (common in short
|
||||||
|
% windows or with very few animals): leave rs.ok = false.
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -123,6 +123,27 @@ classdef tLme < matlab.unittest.TestCase
|
|||||||
testCase.verifyFalse(any(sg.group == 'Naive'));
|
testCase.verifyFalse(any(sg.group == 'Naive'));
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function testLmeReportsHaveSatterthwaiteAndHonest(testCase)
|
||||||
|
% Every fitlme report adds Satterthwaite DF and the honest
|
||||||
|
% random-slope interaction test.
|
||||||
|
cfg = tdcs_config();
|
||||||
|
L = tdcs_lme(tdcs_scenario_data('mergeA2_full'), cfg);
|
||||||
|
testCase.verifyTrue(isfield(L.interaction, 'pSatt'));
|
||||||
|
testCase.verifyTrue(isfield(L.interaction, 'dfSatt'));
|
||||||
|
testCase.verifyTrue(isfield(L, 'interRS') && islogical(L.interRS.ok));
|
||||||
|
% The full-range mergeA2 interaction is significant under obs-level
|
||||||
|
% DF but NOT under the honest per-animal random slope (it collapses).
|
||||||
|
testCase.verifyLessThan(L.interaction.p, 0.05);
|
||||||
|
testCase.verifyTrue(L.interRS.ok);
|
||||||
|
testCase.verifyGreaterThan(L.interRS.p, 0.05);
|
||||||
|
% Report text carries the Satterthwaite column and honest block.
|
||||||
|
here = fileparts(fileparts(mfilename('fullpath')));
|
||||||
|
evalc("tdcs_glm('lme_mergeA2_full')");
|
||||||
|
txt = fileread(fullfile(here, 'results', 'lme_mergeA2_full.txt'));
|
||||||
|
testCase.verifyTrue(contains(txt, 'Satterthwaite'));
|
||||||
|
testCase.verifyTrue(contains(txt, 'HONEST LME'));
|
||||||
|
end
|
||||||
|
|
||||||
function testPaperFormulaReplicatesInteraction(testCase)
|
function testPaperFormulaReplicatesInteraction(testCase)
|
||||||
% The paper's exact formula (behavior ~ stim + day + stim:day +
|
% The paper's exact formula (behavior ~ stim + day + stim:day +
|
||||||
% (1|rat)) on mergeA2 reproduces the paper's interaction
|
% (1|rat)) on mergeA2 reproduces the paper's interaction
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
variation,grouping,window,nRats,nObs,covEqual,interaction_p,interaction_est,stim_p,day_p
|
variation,grouping,window,nRats,nObs,covEqual,interaction_p,interaction_p_satt,interaction_p_rs,interaction_est,stim_p,day_p
|
||||||
unmerge_d0_10,unmerge,d0_10,6,61,1,0.142831730250263,1.55766564374789,0.497389134925754,2.83166776054496e-14
|
unmerge_d0_10,unmerge,d0_10,6,61,1,0.142831730250263,0.142797566084653,0.247946685371652,1.55766564374789,0.497389134925754,2.83166776054496e-14
|
||||||
unmerge_d0_13,unmerge,d0_13,6,70,1,0.1037612192755,1.54665472676189,0.343602740986316,3.0346358823857e-13
|
unmerge_d0_13,unmerge,d0_13,6,70,1,0.1037612192755,0.103490077847449,0.222686826261486,1.54665472676189,0.343602740986316,3.0346358823857e-13
|
||||||
unmerge_d0_5,unmerge,d0_5,6,36,1,0.015149263054839,5.36190476190475,0.688839435920596,1.57055423419133e-07
|
unmerge_d0_5,unmerge,d0_5,6,36,1,0.015149263054839,0.0154996899553134,0.133145634512884,5.36190476190475,0.688839435920596,1.57055423419133e-07
|
||||||
unmerge_d6_10,unmerge,d6_10,5,25,1,0.485980598616448,1.76666666666666,0.0975215172907211,0.227259314093653
|
unmerge_d6_10,unmerge,d6_10,5,25,1,0.485980598616448,0.486366669144796,0.554919025308352,1.76666666666666,0.0975215172907211,0.227259314093653
|
||||||
unmerge_d6_13,unmerge,d6_13,5,34,1,0.0971009786566259,2.26696773140328,0.101395665857404,0.117493504965036
|
unmerge_d6_13,unmerge,d6_13,5,34,1,0.0971009786566259,0.0970128901807668,0.13816578257917,2.26696773140328,0.101395665857404,0.117493504965036
|
||||||
right_only_d0_10,right_only,d0_10,7,72,1,0.072180139606096,1.73004431141938,0.715879551990348,5.94289273347193e-16
|
right_only_d0_10,right_only,d0_10,7,72,1,0.072180139606096,0.0721794737454714,0.124574209202325,1.73004431141938,0.715879551990348,5.94289273347193e-16
|
||||||
right_only_d0_13,right_only,d0_13,7,84,1,0.0480416099228058,1.7001712792199,0.528517264593511,1.17078898575779e-14
|
right_only_d0_13,right_only,d0_13,7,84,1,0.0480416099228058,0.0478799998667326,0.102857124294903,1.7001712792199,0.528517264593511,1.17078898575779e-14
|
||||||
right_only_d0_5,right_only,d0_5,7,42,1,0.00589730538358453,5.30714285714286,0.469556667463413,1.45827052706827e-08
|
right_only_d0_5,right_only,d0_5,7,42,1,0.00589730538358453,0.00612750828678172,0.0876259332262465,5.30714285714286,0.469556667463413,1.45827052706827e-08
|
||||||
right_only_d6_10,right_only,d6_10,6,30,1,0.288979620167748,2.425,0.119238243812818,0.200957769624237
|
right_only_d6_10,right_only,d6_10,6,30,1,0.288979620167748,0.289798539517805,0.378365014630273,2.425,0.119238243812818,0.200957769624237
|
||||||
right_only_d6_13,right_only,d6_13,6,42,1,0.0258497485171371,2.75792254325755,0.109557181635453,0.0976080018855909
|
right_only_d6_13,right_only,d6_13,6,42,1,0.0258497485171371,0.0259134063416617,0.0369743190514673,2.75792254325755,0.109557181635453,0.0976080018855909
|
||||||
naive_a2_d0_10,naive_a2,d0_10,10,104,1,0.125891264174292,1.43371876298297,0.141836960529428,1.59796354184479e-27
|
naive_a2_d0_10,naive_a2,d0_10,10,104,1,0.125891264174292,0.126071775330177,0.30932733005532,1.43371876298297,0.141836960529428,1.59796354184479e-27
|
||||||
naive_a2_d0_13,naive_a2,d0_13,10,124,1,0.0397967517409217,1.61366204723722,0.153633906173184,2.5469330031931e-28
|
naive_a2_d0_13,naive_a2,d0_13,10,124,1,0.0397967517409217,0.0398599319703312,0.0578149015961064,1.61366204723722,0.153633906173184,2.5469330031931e-28
|
||||||
naive_a2_d0_5,naive_a2,d0_5,10,59,1,0.00107331575782455,6.96223661591522,0.960550975833492,1.1254222201539e-09
|
naive_a2_d0_5,naive_a2,d0_5,10,59,1,0.00107331575782455,0.00115218904921896,0.028152310883297,6.96223661591522,0.960550975833492,1.1254222201539e-09
|
||||||
naive_a2_d6_10,naive_a2,d6_10,9,45,1,0.449922139297732,-1.8,0.00929038977362001,8.02730473236241e-05
|
naive_a2_d6_10,naive_a2,d6_10,9,45,1,0.449922139297732,0.450525162691322,0.522263670801849,-1.8,0.00929038977362001,8.02730473236241e-05
|
||||||
naive_a2_d6_13,naive_a2,d6_13,9,65,1,0.439759713746994,1.15527349799111,0.0165675905421826,0.00123453301903328
|
naive_a2_d6_13,naive_a2,d6_13,9,65,1,0.439759713746994,0.439915577313227,0.563543413702517,1.15527349799111,0.0165675905421826,0.00123453301903328
|
||||||
naive_boxa_d0_10,naive_boxa,d0_10,11,115,1,0.194116831474294,1.17792772470162,0.124842156542362,1.7431506758907e-32
|
naive_boxa_d0_10,naive_boxa,d0_10,11,115,1,0.194116831474294,0.194284662755081,0.383551190839722,1.17792772470162,0.124842156542362,1.7431506758907e-32
|
||||||
naive_boxa_d0_13,naive_boxa,d0_13,11,138,1,0.0848056366333164,1.30637615965008,0.140502409490992,1.21292542612342e-34
|
naive_boxa_d0_13,naive_boxa,d0_13,11,138,1,0.0848056366333164,0.0848864932608599,0.140324990567599,1.30637615965008,0.140502409490992,1.21292542612342e-34
|
||||||
naive_boxa_d0_5,naive_boxa,d0_5,11,65,1,0.00254470765200637,6.40329932104724,0.881419104187055,2.50618640106882e-11
|
naive_boxa_d0_5,naive_boxa,d0_5,11,65,1,0.00254470765200637,0.00267934065308424,0.0356145839564805,6.40329932104724,0.881419104187055,2.50618640106882e-11
|
||||||
naive_boxa_d6_10,naive_boxa,d6_10,10,50,1,0.46817073850354,-1.61904761904762,0.014689379915428,1.87688778586107e-05
|
naive_boxa_d6_10,naive_boxa,d6_10,10,50,1,0.46817073850354,0.468724498567948,0.534407300553877,-1.61904761904762,0.014689379915428,1.87688778586107e-05
|
||||||
naive_boxa_d6_13,naive_boxa,d6_13,10,73,1,0.530807487326757,0.873689202983884,0.0303916991304334,6.44631091768978e-05
|
naive_boxa_d6_13,naive_boxa,d6_13,10,73,1,0.530807487326757,0.530942127564996,0.696589180707779,0.873689202983884,0.0303916991304334,6.44631091768978e-05
|
||||||
|
|||||||
|
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 8.5397 6.1599 11.839
|
{'Res Std'} 8.5397 6.1599 11.839
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(20)= 3.27 F(1)= 10.700 p=0.003822
|
stim x day (interaction) t(20)= 3.27 F(1)= 10.700 p=0.003822 p=0.004244 (df=18)
|
||||||
day (learning) t(20)= 2.89 F(1)= 8.337 p=0.009105
|
day (learning) t(20)= 2.89 F(1)= 8.337 p=0.009105 p=0.009807 (df=18)
|
||||||
stim (main, window start) t(20)= -1.42 F(1)= 2.025 p=0.1701
|
stim (main, window start) t(20)= -1.42 F(1)= 2.025 p=0.1701 p=0.1703 (df=20)
|
||||||
interaction 95% CI: [+3.70, +16.70]
|
interaction 95% CI: [+3.70, +16.70]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,9.8)=9.76, p=0.01107
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.003822, slope diff=+10.20)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.003822, slope diff=+10.20)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 13.78 11.942 15.902
|
{'Res Std'} 13.78 11.942 15.902
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(100)= 1.54 F(1)= 2.382 p=0.1259
|
stim x day (interaction) t(100)= 1.54 F(1)= 2.382 p=0.1259 p=0.1261 (df=95)
|
||||||
day (learning) t(100)= 15.09 F(1)= 227.710 p=1.598e-27
|
day (learning) t(100)= 15.09 F(1)=227.710 p=1.598e-27 p=4.059e-27 (df=96)
|
||||||
stim (main, window start) t(100)= 1.48 F(1)= 2.192 p=0.1418
|
stim (main, window start) t(100)= 1.48 F(1)= 2.192 p=0.1418 p=0.1543 (df=20)
|
||||||
interaction 95% CI: [-0.41, +3.28]
|
interaction 95% CI: [-0.41, +3.28]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,9.0)=1.16, p=0.3093
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1259, slope diff=+1.43)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1259, slope diff=+1.43)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 15.264 13.405 17.381
|
{'Res Std'} 15.264 13.405 17.381
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(120)= 2.08 F(1)= 4.320 p=0.0398
|
stim x day (interaction) t(120)= 2.08 F(1)= 4.320 p=0.0398 p=0.03986 (df=117)
|
||||||
day (learning) t(120)= 14.57 F(1)= 212.424 p=2.547e-28
|
day (learning) t(120)= 14.57 F(1)=212.424 p=2.547e-28 p=3.246e-28 (df=119)
|
||||||
stim (main, window start) t(120)= 1.44 F(1)= 2.062 p=0.1536
|
stim (main, window start) t(120)= 1.44 F(1)= 2.062 p=0.1536 p=0.1655 (df=21)
|
||||||
interaction 95% CI: [+0.08, +3.15]
|
interaction 95% CI: [+0.08, +3.15]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,41.7)=3.81, p=0.05781
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.0398, slope diff=+1.61)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.0398, slope diff=+1.61)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -34,7 +50,7 @@ if abs(maxT - maxC) > 2
|
|||||||
else
|
else
|
||||||
cov = '(equal day coverage over this window)';
|
cov = '(equal day coverage over this window)';
|
||||||
end
|
end
|
||||||
y
|
|
||||||
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
||||||
if pI >= 0.05
|
if pI >= 0.05
|
||||||
verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -28,27 +28,41 @@ Model fit statistics:
|
|||||||
489.28 501.74 -238.64 477.28
|
489.28 501.74 -238.64 477.28
|
||||||
|
|
||||||
Fixed effects coefficients (95% CIs):
|
Fixed effects coefficients (95% CIs):
|
||||||
Name Estimate SE tStat DF pValue Lower Upper
|
Name Estimate SE tStat DF pValue
|
||||||
{'(Intercept)'} 9.0584 5.0267 1.8021 55 0.077018 -1.0153 19.132
|
{'(Intercept)'} 9.0584 5.0267 1.8021 55 0.077018
|
||||||
{'day' } 8.2568 1.1279 7.3206 55 1.1254e-09 5.9965 10.517
|
{'day' } 8.2568 1.1279 7.3206 55 1.1254e-09
|
||||||
{'stim' } 0.44958 9.048 0.049688 55 0.96055 -17.683 18.582
|
{'stim' } 0.44958 9.048 0.049688 55 0.96055
|
||||||
{'day:stim' } 6.9622 2.0162 3.4532 55 0.0010733 2.9217 11.003
|
{'day:stim' } 6.9622 2.0162 3.4532 55 0.0010733
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
-1.0153 19.132
|
||||||
|
5.9965 10.517
|
||||||
|
-17.683 18.582
|
||||||
|
2.9217 11.003
|
||||||
|
|
||||||
Random effects covariance parameters (95% CIs):
|
Random effects covariance parameters (95% CIs):
|
||||||
Group: rat (10 Levels)
|
Group: rat (10 Levels)
|
||||||
Name1 Name2 Type Estimate Lower Upper
|
Name1 Name2 Type Estimate
|
||||||
{'(Intercept)'} {'(Intercept)'} {'std'} 9.6432 5.5002 16.907
|
{'(Intercept)'} {'(Intercept)'} {'std'} 9.6432
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
5.5002 16.907
|
||||||
|
|
||||||
Group: Error
|
Group: Error
|
||||||
Name Estimate Lower Upper
|
Name Estimate Lower Upper
|
||||||
{'Res Std'} 12.109 9.9317 14.763
|
{'Res Std'} 12.109 9.9317 14.763
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(55)= 3.45 F(1)= 11.924 p=0.001073
|
stim x day (interaction) t(55)= 3.45 F(1)= 11.924 p=0.001073 p=0.001152 (df=49)
|
||||||
day (learning) t(55)= 7.32 F(1)= 53.591 p=1.125e-09
|
day (learning) t(55)= 7.32 F(1)= 53.591 p=1.125e-09 p=2.092e-09 (df=49)
|
||||||
stim (main, window start) t(55)= 0.05 F(1)= 0.002 p=0.9606
|
stim (main, window start) t(55)= 0.05 F(1)= 0.002 p=0.9606 p=0.9609 (df=20)
|
||||||
interaction 95% CI: [+2.92, +11.00]
|
interaction 95% CI: [+2.92, +11.00]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,9.7)=6.65, p=0.02815
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.001073, slope diff=+6.96)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.001073, slope diff=+6.96)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 10.552 8.376 13.294
|
{'Res Std'} 10.552 8.376 13.294
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(41)= -0.76 F(1)= 0.582 p=0.4499
|
stim x day (interaction) t(41)= -0.76 F(1)= 0.582 p=0.4499 p=0.4505 (df=36)
|
||||||
day (learning) t(41)= 4.38 F(1)= 19.183 p=8.027e-05
|
day (learning) t(41)= 4.38 F(1)= 19.183 p=8.027e-05 p=9.813e-05 (df=36)
|
||||||
stim (main, window start) t(41)= 2.73 F(1)= 7.453 p=0.00929
|
stim (main, window start) t(41)= 2.73 F(1)= 7.453 p=0.00929 p=0.01541 (df=15)
|
||||||
interaction 95% CI: [-6.57, +2.97]
|
interaction 95% CI: [-6.57, +2.97]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,9.0)=0.44, p=0.5223
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4499, slope diff=-1.80)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4499, slope diff=-1.80)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 12.033 10.004 14.473
|
{'Res Std'} 12.033 10.004 14.473
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(61)= 0.78 F(1)= 0.605 p=0.4398
|
stim x day (interaction) t(61)= 0.78 F(1)= 0.605 p=0.4398 p=0.4399 (df=58)
|
||||||
day (learning) t(61)= 3.39 F(1)= 11.485 p=0.001235
|
day (learning) t(61)= 3.39 F(1)= 11.485 p=0.001235 p=0.001274 (df=57)
|
||||||
stim (main, window start) t(61)= 2.46 F(1)= 6.072 p=0.01657
|
stim (main, window start) t(61)= 2.46 F(1)= 6.072 p=0.01657 p=0.02473 (df=17)
|
||||||
interaction 95% CI: [-1.82, +4.13]
|
interaction 95% CI: [-1.82, +4.13]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,7.7)=0.36, p=0.5635
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4398, slope diff=+1.16)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4398, slope diff=+1.16)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 13.706 11.962 15.704
|
{'Res Std'} 13.706 11.962 15.704
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(111)= 1.31 F(1)= 1.707 p=0.1941
|
stim x day (interaction) t(111)= 1.31 F(1)= 1.707 p=0.1941 p=0.1943 (df=104)
|
||||||
day (learning) t(111)= 16.91 F(1)= 285.824 p=1.743e-32
|
day (learning) t(111)= 16.91 F(1)=285.824 p=1.743e-32 p=6.758e-32 (df=106)
|
||||||
stim (main, window start) t(111)= 1.55 F(1)= 2.392 p=0.1248
|
stim (main, window start) t(111)= 1.55 F(1)= 2.392 p=0.1248 p=0.1361 (df=22)
|
||||||
interaction 95% CI: [-0.61, +2.96]
|
interaction 95% CI: [-0.61, +2.96]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,10.1)=0.83, p=0.3836
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1941, slope diff=+1.18)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1941, slope diff=+1.18)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 15.18 13.424 17.167
|
{'Res Std'} 15.18 13.424 17.167
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(134)= 1.74 F(1)= 3.015 p=0.08481
|
stim x day (interaction) t(134)= 1.74 F(1)= 3.015 p=0.08481 p=0.08489 (df=129)
|
||||||
day (learning) t(134)= 16.74 F(1)= 280.201 p=1.213e-34
|
day (learning) t(134)= 16.74 F(1)=280.201 p=1.213e-34 p=2.161e-34 (df=131)
|
||||||
stim (main, window start) t(134)= 1.48 F(1)= 2.198 p=0.1405
|
stim (main, window start) t(134)= 1.48 F(1)= 2.198 p=0.1405 p=0.1519 (df=23)
|
||||||
interaction 95% CI: [-0.18, +2.79]
|
interaction 95% CI: [-0.18, +2.79]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,25.3)=2.32, p=0.1403
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.08481, slope diff=+1.31)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.08481, slope diff=+1.31)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 12.477 10.33 15.071
|
{'Res Std'} 12.477 10.33 15.071
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(61)= 3.15 F(1)= 9.909 p=0.002545
|
stim x day (interaction) t(61)= 3.15 F(1)= 9.909 p=0.002545 p=0.002679 (df=54)
|
||||||
day (learning) t(61)= 8.14 F(1)= 66.293 p=2.506e-11
|
day (learning) t(61)= 8.14 F(1)= 66.293 p=2.506e-11 p=5.765e-11 (df=54)
|
||||||
stim (main, window start) t(61)= 0.15 F(1)= 0.022 p=0.8814
|
stim (main, window start) t(61)= 0.15 F(1)= 0.022 p=0.8814 p=0.8822 (df=24)
|
||||||
interaction 95% CI: [+2.34, +10.47]
|
interaction 95% CI: [+2.34, +10.47]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,10.5)=5.80, p=0.03561
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.002545, slope diff=+6.40)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.002545, slope diff=+6.40)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 10.142 8.1466 12.627
|
{'Res Std'} 10.142 8.1466 12.627
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(46)= -0.73 F(1)= 0.535 p=0.4682
|
stim x day (interaction) t(46)= -0.73 F(1)= 0.535 p=0.4682 p=0.4687 (df=40)
|
||||||
day (learning) t(46)= 4.77 F(1)= 22.779 p=1.877e-05
|
day (learning) t(46)= 4.77 F(1)= 22.779 p=1.877e-05 p=2.436e-05 (df=40)
|
||||||
stim (main, window start) t(46)= 2.54 F(1)= 6.429 p=0.01469
|
stim (main, window start) t(46)= 2.54 F(1)= 6.429 p=0.01469 p=0.02198 (df=16)
|
||||||
interaction 95% CI: [-6.07, +2.84]
|
interaction 95% CI: [-6.07, +2.84]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,10.0)=0.41, p=0.5344
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4682, slope diff=-1.62)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.4682, slope diff=-1.62)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 11.525 9.6827 13.718
|
{'Res Std'} 11.525 9.6827 13.718
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(69)= 0.63 F(1)= 0.397 p=0.5308
|
stim x day (interaction) t(69)= 0.63 F(1)= 0.397 p=0.5308 p=0.5309 (df=65)
|
||||||
day (learning) t(69)= 4.26 F(1)= 18.109 p=6.446e-05
|
day (learning) t(69)= 4.26 F(1)= 18.109 p=6.446e-05 p=6.931e-05 (df=64)
|
||||||
stim (main, window start) t(69)= 2.21 F(1)= 4.886 p=0.03039
|
stim (main, window start) t(69)= 2.21 F(1)= 4.886 p=0.03039 p=0.0412 (df=17)
|
||||||
interaction 95% CI: [-1.89, +3.64]
|
interaction 95% CI: [-1.89, +3.64]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,8.5)=0.16, p=0.6966
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.5308, slope diff=+0.87)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.5308, slope diff=+0.87)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 4.4892 4.0771 4.9429
|
{'Res Std'} 4.4892 4.0771 4.9429
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(227)= 2.66 F(1)= 7.088 p=0.008315
|
stim x day (interaction) t(227)= 2.66 F(1)= 7.088 p=0.008315 p=0.008365 (df=208)
|
||||||
day (learning) t(227)= 8.94 F(1)= 79.838 p=1.43e-16
|
day (learning) t(227)= 8.94 F(1)= 79.838 p=1.43e-16 p=2.16e-16 (df=209)
|
||||||
stim (main, window start) t(227)= 0.92 F(1)= 0.839 p=0.3606
|
stim (main, window start) t(227)= 0.92 F(1)= 0.839 p=0.3606 p=0.3656 (df=37)
|
||||||
interaction 95% CI: [+0.15, +0.97]
|
interaction 95% CI: [+0.15, +0.97]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,24.0)=3.01, p=0.09541
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.008315, slope diff=+0.56)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.008315, slope diff=+0.56)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 11.956 10.063 14.205
|
{'Res Std'} 11.956 10.063 14.205
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(68)= 1.83 F(1)= 3.336 p=0.07218
|
stim x day (interaction) t(68)= 1.83 F(1)= 3.336 p=0.07218 p=0.07218 (df=68)
|
||||||
day (learning) t(68)= 10.55 F(1)= 111.222 p=5.943e-16
|
day (learning) t(68)= 10.55 F(1)=111.222 p=5.943e-16 p=4.533e-16 (df=70)
|
||||||
stim (main, window start) t(68)= 0.37 F(1)= 0.134 p=0.7159
|
stim (main, window start) t(68)= 0.37 F(1)= 0.134 p=0.7159 p=0.7187 (df=19)
|
||||||
interaction 95% CI: [-0.16, +3.62]
|
interaction 95% CI: [-0.16, +3.62]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,20.5)=2.56, p=0.1246
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.07218, slope diff=+1.73)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.07218, slope diff=+1.73)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 14.12 12.139 16.425
|
{'Res Std'} 14.12 12.139 16.425
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(80)= 2.01 F(1)= 4.031 p=0.04804
|
stim x day (interaction) t(80)= 2.01 F(1)= 4.031 p=0.04804 p=0.04788 (df=84)
|
||||||
day (learning) t(80)= 9.45 F(1)= 89.224 p=1.171e-14
|
day (learning) t(80)= 9.45 F(1)= 89.224 p=1.171e-14 p=7.518e-15 (df=84)
|
||||||
stim (main, window start) t(80)= 0.63 F(1)= 0.401 p=0.5285
|
stim (main, window start) t(80)= 0.63 F(1)= 0.401 p=0.5285 p=0.5284 (df=84)
|
||||||
interaction 95% CI: [+0.02, +3.39]
|
interaction 95% CI: [+0.02, +3.39]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,20.2)=2.92, p=0.1029
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.04804, slope diff=+1.70)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.04804, slope diff=+1.70)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 9.9638 7.8829 12.594
|
{'Res Std'} 9.9638 7.8829 12.594
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(38)= 2.92 F(1)= 8.511 p=0.005897
|
stim x day (interaction) t(38)= 2.92 F(1)= 8.511 p=0.005897 p=0.006128 (df=35)
|
||||||
day (learning) t(38)= 7.17 F(1)= 51.382 p=1.458e-08
|
day (learning) t(38)= 7.17 F(1)= 51.382 p=1.458e-08 p=2.321e-08 (df=35)
|
||||||
stim (main, window start) t(38)= -0.73 F(1)= 0.534 p=0.4696
|
stim (main, window start) t(38)= -0.73 F(1)= 0.534 p=0.4696 p=0.4735 (df=20)
|
||||||
interaction 95% CI: [+1.62, +8.99]
|
interaction 95% CI: [+1.62, +8.99]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,7.0)=3.94, p=0.08763
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.005897, slope diff=+5.31)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.005897, slope diff=+5.31)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 8.1802 6.1646 10.855
|
{'Res Std'} 8.1802 6.1646 10.855
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(26)= 1.08 F(1)= 1.172 p=0.289
|
stim x day (interaction) t(26)= 1.08 F(1)= 1.172 p=0.289 p=0.2898 (df=24)
|
||||||
day (learning) t(26)= 1.31 F(1)= 1.722 p=0.201
|
day (learning) t(26)= 1.31 F(1)= 1.722 p=0.201 p=0.2019 (df=24)
|
||||||
stim (main, window start) t(26)= 1.61 F(1)= 2.596 p=0.1192
|
stim (main, window start) t(26)= 1.61 F(1)= 2.596 p=0.1192 p=0.1297 (df=14)
|
||||||
interaction 95% CI: [-2.18, +7.03]
|
interaction 95% CI: [-2.18, +7.03]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,6.0)=0.90, p=0.3784
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.289, slope diff=+2.42)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.289, slope diff=+2.42)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 7.3669 5.8525 9.2731
|
{'Res Std'} 7.3669 5.8525 9.2731
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(38)= 2.32 F(1)= 5.379 p=0.02585
|
stim x day (interaction) t(38)= 2.32 F(1)= 5.379 p=0.02585 p=0.02591 (df=38)
|
||||||
day (learning) t(38)= 1.70 F(1)= 2.885 p=0.09761
|
day (learning) t(38)= 1.70 F(1)= 2.885 p=0.09761 p=0.09766 (df=38)
|
||||||
stim (main, window start) t(38)= 1.64 F(1)= 2.685 p=0.1096
|
stim (main, window start) t(38)= 1.64 F(1)= 2.685 p=0.1096 p=0.1304 (df=11)
|
||||||
interaction 95% CI: [+0.35, +5.17]
|
interaction 95% CI: [+0.35, +5.17]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,21.2)=4.96, p=0.03697
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.02585, slope diff=+2.76)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.02585, slope diff=+2.76)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 12.508 10.37 15.086
|
{'Res Std'} 12.508 10.37 15.086
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(57)= 1.49 F(1)= 2.208 p=0.1428
|
stim x day (interaction) t(57)= 1.49 F(1)= 2.208 p=0.1428 p=0.1428 (df=57)
|
||||||
day (learning) t(57)= 10.08 F(1)= 101.556 p=2.832e-14
|
day (learning) t(57)= 10.08 F(1)=101.556 p=2.832e-14 p=1.931e-14 (df=59)
|
||||||
stim (main, window start) t(57)= 0.68 F(1)= 0.466 p=0.4974
|
stim (main, window start) t(57)= 0.68 F(1)= 0.466 p=0.4974 p=0.5037 (df=17)
|
||||||
interaction 95% CI: [-0.54, +3.66]
|
interaction 95% CI: [-0.54, +3.66]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,13.0)=1.46, p=0.2479
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1428, slope diff=+1.56)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1428, slope diff=+1.56)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 14.676 12.436 17.32
|
{'Res Std'} 14.676 12.436 17.32
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038
|
stim x day (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038 p=0.1035 (df=70)
|
||||||
day (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13
|
day (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13 p=1.823e-13 (df=70)
|
||||||
stim (main, window start) t(66)= 0.95 F(1)= 0.910 p=0.3436
|
stim (main, window start) t(66)= 0.95 F(1)= 0.910 p=0.3436 p=0.3434 (df=70)
|
||||||
interaction 95% CI: [-0.33, +3.42]
|
interaction 95% CI: [-0.33, +3.42]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,12.8)=1.64, p=0.2227
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1038, slope diff=+1.55)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1038, slope diff=+1.55)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 10.703 8.3104 13.785
|
{'Res Std'} 10.703 8.3104 13.785
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(32)= 2.57 F(1)= 6.588 p=0.01515
|
stim x day (interaction) t(32)= 2.57 F(1)= 6.588 p=0.01515 p=0.0155 (df=30)
|
||||||
day (learning) t(32)= 6.67 F(1)= 44.528 p=1.571e-07
|
day (learning) t(32)= 6.67 F(1)= 44.528 p=1.571e-07 p=2.162e-07 (df=30)
|
||||||
stim (main, window start) t(32)= -0.40 F(1)= 0.163 p=0.6888
|
stim (main, window start) t(32)= -0.40 F(1)= 0.163 p=0.6888 p=0.6906 (df=19)
|
||||||
interaction 95% CI: [+1.11, +9.62]
|
interaction 95% CI: [+1.11, +9.62]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,6.0)=3.02, p=0.1331
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.01515, slope diff=+5.36)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.01515, slope diff=+5.36)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 8.6289 6.3295 11.764
|
{'Res Std'} 8.6289 6.3295 11.764
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(21)= 0.71 F(1)= 0.503 p=0.486
|
stim x day (interaction) t(21)= 0.71 F(1)= 0.503 p=0.486 p=0.4864 (df=20)
|
||||||
day (learning) t(21)= 1.24 F(1)= 1.547 p=0.2273
|
day (learning) t(21)= 1.24 F(1)= 1.547 p=0.2273 p=0.2279 (df=20)
|
||||||
stim (main, window start) t(21)= 1.73 F(1)= 3.008 p=0.09752
|
stim (main, window start) t(21)= 1.73 F(1)= 3.008 p=0.09752 p=0.1098 (df=11)
|
||||||
interaction 95% CI: [-3.41, +6.95]
|
interaction 95% CI: [-3.41, +6.95]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,5.0)=0.40, p=0.5549
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.486, slope diff=+1.77)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.486, slope diff=+1.77)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -21,11 +21,27 @@ tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject
|
|||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
|
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||||
|
|
||||||
|
% Honest test: refit with a per-animal random SLOPE so the interaction DF
|
||||||
|
% collapses toward the animal count (guarded -- may not converge in short windows).
|
||||||
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
|
wst = warning('off', 'all');
|
||||||
|
try
|
||||||
|
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
|
Ar = anova(mr, 'DFMethod', 'satterthwaite');
|
||||||
|
ri = strcmp(Ar.Term, 'day:stim');
|
||||||
|
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
|
||||||
|
catch
|
||||||
|
end
|
||||||
|
warning(wst);
|
||||||
|
|
||||||
gi = @(t) find(strcmp(C.Name, t), 1);
|
gi = @(t) find(strcmp(C.Name, t), 1);
|
||||||
ga = @(t) find(strcmp(A.Term, t), 1);
|
ga = @(t) find(strcmp(A.Term, t), 1);
|
||||||
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', nm, ...
|
gs = @(t) find(strcmp(As.Term, t), 1);
|
||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)));
|
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
|
||||||
|
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
||||||
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
||||||
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
@@ -54,11 +70,18 @@ s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim
|
|||||||
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
|
||||||
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
|
||||||
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
|
||||||
s = [s sprintf('%-26s %-13s %-13s %s\n%s\n', 'effect', 't (df)', 'F (df1)', 'p', repmat('-', 1, 66))];
|
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
|
||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
|
if rsOk
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
|
else
|
||||||
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
|
||||||
|
end
|
||||||
|
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
||||||
|
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
|
||||||
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
@@ -70,5 +93,6 @@ fclose(fid);
|
|||||||
% Machine-readable handoff for the summary table (see make_variations.m).
|
% Machine-readable handoff for the summary table (see make_variations.m).
|
||||||
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
|
||||||
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
|
||||||
|
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
|
||||||
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
'covEqual', abs(maxT - maxC) <= 2);
|
'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ Group: Error
|
|||||||
{'Res Std'} 7.7328 5.9847 9.9914
|
{'Res Std'} 7.7328 5.9847 9.9914
|
||||||
|
|
||||||
|
|
||||||
effect t (df) F (df1) p
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
stim x day (interaction) t(30)= 1.71 F(1)= 2.933 p=0.0971
|
stim x day (interaction) t(30)= 1.71 F(1)= 2.933 p=0.0971 p=0.09701 (df=30)
|
||||||
day (learning) t(30)= 1.61 F(1)= 2.598 p=0.1175
|
day (learning) t(30)= 1.61 F(1)= 2.598 p=0.1175 p=0.1174 (df=30)
|
||||||
stim (main, window start) t(30)= 1.69 F(1)= 2.856 p=0.1014
|
stim (main, window start) t(30)= 1.69 F(1)= 2.856 p=0.1014 p=0.1269 (df=9)
|
||||||
interaction 95% CI: [-0.44, +4.97]
|
interaction 95% CI: [-0.44, +4.97]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,13.9)=2.48, p=0.1382
|
||||||
|
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
||||||
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.0971, slope diff=+2.27)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.0971, slope diff=+2.27)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -0,0 +1,345 @@
|
|||||||
|
"""
|
||||||
|
Creates n8n workflow definitions for every entity in the experiments database.
|
||||||
|
Each workflow has an Execute Workflow Trigger + Switch (routes by `action` field)
|
||||||
|
+ one Postgres node per operation. They act as reusable sub-workflows.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json, uuid, requests
|
||||||
|
|
||||||
|
API = "http://localhost:5678/api/v1"
|
||||||
|
KEY = "n8n_api_ca07bd874c628ed0f52d9a2e65073960f9f617c5"
|
||||||
|
CRED = {"id": "9iJuyA9iR5KUzmj5", "name": "Experiments DB (PostgreSQL)"}
|
||||||
|
HEADS = {"X-N8N-API-KEY": KEY, "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
def uid(): return str(uuid.uuid4())
|
||||||
|
|
||||||
|
# ── Node builders ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def trigger_node(x, y):
|
||||||
|
return {
|
||||||
|
"id": uid(), "name": "Trigger",
|
||||||
|
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [x, y],
|
||||||
|
"parameters": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def switch_node(name, actions, x, y):
|
||||||
|
"""Route by $json.action — one output per action string."""
|
||||||
|
rules = []
|
||||||
|
for action in actions:
|
||||||
|
rules.append({
|
||||||
|
"conditions": {
|
||||||
|
"options": {"caseSensitive": False, "leftValue": "", "typeValidation": "loose"},
|
||||||
|
"combinator": "and",
|
||||||
|
"conditions": [{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": action,
|
||||||
|
"operator": {"type": "string", "operation": "equals"},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
"renameOutput": True,
|
||||||
|
"outputKey": action,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"id": uid(), "name": name,
|
||||||
|
"type": "n8n-nodes-base.switch",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [x, y],
|
||||||
|
"parameters": {"rules": {"values": rules}, "options": {}},
|
||||||
|
}
|
||||||
|
|
||||||
|
def pg_node(name, query, params, x, y):
|
||||||
|
"""Postgres executeQuery node. params is a list of n8n expressions for $1,$2,..."""
|
||||||
|
node = {
|
||||||
|
"id": uid(), "name": name,
|
||||||
|
"type": "n8n-nodes-base.postgres",
|
||||||
|
"typeVersion": 2.5,
|
||||||
|
"position": [x, y],
|
||||||
|
"credentials": {"postgres": CRED},
|
||||||
|
"parameters": {
|
||||||
|
"operation": "executeQuery",
|
||||||
|
"query": query,
|
||||||
|
"options": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if params:
|
||||||
|
node["parameters"]["options"]["queryReplacement"] = "={{ [" + ", ".join(params) + "] }}"
|
||||||
|
return node
|
||||||
|
|
||||||
|
# ── Connection helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def conn(src, src_idx, dst):
|
||||||
|
"""Return a connections dict fragment."""
|
||||||
|
return (src["name"], src_idx, dst["name"])
|
||||||
|
|
||||||
|
def build_connections(pairs):
|
||||||
|
"""
|
||||||
|
pairs: list of (src_node, output_index, dst_node)
|
||||||
|
Returns n8n connections object.
|
||||||
|
"""
|
||||||
|
conns = {}
|
||||||
|
for src, idx, dst in pairs:
|
||||||
|
conns.setdefault(src, {"main": []})
|
||||||
|
while len(conns[src]["main"]) <= idx:
|
||||||
|
conns[src]["main"].append([])
|
||||||
|
conns[src]["main"][idx].append({"node": dst, "type": "main", "index": 0})
|
||||||
|
return conns
|
||||||
|
|
||||||
|
# ── Workflow factory ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def make_workflow(name, trigger, switch, pg_nodes, connections):
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"nodes": [trigger, switch] + pg_nodes,
|
||||||
|
"connections": connections,
|
||||||
|
"settings": {"executionOrder": "v1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def post_workflow(wf):
|
||||||
|
r = requests.post(f"{API}/workflows", headers=HEADS, json=wf)
|
||||||
|
if r.ok:
|
||||||
|
d = r.json()
|
||||||
|
print(f" ✓ [{d['id']}] {wf['name']}")
|
||||||
|
else:
|
||||||
|
print(f" ✗ {wf['name']} → {r.status_code} {r.text[:200]}")
|
||||||
|
|
||||||
|
# ── Layouts ────────────────────────────────────────────────────────────────────
|
||||||
|
# Nodes stacked vertically, 220px apart; Switch at col 300, pg nodes at col 560
|
||||||
|
|
||||||
|
def pg_y(i): return i * 220 - 440 # center the stack
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 1. EXPERIMENTS
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_experiments():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list","get","create","update","delete"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Experiments",
|
||||||
|
"SELECT id, title, created_at FROM experiments ORDER BY created_at DESC",
|
||||||
|
[], 560, pg_y(0)),
|
||||||
|
pg_node("Get Experiment",
|
||||||
|
"SELECT * FROM experiments WHERE id = $1",
|
||||||
|
["$json.id"], 560, pg_y(1)),
|
||||||
|
pg_node("Create Experiment",
|
||||||
|
"INSERT INTO experiments (id, title, template, subject_info_template) "
|
||||||
|
"VALUES (gen_random_uuid(), $1, '[]'::jsonb, '[]'::jsonb) RETURNING *",
|
||||||
|
["$json.title"], 560, pg_y(2)),
|
||||||
|
pg_node("Update Experiment",
|
||||||
|
"UPDATE experiments SET title = $1 WHERE id = $2 RETURNING *",
|
||||||
|
["$json.title", "$json.id"], 560, pg_y(3)),
|
||||||
|
pg_node("Delete Experiment",
|
||||||
|
"DELETE FROM experiments WHERE id = $1 RETURNING id",
|
||||||
|
["$json.id"], 560, pg_y(4)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Experiments", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 2. ANIMALS
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_animals():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list","get","create","update","delete"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Animals",
|
||||||
|
"SELECT a.*, COUNT(ds.id)::int AS daily_status_count "
|
||||||
|
"FROM animals a "
|
||||||
|
"LEFT JOIN daily_statuses ds ON ds.animal_id = a.id "
|
||||||
|
"WHERE a.experiment_id = $1 "
|
||||||
|
"GROUP BY a.id ORDER BY a.animal_name",
|
||||||
|
["$json.experiment_id"], 560, pg_y(0)),
|
||||||
|
pg_node("Get Animal",
|
||||||
|
"SELECT a.*, e.title AS experiment_title "
|
||||||
|
"FROM animals a JOIN experiments e ON e.id = a.experiment_id "
|
||||||
|
"WHERE a.id = $1",
|
||||||
|
["$json.id"], 560, pg_y(1)),
|
||||||
|
pg_node("Create Animal",
|
||||||
|
"INSERT INTO animals (id, experiment_id, animal_id_string, animal_name, subject_info) "
|
||||||
|
"VALUES (gen_random_uuid(), $1, $2, $3, $4::jsonb) RETURNING *",
|
||||||
|
["$json.experiment_id", "$json.animal_id_string", "$json.animal_name",
|
||||||
|
"JSON.stringify($json.subject_info ?? {})"], 560, pg_y(2)),
|
||||||
|
pg_node("Update Animal",
|
||||||
|
"UPDATE animals SET animal_id_string=$1, animal_name=$2, subject_info=$3::jsonb "
|
||||||
|
"WHERE id=$4 RETURNING *",
|
||||||
|
["$json.animal_id_string", "$json.animal_name",
|
||||||
|
"JSON.stringify($json.subject_info ?? {})", "$json.id"], 560, pg_y(3)),
|
||||||
|
pg_node("Delete Animal",
|
||||||
|
"DELETE FROM animals WHERE id = $1 RETURNING id",
|
||||||
|
["$json.id"], 560, pg_y(4)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Animals", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 3. DAILY STATUSES
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_daily_statuses():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list","get","create","update","delete"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Daily Statuses",
|
||||||
|
"SELECT ds.id, ds.date, ds.experiment_description, ds.vitals, "
|
||||||
|
"ds.treatment, ds.notes, ds.custom_fields, ds.analysis_summary, "
|
||||||
|
"a.animal_name, a.animal_id_string "
|
||||||
|
"FROM daily_statuses ds JOIN animals a ON a.id = ds.animal_id "
|
||||||
|
"WHERE ds.animal_id = $1 ORDER BY ds.date DESC",
|
||||||
|
["$json.animal_id"], 560, pg_y(0)),
|
||||||
|
pg_node("Get Daily Status",
|
||||||
|
"SELECT ds.*, a.animal_name, a.animal_id_string, "
|
||||||
|
"a.experiment_id, e.title AS experiment_title "
|
||||||
|
"FROM daily_statuses ds "
|
||||||
|
"JOIN animals a ON a.id = ds.animal_id "
|
||||||
|
"JOIN experiments e ON e.id = a.experiment_id "
|
||||||
|
"WHERE ds.id = $1",
|
||||||
|
["$json.id"], 560, pg_y(1)),
|
||||||
|
pg_node("Create Daily Status",
|
||||||
|
"INSERT INTO daily_statuses "
|
||||||
|
"(id, animal_id, date, experiment_description, vitals, treatment, notes, custom_fields) "
|
||||||
|
"VALUES (gen_random_uuid(), $1, $2::date, $3, $4, $5, $6, $7::jsonb) RETURNING *",
|
||||||
|
["$json.animal_id", "$json.date", "$json.experiment_description ?? null",
|
||||||
|
"$json.vitals ?? null", "$json.treatment ?? null", "$json.notes ?? null",
|
||||||
|
"JSON.stringify($json.custom_fields ?? {})"], 560, pg_y(2)),
|
||||||
|
pg_node("Update Daily Status",
|
||||||
|
"UPDATE daily_statuses SET "
|
||||||
|
"experiment_description=$1, vitals=$2, treatment=$3, notes=$4, "
|
||||||
|
"custom_fields=$5::jsonb "
|
||||||
|
"WHERE id=$6 RETURNING *",
|
||||||
|
["$json.experiment_description ?? null", "$json.vitals ?? null",
|
||||||
|
"$json.treatment ?? null", "$json.notes ?? null",
|
||||||
|
"JSON.stringify($json.custom_fields ?? {})", "$json.id"], 560, pg_y(3)),
|
||||||
|
pg_node("Delete Daily Status",
|
||||||
|
"DELETE FROM daily_statuses WHERE id = $1 RETURNING id",
|
||||||
|
["$json.id"], 560, pg_y(4)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Daily Statuses", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 4. DAILY ANALYSES (read + summary-push)
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_daily_analyses():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list","get","push_summary"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Analyses",
|
||||||
|
"SELECT id, daily_status_id, created_at, file_name, "
|
||||||
|
"timestamp_col, note_col, total_note_rows, session_end_ts "
|
||||||
|
"FROM daily_analyses WHERE daily_status_id = $1 ORDER BY created_at DESC",
|
||||||
|
["$json.daily_status_id"], 560, pg_y(0)),
|
||||||
|
pg_node("Get Analysis",
|
||||||
|
"SELECT * FROM daily_analyses WHERE id = $1",
|
||||||
|
["$json.id"], 560, pg_y(1)),
|
||||||
|
pg_node("Push Analysis Summary",
|
||||||
|
"UPDATE daily_statuses SET analysis_summary = $1::jsonb WHERE id = $2 RETURNING id, analysis_summary",
|
||||||
|
["JSON.stringify($json.analysis_summary)", "$json.daily_status_id"],
|
||||||
|
560, pg_y(2)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Daily Analyses", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 5. SESSION FILES
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_session_files():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list","get"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Session Files",
|
||||||
|
"SELECT * FROM session_files WHERE daily_status_id = $1 ORDER BY created_at DESC",
|
||||||
|
["$json.daily_status_id"], 560, pg_y(0)),
|
||||||
|
pg_node("Get Session File",
|
||||||
|
"SELECT * FROM session_files WHERE id = $1",
|
||||||
|
["$json.id"], 560, pg_y(1)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Session Files", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 6. AUDIT LOGS
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_audit_logs():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["list_by_record","list_by_table"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("List Logs by Record",
|
||||||
|
"SELECT * FROM audit_logs "
|
||||||
|
"WHERE table_name = $1 AND record_id = $2 "
|
||||||
|
"ORDER BY timestamp DESC LIMIT $3",
|
||||||
|
["$json.table_name", "$json.record_id", "$json.limit ?? 50"],
|
||||||
|
560, pg_y(0)),
|
||||||
|
pg_node("List Logs by Table",
|
||||||
|
"SELECT * FROM audit_logs "
|
||||||
|
"WHERE table_name = $1 "
|
||||||
|
"ORDER BY timestamp DESC LIMIT $2",
|
||||||
|
["$json.table_name", "$json.limit ?? 100"],
|
||||||
|
560, pg_y(1)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Audit Logs", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# BONUS: Cross-subject report — one query to summarise all subjects in an experiment
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def wf_reports():
|
||||||
|
t = trigger_node(60, 0)
|
||||||
|
sw = switch_node("Route", ["subjects_summary","daily_summary","recent_activity"], 300, 0)
|
||||||
|
ops = [
|
||||||
|
pg_node("Subjects Summary",
|
||||||
|
"SELECT a.id, a.animal_name, a.animal_id_string, a.subject_info, "
|
||||||
|
"COUNT(ds.id)::int AS total_days, "
|
||||||
|
"MAX(ds.date) AS last_entry, "
|
||||||
|
"COUNT(ds.analysis_summary)::int AS days_with_metrics "
|
||||||
|
"FROM animals a "
|
||||||
|
"LEFT JOIN daily_statuses ds ON ds.animal_id = a.id "
|
||||||
|
"WHERE a.experiment_id = $1 "
|
||||||
|
"GROUP BY a.id ORDER BY a.animal_name",
|
||||||
|
["$json.experiment_id"], 560, pg_y(0)),
|
||||||
|
pg_node("Daily Summary",
|
||||||
|
"SELECT ds.date, a.animal_name, "
|
||||||
|
"ds.analysis_summary->>'total' AS total_attempts, "
|
||||||
|
"ds.analysis_summary->>'success_rate' AS success_rate "
|
||||||
|
"FROM daily_statuses ds JOIN animals a ON a.id = ds.animal_id "
|
||||||
|
"WHERE a.experiment_id = $1 AND ds.analysis_summary IS NOT NULL "
|
||||||
|
"ORDER BY ds.date, a.animal_name",
|
||||||
|
["$json.experiment_id"], 560, pg_y(1)),
|
||||||
|
pg_node("Recent Activity",
|
||||||
|
"SELECT al.timestamp, al.table_name, al.action, al.record_id, al.changes "
|
||||||
|
"FROM audit_logs al "
|
||||||
|
"ORDER BY al.timestamp DESC LIMIT $1",
|
||||||
|
["$json.limit ?? 50"], 560, pg_y(2)),
|
||||||
|
]
|
||||||
|
pairs = [(t["name"], 0, sw["name"])] + \
|
||||||
|
[(sw["name"], i, ops[i]["name"]) for i in range(len(ops))]
|
||||||
|
return make_workflow("ExpDB · Reports", t, sw, ops, build_connections(pairs))
|
||||||
|
|
||||||
|
# ── Main ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
workflows = [
|
||||||
|
wf_experiments(),
|
||||||
|
wf_animals(),
|
||||||
|
wf_daily_statuses(),
|
||||||
|
wf_daily_analyses(),
|
||||||
|
wf_session_files(),
|
||||||
|
wf_audit_logs(),
|
||||||
|
wf_reports(),
|
||||||
|
]
|
||||||
|
print(f"Creating {len(workflows)} workflows…\n")
|
||||||
|
for wf in workflows:
|
||||||
|
post_workflow(wf)
|
||||||
|
print("\nDone.")
|
||||||
Executable
+130
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Experiments DB — incremental backup script
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backup.sh daily – backup only if DB changed since last run; keep 5
|
||||||
|
# backup.sh weekly – unconditional backup; keep 2
|
||||||
|
#
|
||||||
|
# Cron (added by setup_cron.sh):
|
||||||
|
# 0 2 * * * /home/sam/docker-images/experiments-database/scripts/backup.sh daily
|
||||||
|
# 0 3 * * 0 /home/sam/docker-images/experiments-database/scripts/backup.sh weekly
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Config ────────────────────────────────────────────────────────────────────
|
||||||
|
MODE="${1:-daily}"
|
||||||
|
BACKUP_ROOT="/home/sam/synology/Backups/Experiments-DB-Backup"
|
||||||
|
DAILY_DIR="$BACKUP_ROOT/daily"
|
||||||
|
WEEKLY_DIR="$BACKUP_ROOT/weekly"
|
||||||
|
STATE_FILE="$BACKUP_ROOT/.last_backup_state"
|
||||||
|
LOG_FILE="$BACKUP_ROOT/backup.log"
|
||||||
|
KEEP_DAILY=5
|
||||||
|
KEEP_WEEKLY=2
|
||||||
|
|
||||||
|
PG_CONTAINER="experiments_postgres"
|
||||||
|
PG_USER="expuser"
|
||||||
|
PG_DB="experiments_db"
|
||||||
|
|
||||||
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
||||||
|
|
||||||
|
die() { log "ERROR: $*"; exit 1; }
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
|
||||||
|
|
||||||
|
# Check postgres container is running
|
||||||
|
if ! docker inspect --format '{{.State.Running}}' "$PG_CONTAINER" 2>/dev/null | grep -q true; then
|
||||||
|
die "Container $PG_CONTAINER is not running."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── DB state fingerprint ──────────────────────────────────────────────────────
|
||||||
|
# Combines row counts from all key tables + latest audit_log timestamp.
|
||||||
|
# Changes in any table (insert, update, delete) alter this string.
|
||||||
|
get_db_state() {
|
||||||
|
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -A -c "
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM experiments) || ',' ||
|
||||||
|
(SELECT COUNT(*) FROM animals) || ',' ||
|
||||||
|
(SELECT COUNT(*) FROM daily_statuses) || ',' ||
|
||||||
|
(SELECT COUNT(*) FROM daily_analyses) || ',' ||
|
||||||
|
(SELECT COUNT(*) FROM session_files) || ',' ||
|
||||||
|
COALESCE(
|
||||||
|
(SELECT MAX(timestamp)::text FROM audit_logs),
|
||||||
|
'no-audit'
|
||||||
|
)
|
||||||
|
AS fingerprint;
|
||||||
|
" 2>/dev/null | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Dump function ─────────────────────────────────────────────────────────────
|
||||||
|
run_dump() {
|
||||||
|
local dest="$1"
|
||||||
|
local tmp="${dest}.tmp"
|
||||||
|
log "Dumping → $dest"
|
||||||
|
docker exec "$PG_CONTAINER" pg_dump \
|
||||||
|
-U "$PG_USER" \
|
||||||
|
--format=custom \
|
||||||
|
--compress=9 \
|
||||||
|
"$PG_DB" > "$tmp" \
|
||||||
|
&& mv "$tmp" "$dest" \
|
||||||
|
|| { rm -f "$tmp"; die "pg_dump failed."; }
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$dest" | cut -f1)
|
||||||
|
log "Backup written ($size): $(basename "$dest")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Prune old backups ─────────────────────────────────────────────────────────
|
||||||
|
prune() {
|
||||||
|
local dir="$1"
|
||||||
|
local keep="$2"
|
||||||
|
local count
|
||||||
|
count=$(ls -1 "$dir"/*.pgdump 2>/dev/null | wc -l)
|
||||||
|
if (( count > keep )); then
|
||||||
|
ls -1t "$dir"/*.pgdump | tail -n "+$((keep + 1))" | while read -r f; do
|
||||||
|
log "Pruning old backup: $(basename "$f")"
|
||||||
|
rm -f "$f"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
|
if [[ "$MODE" == "daily" ]]; then
|
||||||
|
log "--- Daily backup check ---"
|
||||||
|
CURRENT_STATE=$(get_db_state)
|
||||||
|
|
||||||
|
if [[ -z "$CURRENT_STATE" ]]; then
|
||||||
|
die "Could not read DB state."
|
||||||
|
fi
|
||||||
|
|
||||||
|
LAST_STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ "$CURRENT_STATE" == "$LAST_STATE" ]]; then
|
||||||
|
log "No changes detected since last backup. Skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEST="$DAILY_DIR/experiments_db_daily_${TIMESTAMP}.pgdump"
|
||||||
|
run_dump "$DEST"
|
||||||
|
|
||||||
|
# Save new state only after successful dump
|
||||||
|
echo "$CURRENT_STATE" > "$STATE_FILE"
|
||||||
|
log "State fingerprint updated."
|
||||||
|
|
||||||
|
prune "$DAILY_DIR" "$KEEP_DAILY"
|
||||||
|
log "Daily backup complete."
|
||||||
|
|
||||||
|
elif [[ "$MODE" == "weekly" ]]; then
|
||||||
|
log "--- Weekly backup (unconditional) ---"
|
||||||
|
DEST="$WEEKLY_DIR/experiments_db_weekly_${TIMESTAMP}.pgdump"
|
||||||
|
run_dump "$DEST"
|
||||||
|
prune "$WEEKLY_DIR" "$KEEP_WEEKLY"
|
||||||
|
log "Weekly backup complete."
|
||||||
|
|
||||||
|
else
|
||||||
|
die "Unknown mode '$MODE'. Use 'daily' or 'weekly'."
|
||||||
|
fi
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Installs (or updates) cron entries for the experiments DB backup.
|
||||||
|
# Safe to run multiple times — replaces existing ExpDB entries.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SCRIPT="$(cd "$(dirname "$0")" && pwd)/backup.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "$SCRIPT" ]]; then
|
||||||
|
chmod +x "$SCRIPT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any previous ExpDB backup lines, then append fresh ones
|
||||||
|
TMPFILE=$(mktemp)
|
||||||
|
crontab -l 2>/dev/null | grep -v "# ExpDB-backup" > "$TMPFILE" || true
|
||||||
|
|
||||||
|
cat >> "$TMPFILE" <<EOF
|
||||||
|
|
||||||
|
# ExpDB-backup — daily change-detect backup at 02:00
|
||||||
|
0 2 * * * $SCRIPT daily >> /home/sam/synology/Backups/Experiments-DB-Backup/backup.log 2>&1 # ExpDB-backup
|
||||||
|
# ExpDB-backup — weekly unconditional backup every Sunday at 03:00
|
||||||
|
0 3 * * 0 $SCRIPT weekly >> /home/sam/synology/Backups/Experiments-DB-Backup/backup.log 2>&1 # ExpDB-backup
|
||||||
|
EOF
|
||||||
|
|
||||||
|
crontab "$TMPFILE"
|
||||||
|
rm -f "$TMPFILE"
|
||||||
|
|
||||||
|
echo "Cron entries installed:"
|
||||||
|
crontab -l | grep "ExpDB-backup"
|
||||||
Reference in New Issue
Block a user