{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# CAFA-6: Enhanced Ensemble\n\n**Strategy:**\n1. Combine Quick (0.358) and Proven ensemble predictions\n2. Use confidence-weighted averaging\n3. Complete GO ontology propagation\n4. Optimize threshold for Fmax"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "import os\nimport numpy as np\nimport pandas as pd\nfrom collections import defaultdict\nfrom tqdm.auto import tqdm\n\nprint('CAFA-6 Enhanced Ensemble v1')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Paths\nTRAIN_TERMS = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\nGO_OBO = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\nQUICK_KERNEL = '/kaggle/input/cafa-quick-submission-0-358-score/submission.tsv'\nPROVEN_KERNEL = '/kaggle/input/cafa-6-proven-ensemble-submission/submission.tsv'\n\nfor f in [TRAIN_TERMS, GO_OBO, QUICK_KERNEL, PROVEN_KERNEL]:\n    print(f'OK: {os.path.basename(f)}' if os.path.exists(f) else f'MISSING: {f}')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 1. LOAD PREDICTIONS\n# ==========================================\nprint('[1/5] Loading predictions...')\n\ndef load_predictions(path):\n    df = pd.read_csv(path, sep='\\t', header=None, names=['protein', 'go_term', 'score'])\n    preds = defaultdict(dict)\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=f'Loading {os.path.basename(path)}'):\n        preds[row['protein']][row['go_term']] = max(preds[row['protein']].get(row['go_term'], 0), row['score'])\n    return preds, df\n\nquick_preds, quick_df = load_predictions(QUICK_KERNEL)\nproven_preds, proven_df = load_predictions(PROVEN_KERNEL)\n\nprint(f'Quick: {len(quick_preds):,} proteins, {len(quick_df):,} predictions')\nprint(f'Proven: {len(proven_preds):,} proteins, {len(proven_df):,} predictions')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Analyze score distributions\nprint('\\nScore distributions:')\nprint(f'Quick:  min={quick_df[\"score\"].min():.3f}, max={quick_df[\"score\"].max():.3f}, mean={quick_df[\"score\"].mean():.3f}')\nprint(f'Proven: min={proven_df[\"score\"].min():.3f}, max={proven_df[\"score\"].max():.3f}, mean={proven_df[\"score\"].mean():.3f}')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 2. SMART ENSEMBLE COMBINATION\n# ==========================================\nprint('[2/5] Creating enhanced ensemble...')\n\n# Weight: Quick scored 0.358 on LB, we estimate Proven is around 0.36-0.37\n# Use slightly higher weight for higher confidence predictions\n\nensemble_preds = defaultdict(dict)\nall_proteins = set(quick_preds.keys()) | set(proven_preds.keys())\n\nfor protein in tqdm(all_proteins, desc='Combining'):\n    q_terms = quick_preds.get(protein, {})\n    p_terms = proven_preds.get(protein, {})\n    all_terms = set(q_terms.keys()) | set(p_terms.keys())\n    \n    for term in all_terms:\n        q_score = q_terms.get(term, 0)\n        p_score = p_terms.get(term, 0)\n        \n        if q_score > 0 and p_score > 0:\n            # Both agree - use max for high confidence, average for lower\n            if q_score > 0.5 and p_score > 0.5:\n                # High confidence from both - take max\n                combined = max(q_score, p_score)\n            else:\n                # Medium confidence - weighted geometric mean (more conservative)\n                combined = (q_score * p_score) ** 0.5 * 1.1  # slight boost for agreement\n        elif q_score > 0:\n            # Only Quick has prediction - slightly reduce confidence\n            combined = q_score * 0.9\n        else:\n            # Only Proven has prediction - slightly reduce confidence\n            combined = p_score * 0.9\n        \n        combined = min(combined, 1.0)  # Cap at 1.0\n        if combined >= 0.01:\n            ensemble_preds[protein][term] = combined\n\nprint(f'Enhanced ensemble: {len(ensemble_preds):,} proteins')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 3. GO ONTOLOGY PROPAGATION\n# ==========================================\nprint('[3/5] GO ontology propagation...')\n\ndef parse_obo(obo_path):\n    parents = defaultdict(set)\n    with open(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            elif line.startswith('is_a: '):\n                pid = line.split()[1].strip()\n                if cur_id:\n                    parents[cur_id].add(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                        parents[cur_id].add(pid)\n    return parents\n\ndef get_ancestors(term, parents_map, cache):\n    if term in cache:\n        return cache[term]\n    ancestors = set()\n    stack = [term]\n    while stack:\n        cur = stack.pop()\n        for p in parents_map.get(cur, []):\n            if p not in ancestors:\n                ancestors.add(p)\n                stack.append(p)\n    cache[term] = ancestors\n    return ancestors\n\nparents_map = parse_obo(GO_OBO)\nprint(f'Parsed {len(parents_map)} GO terms')\n\nROOTS = {'GO:0003674', 'GO:0008150', 'GO:0005575'}\nancestor_cache = {}\nfinal_rows = []\n\nfor protein, terms_dict in tqdm(ensemble_preds.items(), desc='Propagating'):\n    final_scores = dict(terms_dict)\n    \n    for term, score in list(terms_dict.items()):\n        for ancestor in get_ancestors(term, parents_map, ancestor_cache):\n            final_scores[ancestor] = max(final_scores.get(ancestor, 0), score)\n    \n    # Add root terms\n    for root in ROOTS:\n        final_scores[root] = 1.0\n    \n    for term, score in final_scores.items():\n        if score >= 0.01:\n            final_rows.append((protein, term, round(score, 6)))\n\nprint(f'Total predictions after propagation: {len(final_rows):,}')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 4. SAVE SUBMISSION\n# ==========================================\nprint('[4/5] Saving submission...')\n\nfinal_df = pd.DataFrame(final_rows, columns=['protein_id', 'go_term', 'score'])\nfinal_df = final_df.sort_values(['protein_id', 'score'], ascending=[True, False])\nfinal_df.to_csv('submission.tsv', sep='\\t', index=False, header=False)\n\nprint(f'\\n{\"=\"*60}')\nprint('DONE - CAFA6 Enhanced Ensemble v1')\nprint(f'Predictions: {len(final_df):,}')\nprint(f'Proteins: {final_df[\"protein_id\"].nunique():,}')\nprint(f'{\"=\"*60}')\nprint(final_df.head(10))"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# ==========================================\n# 5. ANALYSIS\n# ==========================================\nprint('[5/5] Analysis...')\n\n# Compare with original kernels\nprint(f'\\nComparison:')\nprint(f'Quick original:    {len(quick_df):,} predictions')\nprint(f'Proven original:   {len(proven_df):,} predictions')\nprint(f'Enhanced ensemble: {len(final_df):,} predictions')\n\n# Score distribution\nprint(f'\\nEnhanced score distribution:')\nprint(f'min={final_df[\"score\"].min():.3f}, max={final_df[\"score\"].max():.3f}, mean={final_df[\"score\"].mean():.3f}')\n\n# Predictions per protein\npreds_per_protein = final_df.groupby('protein_id').size()\nprint(f'\\nPredictions per protein:')\nprint(f'min={preds_per_protein.min()}, max={preds_per_protein.max()}, mean={preds_per_protein.mean():.1f}')"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.0"}}, "nbformat": 4, "nbformat_minor": 4}