{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14084779,"sourceType":"competition"},{"sourceId":268508255,"sourceType":"kernelVersion"},{"sourceId":269339911,"sourceType":"kernelVersion"},{"sourceId":270571028,"sourceType":"kernelVersion"},{"sourceId":271888783,"sourceType":"kernelVersion"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false},"papermill":{"default_parameters":{},"duration":2172.733498,"end_time":"2025-10-30T04:20:53.462753","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-10-30T03:44:40.729255","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Credit goes to this author and notebook\n\nhttps://www.kaggle.com/code/analyticaobscura/cafa-6-decoding-protein-mysteries \n\n-  here trying weighted average models\n-  meta ensemble model is upcoming ","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nfrom tqdm.auto import tqdm\nfrom typing import Callable, Optional, Dict, Any\nfrom multiprocessing import Pool, cpu_count\nimport gc\n\ndef process_single_chunk(args):\n    \"\"\"Process a single chunk - used for parallel processing\"\"\"\n    start, end, all_keys, file_paths, weights, method, num_models = args\n    \n    key_chunk = all_keys[start:end]\n    result = pd.DataFrame({'key': key_chunk})\n\n    # Load scores from all models\n    for idx, path in enumerate(file_paths):\n        model_scores = []\n        for chunk in pd.read_csv(path, sep='\\t', header=None,\n                                 names=['protein', 'go_term', 'score'],\n                                 dtype={'protein': str, 'go_term': str, 'score': float},\n                                 chunksize=1_000_000):\n            chunk['key'] = chunk['protein'] + '_' + chunk['go_term']\n            filtered = chunk[chunk['key'].isin(key_chunk)][['key', 'score']]\n            filtered = filtered.rename(columns={'score': f'score_{idx}'})\n            model_scores.append(filtered)\n        if model_scores:\n            model_df = pd.concat(model_scores, ignore_index=True)\n            model_df = model_df.groupby('key', as_index=False).mean()\n            result = result.merge(model_df, on='key', how='left')\n\n    score_cols = [col for col in result.columns if col.startswith('score_')]\n    result[score_cols] = result[score_cols].fillna(0)\n\n    # Calculate ensemble\n    if method == 'median':\n        result['final_score'] = result[score_cols].median(axis=1)\n    elif method == 'weighted_average':\n        result['final_score'] = sum(result[f'score_{i}'] * weights[i] for i in range(num_models))\n    elif method == 'rank_average':\n        for i in range(num_models):\n            result[f'rank_{i}'] = result[f'score_{i}'].rank(pct=True)\n        result['final_score'] = sum(result[f'rank_{i}'] * weights[i] for i in range(num_models))\n    elif method == 'all':\n        result['median_score'] = result[score_cols].median(axis=1)\n        result['weighted_avg'] = sum(result[f'score_{i}'] * weights[i] for i in range(num_models))\n        for i in range(num_models):\n            result[f'rank_{i}'] = result[f'score_{i}'].rank(pct=True)\n        result['rank_avg'] = sum(result[f'rank_{i}'] * weights[i] for i in range(num_models))\n        result['final_score'] = (result['median_score'] * 0.25 + \n                                result['weighted_avg'] * 0.40 + \n                                result['rank_avg'] * 0.35)\n\n    result['protein'], result['go_term'] = zip(*result['key'].str.rsplit('_', n=1))\n    return result[['protein', 'go_term', 'final_score']]\n\n\ndef stacking_ensemble_fast(\n    file_paths, \n    weights=None, \n    method='all', \n    output_path='submission.tsv', \n    chunksize=5_000_000,\n    n_jobs=-1,\n    oof_callback: Optional[Callable] = None,\n    oof_data: Optional[Dict[str, Any]] = None\n):\n    \"\"\"OPTIMIZED stacking ensemble with parallel processing\"\"\"\n    \n    if weights is None:\n        weights = [1.0 / len(file_paths)] * len(file_paths)\n    else:\n        weights = np.array(weights) / np.array(weights).sum()\n    \n    if n_jobs == -1:\n        n_jobs = max(1, cpu_count() - 1)\n    \n    print(f\"Models: {len(file_paths)} | Weights: {weights} | Method: {method}\")\n    print(f\"Parallel jobs: {n_jobs} | Chunk size: {chunksize:,}\")\n\n    # Step 1: Collect unique keys FASTER\n    print(\"\\nScanning files...\")\n    all_keys = set()\n    for path in tqdm(file_paths, desc=\"Files\"):\n        for chunk in pd.read_csv(path, sep='\\t', header=None,\n                                 names=['protein', 'go_term', 'score'],\n                                 dtype={'protein': str, 'go_term': str},\n                                 usecols=[0, 1],  # Only read first 2 columns\n                                 chunksize=5_000_000):\n            chunk = chunk.dropna()\n            keys = chunk['protein'] + '_' + chunk['go_term']\n            all_keys.update(keys.values)\n            del chunk, keys\n            gc.collect()\n\n    all_keys = sorted(all_keys)\n    print(f\"Total predictions: {len(all_keys):,}\")\n\n    # Step 2: Process chunks in parallel\n    print(\"\\nProcessing chunks in parallel...\")\n    \n    chunk_args = []\n    for start in range(0, len(all_keys), chunksize):\n        end = min(start + chunksize, len(all_keys))\n        chunk_args.append((start, end, all_keys, file_paths, weights, method, len(file_paths)))\n    \n    # Parallel processing\n    if n_jobs > 1:\n        with Pool(n_jobs) as pool:\n            results = list(tqdm(\n                pool.imap(process_single_chunk, chunk_args),\n                total=len(chunk_args),\n                desc=\"Chunks\"\n            ))\n    else:\n        results = [process_single_chunk(args) for args in tqdm(chunk_args, desc=\"Chunks\")]\n    \n    # Step 3: Combine results\n    print(\"\\nCombining results...\")\n    final_df = pd.concat(results, ignore_index=True)\n    del results\n    gc.collect()\n    \n    # Step 4: OOF validation\n    oof_metrics = None\n    if oof_callback and oof_data:\n        print(\"\\nOOF Validation...\")\n        if 'proteins' in oof_data:\n            oof_predictions = final_df[final_df['protein'].isin(oof_data['proteins'])].copy()\n            print(f\"OOF predictions: {len(oof_predictions):,}\")\n            oof_metrics = oof_callback(oof_predictions, oof_data)\n            print(f\"F-max: {oof_metrics.get('f_max', 0):.4f} | Threshold: {oof_metrics.get('best_threshold', 0):.3f}\")\n    \n    # Step 5: Save\n    print(f\"\\nSaving to {output_path}...\")\n    final_df.to_csv(output_path, sep='\\t', index=False, header=False)\n    print(f\"✓ Done! {len(final_df):,} predictions saved\\n\")\n    \n    return final_df, oof_metrics\n\n\ndef cafa_oof_callback(oof_predictions: pd.DataFrame, oof_data: Dict[str, Any]) -> Dict[str, float]:\n    \"\"\"Calculate F-max and CAFA metrics\"\"\"\n    labels = oof_data['labels']\n    merged = oof_predictions.merge(labels, on=['protein', 'go_term'], how='outer')\n    merged['final_score'] = merged['final_score'].fillna(0)\n    merged['label'] = merged['label'].fillna(0)\n    \n    thresholds = oof_data.get('thresholds', np.arange(0.01, 1.0, 0.01))\n    best_f1, best_threshold = 0, 0\n    precision_list, recall_list = [], []\n    \n    for threshold in thresholds:\n        pred_binary = (merged['final_score'] >= threshold).astype(int)\n        true_binary = merged['label'].astype(int)\n        \n        tp = ((pred_binary == 1) & (true_binary == 1)).sum()\n        fp = ((pred_binary == 1) & (true_binary == 0)).sum()\n        fn = ((pred_binary == 0) & (true_binary == 1)).sum()\n        \n        precision = tp / (tp + fp) if (tp + fp) > 0 else 0\n        recall = tp / (tp + fn) if (tp + fn) > 0 else 0\n        f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0\n        \n        precision_list.append(precision)\n        recall_list.append(recall)\n        \n        if f1 > best_f1:\n            best_f1, best_threshold = f1, threshold\n    \n    return {\n        'f_max': best_f1,\n        'best_threshold': best_threshold,\n        'mean_precision': np.mean(precision_list),\n        'mean_recall': np.mean(recall_list),\n        'auc_pr': np.trapz(precision_list, recall_list)\n    }\n\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-11-10T06:29:42.548925Z","iopub.execute_input":"2025-11-10T06:29:42.549276Z","iopub.status.idle":"2025-11-10T06:29:44.864413Z","shell.execute_reply.started":"2025-11-10T06:29:42.549244Z","shell.execute_reply":"2025-11-10T06:29:44.862092Z"},"papermill":{"duration":2165.690556,"end_time":"2025-10-30T04:20:51.727945","exception":false,"start_time":"2025-10-30T03:44:46.037389","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# =============================================================================\n# USAGE - FAST VERSION\n# =============================================================================\n\nfile_paths = [\n    '/kaggle/input/cafa-6-t5-embeddings-with-ensemble/submission.tsv',\n    '/kaggle/input/cafa-6-predictions/submission.tsv'\n]\n\nweights = [0.35, 0.30]\n\n# Fast ensemble with parallel processing\nresult, _ = stacking_ensemble_fast(\n    file_paths=file_paths,\n    weights=weights,\n    method='all',\n    output_path='submission.tsv',\n    chunksize=5_000_000,  # Larger = faster\n    n_jobs=-1  # Use all CPUs\n)\n","metadata":{"papermill":{"duration":0.001724,"end_time":"2025-10-30T04:20:51.73418","exception":false,"start_time":"2025-10-30T04:20:51.732456","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T06:29:44.865665Z","iopub.execute_input":"2025-11-10T06:29:44.867249Z"}},"outputs":[],"execution_count":null}]}