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())