From 1d4168d45d2d67558f669e34468cdcff82ea80d3 Mon Sep 17 00:00:00 2001 From: Nicholas Karlson Date: Sun, 9 Nov 2025 10:36:18 -0800 Subject: [PATCH] feat(ch13): add 2x2 mixed fitness simulator (subjects/long/meta) --- data/synthetic/fitness_long.csv | 161 ++++++++++++++++++++++++++++ data/synthetic/fitness_subjects.csv | 81 ++++++++++++++ scripts/sim_fitness_2x2.py | 38 +++++++ 3 files changed, 280 insertions(+) create mode 100644 data/synthetic/fitness_long.csv create mode 100644 data/synthetic/fitness_subjects.csv create mode 100644 scripts/sim_fitness_2x2.py diff --git a/data/synthetic/fitness_long.csv b/data/synthetic/fitness_long.csv new file mode 100644 index 0000000..75d2192 --- /dev/null +++ b/data/synthetic/fitness_long.csv @@ -0,0 +1,161 @@ +id,time,strength,group,age,sex,bmi +1,pre,94.01841222464901,ProgramB,32,F,29.789201420801504 +2,pre,60.62113282009692,ProgramB,25,M,21.65068374311899 +3,pre,83.82305569402939,ProgramB,44,M,20.310671743317776 +4,pre,96.06920017742435,ProgramB,41,F,25.180642164012607 +5,pre,80.13060153302078,ProgramB,23,M,28.925452698447863 +6,pre,75.49059233099217,ProgramB,31,M,18.910914941315774 +7,pre,92.92312591014843,ProgramB,43,M,23.27017353112055 +8,pre,91.46515747649224,ProgramA,40,F,21.787816061227822 +9,pre,73.35955135318243,ProgramA,37,F,17.83643183985409 +10,pre,66.44748779357568,ProgramA,35,F,26.57224348240437 +11,pre,61.560828108998095,ProgramA,19,F,23.917946285058044 +12,pre,73.19056904683283,ProgramB,38,M,24.25004645502412 +13,pre,53.2899191203695,ProgramB,30,F,21.36690984644044 +14,pre,66.78522405095757,ProgramA,20,M,25.591744570489208 +15,pre,79.43212260358607,ProgramA,24,F,22.112459276794375 +16,pre,80.31237581393506,ProgramB,32,M,23.499838770261338 +17,pre,75.738310647227,ProgramA,37,F,20.121087227364704 +18,pre,59.708061208111246,ProgramB,31,F,19.743640339271316 +19,pre,99.43401994130781,ProgramA,34,F,28.67436149355008 +20,pre,84.70390754578035,ProgramA,41,F,22.225133534115336 +21,pre,89.53015286881464,ProgramB,36,M,25.020881247245306 +22,pre,73.79661404064231,ProgramA,27,M,23.881733478314196 +23,pre,81.59814475919407,ProgramA,35,F,22.455991790332355 +24,pre,82.99097179821537,ProgramA,34,M,22.22213660381973 +25,pre,76.13511171142578,ProgramB,20,M,26.20528907005955 +26,pre,75.81531115029189,ProgramA,19,F,22.943463384131316 +27,pre,77.48272587463066,ProgramB,35,M,23.46994723140048 +28,pre,81.3771962030426,ProgramA,28,M,24.077775450180678 +29,pre,80.0568412105057,ProgramA,34,F,28.117779120882005 +30,pre,82.72749009112756,ProgramB,26,M,26.381788411215982 +31,pre,65.4575444149616,ProgramB,24,M,25.339100937047974 +32,pre,70.03932257995278,ProgramB,22,M,22.027500123263653 +33,pre,72.28470201213922,ProgramB,28,F,19.163109479977372 +34,pre,81.10948552420666,ProgramB,40,M,27.323354761073556 +35,pre,92.86942223289873,ProgramB,28,F,27.382564969773068 +36,pre,89.29345720566154,ProgramB,28,M,23.507520603068308 +37,pre,80.8512219032568,ProgramB,33,M,25.896593445612314 +38,pre,104.52751626424443,ProgramB,44,F,26.735050688598918 +39,pre,90.5205836216117,ProgramA,28,F,26.909145584327437 +40,pre,64.95036780011974,ProgramB,33,M,27.224842156362648 +41,pre,76.9906721439163,ProgramA,29,F,22.405338222686034 +42,pre,88.87718749923995,ProgramA,34,M,29.302405439326513 +43,pre,73.60718415723296,ProgramB,30,M,19.63692429600002 +44,pre,76.77693611751715,ProgramA,35,M,27.016030769477773 +45,pre,81.34378790639951,ProgramB,32,M,25.728762276607654 +46,pre,83.91321687628758,ProgramB,36,F,27.057666897335757 +47,pre,109.6149088809699,ProgramA,43,F,30.576529074838582 +48,pre,82.19834082376622,ProgramA,22,F,29.195559156825272 +49,pre,78.29584166876339,ProgramA,33,F,19.991880824837363 +50,pre,83.79051923825051,ProgramA,29,F,18.0896494690625 +51,pre,74.40718726815953,ProgramA,27,M,26.85911170668829 +52,pre,64.9776267350715,ProgramB,24,M,20.447456544434267 +53,pre,55.902029263994336,ProgramB,19,F,23.956581139733704 +54,pre,92.83342116140729,ProgramA,28,F,26.93904616951818 +55,pre,89.5124140702047,ProgramB,40,M,18.24670927085045 +56,pre,70.92148924650074,ProgramB,20,F,17.0 +57,pre,80.75332321322367,ProgramB,28,M,24.907546571180294 +58,pre,92.46897333597012,ProgramB,44,M,24.15535061364654 +59,pre,87.0629573622323,ProgramA,43,M,23.139689250163272 +60,pre,70.69636040913862,ProgramB,23,M,24.134871673195764 +61,pre,64.27397021562032,ProgramA,18,M,20.98819537421452 +62,pre,72.65193254943277,ProgramA,36,M,18.702769574547627 +63,pre,82.06920806239282,ProgramA,18,F,23.416708021918875 +64,pre,77.61727676120017,ProgramA,26,M,20.599019581327127 +65,pre,72.95562485952844,ProgramB,31,F,18.247815371561124 +66,pre,85.01577227888981,ProgramA,41,M,25.769883749876467 +67,pre,65.54132688100917,ProgramA,20,F,23.785104800522383 +68,pre,95.59944153740457,ProgramA,35,F,25.422849878214848 +69,pre,80.68935857064146,ProgramB,32,M,20.53746770500922 +70,pre,68.91872213022668,ProgramB,21,M,21.696794228571697 +71,pre,81.30962072651573,ProgramB,40,F,20.50334940472942 +72,pre,71.79126859611742,ProgramA,40,M,20.896753465296833 +73,pre,81.79259052182175,ProgramB,31,M,24.68392771212291 +74,pre,94.53042756758786,ProgramA,43,M,21.25958884282769 +75,pre,81.13049194180905,ProgramA,34,M,25.246232010870962 +76,pre,87.89141975349315,ProgramA,42,M,25.189145738963756 +77,pre,98.79442101079493,ProgramB,43,F,31.08806345408036 +78,pre,72.43323583096316,ProgramB,33,F,19.125238339466165 +79,pre,68.36646297137767,ProgramB,25,F,27.107658322422658 +80,pre,71.27007614291902,ProgramA,21,F,23.686792133390536 +1,post,94.35240276145338,ProgramB,32,F,29.789201420801504 +2,post,71.59532739852435,ProgramB,25,M,21.65068374311899 +3,post,93.83166174426583,ProgramB,44,M,20.310671743317776 +4,post,108.95234767612473,ProgramB,41,F,25.180642164012607 +5,post,92.05359345404365,ProgramB,23,M,28.925452698447863 +6,post,92.75016755590978,ProgramB,31,M,18.910914941315774 +7,post,100.66019284418684,ProgramB,43,M,23.27017353112055 +8,post,98.80470555050915,ProgramA,40,F,21.787816061227822 +9,post,87.63748290569046,ProgramA,37,F,17.83643183985409 +10,post,77.01731438564923,ProgramA,35,F,26.57224348240437 +11,post,78.43764728025583,ProgramA,19,F,23.917946285058044 +12,post,76.05507207966791,ProgramB,38,M,24.25004645502412 +13,post,63.54659789639327,ProgramB,30,F,21.36690984644044 +14,post,79.68530034483395,ProgramA,20,M,25.591744570489208 +15,post,86.98193792853941,ProgramA,24,F,22.112459276794375 +16,post,84.24440330485712,ProgramB,32,M,23.499838770261338 +17,post,75.7086563240889,ProgramA,37,F,20.121087227364704 +18,post,71.1844257732623,ProgramB,31,F,19.743640339271316 +19,post,107.08348698105014,ProgramA,34,F,28.67436149355008 +20,post,90.40448431342597,ProgramA,41,F,22.225133534115336 +21,post,108.57695773116724,ProgramB,36,M,25.020881247245306 +22,post,90.01528181586862,ProgramA,27,M,23.881733478314196 +23,post,92.30467283395872,ProgramA,35,F,22.455991790332355 +24,post,102.96727860279405,ProgramA,34,M,22.22213660381973 +25,post,94.31750595582037,ProgramB,20,M,26.20528907005955 +26,post,78.56081980540857,ProgramA,19,F,22.943463384131316 +27,post,81.28945148848096,ProgramB,35,M,23.46994723140048 +28,post,81.3866644980942,ProgramA,28,M,24.077775450180678 +29,post,88.75081241995906,ProgramA,34,F,28.117779120882005 +30,post,97.5091828435643,ProgramB,26,M,26.381788411215982 +31,post,77.8193177503908,ProgramB,24,M,25.339100937047974 +32,post,80.6264843244119,ProgramB,22,M,22.027500123263653 +33,post,87.32319270656444,ProgramB,28,F,19.163109479977372 +34,post,88.92918140526325,ProgramB,40,M,27.323354761073556 +35,post,104.75746995081097,ProgramB,28,F,27.382564969773068 +36,post,102.46765701718357,ProgramB,28,M,23.507520603068308 +37,post,90.61212080610967,ProgramB,33,M,25.896593445612314 +38,post,110.71056118745985,ProgramB,44,F,26.735050688598918 +39,post,92.91635013213602,ProgramA,28,F,26.909145584327437 +40,post,79.9646241163009,ProgramB,33,M,27.224842156362648 +41,post,89.20253628709528,ProgramA,29,F,22.405338222686034 +42,post,93.72846948524082,ProgramA,34,M,29.302405439326513 +43,post,79.91184520916171,ProgramB,30,M,19.63692429600002 +44,post,96.22975600075945,ProgramA,35,M,27.016030769477773 +45,post,96.76806797456139,ProgramB,32,M,25.728762276607654 +46,post,97.89568029263289,ProgramB,36,F,27.057666897335757 +47,post,103.95906104253295,ProgramA,43,F,30.576529074838582 +48,post,90.96657374120296,ProgramA,22,F,29.195559156825272 +49,post,75.95953175992013,ProgramA,33,F,19.991880824837363 +50,post,84.11056665732802,ProgramA,29,F,18.0896494690625 +51,post,81.24246260040444,ProgramA,27,M,26.85911170668829 +52,post,81.16758513821792,ProgramB,24,M,20.447456544434267 +53,post,70.68583984916731,ProgramB,19,F,23.956581139733704 +54,post,96.78265091467308,ProgramA,28,F,26.93904616951818 +55,post,98.81870421271056,ProgramB,40,M,18.24670927085045 +56,post,78.54689141993701,ProgramB,20,F,17.0 +57,post,98.70236672495025,ProgramB,28,M,24.907546571180294 +58,post,96.19417652778705,ProgramB,44,M,24.15535061364654 +59,post,99.58565092505327,ProgramA,43,M,23.139689250163272 +60,post,82.55031769790006,ProgramB,23,M,24.134871673195764 +61,post,70.5212107977635,ProgramA,18,M,20.98819537421452 +62,post,91.7555658811237,ProgramA,36,M,18.702769574547627 +63,post,92.49264998204795,ProgramA,18,F,23.416708021918875 +64,post,81.49593857818603,ProgramA,26,M,20.599019581327127 +65,post,79.1101873738143,ProgramB,31,F,18.247815371561124 +66,post,92.04590605504494,ProgramA,41,M,25.769883749876467 +67,post,77.49258596402377,ProgramA,20,F,23.785104800522383 +68,post,110.65909820156081,ProgramA,35,F,25.422849878214848 +69,post,91.86503434750978,ProgramB,32,M,20.53746770500922 +70,post,85.47579362124341,ProgramB,21,M,21.696794228571697 +71,post,88.0387760458903,ProgramB,40,F,20.50334940472942 +72,post,82.73019677588498,ProgramA,40,M,20.896753465296833 +73,post,90.4517915959734,ProgramB,31,M,24.68392771212291 +74,post,104.77379934061076,ProgramA,43,M,21.25958884282769 +75,post,84.26537943544119,ProgramA,34,M,25.246232010870962 +76,post,95.84165654858498,ProgramA,42,M,25.189145738963756 +77,post,107.96855007222123,ProgramB,43,F,31.08806345408036 +78,post,84.98339137660558,ProgramB,33,F,19.125238339466165 +79,post,73.54205343002458,ProgramB,25,F,27.107658322422658 +80,post,84.90732247045304,ProgramA,21,F,23.686792133390536 diff --git a/data/synthetic/fitness_subjects.csv b/data/synthetic/fitness_subjects.csv new file mode 100644 index 0000000..ba8190d --- /dev/null +++ b/data/synthetic/fitness_subjects.csv @@ -0,0 +1,81 @@ +id,group,age,sex,bmi +1,ProgramB,32,F,29.789201420801504 +2,ProgramB,25,M,21.65068374311899 +3,ProgramB,44,M,20.310671743317776 +4,ProgramB,41,F,25.180642164012607 +5,ProgramB,23,M,28.925452698447863 +6,ProgramB,31,M,18.910914941315774 +7,ProgramB,43,M,23.27017353112055 +8,ProgramA,40,F,21.787816061227822 +9,ProgramA,37,F,17.83643183985409 +10,ProgramA,35,F,26.57224348240437 +11,ProgramA,19,F,23.917946285058044 +12,ProgramB,38,M,24.25004645502412 +13,ProgramB,30,F,21.36690984644044 +14,ProgramA,20,M,25.591744570489208 +15,ProgramA,24,F,22.112459276794375 +16,ProgramB,32,M,23.499838770261338 +17,ProgramA,37,F,20.121087227364704 +18,ProgramB,31,F,19.743640339271316 +19,ProgramA,34,F,28.67436149355008 +20,ProgramA,41,F,22.225133534115336 +21,ProgramB,36,M,25.020881247245306 +22,ProgramA,27,M,23.881733478314196 +23,ProgramA,35,F,22.455991790332355 +24,ProgramA,34,M,22.22213660381973 +25,ProgramB,20,M,26.20528907005955 +26,ProgramA,19,F,22.943463384131316 +27,ProgramB,35,M,23.46994723140048 +28,ProgramA,28,M,24.077775450180678 +29,ProgramA,34,F,28.117779120882005 +30,ProgramB,26,M,26.381788411215982 +31,ProgramB,24,M,25.339100937047974 +32,ProgramB,22,M,22.027500123263653 +33,ProgramB,28,F,19.163109479977372 +34,ProgramB,40,M,27.323354761073556 +35,ProgramB,28,F,27.382564969773068 +36,ProgramB,28,M,23.507520603068308 +37,ProgramB,33,M,25.896593445612314 +38,ProgramB,44,F,26.735050688598918 +39,ProgramA,28,F,26.909145584327437 +40,ProgramB,33,M,27.224842156362648 +41,ProgramA,29,F,22.405338222686034 +42,ProgramA,34,M,29.302405439326513 +43,ProgramB,30,M,19.63692429600002 +44,ProgramA,35,M,27.016030769477773 +45,ProgramB,32,M,25.728762276607654 +46,ProgramB,36,F,27.057666897335757 +47,ProgramA,43,F,30.576529074838582 +48,ProgramA,22,F,29.195559156825272 +49,ProgramA,33,F,19.991880824837363 +50,ProgramA,29,F,18.0896494690625 +51,ProgramA,27,M,26.85911170668829 +52,ProgramB,24,M,20.447456544434267 +53,ProgramB,19,F,23.956581139733704 +54,ProgramA,28,F,26.93904616951818 +55,ProgramB,40,M,18.24670927085045 +56,ProgramB,20,F,17.0 +57,ProgramB,28,M,24.907546571180294 +58,ProgramB,44,M,24.15535061364654 +59,ProgramA,43,M,23.139689250163272 +60,ProgramB,23,M,24.134871673195764 +61,ProgramA,18,M,20.98819537421452 +62,ProgramA,36,M,18.702769574547627 +63,ProgramA,18,F,23.416708021918875 +64,ProgramA,26,M,20.599019581327127 +65,ProgramB,31,F,18.247815371561124 +66,ProgramA,41,M,25.769883749876467 +67,ProgramA,20,F,23.785104800522383 +68,ProgramA,35,F,25.422849878214848 +69,ProgramB,32,M,20.53746770500922 +70,ProgramB,21,M,21.696794228571697 +71,ProgramB,40,F,20.50334940472942 +72,ProgramA,40,M,20.896753465296833 +73,ProgramB,31,M,24.68392771212291 +74,ProgramA,43,M,21.25958884282769 +75,ProgramA,34,M,25.246232010870962 +76,ProgramA,42,M,25.189145738963756 +77,ProgramB,43,F,31.08806345408036 +78,ProgramB,33,F,19.125238339466165 +79,ProgramB,25,F,27.107658322422658 +80,ProgramA,21,F,23.686792133390536 diff --git a/scripts/sim_fitness_2x2.py b/scripts/sim_fitness_2x2.py new file mode 100644 index 0000000..4e0d00f --- /dev/null +++ b/scripts/sim_fitness_2x2.py @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: MIT +import os, json, time +import numpy as np, pandas as pd +rng = np.random.default_rng(7) + +N = 80 +GROUPS = ["ProgramA","ProgramB"] +def simulate(): + os.makedirs("data/synthetic", exist_ok=True) + subjects = pd.DataFrame({ + "id": np.arange(1, N+1), + "group": rng.choice(GROUPS, size=N, replace=True), + "age": rng.integers(18, 45, size=N), + "sex": rng.choice(["F","M"], size=N, p=[0.5,0.5]), + "bmi": rng.normal(24, 3.5, size=N).clip(17, 38), + }) + # baseline strength varies by covariates a bit + base = 80 + 0.8*(subjects["age"]-30) + 1.2*(subjects["bmi"]-24) + rng.normal(0, 8, N) + # program effects (post - pre), slightly larger for ProgramB + gain_A = rng.normal(8, 4, N) + gain_B = rng.normal(12, 4, N) + gain = np.where(subjects["group"]=="ProgramA", gain_A, gain_B) + + long = pd.concat([ + pd.DataFrame({"id": subjects["id"], "time":"pre", "strength": base + rng.normal(0, 3, N)}), + pd.DataFrame({"id": subjects["id"], "time":"post", "strength": base + gain + rng.normal(0, 3, N)}) + ], ignore_index=True).merge(subjects, on="id") + + subjects.to_csv("data/synthetic/fitness_subjects.csv", index=False) + long.to_csv("data/synthetic/fitness_long.csv", index=False) + meta = dict( + seed=7, n= int(N), design="2x2 mixed (Group between × Time within)", + programs=GROUPS, dv="strength_1RM_like", + generated_utc=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + ) + with open("data/synthetic/fitness_meta.json","w") as f: json.dump(meta, f, indent=2) + print("Wrote fitness_subjects.csv, fitness_long.csv, fitness_meta.json") +if __name__ == "__main__": simulate()