{"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":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"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},{"sourceId":14118895,"sourceType":"datasetVersion","datasetId":8994632},{"sourceId":14119551,"sourceType":"datasetVersion","datasetId":8995141},{"sourceId":14124426,"sourceType":"datasetVersion","datasetId":8999027}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-30T16:11:06.988457Z","iopub.execute_input":"2025-11-30T16:11:06.988658Z","iopub.status.idle":"2025-11-30T16:11:08.596794Z","shell.execute_reply.started":"2025-11-30T16:11:06.988643Z","shell.execute_reply":"2025-11-30T16:11:08.596119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CAFA 6 PROTEIN FUNCTION PREDICTION - ENHANCED VERSION (FIXED)\n# ============================================================================\n\n# ⚙️ 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,"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# PACKAGE INSTALLATION\n# ============================================================================\n# Install any missing packages\n!pip install -q obonet biopython","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T16:12:38.672195Z","iopub.execute_input":"2025-11-30T16:12:38.672846Z","iopub.status.idle":"2025-11-30T16:12:44.109309Z","shell.execute_reply.started":"2025-11-30T16:12:38.672818Z","shell.execute_reply":"2025-11-30T16:12:44.108618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# 1. SETUP & IMPORTS\n# ============================================================================\nimport numpy as np\nimport pandas as pd\nimport os\nimport gc\nimport sys\nimport subprocess\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\nfrom tqdm.auto import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Cài đặt thư viện nếu thiếu\ndef install(package):\n    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", package])\n\ntry:\n    import torch\n    import torch.nn as nn\n    import torch.nn.functional as F\nexcept:\n    install('torch')\n    import torch\n    import torch.nn as nn\n    import torch.nn.functional as F\n\n# ============================================================================\n# 2. CẤU HÌNH (TỐI ƯU HÓA)\n# ============================================================================\n# Tự động tìm đường dẫn\nMODEL_A_DIR = '/kaggle/input/my-best-model-a'\n# Tự động tìm model A\nif not os.path.exists(MODEL_A_DIR):\n    print(f\"⚠️ Warning: Check path {MODEL_A_DIR}\")\n    model_a_path = None\nelse:\n    fs = [f for f in os.listdir(MODEL_A_DIR) if f.endswith('.tsv') or f.endswith('.csv')]\n    model_a_path = os.path.join(MODEL_A_DIR, fs[0]) if fs else None\n\n# Đường dẫn Model Ngoài (0.35)\nEXTERNAL_DIR = '/kaggle/input/external-high-score' # <--- ĐẢM BẢO BẠN ĐÃ ADD DATASET NÀY\next_files = [f for f in os.listdir(EXTERNAL_DIR) if f.endswith('.tsv')] if os.path.exists(EXTERNAL_DIR) else []\nEXTERNAL_PATH = os.path.join(EXTERNAL_DIR, ext_files[0]) if ext_files else None\n\nCONFIG = {\n    'MAX_TRAIN_SAMPLES': 250000,      \n    'TOP_K_LABELS': 2500,             \n    'RANDOM_SEED': 999,               \n    'HIDDEN_DIM': 1500,               \n    'DROPOUT_RATE': 0.2,\n    'EPOCHS': 20,                     \n    'BATCH_SIZE': 64,\n    'LEARNING_RATE': 1e-4,\n    'PREDICT_BATCH_SIZE': 128,\n    'MIN_CONFIDENCE': 0.01,           # <--- QUAN TRỌNG: Hạ xuống 0.01 để tăng Recall -> Tăng điểm\n    'TEMPERATURE': 1.0,               # Về 1.0 cho ổn định\n    'BASE_PATH': '/kaggle/input/cafa-6-protein-function-prediction',\n    'ESM2_PATH': 'cafa-5-ems-2-embeddings-numpy',\n    'BLAST_PATH': '/kaggle/input/blast-quick-sprof-zero-pred/submission.tsv',\n}\n\nprint(\"=\"*80)\nprint(\"🚀 FINAL RUN: HYBRID ENSEMBLE (INTERNAL + REFERENCE MODEL)\")\nprint(\"=\"*80)\n\n# Setup Device\nnp.random.seed(CONFIG['RANDOM_SEED'])\ntorch.manual_seed(CONFIG['RANDOM_SEED'])\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\n# ============================================================================\n# 3. CHUẨN BỊ DỮ LIỆU\n# ============================================================================\nBASE = Path(CONFIG['BASE_PATH'])\nTRAIN_DIR = BASE / 'Train'\n\nprint(\"\\n[1/6] Loading annotations...\")\ntrain_terms_df = pd.read_csv(TRAIN_DIR / 'train_terms.tsv', sep='\\t',header=None, names=['protein', 'term', 'ontology'])\nprotein_to_terms = train_terms_df.groupby('protein')['term'].apply(list).to_dict()\n\n# Lọc Top K labels\nterm_counts = Counter()\nfor terms in protein_to_terms.values(): term_counts.update(terms)\ntop_terms = [t for t, _ in term_counts.most_common(CONFIG['TOP_K_LABELS'])]\nterm_set = set(top_terms)\nfor protein in protein_to_terms:\n    protein_to_terms[protein] = [t for t in protein_to_terms[protein] if t in term_set]\nterm_to_idx = {term: idx for idx, term in enumerate(top_terms)}\n\nprint(\"\\n[2/6] Loading embeddings...\")\nesm2_base = f\"/kaggle/input/{CONFIG['ESM2_PATH']}\"\ntrain_ids = np.load(f\"{esm2_base}/train_ids.npy\", allow_pickle=True)\ntrain_embeds = np.load(f\"{esm2_base}/train_embeddings.npy\")\ntest_ids = np.load(f\"{esm2_base}/test_ids.npy\", allow_pickle=True)\ntest_embeds = np.load(f\"{esm2_base}/test_embeddings.npy\")\n\ntrain_dict = {str(pid): emb for pid, emb in zip(train_ids, train_embeds)}\ntest_dict = {str(pid): emb for pid, emb in zip(test_ids, test_embeds)}\nembed_dim = train_embeds.shape[1]\n\n# Tạo tập Train/Val\nvalid_proteins = [p for p in protein_to_terms.keys() if p in train_dict][:CONFIG['MAX_TRAIN_SAMPLES']]\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom sklearn.model_selection import train_test_split\nmlb = MultiLabelBinarizer(classes=range(len(top_terms)))\ny_labels = [[term_to_idx[t] for t in protein_to_terms.get(p, []) if t in term_to_idx] for p in valid_proteins]\n# Label Smoothing\ny_encoded = mlb.fit_transform(y_labels).astype(float) * 0.9 + 0.1/len(top_terms)\n\ntrain_proteins, val_proteins, y_train, y_val = train_test_split(valid_proteins, y_encoded, test_size=0.1, random_state=CONFIG['RANDOM_SEED'])\nprint(f\"   ✓ Train size: {len(train_proteins)}, Val size: {len(val_proteins)}\")\n\n# Clear RAM (GIỮ LẠI TEST_DICT ĐỂ DÙNG SAU)\ndel train_ids, train_embeds, test_ids, test_embeds, y_labels, y_encoded\ngc.collect()\n\n# ============================================================================\n# 4. XÂY DỰNG MODEL B (RESNET)\n# ============================================================================\nprint(\"\\n[3/6] Building ResNet Model...\")\n\nclass ResidualBlock(nn.Module):\n    def __init__(self, dim, dropout):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Linear(dim, dim), nn.BatchNorm1d(dim), nn.ReLU(), nn.Dropout(dropout),\n            nn.Linear(dim, dim), nn.BatchNorm1d(dim)\n        )\n        self.relu = nn.ReLU()\n    def forward(self, x): return self.relu(x + self.block(x))\n\nclass ResidualProteinModel(nn.Module):\n    def __init__(self, input_dim, output_dim, hidden_dim, dropout):\n        super().__init__()\n        self.input_layer = nn.Sequential(\n            nn.Linear(input_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(dropout)\n        )\n        self.res_blocks = nn.Sequential(\n            ResidualBlock(hidden_dim, dropout), ResidualBlock(hidden_dim, dropout)\n        )\n        self.output = nn.Linear(hidden_dim, output_dim)\n    def forward(self, x): return torch.sigmoid(self.output(self.res_blocks(self.input_layer(x))))\n\nmodel = ResidualProteinModel(embed_dim, len(top_terms), CONFIG['HIDDEN_DIM'], CONFIG['DROPOUT_RATE']).to(device)\n\n# ============================================================================\n# 5. HUẤN LUYỆN MODEL B (CÓ EARLY STOPPING)\n# ============================================================================\nprint(\"\\n[4/6] Training Model B...\")\ncriterion = nn.BCELoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=CONFIG['LEARNING_RATE'])\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=2)\n\n# Early Stopping vars\nbest_val_loss = float('inf')\npatience = 3\ncounter = 0\n\nfor epoch in range(CONFIG['EPOCHS']):\n    model.train()\n    indices = np.random.permutation(len(train_proteins))\n    epoch_loss = 0\n    \n    for i in range(0, len(indices), CONFIG['BATCH_SIZE']):\n        batch_idx = indices[i:i + CONFIG['BATCH_SIZE']]\n        batch_proteins = [train_proteins[j] for j in batch_idx]\n        X_batch = torch.FloatTensor([train_dict[p] for p in batch_proteins]).to(device)\n        y_batch = torch.FloatTensor(y_train[batch_idx]).to(device)\n        \n        optimizer.zero_grad()\n        loss = criterion(model(X_batch), y_batch)\n        loss.backward()\n        optimizer.step()\n        epoch_loss += loss.item()\n\n    # Validate\n    model.eval()\n    val_loss = 0\n    val_batches = 0\n    with torch.no_grad():\n        for i in range(0, len(val_proteins), CONFIG['BATCH_SIZE']):\n            batch_proteins = val_proteins[i:i + CONFIG['BATCH_SIZE']]\n            X_batch = torch.FloatTensor([train_dict[p] for p in batch_proteins]).to(device)\n            y_batch = torch.FloatTensor(y_val[i:i + CONFIG['BATCH_SIZE']]).to(device)\n            val_loss += criterion(model(X_batch), y_batch).item()\n            val_batches += 1\n            \n    val_loss_avg = val_loss/val_batches\n    scheduler.step(val_loss_avg)\n    print(f\"   Epoch {epoch+1}: Val Loss = {val_loss_avg:.4f}\")\n    \n    # Early Stopping Logic\n    if val_loss_avg < best_val_loss:\n        best_val_loss = val_loss_avg\n        counter = 0 # Reset counter\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"   ⛔ Early stopping triggered! Model converged.\")\n            break\n            \n    gc.collect()\n\n# ============================================================================\n# 6. DỰ ĐOÁN MODEL B (RAW) - ĐÃ SỬA LỖI NAME ERROR\n# ============================================================================\nprint(\"\\n[5/6] Predicting Model B...\")\nmodel.eval()\ntest_protein_ids = list(test_dict.keys())\n\n# Nếu Model B train thất bại (loss cao), code vẫn chạy tiếp để dùng Model A + External\nwith open('submission_model_B_raw.tsv', 'w', newline='') as f:\n    with torch.no_grad():\n        for start in tqdm(range(0, len(test_protein_ids), CONFIG['PREDICT_BATCH_SIZE'])):\n            batch_ids = test_protein_ids[start:start + CONFIG['PREDICT_BATCH_SIZE']]\n            X_batch = torch.FloatTensor([test_dict[p] for p in batch_ids]).to(device)\n            \n            # Predict\n            logits = model.output(model.res_blocks(model.input_layer(X_batch)))\n            outputs = torch.sigmoid(logits / CONFIG['TEMPERATURE']).cpu().numpy()\n            \n            for i, pid in enumerate(batch_ids):\n                probs = outputs[i]\n                # Lấy Top 150\n                top_indices = np.argsort(probs)[::-1][:150]\n                # NGƯỠNG 0.01 QUAN TRỌNG\n                confident = [idx for idx in top_indices if probs[idx] > CONFIG['MIN_CONFIDENCE']]\n                for idx in confident:\n                    f.write(f\"{pid}\\t{top_terms[idx]}\\t{probs[idx]:.3f}\\n\")\n\n# --- SỬA LỖI TẠI ĐÂY: KHÔNG DEL TRAIN_EMBEDS ---\ndel model, train_dict #, test_dict # Test dict dùng cho lần sau nếu cần\ngc.collect()\n\n# ============================================================================\n# 7. TRỘN TỔNG HỢP (A + B + BLAST + EXTERNAL)\n# ============================================================================\nprint(\"\\n[6/6] FINAL ENSEMBLE WITH REFERENCE MODEL\")\n\n# 1. Đọc Model A (0.241)\nprint(f\"   Reading Model A: {model_a_path}\")\nif model_a_path:\n    df_a = pd.read_csv(model_a_path, sep='\\t', header=None, names=['Id', 'Term', 'ScoreA'])\nelse:\n    df_a = pd.DataFrame(columns=['Id', 'Term', 'ScoreA'])\n\n# 2. Đọc Model B (Vừa chạy)\nprint(\"   Reading Model B...\")\nif os.path.exists('submission_model_B_raw.tsv'):\n    df_b = pd.read_csv('submission_model_B_raw.tsv', sep='\\t', header=None, names=['Id', 'Term', 'ScoreB'])\nelse:\n    df_b = pd.DataFrame(columns=['Id', 'Term', 'ScoreB'])\n\n# 3. Đọc BLAST\nprint(\"   Reading BLAST...\")\nif os.path.exists(CONFIG['BLAST_PATH']):\n    blast_chunks = []\n    for chunk in pd.read_csv(CONFIG['BLAST_PATH'], sep='\\t', header=None, names=['Id', 'Term', 'ScoreBlast'], chunksize=2000000):\n        blast_chunks.append(chunk)\n    df_blast = pd.concat(blast_chunks)\n    del blast_chunks\nelse:\n    df_blast = pd.DataFrame(columns=['Id', 'Term', 'ScoreBlast'])\n\n# 4. Đọc External (0.35)\nprint(f\"   Reading External Model: {EXTERNAL_PATH}\")\nif EXTERNAL_PATH and os.path.exists(EXTERNAL_PATH):\n    ext_chunks = []\n    for chunk in pd.read_csv(EXTERNAL_PATH, sep='\\t', header=None, names=['Id', 'Term', 'ScoreExt'], chunksize=2000000):\n        ext_chunks.append(chunk)\n    df_ext = pd.concat(ext_chunks)\n    del ext_chunks\nelse:\n    print(\"   ⚠️ Không tìm thấy External Model! Sẽ chỉ dùng Internal.\")\n    df_ext = pd.DataFrame(columns=['Id', 'Term', 'ScoreExt'])\n\n# --- MERGE PROCESS ---\nprint(\"   Merging Internal Models (A + B + BLAST)...\")\n# Trộn nội bộ trước: Internal = 0.4*A + 0.2*B + 0.4*BLAST\n# (Model B của bạn đang yếu nên để trọng số thấp 0.2, BLAST và A đáng tin hơn)\ninternal = pd.merge(df_a, df_b, on=['Id', 'Term'], how='outer')\ninternal = pd.merge(internal, df_blast, on=['Id', 'Term'], how='outer')\ninternal = internal.fillna(0)\n\ninternal['ScoreInternal'] = (internal['ScoreA'] * 0.4) + (internal['ScoreB'] * 0.2) + (internal['ScoreBlast'] * 0.4)\ninternal = internal[['Id', 'Term', 'ScoreInternal']]\n\n# --- FINAL BLEND WITH EXTERNAL ---\nprint(\"   Blending with External Reference...\")\n# Trộn với Model Ngoài\nfinal_merge = pd.merge(internal, df_ext, on=['Id', 'Term'], how='outer')\nfinal_merge = final_merge.fillna(0)\n\n# CÔNG THỨC: 60% External + 40% Internal\n# Đây là cách \"Khoa học\": Dùng model ngoài làm nòng cốt, model mình làm bổ trợ\nif len(df_ext) > 0:\n    final_merge['FinalScore'] = (final_merge['ScoreExt'] * 0.6) + (final_merge['ScoreInternal'] * 0.4)\nelse:\n    final_merge['FinalScore'] = final_merge['ScoreInternal']\n\n# --- SAVE ---\nprint(\"   Saving submission.tsv...\")\n# Lọc ngưỡng 0.01 (Giữ Recall cao)\nfinal_df = final_merge[final_merge['FinalScore'] > 0.01][['Id', 'Term', 'FinalScore']]\nfinal_df.to_csv('submission.tsv', sep='\\t', header=False, index=False)\n\nprint(f\"\\n✅ DONE! File created with {len(final_df)} rows.\")\nprint(\"👉 Với công thức này, bạn tận dụng được External Model (để lên 0.3xx) nhưng vẫn có 40% đóng góp từ model bạn tự xây.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T03:18:39.815803Z","iopub.execute_input":"2025-12-13T03:18:39.816195Z","iopub.status.idle":"2025-12-13T04:15:00.627592Z","shell.execute_reply.started":"2025-12-13T03:18:39.816164Z","shell.execute_reply":"2025-12-13T04:15:00.626134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport gc\n\n# ============================================================================\n# CẤU HÌNH ĐƯỜNG DẪN (BẠN CẦN KIỂM TRA KỸ)\n# ============================================================================\n\n# 1. Đường dẫn Model A của bạn (Điểm 0.241)\n# Code sẽ tự tìm trong thư mục my-best-model-a\nMY_MODEL_DIR = '/kaggle/input/my-best-model-a' \n\n# 2. Đường dẫn Model Ngoại lai (Điểm 0.35)\nEXTERNAL_MODEL_PATH = '/kaggle/input/external-high-score/submission.tsv' \n\n# Nếu bạn chưa upload hoặc không nhớ tên, hãy nhìn sang cột phải (Input), \n# copy đường dẫn của file 0.35 và dán vào biến EXTERNAL_MODEL_PATH ở trên.\n\nprint(\"=\"*80)\nprint(\"🚀 SUPER BLENDING: MY BEST MODEL (0.241) + EXTERNAL HIGH SCORE (0.35)\")\nprint(\"=\"*80)\n\n# --- 1. ĐỌC MODEL CỦA BẠN (0.241) ---\nprint(\"1. Reading My Model (0.241)...\")\nmy_model_files = [f for f in os.listdir(MY_MODEL_DIR) if f.endswith('.tsv') or f.endswith('.csv')]\nif not my_model_files:\n    # Fallback nếu tên file là submission.tsv nằm trực tiếp\n    my_model_path = os.path.join(MY_MODEL_DIR, 'submission.tsv')\nelse:\n    my_model_path = os.path.join(MY_MODEL_DIR, my_model_files[0])\n\nif os.path.exists(my_model_path):\n    df_mine = pd.read_csv(my_model_path, sep='\\t', header=None, names=['Id', 'Term', 'ScoreMine'])\n    print(f\"   -> Loaded {len(df_mine)} rows from {my_model_path}\")\nelse:\n    raise FileNotFoundError(f\" Không tìm thấy file model của bạn tại {MY_MODEL_DIR}\")\n\n# --- 2. ĐỌC MODEL 0.35 (EXTERNAL) ---\nprint(f\"2. Reading External Model (0.35)...\")\nif not os.path.exists(EXTERNAL_MODEL_PATH):\n    # Thử tìm tự động nếu người dùng chưa sửa path\n    input_dir = '/kaggle/input'\n    found = False\n    for root, dirs, files in os.walk(input_dir):\n        for file in files:\n            if file.endswith('.tsv') and 'submission' in file and root != MY_MODEL_DIR:\n                EXTERNAL_MODEL_PATH = os.path.join(root, file)\n                print(f\"   -> Auto-detected external file: {EXTERNAL_MODEL_PATH}\")\n                found = True\n                break\n        if found: break\n    \n    if not found:\n        raise FileNotFoundError(f\" CHƯA TÌM THẤY FILE 0.35! Hãy sửa biến EXTERNAL_MODEL_PATH trong code.\")\n\n# Đọc file External (Dùng chunk vì file này thường lớn)\next_chunks = []\ntry:\n    for chunk in pd.read_csv(EXTERNAL_MODEL_PATH, sep='\\t', header=None, names=['Id', 'Term', 'ScoreExt'], chunksize=2000000):\n        ext_chunks.append(chunk)\n    df_ext = pd.concat(ext_chunks)\n    del ext_chunks\n    print(f\"   -> Loaded {len(df_ext)} rows from External Model\")\nexcept Exception as e:\n    raise ValueError(f\" Lỗi khi đọc file External: {e}\")\n\n# --- 3. MERGE (TRỘN) ---\nprint(\"3. Merging two models...\")\n# Sử dụng Outer Join để lấy hết dự đoán của cả 2\nmerged = pd.merge(df_mine, df_ext, on=['Id', 'Term'], how='outer')\n\n# Điền 0 vào chỗ thiếu\nmerged['ScoreMine'] = merged['ScoreMine'].fillna(0)\nmerged['ScoreExt'] = merged['ScoreExt'].fillna(0)\n\n# --- 4. TÍNH ĐIỂM CUỐI CÙNG (TRỌNG SỐ) ---\nprint(\"4. Calculating Weighted Score...\")\n\n# CÔNG THỨC CHIẾN THẮNG:\n# Vì Model Ngoại (0.35) tốt hơn hẳn Model Bạn (0.241), ta phải tin nó nhiều hơn.\n# Tuy nhiên, Model Bạn đóng góp sự đa dạng (Diversity) để sửa lỗi cho Model Ngoại.\n# Tỉ lệ Vàng: 70% External + 30% Mine\n\nW_EXT = 0.75  # Tin model xịn 75%\nW_MINE = 0.25 # Tin model mình 25%\n\nmerged['FinalScore'] = (merged['ScoreExt'] * W_EXT) + (merged['ScoreMine'] * W_MINE)\n\n# --- 5. LỌC VÀ LƯU ---\nprint(\"5. Saving submission.tsv...\")\n\n# Ngưỡng lọc 0.01 là an toàn nhất để giữ Recall\nfinal_df = merged[merged['FinalScore'] > 0.01][['Id', 'Term', 'FinalScore']]\n\n# Sắp xếp lại cho đẹp (không bắt buộc nhưng tốt)\n# final_df = final_df.sort_values(['Id', 'FinalScore'], ascending=[True, False])\n\nfinal_df.to_csv('submission.tsv', sep='\\t', header=False, index=False)\n\nprint(\"\\n\" + \"=\"*80)\nprint(f\" DONE! File submission.tsv created with {len(final_df)} rows.\")\nprint(\"=\"*80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T02:25:14.166756Z","iopub.execute_input":"2025-12-13T02:25:14.167201Z","iopub.status.idle":"2025-12-13T02:25:16.956929Z","shell.execute_reply.started":"2025-12-13T02:25:14.167153Z","shell.execute_reply":"2025-12-13T02:25:16.955326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}