{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"3147f71c","cell_type":"markdown","source":"# CAFA-6: clean baseline notebook\n\nDit notebook houdt **paden + experimenten** in het notebook, en zet **stabiele helpers** in `cafa_utils.py` en `cafa_pipeline.py`.\n\n- `cafa_utils.py`: inlezen + label matrix (stabiel)\n- `cafa_pipeline.py`: feature extractors + split helper (mag je aanpassen)\n- Model/training/submission: **hier in het notebook** (experimenteel)\n","metadata":{}},{"id":"21007f1f","cell_type":"code","source":"%%writefile cafa_utils.py\nimport numpy as np\nimport pandas as pd\nfrom collections import Counter\n\ndef read_fasta(path: str) -> pd.DataFrame:\n    \"\"\"Read a FASTA file into a DataFrame with columns: protein_id, sequence.\"\"\"\n    records = []\n    with open(path, \"r\") as f:\n        pid = None\n        seq_parts = []\n        for line in f:\n            line = line.strip()\n            if not line:\n                continue\n            if line.startswith(\">\"):\n                if pid is not None:\n                    records.append((pid, \"\".join(seq_parts)))\n                header = line[1:]\n                pid = header.split(\"|\")[1] if \"|\" in header else header.split()[0]\n                seq_parts = []\n            else:\n                seq_parts.append(line)\n        if pid is not None:\n            records.append((pid, \"\".join(seq_parts)))\n    return pd.DataFrame(records, columns=[\"protein_id\", \"sequence\"])\n\ndef build_train_df(train_fasta: str, train_terms_tsv: str) -> pd.DataFrame:\n    \"\"\"Load train sequences + terms and return a merged DataFrame.\"\"\"\n    train_seq = read_fasta(train_fasta)\n    train_terms_df = pd.read_csv(train_terms_tsv, sep=\"\\t\").rename(columns={\"EntryID\": \"protein_id\"})\n\n    terms_per_protein = (\n        train_terms_df.groupby(\"protein_id\")[\"term\"].apply(list).reset_index()\n    ).rename(columns={\"term\": \"terms\"})\n\n    train_df = train_seq.merge(terms_per_protein, on=\"protein_id\", how=\"left\")\n    train_df[\"terms\"] = train_df[\"terms\"].apply(lambda x: x if isinstance(x, list) else [])\n    train_df[\"n_terms\"] = train_df[\"terms\"].apply(len)\n    return train_df\n\ndef build_label_matrix(train_df: pd.DataFrame, top_k: int = 1000):\n    \"\"\"Build a multi-hot label matrix for the top_k most frequent terms.\"\"\"\n    all_terms = []\n    for ts in train_df[\"terms\"]:\n        all_terms.extend(ts)\n\n    term_counts = Counter(all_terms)\n    top_terms = [t for t, _ in term_counts.most_common(top_k)]\n    term_to_idx = {t: i for i, t in enumerate(top_terms)}\n\n    Y = np.zeros((len(train_df), top_k), dtype=np.int8)\n    for i, ts in enumerate(train_df[\"terms\"]):\n        for t in ts:\n            j = term_to_idx.get(t)\n            if j is not None:\n                Y[i, j] = 1\n\n    return Y, top_terms, term_to_idx\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:54:56.233095Z","iopub.execute_input":"2026-01-28T15:54:56.233414Z","iopub.status.idle":"2026-01-28T15:54:56.240401Z","shell.execute_reply.started":"2026-01-28T15:54:56.233389Z","shell.execute_reply":"2026-01-28T15:54:56.239314Z"}},"outputs":[],"execution_count":null},{"id":"e3c41242","cell_type":"code","source":"%%writefile cafa_pipeline.py\nimport numpy as np\nimport pandas as pd\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.model_selection import train_test_split\n\ndef featurize_aa_composition(seqs: pd.Series) -> np.ndarray:\n    \"\"\"Baseline feature: amino-acid composition (20 dims).\"\"\"\n    aas = \"ACDEFGHIKLMNPQRSTVWY\"\n    aa_to_i = {a: i for i, a in enumerate(aas)}\n    X = np.zeros((len(seqs), len(aas)), dtype=np.float32)\n\n    for r, s in enumerate(seqs):\n        if not isinstance(s, str) or len(s) == 0:\n            continue\n        for ch in s:\n            j = aa_to_i.get(ch)\n            if j is not None:\n                X[r, j] += 1.0\n        X[r] /= max(len(s), 1)\n    return X\n\ndef featurize_kmer_tfidf(\n    train_seqs: pd.Series,\n    test_seqs: pd.Series,\n    ngram_range=(2, 4),\n    max_features=100_000,\n):\n    \"\"\"TF-IDF on character n-grams (k-mers) for protein sequences.\"\"\"\n    vectorizer = TfidfVectorizer(\n        analyzer=\"char\",\n        ngram_range=ngram_range,\n        lowercase=False,\n        max_features=max_features,\n    )\n    X_train = vectorizer.fit_transform(train_seqs)\n    X_test = vectorizer.transform(test_seqs)\n    return X_train, X_test, vectorizer\n\ndef make_train_valid_indices(n_samples: int, test_size: float = 0.2, random_state: int = 42):\n    \"\"\"Simple random split indices (multi-label stratification is non-trivial).\"\"\"\n    idx = np.arange(n_samples)\n    tr_idx, va_idx = train_test_split(idx, test_size=test_size, random_state=random_state, shuffle=True)\n    return tr_idx, va_idx\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:54:56.241758Z","iopub.execute_input":"2026-01-28T15:54:56.242063Z","iopub.status.idle":"2026-01-28T15:54:56.262789Z","shell.execute_reply.started":"2026-01-28T15:54:56.242041Z","shell.execute_reply":"2026-01-28T15:54:56.261753Z"}},"outputs":[],"execution_count":null},{"id":"9ba3e138","cell_type":"code","source":"# ============================================================\n# 0) Imports\n# ============================================================\nimport os\nimport numpy as np\nimport pandas as pd\n\nfrom cafa_utils import build_train_df, build_label_matrix, read_fasta\nfrom cafa_pipeline import (\n    featurize_aa_composition,\n    featurize_kmer_tfidf,\n    make_train_valid_indices,\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:54:56.263873Z","iopub.execute_input":"2026-01-28T15:54:56.264134Z","iopub.status.idle":"2026-01-28T15:54:57.239531Z","shell.execute_reply.started":"2026-01-28T15:54:56.264106Z","shell.execute_reply":"2026-01-28T15:54:57.238834Z"}},"outputs":[],"execution_count":null},{"id":"39fd8d06","cell_type":"code","source":"# ============================================================\n# 1) Paths (environment-specific -> hoort in notebook)\n# ============================================================\nDATA_DIR = \"/kaggle/input/cafa-6-protein-function-prediction\"\nTRAIN_DIR = os.path.join(DATA_DIR, \"Train\")\nTEST_DIR  = os.path.join(DATA_DIR, \"Test\")\n\ntrain_fasta = os.path.join(TRAIN_DIR, \"train_sequences.fasta\")\ntrain_terms = os.path.join(TRAIN_DIR, \"train_terms.tsv\")\ntest_fasta  = os.path.join(TEST_DIR,  \"testsuperset.fasta\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:54:57.241022Z","iopub.execute_input":"2026-01-28T15:54:57.241348Z","iopub.status.idle":"2026-01-28T15:54:57.246884Z","shell.execute_reply.started":"2026-01-28T15:54:57.241328Z","shell.execute_reply":"2026-01-28T15:54:57.246030Z"}},"outputs":[],"execution_count":null},{"id":"289d3d79","cell_type":"code","source":"# ============================================================\n# 2) Load data\n# ============================================================\ntrain_df = build_train_df(train_fasta, train_terms)\ntest_seq = read_fasta(test_fasta)\n\nprint(\"train_df:\", train_df.shape, \"| avg terms/protein:\", train_df[\"n_terms\"].mean())\nprint(\"test_seq :\", test_seq.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:54:57.248124Z","iopub.execute_input":"2026-01-28T15:54:57.248462Z","iopub.status.idle":"2026-01-28T15:55:02.965566Z","shell.execute_reply.started":"2026-01-28T15:54:57.248438Z","shell.execute_reply":"2026-01-28T15:55:02.964524Z"}},"outputs":[],"execution_count":null},{"id":"2cb159b9","cell_type":"code","source":"# ============================================================\n# 3) Labels (Top-K)\n# ============================================================\nTOP_K = 1000\nY, top_terms, term_to_idx = build_label_matrix(train_df, top_k=TOP_K)\n\nprint(\"Y:\", Y.shape, \"sum=\", int(Y.sum()))\nprint(\"Y density:\", float(Y.mean()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:55:02.966454Z","iopub.execute_input":"2026-01-28T15:55:02.966681Z","iopub.status.idle":"2026-01-28T15:55:03.260012Z","shell.execute_reply.started":"2026-01-28T15:55:02.966657Z","shell.execute_reply":"2026-01-28T15:55:03.259239Z"}},"outputs":[],"execution_count":null},{"id":"2c60c762","cell_type":"code","source":"# ============================================================\n# 4) Features (experimenteel)\n#    Kies 1: 'aa' (snel baseline) of 'kmer' (jouw ~0.144 setup)\n# ============================================================\nFEATURES = \"kmer\"  # \"aa\" of \"kmer\"\n\nif FEATURES == \"aa\":\n    X_train = featurize_aa_composition(train_df[\"sequence\"])\n    X_test  = featurize_aa_composition(test_seq[\"sequence\"])\n    vectorizer = None\nelse:\n    NGRAM_RANGE = (2, 4)\n    MAX_FEATURES = 100_000\n    X_train, X_test, vectorizer = featurize_kmer_tfidf(\n        train_df[\"sequence\"],\n        test_seq[\"sequence\"],\n        ngram_range=NGRAM_RANGE,\n        max_features=MAX_FEATURES\n    )\n\nprint(\"X_train:\", X_train.shape)\nprint(\"X_test :\", X_test.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:55:03.260971Z","iopub.execute_input":"2026-01-28T15:55:03.261269Z","iopub.status.idle":"2026-01-28T15:58:41.990088Z","shell.execute_reply.started":"2026-01-28T15:55:03.261245Z","shell.execute_reply":"2026-01-28T15:58:41.989497Z"}},"outputs":[],"execution_count":null},{"id":"a9282a01","cell_type":"code","source":"# ============================================================\n# 5) Train/valid split (experimenteel)\n# ============================================================\ntr_idx, va_idx = make_train_valid_indices(len(train_df), test_size=0.2, random_state=42)\n\nprint(\"train n:\", len(tr_idx), \"valid n:\", len(va_idx))\nprint(\"avg labels/protein (train):\", float(Y[tr_idx].sum(1).mean()))\nprint(\"avg labels/protein (valid):\", float(Y[va_idx].sum(1).mean()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-28T15:58:41.990921Z","iopub.execute_input":"2026-01-28T15:58:41.991122Z","iopub.status.idle":"2026-01-28T15:58:42.055018Z","shell.execute_reply.started":"2026-01-28T15:58:41.991103Z","shell.execute_reply":"2026-01-28T15:58:42.054302Z"}},"outputs":[],"execution_count":null},{"id":"e78fd420","cell_type":"code","source":"# ============================================================\n# 6) Model (experimenteel) - hier blijf je tweaken\n# ============================================================\nfrom sklearn.linear_model import SGDClassifier\nfrom sklearn.multiclass import OneVsRestClassifier\n\nmodel = OneVsRestClassifier(\n    SGDClassifier(\n        loss=\"log_loss\",\n        alpha=1e-5,\n        max_iter=200,\n        tol=1e-3,\n        n_jobs=-1,\n        early_stopping=True,\n        validation_fraction=0.1,\n        n_iter_no_change=5,\n        class_weight=\"balanced\",\n    )\n)\n\nmodel.fit(X_train[tr_idx], Y[tr_idx])\nva_proba = model.predict_proba(X_train[va_idx])\n\nprint(\"valid proba:\", va_proba.shape)\nprint(\"n classifiers:\", len(model.estimators_))\nprint(\"coef shape of first:\", model.estimators_[0].coef_.shape)\nprint(\"mean abs coef:\", float(np.mean([np.mean(np.abs(est.coef_)) for est in model.estimators_ if hasattr(est, \"coef_\")])))\n","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-28T19:07:40.357Z"}},"outputs":[],"execution_count":null},{"id":"a6dc97ce","cell_type":"markdown","source":"## Submission\n\nIk laat de submission bewust in het notebook (want het format/thresholding/post-processing verandert vaak).\n\nPlak hier jouw huidige submission-blok (of je bestaande code) en we kunnen het daarna eventueel als helper-functie uitbesteden zodra het stabiel is.","metadata":{}},{"id":"d4adc01a","cell_type":"code","source":"\n# ============================================================\n# 9) Submission helper (EXPERIMENTEEL)\n# ============================================================\n\ndef make_submission(\n    P_test,\n    test_ids,\n    top_terms,\n    thresh=0.05,\n    top_n=100,\n    out_path=\"submission.tsv\"\n):\n    \"\"\"\n    Build CAFA6 submission file.\n    Parameters are intentionally kept flexible for experimentation.\n    \"\"\"\n    import numpy as np\n    import pandas as pd\n\n    rows = []\n    for i, pid in enumerate(test_ids):\n        probs = P_test[i]\n        order = np.argsort(-probs)\n\n        n_added = 0\n        for j in order:\n            s = probs[j]\n            if s < thresh:\n                break\n            rows.append((pid, top_terms[j], float(s)))\n            n_added += 1\n            if n_added >= top_n:\n                break\n\n    sub = pd.DataFrame(rows, columns=[\"protein_id\", \"term\", \"score\"])\n    sub.to_csv(out_path, sep=\"\\t\", index=False, header=False)\n    print(f\"Saved {out_path} | rows={len(sub)} proteins={sub['protein_id'].nunique()}\")\n    return sub\n","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-28T19:07:40.356Z"}},"outputs":[],"execution_count":null},{"id":"4f0538f1","cell_type":"code","source":"# ============================================================\n# 10) Predict test + write submission (EXPERIMENTEEL)\n# ============================================================\n\n# Predict probabilities for test set\nP_test = model.predict_proba(X_test)\n\n# Tune these:\nTHRESH = 0.05\nTOP_N_PER_PROTEIN = 100\n\nsub = make_submission(\n    P_test=P_test,\n    test_ids=test_seq[\"protein_id\"].values,\n    top_terms=top_terms,\n    thresh=THRESH,\n    top_n=TOP_N_PER_PROTEIN,\n    out_path=\"submission.tsv\"\n)\n\ndisplay(sub.head())\n","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-28T19:07:40.357Z"}},"outputs":[],"execution_count":null}]}