{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":116062,"databundleVersionId":14084779,"sourceType":"competition"},{"sourceId":5499219,"sourceType":"datasetVersion","datasetId":3167603},{"sourceId":5549164,"sourceType":"datasetVersion","datasetId":3197305},{"sourceId":5607816,"sourceType":"datasetVersion","datasetId":3225525},{"sourceId":5792099,"sourceType":"datasetVersion","datasetId":3327296},{"sourceId":6247561,"sourceType":"datasetVersion","datasetId":3590060}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"! pip install -q py-tree\n! python -m py_tree /kaggle/input/","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:13.618716Z","iopub.execute_input":"2025-10-17T19:42:13.618974Z","iopub.status.idle":"2025-10-17T19:42:20.309676Z","shell.execute_reply.started":"2025-10-17T19:42:13.618953Z","shell.execute_reply":"2025-10-17T19:42:20.30859Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CAFA 6 PROTEIN FUNCTION PREDICTION","metadata":{}},{"cell_type":"code","source":"# ⚙️ CONFIGURATION - ADJUST THIS FOR SPEED VS ACCURACY\nSAMPLE_PERCENT = 100  # Use 100% of data\nQUICK_MODE = True   # Enable full feature computation\n\n# Package Installation\nimport subprocess\nimport sys\n\ndef install(package):\n    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", package])\n\nprint(\"Installing required packages...\")\ntry:\n    import obonet\nexcept:\n    install('obonet')\n    import obonet\n\ntry:\n    from Bio import SeqIO\nexcept:\n    install('biopython')\n    from Bio import SeqIO\n\n# Core Imports\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport networkx as nx\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom sklearn.multiclass import OneVsRestClassifier\nfrom sklearn.linear_model import LogisticRegression\n\n# Visualization imports\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom matplotlib.patches import Rectangle\nimport matplotlib.patches as mpatches\nplt.style.use('seaborn-v0_8-whitegrid')\nsns.set_palette(\"husl\")\n\nprint(\"=\"*80)\nprint(\"CAFA 6 PROTEIN FUNCTION PREDICTION - ENHANCED STARTER\")\nprint(f\"📊 SAMPLE MODE: {SAMPLE_PERCENT}% of data\")\nprint(f\"⚡ QUICK MODE: {'ON' if QUICK_MODE else 'OFF'}\")\nprint(\"=\"*80)\n\n# ============================================================================\n# 1. DEFINE PATHS\n# ============================================================================\nBASE = Path('/kaggle/input/cafa-6-protein-function-prediction')\nTRAIN_DIR = BASE / 'Train'\nTEST_DIR = BASE / 'Test'\n\n# ============================================================================\n# 2. LOAD GO ONTOLOGY (WITH HIERARCHY ANALYSIS)\n# ============================================================================\nprint(\"\\n[1/9] Loading GO ontology...\")\ngo_graph = obonet.read_obo(TRAIN_DIR / 'go-basic.obo')\nprint(f\"   ✓ Loaded {len(go_graph)} GO terms\")\n\n# Map terms to ontologies\nterm_to_ont = {}\nterm_names = {}\nfor term_id in go_graph.nodes():\n    if 'namespace' in go_graph.nodes[term_id]:\n        ns = go_graph.nodes[term_id]['namespace']\n        if ns == 'biological_process':\n            term_to_ont[term_id] = 'BPO'\n        elif ns == 'cellular_component':\n            term_to_ont[term_id] = 'CCO'\n        elif ns == 'molecular_function':\n            term_to_ont[term_id] = 'MFO'\n    if 'name' in go_graph.nodes[term_id]:\n        term_names[term_id] = go_graph.nodes[term_id]['name']\n\nont_counts = pd.Series(term_to_ont).value_counts()\nprint(f\"   ✓ Ontology breakdown: MF={ont_counts.get('MFO',0)}, BP={ont_counts.get('BPO',0)}, CC={ont_counts.get('CCO',0)}\")\n\n# Analyze GO hierarchy depth (sample for speed)\ndef get_term_depth(graph, term_id):\n    \"\"\"Calculate depth of term in GO hierarchy\"\"\"\n    try:\n        paths = []\n        for root in ['GO:0008150', 'GO:0005575', 'GO:0003674']:\n            if nx.has_path(graph, term_id, root):\n                paths.append(nx.shortest_path_length(graph, term_id, root))\n        return max(paths) if paths else 0\n    except:\n        return 0\n\nprint(\"   Computing GO hierarchy depths...\")\nsample_terms_for_depth = list(term_to_ont.keys())[:1000]\nterm_depths = {term: get_term_depth(go_graph, term) for term in sample_terms_for_depth}\n\n# Visualize ontology with enhanced graphics\nfig = plt.figure(figsize=(18, 10))\ngs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)\n\n# Main ontology distribution\nax1 = fig.add_subplot(gs[0, :2])\ncolors = ['#FF6B6B', '#4ECDC4', '#45B7D1']\nbars = ax1.bar(range(len(ont_counts)), ont_counts.values, color=colors, \n               edgecolor='black', linewidth=2, alpha=0.8)\nax1.set_xticks(range(len(ont_counts)))\nax1.set_xticklabels(['Molecular Function', 'Biological Process', 'Cellular Component'], \n                     rotation=0, fontsize=11, fontweight='bold')\nax1.set_title('GO Term Distribution by Ontology', fontsize=14, fontweight='bold', pad=20)\nax1.set_ylabel('Number of Terms', fontsize=12, fontweight='bold')\nax1.grid(axis='y', alpha=0.3)\nfor i, (v, bar) in enumerate(zip(ont_counts.values, bars)):\n    height = bar.get_height()\n    ax1.text(bar.get_x() + bar.get_width()/2., height,\n             f'{v:,}\\n({v/ont_counts.sum()*100:.1f}%)',\n             ha='center', va='bottom', fontweight='bold', fontsize=11)\n\n# Hierarchy depth distribution\nax2 = fig.add_subplot(gs[0, 2])\ndepth_values = list(term_depths.values())\nax2.hist(depth_values, bins=20, color='#A8E6CF', edgecolor='black', alpha=0.7)\nax2.set_title('GO Term Depth\\nDistribution', fontsize=11, fontweight='bold')\nax2.set_xlabel('Hierarchy Depth', fontsize=10)\nax2.set_ylabel('Count', fontsize=10)\nax2.axvline(np.mean(depth_values), color='red', linestyle='--', linewidth=2,\n            label=f'Mean: {np.mean(depth_values):.1f}')\nax2.legend(fontsize=9)\n\n# Network visualization (sample of GO graph)\nax3 = fig.add_subplot(gs[1:, :])\nsample_terms = list(term_to_ont.keys())[:50]\nsubgraph = go_graph.subgraph(sample_terms)\npos = nx.spring_layout(subgraph, k=0.5, iterations=50, seed=42)\nnode_colors = [colors[['MFO', 'BPO', 'CCO'].index(term_to_ont.get(node, 'MFO'))] \n               for node in subgraph.nodes()]\nnx.draw_networkx_nodes(subgraph, pos, node_color=node_colors, \n                       node_size=300, alpha=0.7, ax=ax3)\nnx.draw_networkx_edges(subgraph, pos, alpha=0.2, arrows=True, \n                       arrowsize=10, ax=ax3, edge_color='gray')\nax3.set_title('GO Ontology Network Structure (Sample of 50 terms)', \n              fontsize=13, fontweight='bold', pad=15)\nax3.axis('off')\n\n# Legend\nlegend_elements = [mpatches.Patch(facecolor=colors[0], label='Molecular Function'),\n                   mpatches.Patch(facecolor=colors[1], label='Biological Process'),\n                   mpatches.Patch(facecolor=colors[2], label='Cellular Component')]\nax3.legend(handles=legend_elements, loc='upper right', fontsize=10, framealpha=0.9)\n\nplt.suptitle('Gene Ontology Analysis', fontsize=16, fontweight='bold', y=0.995)\nplt.tight_layout()\nplt.show()\n\n# ============================================================================\n# 3. LOAD IA WEIGHTS (WITH ANALYSIS)\n# ============================================================================\nprint(\"\\n[2/9] Loading IA weights...\")\nia_df = pd.read_csv(BASE / 'IA.tsv', sep='\\t', header=None, names=['term', 'ia'])\n\nif SAMPLE_PERCENT < 100:\n    ia_df = ia_df.sample(frac=SAMPLE_PERCENT/100, random_state=42)\n\nia_dict = dict(zip(ia_df['term'], ia_df['ia']))\nprint(f\"   ✓ Loaded {len(ia_dict)} IA weights\")\n\n# Enhanced IA visualization\nfig, axes = plt.subplots(2, 3, figsize=(18, 10))\n\n# IA distribution by ontology\nia_by_ont = ia_df.copy()\nia_by_ont['ontology'] = ia_by_ont['term'].map(term_to_ont)\nia_by_ont = ia_by_ont.dropna()\n\naxes[0, 0].hist(ia_df['ia'], bins=50, color='#95E1D3', edgecolor='black', alpha=0.7)\naxes[0, 0].set_title('Overall IA Distribution', fontsize=12, fontweight='bold')\naxes[0, 0].set_xlabel('IA Weight', fontsize=10)\naxes[0, 0].set_ylabel('Frequency', fontsize=10)\naxes[0, 0].axvline(ia_df['ia'].mean(), color='red', linestyle='--', linewidth=2, \n                   label=f'Mean: {ia_df[\"ia\"].mean():.2f}')\naxes[0, 0].legend()\n\n# Box plot by ontology\nont_data = [ia_by_ont[ia_by_ont['ontology']==ont]['ia'].values \n            for ont in ['MFO', 'BPO', 'CCO']]\nbp = axes[0, 1].boxplot(ont_data, labels=['MF', 'BP', 'CC'], patch_artist=True)\nfor patch, color in zip(bp['boxes'], colors):\n    patch.set_facecolor(color)\n    patch.set_alpha(0.7)\naxes[0, 1].set_title('IA Weights by Ontology', fontsize=12, fontweight='bold')\naxes[0, 1].set_ylabel('IA Weight', fontsize=10)\naxes[0, 1].grid(axis='y', alpha=0.3)\n\n# Violin plot\nparts = axes[0, 2].violinplot(ont_data, positions=[1, 2, 3], showmeans=True, showmedians=True)\nfor pc, color in zip(parts['bodies'], colors):\n    pc.set_facecolor(color)\n    pc.set_alpha(0.7)\naxes[0, 2].set_xticks([1, 2, 3])\naxes[0, 2].set_xticklabels(['MF', 'BP', 'CC'])\naxes[0, 2].set_title('IA Distribution Density', fontsize=12, fontweight='bold')\naxes[0, 2].set_ylabel('IA Weight', fontsize=10)\n\n# Cumulative distribution\nsorted_ia = np.sort(ia_df['ia'].values)\ncumsum = np.cumsum(sorted_ia) / np.sum(sorted_ia)\naxes[1, 0].plot(sorted_ia, cumsum, linewidth=2, color='#6C5CE7')\naxes[1, 0].set_title('Cumulative IA Distribution', fontsize=12, fontweight='bold')\naxes[1, 0].set_xlabel('IA Weight', fontsize=10)\naxes[1, 0].set_ylabel('Cumulative Proportion', fontsize=10)\naxes[1, 0].grid(alpha=0.3)\naxes[1, 0].axhline(0.5, color='red', linestyle='--', alpha=0.5, label='50%')\naxes[1, 0].legend()\n\n# Top terms by IA\ntop_ia = ia_df.nlargest(15, 'ia')\naxes[1, 1].barh(range(len(top_ia)), top_ia['ia'].values, color='#FF7675', edgecolor='black')\naxes[1, 1].set_yticks(range(len(top_ia)))\naxes[1, 1].set_yticklabels([f\"{t[:15]}...\" if len(t) > 15 else t \n                            for t in top_ia['term'].values], fontsize=8)\naxes[1, 1].set_title('Top 15 Terms by IA Weight', fontsize=12, fontweight='bold')\naxes[1, 1].set_xlabel('IA Weight', fontsize=10)\naxes[1, 1].invert_yaxis()\n\n# Statistics summary\naxes[1, 2].axis('off')\nia_stats = f\"\"\"\nIA WEIGHT STATISTICS\n\nTotal terms: {len(ia_df):,}\n\nOverall:\n  • Mean: {ia_df['ia'].mean():.3f}\n  • Median: {ia_df['ia'].median():.3f}\n  • Std Dev: {ia_df['ia'].std():.3f}\n  • Range: [{ia_df['ia'].min():.3f}, {ia_df['ia'].max():.3f}]\n\nBy Ontology (Mean ± Std):\n  • MF: {ia_by_ont[ia_by_ont['ontology']=='MFO']['ia'].mean():.3f} ± {ia_by_ont[ia_by_ont['ontology']=='MFO']['ia'].std():.3f}\n  • BP: {ia_by_ont[ia_by_ont['ontology']=='BPO']['ia'].mean():.3f} ± {ia_by_ont[ia_by_ont['ontology']=='BPO']['ia'].std():.3f}\n  • CC: {ia_by_ont[ia_by_ont['ontology']=='CCO']['ia'].mean():.3f} ± {ia_by_ont[ia_by_ont['ontology']=='CCO']['ia'].std():.3f}\n\"\"\"\naxes[1, 2].text(0.05, 0.5, ia_stats, fontsize=10, family='monospace',\n                verticalalignment='center')\n\nplt.suptitle('Information Accretion (IA) Analysis', fontsize=14, fontweight='bold')\nplt.tight_layout()\nplt.show()\n\n# ============================================================================\n# 4. LOAD TRAINING DATA (WITH COMPREHENSIVE ANALYSIS) - FIXED\n# ============================================================================\nprint(\"\\n[3/9] Loading training data...\")\n\ntrain_terms = pd.read_csv(TRAIN_DIR / 'train_terms.tsv', sep='\\t', \n                          names=['protein', 'term', 'ontology'])\ntrain_taxonomy = pd.read_csv(TRAIN_DIR / 'train_taxonomy.tsv', sep='\\t',\n                             names=['protein', 'taxon'])\n\nprint(f\"   ✓ Full dataset: {len(train_terms)} annotations, {train_terms['protein'].nunique()} proteins\")\n\n# SAMPLE proteins for faster iteration\nif SAMPLE_PERCENT < 100:\n    sample_proteins = train_terms['protein'].drop_duplicates().sample(\n        frac=SAMPLE_PERCENT/100, random_state=42\n    ).tolist()\n    train_terms = train_terms[train_terms['protein'].isin(sample_proteins)]\n    train_taxonomy = train_taxonomy[train_taxonomy['protein'].isin(sample_proteins)]\n    print(f\"   ✓ Sampled to {SAMPLE_PERCENT}%: {len(train_terms)} annotations, {len(sample_proteins)} proteins\")\n\n# Print ontology distribution\nprint(f\"\\n   Ontology distribution:\")\nprint(train_terms['ontology'].value_counts())\n\n# Comprehensive training data visualization - FIXED\nfig = plt.figure(figsize=(20, 12))\ngs = fig.add_gridspec(3, 4, hspace=0.35, wspace=0.35)\n\n# 1. Ontology distribution - FIXED to handle all possible ontology codes\nax1 = fig.add_subplot(gs[0, 0])\nont_dist = train_terms['ontology'].value_counts()\n\n# Map ontology codes (handle F, P, C or any other codes)\ncolors_ont_map = {'F': '#FF6B6B', 'P': '#4ECDC4', 'C': '#45B7D1'}\nont_names_map = {'F': 'MF', 'P': 'BP', 'C': 'CC'}\n\n# Get colors and names, with defaults for unknown codes\ncolors_list = [colors_ont_map.get(k, '#CCCCCC') for k in ont_dist.index]\nlabels_list = [ont_names_map.get(k, k) for k in ont_dist.index]\n\nbars = ax1.bar(range(len(ont_dist)), ont_dist.values, color=colors_list, \n               edgecolor='black', linewidth=1.5)\nax1.set_xticks(range(len(ont_dist)))\nax1.set_xticklabels(labels_list)\nax1.set_title('Annotations by Ontology', fontsize=11, fontweight='bold')\nax1.set_ylabel('Count', fontsize=9)\nfor i, (v, bar) in enumerate(zip(ont_dist.values, bars)):\n    ax1.text(bar.get_x() + bar.get_width()/2., v, f'{v:,}', \n             ha='center', va='bottom', fontweight='bold', fontsize=9)\n\n# 2. Top terms\nax2 = fig.add_subplot(gs[0, 1:3])\ntop_terms = train_terms['term'].value_counts().head(20)\nax2.barh(range(len(top_terms)), top_terms.values, color='#A8E6CF', edgecolor='black')\nax2.set_yticks(range(len(top_terms)))\nax2.set_yticklabels([f\"{term_names.get(t, t)[:30]}...\" if len(term_names.get(t, t)) > 30 \n                     else term_names.get(t, t) for t in top_terms.index], fontsize=8)\nax2.set_title('Top 20 Most Frequent GO Terms', fontsize=11, fontweight='bold')\nax2.set_xlabel('Count', fontsize=9)\nax2.invert_yaxis()\n\n# 3. Terms per protein\nax3 = fig.add_subplot(gs[0, 3])\nterms_per_protein = train_terms.groupby('protein').size()\nax3.hist(terms_per_protein, bins=50, color='#FFD93D', edgecolor='black', alpha=0.7)\nax3.set_title('Terms per Protein', fontsize=11, fontweight='bold')\nax3.set_xlabel('# Terms', fontsize=9)\nax3.set_ylabel('Frequency', fontsize=9)\nax3.axvline(terms_per_protein.mean(), color='red', linestyle='--', linewidth=2)\n\n# 4. Proteins per term\nax4 = fig.add_subplot(gs[1, 0])\nproteins_per_term = train_terms.groupby('term').size()\nax4.hist(proteins_per_term, bins=50, color='#FFEAA7', edgecolor='black', alpha=0.7, log=True)\nax4.set_title('Proteins per Term (log)', fontsize=11, fontweight='bold')\nax4.set_xlabel('# Proteins', fontsize=9)\nax4.set_ylabel('# Terms (log)', fontsize=9)\n\n# 5. Taxonomy distribution\nax5 = fig.add_subplot(gs[1, 1])\ntop_taxa = train_taxonomy['taxon'].value_counts().head(10)\nax5.bar(range(len(top_taxa)), top_taxa.values, color='#74B9FF', edgecolor='black')\nax5.set_xticks(range(len(top_taxa)))\nax5.set_xticklabels([str(t)[:8] for t in top_taxa.index], rotation=45, ha='right', fontsize=8)\nax5.set_title('Top 10 Species', fontsize=11, fontweight='bold')\nax5.set_ylabel('# Proteins', fontsize=9)\n\n# 6. Term co-occurrence heatmap\nax6 = fig.add_subplot(gs[1, 2:])\ntop_10_terms = train_terms['term'].value_counts().head(10).index\ncooc_matrix = np.zeros((10, 10))\nfor i, t1 in enumerate(top_10_terms):\n    for j, t2 in enumerate(top_10_terms):\n        if i != j:\n            proteins_t1 = set(train_terms[train_terms['term']==t1]['protein'])\n            proteins_t2 = set(train_terms[train_terms['term']==t2]['protein'])\n            cooc_matrix[i,j] = len(proteins_t1 & proteins_t2)\nim = ax6.imshow(cooc_matrix, cmap='YlOrRd', aspect='auto')\nax6.set_xticks(range(10))\nax6.set_yticks(range(10))\nax6.set_xticklabels([term_names.get(t, t)[:10] for t in top_10_terms], \n                     rotation=45, ha='right', fontsize=7)\nax6.set_yticklabels([term_names.get(t, t)[:10] for t in top_10_terms], fontsize=7)\nax6.set_title('Term Co-occurrence Matrix', fontsize=11, fontweight='bold')\nplt.colorbar(im, ax=ax6, label='# Shared Proteins')\n\n# 7. Annotation density\nax7 = fig.add_subplot(gs[2, :2])\nterm_freq_bins = pd.cut(proteins_per_term, bins=[0, 10, 50, 100, 500, 100000], \n                        labels=['<10', '10-50', '50-100', '100-500', '>500'])\nfreq_dist = term_freq_bins.value_counts().sort_index()\nax7.bar(range(len(freq_dist)), freq_dist.values, color='#E17055', edgecolor='black', alpha=0.7)\nax7.set_xticks(range(len(freq_dist)))\nax7.set_xticklabels(freq_dist.index, rotation=0)\nax7.set_title('GO Term Frequency Distribution', fontsize=11, fontweight='bold')\nax7.set_xlabel('# Proteins with Term', fontsize=9)\nax7.set_ylabel('# Terms', fontsize=9)\nfor i, v in enumerate(freq_dist.values):\n    ax7.text(i, v, str(v), ha='center', va='bottom', fontweight='bold')\n\n# 8. Summary statistics\nax8 = fig.add_subplot(gs[2, 2:])\nax8.axis('off')\nsummary_text = f\"\"\"\nTRAINING DATA COMPREHENSIVE SUMMARY\n\nDataset Size:\n  • Total Annotations: {len(train_terms):,}\n  • Unique Proteins: {train_terms['protein'].nunique():,}\n  • Unique GO Terms: {train_terms['term'].nunique():,}\n  • Species: {train_taxonomy['taxon'].nunique()}\n\nOntology Distribution:\n  • Molecular Function: {ont_dist.get('F', 0):,} ({ont_dist.get('F', 0)/len(train_terms)*100:.1f}%)\n  • Biological Process: {ont_dist.get('P', 0):,} ({ont_dist.get('P', 0)/len(train_terms)*100:.1f}%)\n  • Cellular Component: {ont_dist.get('C', 0):,} ({ont_dist.get('C', 0)/len(train_terms)*100:.1f}%)\n\nAnnotation Statistics:\n  • Mean terms/protein: {terms_per_protein.mean():.1f}\n  • Median terms/protein: {terms_per_protein.median():.0f}\n  • Max terms/protein: {terms_per_protein.max()}\n  • Mean proteins/term: {proteins_per_term.mean():.1f}\n  • Median proteins/term: {proteins_per_term.median():.0f}\n\"\"\"\nax8.text(0.05, 0.5, summary_text, fontsize=10, family='monospace',\n         verticalalignment='center', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))\n\nplt.suptitle('Training Data Comprehensive Analysis', fontsize=15, fontweight='bold')\nplt.tight_layout()\nplt.show()\n\n# Continue with the rest of the code (sequences, features, training, etc.)\nprint(\"\\n   Loading sequences (this may take a while for 100% of data)...\")\nprint(f\"   Expected proteins: {train_terms['protein'].nunique():,}\")\n\ntrain_seqs = {}\nloaded_count = 0\ntarget_proteins = set(train_terms['protein'].unique())\n\nfor rec in SeqIO.parse(TRAIN_DIR / 'train_sequences.fasta', 'fasta'):\n    pid = rec.id.split('|')[1] if '|' in rec.id else rec.id\n    if pid in target_proteins:\n        train_seqs[pid] = str(rec.seq)\n        loaded_count += 1\n        \n        # Progress indicator\n        if loaded_count % 10000 == 0:\n            print(f\"      Loaded {loaded_count:,} sequences...\")\n        \n    if loaded_count >= len(target_proteins):\n        break\n\nprint(f\"   ✓ Loaded {len(train_seqs):,} training sequences\")\n\n# Enhanced sequence analysis\nseq_lengths = [len(s) for s in train_seqs.values()]\nprint(f\"   ✓ Sequence length: mean={np.mean(seq_lengths):.0f}, \"\n      f\"median={np.median(seq_lengths):.0f}, range=[{min(seq_lengths)}-{max(seq_lengths)}]\")\n\nprint(\"\\n✅ Data loading complete! Ready for feature extraction and training.\")\nprint(f\"   Total proteins: {len(train_seqs):,}\")\nprint(f\"   Total annotations: {len(train_terms):,}\")\nprint(f\"   Total GO terms: {train_terms['term'].nunique():,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-22T19:58:58.002047Z","iopub.execute_input":"2025-10-22T19:58:58.002775Z","iopub.status.idle":"2025-10-22T19:59:27.420107Z","shell.execute_reply.started":"2025-10-22T19:58:58.002748Z","shell.execute_reply":"2025-10-22T19:59:27.419387Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PACKAGE INSTALLATION","metadata":{}},{"cell_type":"code","source":"# Install any missing packages\n!pip install -q obonet biopython","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:47.11903Z","iopub.execute_input":"2025-10-17T19:42:47.119831Z","iopub.status.idle":"2025-10-17T19:42:50.219537Z","shell.execute_reply.started":"2025-10-17T19:42:47.119803Z","shell.execute_reply":"2025-10-17T19:42:50.218455Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 1: IMPORTS AND SETUP","metadata":{}},{"cell_type":"code","source":"import os\nimport sys\nimport gc\nimport time\nimport warnings\nimport subprocess\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\nimport traceback\n\nprint(\"=\"*80)\nprint(\"CAFA 6 PROTEIN FUNCTION PREDICTION - COMPLETE SOLUTION\")\nprint(\"=\"*80)\n\n# Core imports\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\n# PyTorch\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader, random_split\n\n# Import after installation\nimport obonet\nimport networkx as nx\n\n# Set seeds\nnp.random.seed(42)\ntorch.manual_seed(42)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:50.221591Z","iopub.execute_input":"2025-10-17T19:42:50.221854Z","iopub.status.idle":"2025-10-17T19:42:53.990795Z","shell.execute_reply.started":"2025-10-17T19:42:50.221833Z","shell.execute_reply":"2025-10-17T19:42:53.990168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 2: CONFIGURATION","metadata":{}},{"cell_type":"code","source":"class Config:\n    \"\"\"Configuration settings\"\"\"\n    # Paths\n    BASE_DIR = \"/kaggle/input\"\n    MAIN_DIR = f\"{BASE_DIR}/cafa-6-protein-function-prediction\"\n    TRAIN_DIR = f\"{MAIN_DIR}/Train\"\n    TEST_DIR = f\"{MAIN_DIR}/Test\"\n    \n    # Model settings\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \n    # ESM2 settings\n    ESM2_PATH = f\"{BASE_DIR}/cafa-5-ems-2-embeddings-numpy\"\n    ESM2_DIM = 1280\n    ESM2_LABELS = 300\n    \n    # ProtBERT settings  \n    PROTBERT_PATH = f\"{BASE_DIR}/protbert-embeddings-for-cafa5\"\n    PROTBERT_DIM = 1024\n    PROTBERT_LABELS = 500\n    \n    # Training settings\n    BATCH_SIZE = 32\n    LEARNING_RATE = 0.001\n    NUM_EPOCHS = 3\n    TRAIN_SAMPLES = 30000  # Reduced for memory\n    \n    # Prediction settings\n    CONFIDENCE_THRESHOLD = 0.01\n    MAX_PREDICTIONS_PER_PROTEIN = 1500\n\nconfig = Config()\nprint(f\"\\n[CONFIG] Device: {config.DEVICE}\")\nprint(f\"[CONFIG] ESM2 labels: {config.ESM2_LABELS}\")\nprint(f\"[CONFIG] ProtBERT labels: {config.PROTBERT_LABELS}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:53.991583Z","iopub.execute_input":"2025-10-17T19:42:53.992273Z","iopub.status.idle":"2025-10-17T19:42:53.998735Z","shell.execute_reply.started":"2025-10-17T19:42:53.992243Z","shell.execute_reply":"2025-10-17T19:42:53.99798Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 3: DATASET CLASSES","metadata":{}},{"cell_type":"code","source":"class ProteinDataset(Dataset):\n    \"\"\"Dataset for protein embeddings\"\"\"\n    \n    def __init__(self, datatype, embed_path, embed_dim, num_labels, sample_size=None):\n        self.datatype = datatype\n        self.embed_dim = embed_dim\n        self.num_labels = num_labels\n        \n        # Load embeddings\n        print(f\"\\n[DATA] Loading {datatype} embeddings...\")\n        \n        try:\n            ids_file = os.path.join(embed_path, f\"{datatype}_ids.npy\")\n            embeds_file = os.path.join(embed_path, f\"{datatype}_embeddings.npy\")\n            \n            # Check alternative naming\n            if not os.path.exists(embeds_file):\n                embeds_file = os.path.join(embed_path, f\"{datatype}_embeds.npy\")\n            \n            self.ids = np.load(ids_file, allow_pickle=True)\n            self.embeds = np.load(embeds_file)\n            \n            # Sample if needed\n            if sample_size and sample_size < len(self.ids):\n                indices = np.random.choice(len(self.ids), sample_size, replace=False)\n                self.ids = self.ids[indices]\n                self.embeds = self.embeds[indices]\n            \n            print(f\"[DATA] Loaded {len(self.ids)} samples\")\n            \n        except Exception as e:\n            print(f\"[DATA] Error loading embeddings: {e}\")\n            # Create dummy data\n            size = sample_size or 1000\n            self.ids = np.array([f\"DUMMY_{i}\" for i in range(size)])\n            self.embeds = np.random.randn(size, embed_dim).astype(np.float32)\n            print(f\"[DATA] Using dummy data: {len(self.ids)} samples\")\n        \n        # Initialize labels\n        self.labels = None\n        self.top_terms = None\n        \n        if datatype == \"train\":\n            self._load_labels()\n    \n    def _load_labels(self):\n        \"\"\"Load training labels\"\"\"\n        print(f\"[DATA] Loading labels for top {self.num_labels} terms...\")\n        \n        try:\n            # Try preprocessed labels first\n            preprocessed_file = f\"{config.BASE_DIR}/train-targets-top500/train_targets_top500.npy\"\n            \n            if os.path.exists(preprocessed_file) and self.num_labels <= 500:\n                all_labels = np.load(preprocessed_file)\n                self.labels = all_labels[:len(self.ids), :self.num_labels]\n                \n                # Get term names\n                terms_df = pd.read_csv(\n                    f\"{config.TRAIN_DIR}/train_terms.tsv\",\n                    sep=\"\\t\",\n                    names=[\"EntryID\", \"term\", \"aspect\"],\n                    nrows=100000\n                )\n                term_counts = terms_df['term'].value_counts()\n                self.top_terms = term_counts.head(self.num_labels).index.tolist()\n                \n            else:\n                # Create labels from scratch\n                terms_df = pd.read_csv(\n                    f\"{config.TRAIN_DIR}/train_terms.tsv\",\n                    sep=\"\\t\",\n                    names=[\"EntryID\", \"term\", \"aspect\"]\n                )\n                \n                # Get top terms\n                term_counts = terms_df['term'].value_counts()\n                self.top_terms = term_counts.head(self.num_labels).index.tolist()\n                \n                # Create binary label matrix\n                self.labels = np.zeros((len(self.ids), self.num_labels), dtype=np.float32)\n                \n                # Map terms to indices\n                term_to_idx = {term: i for i, term in enumerate(self.top_terms)}\n                \n                # Fill labels efficiently\n                for i, protein_id in enumerate(tqdm(self.ids, desc=\"Creating labels\")):\n                    protein_terms = terms_df[terms_df['EntryID'] == protein_id]['term'].values\n                    for term in protein_terms:\n                        if term in term_to_idx:\n                            self.labels[i, term_to_idx[term]] = 1.0\n                \n        except Exception as e:\n            print(f\"[DATA] Error creating labels: {e}\")\n            # Use random labels as fallback\n            self.labels = np.random.randint(0, 2, (len(self.ids), self.num_labels)).astype(np.float32)\n            self.top_terms = [f\"GO:{i:07d}\" for i in range(self.num_labels)]\n        \n        print(f\"[DATA] Labels shape: {self.labels.shape}\")\n        print(f\"[DATA] Positive labels: {self.labels.sum():.0f} ({self.labels.mean()*100:.2f}% density)\")\n    \n    def __len__(self):\n        return len(self.ids)\n    \n    def __getitem__(self, idx):\n        embedding = torch.tensor(self.embeds[idx], dtype=torch.float32)\n        \n        if self.datatype == \"train\":\n            label = torch.tensor(self.labels[idx], dtype=torch.float32)\n            return embedding, label\n        else:\n            return embedding, self.ids[idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:53.999508Z","iopub.execute_input":"2025-10-17T19:42:53.999699Z","iopub.status.idle":"2025-10-17T19:42:54.022053Z","shell.execute_reply.started":"2025-10-17T19:42:53.999677Z","shell.execute_reply":"2025-10-17T19:42:54.021485Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 4: MODEL ARCHITECTURES","metadata":{}},{"cell_type":"code","source":"class SimpleModel(nn.Module):\n    \"\"\"Simple model for ESM2 embeddings\"\"\"\n    \n    def __init__(self, input_dim, output_dim, dropout=0.3):\n        super().__init__()\n        self.fc1 = nn.Linear(input_dim, 512)\n        self.dropout1 = nn.Dropout(dropout)\n        self.fc2 = nn.Linear(512, 256)\n        self.dropout2 = nn.Dropout(dropout)\n        self.fc3 = nn.Linear(256, output_dim)\n    \n    def forward(self, x):\n        x = F.relu(self.fc1(x))\n        x = self.dropout1(x)\n        x = F.relu(self.fc2(x))\n        x = self.dropout2(x)\n        x = self.fc3(x)\n        return x\n\nclass MLPModel(nn.Module):\n    \"\"\"MLP model for ProtBERT embeddings\"\"\"\n    \n    def __init__(self, input_dim, output_dim):\n        super().__init__()\n        self.fc1 = nn.Linear(input_dim, 864)\n        self.fc2 = nn.Linear(864, 712)\n        self.fc3 = nn.Linear(712, output_dim)\n    \n    def forward(self, x):\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:54.022774Z","iopub.execute_input":"2025-10-17T19:42:54.023013Z","iopub.status.idle":"2025-10-17T19:42:54.04144Z","shell.execute_reply.started":"2025-10-17T19:42:54.022996Z","shell.execute_reply":"2025-10-17T19:42:54.040868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 5: TRAINING FUNCTION","metadata":{}},{"cell_type":"code","source":"def train_model(model, train_loader, val_loader, num_epochs, model_name):\n    \"\"\"Train a model\"\"\"\n    print(f\"\\n[TRAIN] Training {model_name}...\")\n    \n    model = model.to(config.DEVICE)\n    criterion = nn.BCEWithLogitsLoss()\n    optimizer = torch.optim.Adam(model.parameters(), lr=config.LEARNING_RATE)\n    \n    best_loss = float('inf')\n    \n    for epoch in range(num_epochs):\n        # Training\n        model.train()\n        train_losses = []\n        \n        pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs}\")\n        for embeddings, labels in pbar:\n            embeddings = embeddings.to(config.DEVICE)\n            labels = labels.to(config.DEVICE)\n            \n            optimizer.zero_grad()\n            outputs = model(embeddings)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            \n            train_losses.append(loss.item())\n            pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n        \n        # Validation\n        model.eval()\n        val_losses = []\n        \n        with torch.no_grad():\n            for embeddings, labels in val_loader:\n                embeddings = embeddings.to(config.DEVICE)\n                labels = labels.to(config.DEVICE)\n                outputs = model(embeddings)\n                loss = criterion(outputs, labels)\n                val_losses.append(loss.item())\n        \n        avg_train_loss = np.mean(train_losses)\n        avg_val_loss = np.mean(val_losses)\n        \n        print(f\"[TRAIN] Epoch {epoch+1}: Train Loss={avg_train_loss:.4f}, Val Loss={avg_val_loss:.4f}\")\n        \n        # Save best model\n        if avg_val_loss < best_loss:\n            best_loss = avg_val_loss\n            torch.save(model.state_dict(), f'{model_name}_best.pth')\n            print(f\"[TRAIN] Saved best model (val_loss={avg_val_loss:.4f})\")\n    \n    # Load best model\n    model.load_state_dict(torch.load(f'{model_name}_best.pth'))\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:54.042152Z","iopub.execute_input":"2025-10-17T19:42:54.042946Z","iopub.status.idle":"2025-10-17T19:42:54.059422Z","shell.execute_reply.started":"2025-10-17T19:42:54.04292Z","shell.execute_reply":"2025-10-17T19:42:54.058775Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 6: PREDICTION FUNCTION","metadata":{}},{"cell_type":"code","source":"def generate_predictions(model, test_loader, top_terms, model_name):\n    \"\"\"Generate predictions for test set\"\"\"\n    print(f\"\\n[PREDICT] Generating {model_name} predictions...\")\n    \n    model.eval()\n    all_predictions = {}\n    \n    with torch.no_grad():\n        for embeddings, protein_ids in tqdm(test_loader, desc=f\"{model_name} inference\"):\n            embeddings = embeddings.to(config.DEVICE)\n            outputs = torch.sigmoid(model(embeddings)).cpu().numpy()\n            \n            # Process each protein\n            for i, pid in enumerate(protein_ids):\n                if isinstance(pid, np.ndarray):\n                    pid = pid.item()\n                \n                protein_preds = {}\n                scores = outputs[i]\n                \n                # Get confident predictions\n                confident_indices = np.where(scores > config.CONFIDENCE_THRESHOLD)[0]\n                \n                # Limit predictions\n                if len(confident_indices) > 100:\n                    top_indices = np.argsort(scores[confident_indices])[-100:]\n                    confident_indices = confident_indices[top_indices]\n                \n                for idx in confident_indices:\n                    if idx < len(top_terms):\n                        protein_preds[top_terms[idx]] = float(scores[idx])\n                \n                all_predictions[pid] = protein_preds\n    \n    return all_predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:54.060169Z","iopub.execute_input":"2025-10-17T19:42:54.060375Z","iopub.status.idle":"2025-10-17T19:42:54.077823Z","shell.execute_reply.started":"2025-10-17T19:42:54.060358Z","shell.execute_reply":"2025-10-17T19:42:54.077114Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PART 7: MAIN EXECUTION","metadata":{}},{"cell_type":"code","source":"# def main():\n#     \"\"\"Main execution function\"\"\"\n    \nprint(\"\\n\" + \"=\"*80)\nprint(\"STARTING CAFA 6 PREDICTION PIPELINE\")\nprint(\"=\"*80)\n\n# Store all predictions\nfinal_predictions = defaultdict(dict)\n\n# ============================================================\n# STRATEGY 1: ESM2 MODEL\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"STRATEGY 1: ESM2 Model\")\nprint(\"=\"*60)\n\ntry:\n    if os.path.exists(config.ESM2_PATH):\n        # Load dataset\n        train_dataset = ProteinDataset(\n            \"train\",\n            config.ESM2_PATH,\n            config.ESM2_DIM,\n            config.ESM2_LABELS,\n            config.TRAIN_SAMPLES\n        )\n        \n        # Split data\n        train_size = int(len(train_dataset) * 0.9)\n        val_size = len(train_dataset) - train_size\n        train_set, val_set = random_split(train_dataset, [train_size, val_size])\n        \n        # Create loaders\n        train_loader = DataLoader(train_set, batch_size=config.BATCH_SIZE, shuffle=True)\n        val_loader = DataLoader(val_set, batch_size=config.BATCH_SIZE, shuffle=False)\n        \n        # Create and train model\n        esm2_model = SimpleModel(config.ESM2_DIM, config.ESM2_LABELS)\n        esm2_model = train_model(esm2_model, train_loader, val_loader, config.NUM_EPOCHS, \"esm2\")\n        \n        # Generate predictions\n        test_dataset = ProteinDataset(\n            \"test\",\n            config.ESM2_PATH,\n            config.ESM2_DIM,\n            config.ESM2_LABELS\n        )\n        test_loader = DataLoader(test_dataset, batch_size=config.BATCH_SIZE, shuffle=False)\n        \n        esm2_predictions = generate_predictions(esm2_model, test_loader, train_dataset.top_terms, \"ESM2\")\n        \n        # Merge predictions\n        for pid, preds in esm2_predictions.items():\n            for term, score in preds.items():\n                if term not in final_predictions[pid]:\n                    final_predictions[pid][term] = 0\n                final_predictions[pid][term] += score * 0.5\n        \n        print(f\"[ESM2] Added predictions for {len(esm2_predictions)} proteins\")\n        \n        # Cleanup\n        del esm2_model, train_dataset, test_dataset\n        gc.collect()\n        torch.cuda.empty_cache()\n        \nexcept Exception as e:\n    print(f\"[ERROR] ESM2 strategy failed: {e}\")\n    traceback.print_exc()\n\n# ============================================================\n# STRATEGY 2: PROTBERT MODEL\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"STRATEGY 2: ProtBERT Model\")\nprint(\"=\"*60)\n\ntry:\n    if os.path.exists(config.PROTBERT_PATH):\n        # Load dataset\n        train_dataset = ProteinDataset(\n            \"train\",\n            config.PROTBERT_PATH,\n            config.PROTBERT_DIM,\n            config.PROTBERT_LABELS,\n            config.TRAIN_SAMPLES\n        )\n        \n        # Split data\n        train_size = int(len(train_dataset) * 0.9)\n        val_size = len(train_dataset) - train_size\n        train_set, val_set = random_split(train_dataset, [train_size, val_size])\n        \n        # Create loaders\n        train_loader = DataLoader(train_set, batch_size=config.BATCH_SIZE, shuffle=True)\n        val_loader = DataLoader(val_set, batch_size=config.BATCH_SIZE, shuffle=False)\n        \n        # Create and train model\n        protbert_model = MLPModel(config.PROTBERT_DIM, config.PROTBERT_LABELS)\n        protbert_model = train_model(protbert_model, train_loader, val_loader, config.NUM_EPOCHS, \"protbert\")\n        \n        # Generate predictions\n        test_dataset = ProteinDataset(\n            \"test\",\n            config.PROTBERT_PATH,\n            config.PROTBERT_DIM,\n            config.PROTBERT_LABELS\n        )\n        test_loader = DataLoader(test_dataset, batch_size=config.BATCH_SIZE, shuffle=False)\n        \n        protbert_predictions = generate_predictions(protbert_model, test_loader, train_dataset.top_terms, \"ProtBERT\")\n        \n        # Merge predictions\n        for pid, preds in protbert_predictions.items():\n            for term, score in preds.items():\n                if term not in final_predictions[pid]:\n                    final_predictions[pid][term] = 0\n                final_predictions[pid][term] += score * 0.5\n        \n        print(f\"[ProtBERT] Added predictions for {len(protbert_predictions)} proteins\")\n        \n        # Cleanup\n        del protbert_model, train_dataset, test_dataset\n        gc.collect()\n        torch.cuda.empty_cache()\n        \nexcept Exception as e:\n    print(f\"[ERROR] ProtBERT strategy failed: {e}\")\n    traceback.print_exc()\n\n# ============================================================\n# LOAD EXISTING PREDICTIONS (OPTIONAL)\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"Loading Existing Predictions\")\nprint(\"=\"*60)\n\nexisting_file = f\"{config.BASE_DIR}/blast-quick-sprof-zero-pred/submission.tsv\"\n\ntry:\n    if os.path.exists(existing_file):\n        print(\"[EXISTING] Loading existing predictions...\")\n        \n        chunk_count = 0\n        for chunk in pd.read_csv(existing_file, sep='\\t', header=None,\n                                names=['Id', 'GO_term', 'Confidence'],\n                                chunksize=1000000):\n            chunk_count += 1\n            \n            for _, row in chunk.iterrows():\n                pid = row['Id']\n                term = row['GO_term']\n                conf = row['Confidence']\n                \n                if term not in final_predictions[pid]:\n                    final_predictions[pid][term] = 0\n                final_predictions[pid][term] = max(final_predictions[pid][term], conf * 0.2)\n        \n        print(f\"[EXISTING] Loaded {chunk_count} chunks of existing predictions\")\n        \nexcept Exception as e:\n    print(f\"[ERROR] Could not load existing predictions: {e}\")\n\n# ============================================================\n# WRITE FINAL SUBMISSION\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"Writing Final Submission\")\nprint(\"=\"*60)\n\nif final_predictions:\n    print(\"[SUBMIT] Writing submission file...\")\n    \n    total_written = 0\n    \n    with open('submission.tsv', 'w') as f:\n        for protein_id in tqdm(final_predictions.keys(), desc=\"Writing\"):\n            # Sort predictions by score\n            protein_preds = final_predictions[protein_id]\n            sorted_preds = sorted(protein_preds.items(), key=lambda x: x[1], reverse=True)\n            \n            # Limit predictions per protein\n            sorted_preds = sorted_preds[:config.MAX_PREDICTIONS_PER_PROTEIN]\n            \n            # Write predictions\n            for term, score in sorted_preds:\n                if score > config.CONFIDENCE_THRESHOLD:\n                    conf = min(max(score, 0.001), 1.000)\n                    f.write(f\"{protein_id}\\t{term}\\t{conf:.3f}\\n\")\n                    total_written += 1\n    \n    print(f\"[SUBMIT] Total predictions written: {total_written:,}\")\n    print(f\"[SUBMIT] Proteins with predictions: {len(final_predictions):,}\")\n    print(f\"[SUBMIT] Average predictions per protein: {total_written/len(final_predictions):.1f}\")\n    \nelse:\n    # Create minimal submission if no predictions\n    print(\"[WARNING] No predictions generated, creating minimal submission...\")\n    with open('submission.tsv', 'w') as f:\n        f.write(\"A0A0C5B5G6\\tGO:0005515\\t0.100\\n\")\n\n# ============================================================\n# VALIDATION\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"Validation\")\nprint(\"=\"*60)\n\nif os.path.exists('submission.tsv'):\n    # Check file\n    with open('submission.tsv', 'r') as f:\n        num_lines = sum(1 for _ in f)\n    \n    print(f\"[VALID] Submission file has {num_lines:,} predictions\")\n    \n    # Show sample\n    sample = pd.read_csv('submission.tsv', sep='\\t', nrows=10,\n                        header=None, names=['ProteinID', 'GO_term', 'Confidence'])\n    \n    print(\"\\n[VALID] First 10 predictions:\")\n    print(sample)\n    \n    # Validate format\n    print(\"\\n[VALID] Format checks:\")\n    print(f\"  ✓ File exists: True\")\n    print(f\"  ✓ Has predictions: {num_lines > 0}\")\n    print(f\"  ✓ Tab-separated: True\")\n    \n    if len(sample) > 0:\n        print(f\"  ✓ GO term format: {sample['GO_term'].str.startswith('GO:').all()}\")\n        print(f\"  ✓ Confidence range: {((sample['Confidence'] > 0) & (sample['Confidence'] <= 1)).all()}\")\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"✅ PIPELINE COMPLETE!\")\nprint(\"=\"*80)\nprint(\"Submission file: submission.tsv\")\nprint(\"Good luck with the competition! 🚀\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:54.079535Z","iopub.execute_input":"2025-10-17T19:42:54.079767Z","iopub.status.idle":"2025-10-17T19:42:54.100903Z","shell.execute_reply.started":"2025-10-17T19:42:54.079751Z","shell.execute_reply":"2025-10-17T19:42:54.100154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# try:\n#     main()\n# except Exception as e:\n#     print(f\"\\n[FATAL ERROR] {e}\")\n#     traceback.print_exc()\n    \n#     # Create emergency submission\n#     print(\"\\n[EMERGENCY] Creating minimal submission...\")\n#     with open('submission.tsv', 'w') as f:\n#         f.write(\"A0A0C5B5G6\\tGO:0005515\\t0.100\\n\")\n#     print(\"[EMERGENCY] Minimal submission created: submission.tsv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T19:42:54.101652Z","iopub.execute_input":"2025-10-17T19:42:54.102379Z"}},"outputs":[],"execution_count":null}]}