{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# CAFA6 Pure GOA Conservative\n\n**Key Insight**: Over-prediction hurts F-max. Fewer, more confident predictions = higher precision.\n\n**Strategy**: Pure GOA (no blending) + higher score threshold + term limit per protein.\n\n**Expected**: ~0.38+ by improving precision without sacrificing core recall."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "import os\nimport gc\nimport pandas as pd\nimport numpy as np\nimport networkx as nx\nfrom tqdm.auto import tqdm\n\n# ==========================================\n# CONFIGURATION - CONSERVATIVE SETTINGS\n# ==========================================\nOBO_PATH = \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\"\nGOA_PATH = '/kaggle/input/cafa6-goa-predictions/goa_submission.tsv'\nIA_PATH = '/kaggle/input/cafa-6-protein-function-prediction/IA.txt'\nSUBMISSION_OUTPUT = 'submission.tsv'\n\n# CONSERVATIVE PARAMETERS\nMIN_SCORE_THRESHOLD = 0.01  # Higher threshold (was 0.001)\nMAX_TERMS_PER_PROTEIN = 800  # Limit predictions per protein\n\nprint(f\"OBO Path: {OBO_PATH}\")\nprint(f\"GOA Path: {GOA_PATH}\")\nprint(f\"IA Path: {IA_PATH}\")\nprint(f\"Output: {SUBMISSION_OUTPUT}\")\nprint(f\"\\nConservative Settings:\")\nprint(f\"  - Min Score: {MIN_SCORE_THRESHOLD}\")\nprint(f\"  - Max Terms/Protein: {MAX_TERMS_PER_PROTEIN}\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 1. BUILD GO GRAPH\n# ==========================================\ndef build_go_graph(go_obo_path):\n    print(f\"[1/5] Parsing OBO into DAG...\")\n    go_graph = nx.DiGraph()\n    \n    with open(go_obo_path, \"r\") as f:\n        cur_id = None\n        for line in f:\n            line = line.strip()\n            if line == \"[Term]\":\n                cur_id = None\n            elif line.startswith(\"id: \"):\n                cur_id = line.split(\"id: \")[1].strip()\n                go_graph.add_node(cur_id)\n            elif line.startswith(\"is_a: \"):\n                pid = line.split()[1].strip()\n                if cur_id:\n                    go_graph.add_edge(cur_id, pid)\n            elif line.startswith(\"relationship: part_of \"):\n                parts = line.split()\n                if len(parts) >= 3:\n                    pid = parts[2].strip()\n                    if cur_id:\n                        go_graph.add_edge(cur_id, pid)\n\n    if not nx.is_directed_acyclic_graph(go_graph):\n        cycles = list(nx.simple_cycles(go_graph))\n        for cycle in cycles:\n            go_graph.remove_edge(cycle[0], cycle[1])\n\n    topo_order = list(nx.topological_sort(go_graph))\n    term_parents_map = {n: list(go_graph.successors(n)) for n in go_graph.nodes()}\n    \n    print(f\"      -> Built graph with {len(go_graph)} nodes\")\n    return topo_order, term_parents_map\n\ntopo_order, term_parents_map = build_go_graph(OBO_PATH)"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 2. LOAD IA WEIGHTS (for ranking)\n# ==========================================\nprint(f\"[2/5] Loading IA weights...\")\n\nia_weights = {}\nif os.path.exists(IA_PATH):\n    ia_df = pd.read_csv(IA_PATH, sep='\\t', header=None, names=['go_term', 'ia'])\n    ia_weights = dict(zip(ia_df['go_term'], ia_df['ia']))\n    print(f\"      -> Loaded {len(ia_weights):,} IA weights\")\n    print(f\"      -> IA range: [{min(ia_weights.values()):.3f}, {max(ia_weights.values()):.3f}]\")\nelse:\n    print(\"      -> IA file not found, using uniform weights\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 3. LOAD GOA PREDICTIONS (PURE - NO BLENDING)\n# ==========================================\nprint(f\"[3/5] Loading GOA predictions (PURE)...\")\n\ngoa_df = pd.read_csv(GOA_PATH, sep='\\t', header=None, \n                     names=['protein_id', 'go_term', 'score'], \n                     dtype={'score': np.float32},\n                     on_bad_lines='skip')\n\nprint(f\"      -> Loaded {len(goa_df):,} predictions\")\nprint(f\"      -> Unique proteins: {goa_df['protein_id'].nunique():,}\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 4. PROPAGATE + CONSERVATIVE PRUNING\n# ==========================================\ndef propagate_and_prune(df, topo_order, term_parents_map, ia_weights):\n    print(\"[4/5] Propagating + Conservative Pruning...\")\n    \n    groups = df.groupby('protein_id')\n    final_rows = []\n    \n    for pid, group in tqdm(groups, desc=\"Processing\"):\n        # Map term -> score\n        scores = dict(zip(group['go_term'], group['score']))\n        \n        # Hard-max propagation\n        for child in topo_order:\n            if child in scores:\n                child_score = scores[child]\n                if child in term_parents_map:\n                    for parent in term_parents_map[child]:\n                        current_parent_score = scores.get(parent, 0.0)\n                        if child_score > current_parent_score:\n                            scores[parent] = child_score\n        \n        # Apply threshold filter\n        filtered = [(term, score) for term, score in scores.items() \n                    if score >= MIN_SCORE_THRESHOLD]\n        \n        # If too many terms, keep top by Score * IA ranking\n        if len(filtered) > MAX_TERMS_PER_PROTEIN:\n            # Rank by score * IA (prefer high-confidence, specific terms)\n            ranked = sorted(filtered, \n                          key=lambda x: x[1] * ia_weights.get(x[0], 1.0), \n                          reverse=True)\n            filtered = ranked[:MAX_TERMS_PER_PROTEIN]\n        \n        for term, score in filtered:\n            final_rows.append((pid, term, score))\n\n    return pd.DataFrame(final_rows, columns=['protein_id', 'go_term', 'score'])\n\nsubmission_final = propagate_and_prune(goa_df, topo_order, term_parents_map, ia_weights)\nprint(f\"      -> After conservative pruning: {len(submission_final):,} predictions\")\n\ndel goa_df\ngc.collect()"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 5. SAVE SUBMISSION\n# ==========================================\nprint(f\"[5/5] Saving submission...\")\n\nsubmission_final['score'] = submission_final['score'].clip(0.001, 1.0).round(3)\nsubmission_final.to_csv(SUBMISSION_OUTPUT, sep='\\t', index=False, header=False)\n\nprint(\"================================================\")\nprint(f\"[Done] Saved to {SUBMISSION_OUTPUT}\")\nprint(\"================================================\")\nprint(f\"Final stats:\")\nprint(f\"  - Total predictions: {len(submission_final):,}\")\nprint(f\"  - Unique proteins: {submission_final['protein_id'].nunique():,}\")\nprint(f\"  - Avg terms/protein: {len(submission_final) / submission_final['protein_id'].nunique():.1f}\")\nprint(f\"  - Score range: [{submission_final['score'].min():.3f}, {submission_final['score'].max():.3f}]\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Verify\nif os.path.exists(SUBMISSION_OUTPUT):\n    size = os.path.getsize(SUBMISSION_OUTPUT)\n    print(f\"SUCCESS: {SUBMISSION_OUTPUT} ({size:,} bytes)\")\nelse:\n    print(f\"ERROR: {SUBMISSION_OUTPUT} not found!\")"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.12"}}, "nbformat": 4, "nbformat_minor": 5}