Spaces:
Sleeping
Sleeping
File size: 4,880 Bytes
16d2e95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | """Validate a generated Stars pair against a known-good target MasterSheet.
Checkpoint-1 validation: the goal is *structural* correctness on the ITT
variables (raw timepoints + computed %Basal) and the derived metadata, not a
byte-exact reproduction of the full multi-file MasterSheet. So this compares
only the samples and variables present in BOTH files and reports:
* sample overlap (how many generated samples are found in the target),
* value agreement on shared (variable, sample) cells, within a tolerance,
* metadata decode agreement (Genotype / Sex / Diet).
Usage:
python scripts/validate_reshape.py <gen_data.csv> <gen_metadata.csv> \\
[--target-data data_MasterSheet.csv] [--target-meta metadata_MasterSheet.csv] \\
[--tol 0.5]
Exit code is 0 when no value/metadata mismatches are found among shared cells.
"""
import argparse
import sys
import pandas as pd
# ITT-relevant variable rows we care about for Checkpoint 1.
ITT_VAR_PREFIXES = ("T", "%Basal")
def _load_data(path: str) -> pd.DataFrame:
"""Load a Stars data matrix (index = Sample_ID / variable names, cols = samples)."""
return pd.read_csv(path, index_col=0)
def _data_variables(index) -> list[str]:
"""All data-variable rows (everything except the Subject_title identifier row)."""
return [str(v) for v in index if str(v) != "Subject_title"]
def validate(gen_data: str, gen_meta: str, tgt_data: str, tgt_meta: str, tol: float) -> int:
gd = _load_data(gen_data)
td = _load_data(tgt_data)
gm = pd.read_csv(gen_meta)
tm = pd.read_csv(tgt_meta)
gen_samples = [c for c in gd.columns]
shared_samples = [s for s in gen_samples if s in set(td.columns)]
print(f"Generated samples : {len(gen_samples)}")
print(f"Target samples : {len(td.columns)}")
print(f"Shared samples : {len(shared_samples)}")
missing = [s for s in gen_samples if s not in set(td.columns)]
if missing:
print(f" generated samples NOT in target ({len(missing)}): {missing[:8]}"
f"{' …' if len(missing) > 8 else ''}")
variables = [v for v in _data_variables(gd.index) if v in set(td.index)]
print(f"Shared data variables: {len(variables)} {variables}")
# --- Value agreement on shared cells ---
cells = 0
mismatches = []
for var in variables:
for s in shared_samples:
g = pd.to_numeric(gd.loc[var, s], errors="coerce")
t = pd.to_numeric(td.loc[var, s], errors="coerce")
if pd.isna(g) and pd.isna(t):
continue
cells += 1
if pd.isna(g) != pd.isna(t) or (pd.notna(g) and abs(g - t) > tol):
mismatches.append((var, s, g, t))
print(f"\nValue cells compared: {cells}")
print(f"Value mismatches : {len(mismatches)} (tol={tol})")
for var, s, g, t in mismatches[:10]:
print(f" {var:>12} / {s:<16} gen={g} target={t}")
# --- Metadata decode agreement ---
# Drop duplicate Sample_IDs in the target (the source has genuine collisions,
# e.g. KO-M-C-11 appears twice) so the lookup returns a scalar, not a Series.
tm_idx = tm.drop_duplicates(subset="Sample_ID").set_index("Sample_ID")
meta_cols = [c for c in ("Genotype", "Sex", "Diet") if c in gm.columns and c in tm.columns]
meta_checked = 0
meta_mismatch = []
for _, row in gm.iterrows():
sid = row["Sample_ID"]
if sid not in tm_idx.index:
continue
for c in meta_cols:
meta_checked += 1
if str(row[c]).strip() != str(tm_idx.loc[sid, c]).strip():
meta_mismatch.append((sid, c, row[c], tm_idx.loc[sid, c]))
print(f"\nMetadata cells compared: {meta_checked}")
print(f"Metadata mismatches : {len(meta_mismatch)}")
for sid, c, g, t in meta_mismatch[:10]:
print(f" {sid:<16} {c}: gen={g} target={t}")
ok = not mismatches and not meta_mismatch and len(shared_samples) > 0
print(f"\n{'PASS' if ok else 'FAIL'}: "
f"{len(shared_samples)} shared samples, "
f"{cells - len(mismatches)}/{cells} value cells match, "
f"{meta_checked - len(meta_mismatch)}/{meta_checked} metadata cells match")
return 0 if ok else 1
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("gen_data", help="generated data_*.csv")
ap.add_argument("gen_meta", help="generated metadata_*.csv")
ap.add_argument("--target-data", required=True, help="target data_MasterSheet.csv")
ap.add_argument("--target-meta", required=True, help="target metadata_MasterSheet.csv")
ap.add_argument("--tol", type=float, default=0.5, help="numeric tolerance (default 0.5)")
args = ap.parse_args()
return validate(args.gen_data, args.gen_meta, args.target_data, args.target_meta, args.tol)
if __name__ == "__main__":
sys.exit(main())
|