#!/usr/bin/env python3 """Check the larger ClinVar datasets before DNABERT-2 training. This script checks generated larger datasets when present: - training/csv_files_20k_alt/ and training/csv_files_20k/ - training/csv_files_10k_alt/ and training/csv_files_10k/ - training/csv_files_large_alt/ and training/csv_files_large/ """ from __future__ import annotations from pathlib import Path from urllib.parse import unquote import pandas as pd DATASETS = [ ( "20k alternate-sequence dataset", Path("training/csv_files_20k_alt"), { "train": "train_with_alt_sequences.csv", "val": "val_with_alt_sequences.csv", "test": "test_with_alt_sequences.csv", }, ), ( "20k reference-sequence dataset", Path("training/csv_files_20k"), { "train": "train_with_sequences.csv", "val": "val_with_sequences.csv", "test": "test_with_sequences.csv", }, ), ( "10k alternate-sequence dataset", Path("training/csv_files_10k_alt"), { "train": "train_with_alt_sequences.csv", "val": "val_with_alt_sequences.csv", "test": "test_with_alt_sequences.csv", }, ), ( "10k reference-sequence dataset", Path("training/csv_files_10k"), { "train": "train_with_sequences.csv", "val": "val_with_sequences.csv", "test": "test_with_sequences.csv", }, ), ( "large alternate-sequence dataset", Path("training/csv_files_large_alt"), { "train": "train_with_alt_sequences.csv", "val": "val_with_alt_sequences.csv", "test": "test_with_alt_sequences.csv", }, ), ( "large reference-sequence dataset", Path("training/csv_files_large"), { "train": "train_with_sequences.csv", "val": "val_with_sequences.csv", "test": "test_with_sequences.csv", }, ), ] REQUIRED_COLUMNS = {"sequence", "label"} EXAMPLE_COLUMNS = ["variant_id", "REF", "ALT", "label"] SUSPICIOUS_CLNSIG_TERMS = ( "conflicting", "uncertain", "not provided", "not_provided", "risk_factor", "risk factor", "association", "drug_response", "drug response", "protective", ) def project_root() -> Path: return Path(__file__).resolve().parents[1] def normalize_clnsig(value: object) -> str: decoded = unquote(str(value)) return ( decoded.replace("_", " ") .replace("-", " ") .replace("/", " ") .replace("|", " ") .replace(",", " ") .strip() .lower() ) def suspicious_clnsig_count(df: pd.DataFrame) -> int: if "CLNSIG" not in df.columns: return 0 normalized = df["CLNSIG"].fillna("").apply(normalize_clnsig) return int(normalized.apply(lambda value: any(term in value for term in SUSPICIOUS_CLNSIG_TERMS)).sum()) def sequence_lengths(df: pd.DataFrame) -> pd.Series: return df["sequence"].fillna("").astype(str).str.strip().str.len() def missing_sequence_count(df: pd.DataFrame) -> int: cleaned = df["sequence"].fillna("").astype(str).str.strip() return int((cleaned == "").sum()) def print_sequence_length_summary(lengths: pd.Series) -> None: non_empty = lengths[lengths > 0] if non_empty.empty: print("Sequence length min/mean/max: no non-empty sequences") return print( "Sequence length min/mean/max: " f"{int(non_empty.min())} / " f"{float(non_empty.mean()):.2f} / " f"{int(non_empty.max())}" ) def print_examples(df: pd.DataFrame, lengths: pd.Series) -> None: print("First 3 examples:") if df.empty: print(" no rows") return columns = [column for column in EXAMPLE_COLUMNS if column in df.columns] examples = df[columns].head(3).copy() examples["sequence_length"] = lengths.head(3).to_list() with pd.option_context("display.max_columns", None, "display.width", 160, "display.max_colwidth", 80): print(examples.to_string(index=False)) def check_split(split_name: str, csv_path: Path) -> tuple[pd.DataFrame | None, bool]: print("-" * 80) print(f"{split_name.upper()} SPLIT") print("-" * 80) print(f"File path: {csv_path}") if not csv_path.exists(): print("ERROR: file is missing.") print() return None, False df = pd.read_csv(csv_path) print(f"Rows: {len(df):,}") print(f"Columns: {list(df.columns)}") missing_columns = sorted(REQUIRED_COLUMNS - set(df.columns)) if missing_columns: print(f"ERROR: missing required columns: {missing_columns}") print() return df, False labels = pd.to_numeric(df["label"], errors="coerce") print("Label distribution:") print(labels.value_counts(dropna=False).sort_index().to_string()) missing_sequences = missing_sequence_count(df) lengths = sequence_lengths(df) print(f"Missing sequence count: {missing_sequences:,}") print_sequence_length_summary(lengths) if "CLNSIG" in df.columns: suspicious_count = suspicious_clnsig_count(df) print(f"CLNSIG suspicious rows: {suspicious_count:,}") else: print("CLNSIG suspicious rows: CLNSIG column not present") print_examples(df, lengths) print() valid_labels = labels.isin([0, 1]).all() has_sequences = missing_sequences == 0 and int((lengths > 0).sum()) == len(df) usable = len(df) > 0 and valid_labels and has_sequences return df, usable def check_dataset(dataset_name: str, dataset_dir: Path, split_files: dict[str, str]) -> tuple[int, bool]: print("=" * 80) print(dataset_name.upper()) print("=" * 80) print(f"Dataset directory: {dataset_dir}") if not dataset_dir.exists(): print("Dataset directory is missing, skipping.") print() return 0, True total_rows = 0 dataset_usable = True frames = [] for split_name, filename in split_files.items(): df, split_usable = check_split(split_name, dataset_dir / filename) dataset_usable = dataset_usable and split_usable if df is not None: total_rows += len(df) if "label" in df.columns: temp = df[["label"]].copy() temp["split"] = split_name frames.append(temp) print(f"Total rows across train/val/test: {total_rows:,}") if frames: combined = pd.concat(frames, ignore_index=True) combined["label"] = pd.to_numeric(combined["label"], errors="coerce") print("Combined label distribution:") print(combined["label"].value_counts(dropna=False).sort_index().to_string()) if dataset_usable: print("Usability check: OK, this dataset has rows, labels, and non-empty sequences.") else: print("Usability check: FAILED, fix the issues above before training.") print() return total_rows, dataset_usable def main() -> None: root = project_root() print("Large ClinVar dataset check") print(f"Project root: {root}") print() overall_usable = True datasets_found = 0 for dataset_name, relative_dir, split_files in DATASETS: dataset_dir = root / relative_dir total_rows, usable = check_dataset(dataset_name, dataset_dir, split_files) if dataset_dir.exists(): datasets_found += 1 overall_usable = overall_usable and usable and total_rows > 0 print("=" * 80) print("FINAL RESULT") print("=" * 80) if datasets_found == 0: print("No larger dataset folders were found yet. Run prepare_larger_clinvar_dataset.py first.") elif overall_usable: print("The larger dataset files found look usable for training.") else: print("At least one larger dataset found is not fully usable. Review the errors above.") if __name__ == "__main__": main()