{"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":99552,"databundleVersionId":13190393,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Dataset Distribution\n\nBeing able to understand the distribution of the dataset is important for class balancing during training. Here I have visualized features and target distribution","metadata":{}},{"cell_type":"code","source":"# packages\n\n# standard\nimport numpy as np\nimport pandas as pd\nimport time\n\n# plots\nimport matplotlib.pyplot as plt\nimport plotly.express as px\nimport seaborn as sns\n\n# image\nimport pydicom as dicom\n\n# other stuff\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport ast","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-05T16:25:33.268833Z","iopub.execute_input":"2025-08-05T16:25:33.269135Z","iopub.status.idle":"2025-08-05T16:25:39.204783Z","shell.execute_reply.started":"2025-08-05T16:25:33.269113Z","shell.execute_reply":"2025-08-05T16:25:39.203809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# configs\npd.set_option('display.max_columns', None) # we want to display all columns in this notebook\npd.set_option('display.max_rows', 100) # increase number of displayed rows\npd.set_option('max_colwidth', None) # make full cells content visible\n\n# random seed\nmy_random_seed = 123\n\n# aesthetics\ndefault_color_1 = 'darkblue'\ndefault_color_2 = 'darkgreen'\ndefault_color_3 = 'darkred'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-05T16:26:28.358242Z","iopub.execute_input":"2025-08-05T16:26:28.358623Z","iopub.status.idle":"2025-08-05T16:26:28.365112Z","shell.execute_reply.started":"2025-08-05T16:26:28.358577Z","shell.execute_reply":"2025-08-05T16:26:28.364248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Data","metadata":{}},{"cell_type":"code","source":"# ===============================================================================\n# DATA LOADING\n# ===============================================================================\n\nprint(\"=\"*80)\nprint(\"RSNA INTRACRANIAL ANEURYSM DETECTION - DATASET ANALYSIS\")\nprint(\"=\"*80)\n\nt1 = time.time()\ndf_train = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\ndf_train_local = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train_localizers.csv')\nt2 = time.time()\nprint(f'Data loading time: {np.round(t2-t1,4)} seconds')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-05T16:26:30.579336Z","iopub.execute_input":"2025-08-05T16:26:30.58011Z","iopub.status.idle":"2025-08-05T16:26:30.64885Z","shell.execute_reply.started":"2025-08-05T16:26:30.580077Z","shell.execute_reply":"2025-08-05T16:26:30.647925Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================================================================\n# VISUALIZATIONS\n# ===============================================================================\n\nprint(\"\\n\" + \"=\"*50)\nprint(\"PART 1: VISUAL ANALYSIS\")\nprint(\"=\"*50)\n\n# Dataset Overview\nprint(f\"\\nDATASET SHAPES:\")\nprint(f\"Training data: {df_train.shape}\")\nprint(f\"Localizer data: {df_train_local.shape}\")\n\n# Basic statistics visualization\nfig, axes = plt.subplots(2, 2, figsize=(15, 10))\n\n# 1. Age distribution\naxes[0,0].hist(df_train['PatientAge'], bins=25, color=default_color_1, alpha=0.7)\naxes[0,0].set_title('Patient Age Distribution')\naxes[0,0].set_xlabel('Age')\naxes[0,0].set_ylabel('Frequency')\naxes[0,0].grid(True, alpha=0.3)\n\n# 2. Sex distribution\nsex_counts = df_train['PatientSex'].value_counts()\naxes[0,1].pie(sex_counts.values, labels=sex_counts.index, autopct='%1.1f%%', colors=['lightblue', 'lightpink'])\naxes[0,1].set_title('Patient Sex Distribution')\n\n# 3. Modality distribution\nmodality_counts = df_train['Modality'].value_counts()\naxes[1,0].bar(modality_counts.index, modality_counts.values, color=default_color_2)\naxes[1,0].set_title('Imaging Modality Distribution')\naxes[1,0].set_xlabel('Modality')\naxes[1,0].set_ylabel('Count')\naxes[1,0].tick_params(axis='x', rotation=45)\nfor i, v in enumerate(modality_counts.values):\n    axes[1,0].text(i, v + 10, str(v), ha='center')\n\n# 4. Aneurysm presence\naneurysm_counts = df_train['Aneurysm Present'].value_counts()\naxes[1,1].bar(['No Aneurysm', 'Aneurysm Present'], aneurysm_counts.values, color=[default_color_3, 'orange'])\naxes[1,1].set_title('Aneurysm Presence Distribution')\naxes[1,1].set_ylabel('Count')\nfor i, v in enumerate(aneurysm_counts.values):\n    axes[1,1].text(i, v + 20, str(v), ha='center')\n\nplt.tight_layout()\nplt.show()\n\n# Anatomical location analysis\nlocation_columns = [\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation'\n]\n\n# Location frequency (only positive cases)\nlocation_sums = df_train[location_columns].sum().sort_values(ascending=False)\n\nplt.figure(figsize=(12, 8))\nbars = plt.bar(range(len(location_sums)), location_sums.values, color='steelblue')\nplt.title('Aneurysm Frequency by Anatomical Location')\nplt.xlabel('Anatomical Location')\nplt.ylabel('Number of Aneurysms')\nplt.xticks(range(len(location_sums)), location_sums.index, rotation=45, ha='right')\n\n# Add value labels on bars\nfor bar, value in zip(bars, location_sums.values):\n    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, \n             str(value), ha='center', va='bottom', fontsize=9)\n\nplt.tight_layout()\nplt.show()\n\n# Modality vs Aneurysm presence\nplt.figure(figsize=(10, 6))\nmodality_aneurysm = pd.crosstab(df_train['Modality'], df_train['Aneurysm Present'])\nmodality_aneurysm_pct = modality_aneurysm.div(modality_aneurysm.sum(axis=1), axis=0) * 100\n\nax = modality_aneurysm_pct.plot(kind='bar', stacked=True, \n                                color=['lightcoral', 'lightgreen'],\n                                figsize=(10, 6))\nplt.title('Aneurysm Presence Rate by Imaging Modality')\nplt.xlabel('Modality')\nplt.ylabel('Percentage')\nplt.legend(['No Aneurysm', 'Aneurysm Present'])\nplt.xticks(rotation=45)\n\n# Add percentage labels\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%', label_type='center')\n\nplt.tight_layout()\nplt.show()\n\n# Localizer data analysis\nif not df_train_local.empty:\n    # Convert coordinates to dictionary if needed\n    if isinstance(df_train_local['coordinates'].iloc[0], str):\n        df_train_local['coordinates'] = df_train_local['coordinates'].map(ast.literal_eval)\n    \n    # Extract coordinates\n    df_train_local['x'] = df_train_local['coordinates'].map(lambda d: d['x'])\n    df_train_local['y'] = df_train_local['coordinates'].map(lambda d: d['y'])\n    \n    # Location distribution in localizer data\n    plt.figure(figsize=(12, 8))\n    location_local_counts = df_train_local['location'].value_counts()\n    bars = plt.bar(range(len(location_local_counts)), location_local_counts.values, color='darkorange')\n    plt.title('Localized Aneurysm Distribution by Anatomical Location')\n    plt.xlabel('Anatomical Location')\n    plt.ylabel('Count')\n    plt.xticks(range(len(location_local_counts)), location_local_counts.index, rotation=45, ha='right')\n    \n    # Add value labels\n    for bar, value in zip(bars, location_local_counts.values):\n        plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, \n                 str(value), ha='center', va='bottom')\n    \n    plt.tight_layout()\n    plt.show()\n    \n    # Spatial distribution of aneurysms\n    plt.figure(figsize=(12, 10))\n    scatter = plt.scatter(df_train_local['x'], df_train_local['y'], \n                         c=df_train_local['location'].astype('category').cat.codes, \n                         cmap='tab20', alpha=0.7, s=50)\n    plt.title('Spatial Distribution of Aneurysms (X-Y Coordinates)')\n    plt.xlabel('X Coordinate')\n    plt.ylabel('Y Coordinate')\n    \n    # Create custom legend\n    unique_locations = df_train_local['location'].unique()\n    legend_elements = [plt.Line2D([0], [0], marker='o', color='w', \n                                 markerfacecolor=plt.cm.tab20(i/len(unique_locations)), \n                                 markersize=8, label=loc) \n                      for i, loc in enumerate(unique_locations)]\n    plt.legend(handles=legend_elements, bbox_to_anchor=(1.05, 1), loc='upper left')\n    plt.tight_layout()\n    plt.show()\n\n        # Bounding box position analysis\n    fig, axes = plt.subplots(2, 2, figsize=(15, 10))\n    \n    # X-coordinate distribution\n    axes[0,0].hist(df_train_local['x'], bins=30, color='skyblue', alpha=0.7, edgecolor='black')\n    axes[0,0].set_title('Distribution of Aneurysm X-Coordinates')\n    axes[0,0].set_xlabel('X Coordinate (pixels)')\n    axes[0,0].set_ylabel('Frequency')\n    axes[0,0].grid(True, alpha=0.3)\n    \n    # Y-coordinate distribution\n    axes[0,1].hist(df_train_local['y'], bins=30, color='lightcoral', alpha=0.7, edgecolor='black')\n    axes[0,1].set_title('Distribution of Aneurysm Y-Coordinates')\n    axes[0,1].set_xlabel('Y Coordinate (pixels)')\n    axes[0,1].set_ylabel('Frequency')\n    axes[0,1].grid(True, alpha=0.3)\n    \n    # X-coordinate by location (box plot)\n    unique_locations_short = df_train_local['location'].unique()[:8]  # Limit for readability\n    data_subset = df_train_local[df_train_local['location'].isin(unique_locations_short)]\n    axes[1,0].boxplot([data_subset[data_subset['location'] == loc]['x'].values \n                       for loc in unique_locations_short], \n                      labels=[loc[:20] + '...' if len(loc) > 20 else loc \n                             for loc in unique_locations_short])\n    axes[1,0].set_title('X-Coordinate Distribution by Location (Top 8)')\n    axes[1,0].set_xlabel('Anatomical Location')\n    axes[1,0].set_ylabel('X Coordinate')\n    axes[1,0].tick_params(axis='x', rotation=45)\n    \n    # Y-coordinate by location (box plot)\n    axes[1,1].boxplot([data_subset[data_subset['location'] == loc]['y'].values \n                       for loc in unique_locations_short], \n                      labels=[loc[:20] + '...' if len(loc) > 20 else loc \n                             for loc in unique_locations_short])\n    axes[1,1].set_title('Y-Coordinate Distribution by Location (Top 8)')\n    axes[1,1].set_xlabel('Anatomical Location')\n    axes[1,1].set_ylabel('Y Coordinate')\n    axes[1,1].tick_params(axis='x', rotation=45)\n    \n    plt.tight_layout()\n    plt.show()\n    \n    # Heatmap of aneurysm positions\n    plt.figure(figsize=(12, 8))\n    \n    # Create 2D histogram\n    x_bins = 20\n    y_bins = 20\n    hist, x_edges, y_edges = np.histogram2d(df_train_local['x'], df_train_local['y'], \n                                           bins=[x_bins, y_bins])\n    \n    # Plot heatmap\n    plt.imshow(hist.T, origin='lower', cmap='YlOrRd', aspect='auto',\n              extent=[x_edges[0], x_edges[-1], y_edges[0], y_edges[-1]])\n    plt.colorbar(label='Aneurysm Count')\n    plt.title('Spatial Density Heatmap of Aneurysm Locations')\n    plt.xlabel('X Coordinate (pixels)')\n    plt.ylabel('Y Coordinate (pixels)')\n    plt.show()\n\n\nprint(\"\\nVisual analysis complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-05T16:29:37.18471Z","iopub.execute_input":"2025-08-05T16:29:37.185094Z","iopub.status.idle":"2025-08-05T16:29:40.792243Z","shell.execute_reply.started":"2025-08-05T16:29:37.185069Z","shell.execute_reply":"2025-08-05T16:29:40.791268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================================================================\n# TEXT SUMMARY\n# ===============================================================================\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"PART 2: TEXT SUMMARY\")\nprint(\"=\"*80)\n\ndef generate_dataset_summary():\n    \"\"\"Generate a comprehensive text summary for LLM consumption\"\"\"\n    \n    summary = []\n    summary.append(\"RSNA INTRACRANIAL ANEURYSM DETECTION - DATASET SUMMARY\")\n    summary.append(\"=\" * 60)\n    \n    # Basic dataset info\n    summary.append(f\"\\nDATASET OVERVIEW:\")\n    summary.append(f\"- Training samples: {len(df_train):,}\")\n    summary.append(f\"- Localizer samples: {len(df_train_local):,}\")\n    summary.append(f\"- Features: {len(df_train.columns)}\")\n    summary.append(f\"- Target variable: Aneurysm Present (binary)\")\n    \n    # Patient demographics\n    summary.append(f\"\\nPATIENT DEMOGRAPHICS:\")\n    summary.append(f\"- Age range: {df_train['PatientAge'].min()}-{df_train['PatientAge'].max()} years\")\n    summary.append(f\"- Mean age: {df_train['PatientAge'].mean():.1f} ± {df_train['PatientAge'].std():.1f} years\")\n    summary.append(f\"- Median age: {df_train['PatientAge'].median():.0f} years\")\n    \n    sex_counts = df_train['PatientSex'].value_counts()\n    for sex, count in sex_counts.items():\n        pct = (count / len(df_train)) * 100\n        summary.append(f\"- {sex}: {count:,} ({pct:.1f}%)\")\n    \n    # Imaging modalities\n    summary.append(f\"\\nIMAGING MODALITIES:\")\n    modality_counts = df_train['Modality'].value_counts().sort_values(ascending=False)\n    for modality, count in modality_counts.items():\n        pct = (count / len(df_train)) * 100\n        summary.append(f\"- {modality}: {count:,} ({pct:.1f}%)\")\n    \n    # Target distribution\n    summary.append(f\"\\nTARGET DISTRIBUTION:\")\n    aneurysm_counts = df_train['Aneurysm Present'].value_counts()\n    no_aneurysm = aneurysm_counts[0]\n    aneurysm = aneurysm_counts[1]\n    summary.append(f\"- No aneurysm: {no_aneurysm:,} ({(no_aneurysm/len(df_train)*100):.1f}%)\")\n    summary.append(f\"- Aneurysm present: {aneurysm:,} ({(aneurysm/len(df_train)*100):.1f}%)\")\n    summary.append(f\"- Class balance ratio: {no_aneurysm/aneurysm:.2f}:1 (negative:positive)\")\n    \n    # Anatomical locations\n    summary.append(f\"\\nANATOMICAL LOCATION FREQUENCY:\")\n    location_sums = df_train[location_columns].sum().sort_values(ascending=False)\n    for location, count in location_sums.items():\n        pct_of_total = (count / len(df_train)) * 100\n        pct_of_positive = (count / aneurysm) * 100 if aneurysm > 0 else 0\n        summary.append(f\"- {location}: {count} ({pct_of_total:.2f}% of all, {pct_of_positive:.1f}% of positive cases)\")\n    \n    # Modality vs aneurysm analysis\n    summary.append(f\"\\nMODALITY vs ANEURYSM PRESENCE:\")\n    for modality in df_train['Modality'].unique():\n        modality_data = df_train[df_train['Modality'] == modality]\n        total_modality = len(modality_data)\n        aneurysm_modality = modality_data['Aneurysm Present'].sum()\n        aneurysm_rate = (aneurysm_modality / total_modality) * 100\n        summary.append(f\"- {modality}: {aneurysm_modality}/{total_modality} positive ({aneurysm_rate:.1f}%)\")\n    \n    # Localizer data analysis\n    if not df_train_local.empty:\n        summary.append(f\"\\nLOCALIZER DATA ANALYSIS:\")\n        summary.append(f\"- Total localized aneurysms: {len(df_train_local):,}\")\n        summary.append(f\"- Unique series with localizations: {df_train_local['SeriesInstanceUID'].nunique():,}\")\n        \n        # Convert coordinates if needed\n        if isinstance(df_train_local['coordinates'].iloc[0], str):\n            df_train_local_temp = df_train_local.copy()\n            df_train_local_temp['coordinates'] = df_train_local_temp['coordinates'].map(ast.literal_eval)\n            df_train_local_temp['x'] = df_train_local_temp['coordinates'].map(lambda d: d['x'])\n            df_train_local_temp['y'] = df_train_local_temp['coordinates'].map(lambda d: d['y'])\n        else:\n            df_train_local_temp = df_train_local.copy()\n        \n        summary.append(f\"- X-coordinate range: {df_train_local_temp['x'].min():.1f} to {df_train_local_temp['x'].max():.1f}\")\n        summary.append(f\"- Y-coordinate range: {df_train_local_temp['y'].min():.1f} to {df_train_local_temp['y'].max():.1f}\")\n        \n        summary.append(f\"\\nLOCALIZED ANEURYSM DISTRIBUTION:\")\n        location_local_counts = df_train_local['location'].value_counts().sort_values(ascending=False)\n        for location, count in location_local_counts.items():\n            pct = (count / len(df_train_local)) * 100\n            summary.append(f\"- {location}: {count} ({pct:.1f}%)\")\n    \n    # Data quality insights\n    summary.append(f\"\\nDATA QUALITY INSIGHTS:\")\n    summary.append(f\"- Missing values in training data: {df_train.isnull().sum().sum()}\")\n    summary.append(f\"- Missing values in localizer data: {df_train_local.isnull().sum().sum()}\")\n    \n    # Multiple aneurysms analysis\n    aneurysm_per_case = df_train[location_columns].sum(axis=1)\n    multiple_aneurysms = (aneurysm_per_case > 1).sum()\n    summary.append(f\"- Cases with multiple aneurysms: {multiple_aneurysms} ({(multiple_aneurysms/aneurysm*100):.1f}% of positive cases)\")\n    summary.append(f\"- Maximum aneurysms per case: {aneurysm_per_case.max()}\")\n    \n    # Coverage analysis\n    total_positive_cases = df_train['Aneurysm Present'].sum()\n    cases_with_localization = df_train[df_train['SeriesInstanceUID'].isin(df_train_local['SeriesInstanceUID'].unique())]['Aneurysm Present'].sum()\n    coverage = (cases_with_localization / total_positive_cases) * 100 if total_positive_cases > 0 else 0\n    \n    summary.append(f\"\\nLOCALIZATION COVERAGE:\")\n    summary.append(f\"- Positive cases with localization data: {cases_with_localization}/{total_positive_cases} ({coverage:.1f}%)\")\n\n        # Bounding box position statistics\n    summary.append(f\"\\nBOUNDING BOX POSITION STATISTICS:\")\n    summary.append(f\"- X-coordinate statistics:\")\n    summary.append(f\"  * Mean: {df_train_local_temp['x'].mean():.1f} ± {df_train_local_temp['x'].std():.1f}\")\n    summary.append(f\"  * Median: {df_train_local_temp['x'].median():.1f}\")\n    summary.append(f\"  * Range: {df_train_local_temp['x'].min():.1f} - {df_train_local_temp['x'].max():.1f}\")\n    summary.append(f\"  * IQR: {df_train_local_temp['x'].quantile(0.25):.1f} - {df_train_local_temp['x'].quantile(0.75):.1f}\")\n    \n    summary.append(f\"- Y-coordinate statistics:\")\n    summary.append(f\"  * Mean: {df_train_local_temp['y'].mean():.1f} ± {df_train_local_temp['y'].std():.1f}\")\n    summary.append(f\"  * Median: {df_train_local_temp['y'].median():.1f}\")\n    summary.append(f\"  * Range: {df_train_local_temp['y'].min():.1f} - {df_train_local_temp['y'].max():.1f}\")\n    summary.append(f\"  * IQR: {df_train_local_temp['y'].quantile(0.25):.1f} - {df_train_local_temp['y'].quantile(0.75):.1f}\")\n    \n    # Position analysis by anatomical location\n    summary.append(f\"\\nPOSITION ANALYSIS BY ANATOMICAL LOCATION:\")\n    for location in df_train_local['location'].value_counts().head(5).index:  # Top 5 locations\n        location_data = df_train_local_temp[df_train_local_temp['location'] == location]\n        x_mean = location_data['x'].mean()\n        x_std = location_data['x'].std()\n        y_mean = location_data['y'].mean()\n        y_std = location_data['y'].std()\n        summary.append(f\"- {location}:\")\n        summary.append(f\"  * X: {x_mean:.1f} ± {x_std:.1f}, Y: {y_mean:.1f} ± {y_std:.1f}\")\n        summary.append(f\"  * Sample size: {len(location_data)}\")\n    \n    # Spatial clustering analysis\n    x_clusters = []\n    y_clusters = []\n    \n    # Simple quartile-based clustering\n    x_q1, x_q3 = df_train_local_temp['x'].quantile([0.25, 0.75])\n    y_q1, y_q3 = df_train_local_temp['y'].quantile([0.25, 0.75])\n    \n    # Count aneurysms in different quadrants\n    left_upper = len(df_train_local_temp[(df_train_local_temp['x'] <= x_q1) & (df_train_local_temp['y'] >= y_q3)])\n    right_upper = len(df_train_local_temp[(df_train_local_temp['x'] >= x_q3) & (df_train_local_temp['y'] >= y_q3)])\n    left_lower = len(df_train_local_temp[(df_train_local_temp['x'] <= x_q1) & (df_train_local_temp['y'] <= y_q1)])\n    right_lower = len(df_train_local_temp[(df_train_local_temp['x'] >= x_q3) & (df_train_local_temp['y'] <= y_q1)])\n    center = len(df_train_local_temp) - (left_upper + right_upper + left_lower + right_lower)\n    \n    summary.append(f\"\\nSPATIAL DISTRIBUTION QUADRANTS:\")\n    summary.append(f\"- Left-Upper: {left_upper} ({left_upper/len(df_train_local_temp)*100:.1f}%)\")\n    summary.append(f\"- Right-Upper: {right_upper} ({right_upper/len(df_train_local_temp)*100:.1f}%)\")\n    summary.append(f\"- Left-Lower: {left_lower} ({left_lower/len(df_train_local_temp)*100:.1f}%)\")\n    summary.append(f\"- Right-Lower: {right_lower} ({right_lower/len(df_train_local_temp)*100:.1f}%)\")\n    summary.append(f\"- Center: {center} ({center/len(df_train_local_temp)*100:.1f}%)\")\n\n    \n    return \"\\n\".join(summary)\n\n# Generate and print the summary\ndataset_summary = generate_dataset_summary()\nprint(dataset_summary)\n\n# Save summary to text file (optional)\n# with open('dataset_summary.txt', 'w') as f:\n#     f.write(dataset_summary)\n\nprint(f\"\\n{'='*80}\")\nprint(\"ANALYSIS COMPLETE\")\nprint(f\"{'='*80}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-05T16:30:39.948692Z","iopub.execute_input":"2025-08-05T16:30:39.948985Z","iopub.status.idle":"2025-08-05T16:30:40.010173Z","shell.execute_reply.started":"2025-08-05T16:30:39.948966Z","shell.execute_reply":"2025-08-05T16:30:40.009051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}