2aba/week2_analysis.ipynb
2026-05-08 10:39:56 +03:00

542 lines
24 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"id": "849d0fe9d43b3ad2",
"metadata": {},
"source": [
"# Анализ данных пассажиров Титаника\n",
"\n",
"**Источник данных:** Kaggle - Titanic: Machine Learning from Disaster\n",
"**Цель анализа:** Изучить факторы, влияющие на выживаемость пассажиров\n",
"\n",
"**Дата анализа:** 07.05.2026"
]
},
{
"cell_type": "markdown",
"id": "7b68a95f07cd5093",
"metadata": {},
"source": [
"# Импорт библиотек (изменен стиль импорта)\n",
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"from tqdm import tqdm\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# Настройка отображения\n",
"%matplotlib inline\n",
"plt.rcParams['figure.figsize'] = (12, 6)\n",
"plt.style.use('seaborn-v0_8-darkgrid')\n",
"sns.set_palette(\"husl\")\n",
"\n",
"print(\"✅ Библиотеки загружены\")\n",
"\n",
"# Загрузка встроенного датасета Titanic\n",
"df = sns.load_dataset('titanic')\n",
"print(f\"✅ Загружено {len(df)} записей о пассажирах\")\n",
"df.head(10) # Вывод в последней строке ячейки"
]
},
{
"cell_type": "markdown",
"id": "82fb50b6dfcb725a",
"metadata": {},
"source": [
"print(\"📊 ПЕРВИЧНЫЙ АНАЛИЗ ДАННЫХ\")\n",
"print(\"=\" * 50)\n",
"\n",
"print(\"\\n1. Информация о данных (df.info()):\")\n",
"print(df.info())\n",
"\n",
"print(\"\\n2. Статистическое описание (df.describe()):\")\n",
"print(df.describe())\n",
"\n",
"# Дополнительный анализ\n",
"print(\"\\n3. Анализ выживаемости по полу (изменено от примера):\")\n",
"print(pd.crosstab(df['sex'], df['alive'], normalize='index') * 100)"
]
},
{
"cell_type": "markdown",
"id": "8624dc43d9bf124d",
"metadata": {},
"source": [
"# Анализ пропущенных значений\n",
"missing_data = df.isnull().sum()\n",
"missing_percent = (missing_data / len(df)) * 100\n",
"\n",
"missing_df = pd.DataFrame({\n",
" 'Кол-во пропусков': missing_data,\n",
" 'Процент': missing_percent\n",
"}).sort_values('Кол-во пропусков', ascending=False)\n",
"\n",
"print(\"📊 АНАЛИЗ ПРОПУЩЕННЫХ ДАННЫХ\")\n",
"print(\"=\" * 40)\n",
"print(missing_df[missing_df['Кол-во пропусков'] > 0])\n",
"\n",
"# Заполнение пропусков\n",
"df['age'].fillna(df['age'].median(), inplace=True)\n",
"df['embarked'].fillna(df['embarked'].mode()[0], inplace=True)\n",
"df.drop('deck', axis=1, inplace=True)\n",
"\n",
"print(f\"\\n✅ Пропуски заполнены. Размер данных: {df.shape}\")"
]
},
{
"cell_type": "markdown",
"id": "ecea1247c0fbb76f",
"metadata": {},
"source": [
"# Гистограмма распределения возрастов\n",
"plt.figure(figsize=(12, 6))\n",
"\n",
"# Разделение по выжившим/погибшим\n",
"survived = df[df['survived'] == 1]['age']\n",
"died = df[df['survived'] == 0]['age']\n",
"\n",
"plt.hist(survived, bins=20, alpha=0.7, label='Выжившие', color='green', edgecolor='black')\n",
"plt.hist(died, bins=20, alpha=0.7, label='Погибшие', color='red', edgecolor='black')\n",
"\n",
"plt.xlabel('Возраст', fontsize=12)\n",
"plt.ylabel('Количество пассажиров', fontsize=12)\n",
"plt.title('Распределение возрастов пассажиров: Выжившие vs Погибшие', fontsize=14)\n",
"plt.axvline(survived.mean(), color='darkgreen', linestyle='--',\n",
" label=f'Ср. возраст выживших: {survived.mean():.1f}', alpha=0.8)\n",
"plt.axvline(died.mean(), color='darkred', linestyle='--',\n",
" label=f'Ср. возраст погибших: {died.mean():.1f}', alpha=0.8)\n",
"plt.legend()\n",
"plt.grid(alpha=0.3)\n",
"plt.show()\n",
"\n",
"print(f\"\\n📊 Вывод: Средний возраст выживших ({survived.mean():.1f}) выше, чем погибших ({died.mean():.1f})\")"
]
},
{
"cell_type": "markdown",
"id": "a2e54f37810622bd",
"metadata": {},
"source": [
"# Scatterplot: возраст vs стоимость билета\n",
"plt.figure(figsize=(12, 6))\n",
"\n",
"# Разные цвета для выживших и погибших\n",
"colors = df['survived'].map({0: 'red', 1: 'green'})\n",
"plt.scatter(df['age'], df['fare'], c=colors, alpha=0.6, s=50)\n",
"\n",
"plt.xlabel('Возраст (лет)', fontsize=12)\n",
"plt.ylabel('Стоимость билета ($)', fontsize=12)\n",
"plt.title('Зависимость стоимости билета от возраста пассажира', fontsize=14)\n",
"\n",
"# Добавление регрессионной линии для общего тренда\n",
"z = np.polyfit(df['age'].dropna(), df['fare'].dropna(), 1)\n",
"p = np.poly1d(z)\n",
"plt.plot(df['age'].dropna().sort_values(), p(df['age'].dropna().sort_values()),\n",
" 'b--', alpha=0.8, label='Тренд')\n",
"\n",
"# Легенда\n",
"from matplotlib.patches import Patch\n",
"legend_elements = [Patch(facecolor='green', alpha=0.6, label='Выжившие'),\n",
" Patch(facecolor='red', alpha=0.6, label='Погибшие')]\n",
"plt.legend(handles=legend_elements)\n",
"plt.grid(alpha=0.3)\n",
"plt.show()\n",
"\n",
"print(f\"\\n📊 Корреляция между возрастом и стоимостью билета: {df['age'].corr(df['fare']):.3f}\")"
]
},
{
"cell_type": "markdown",
"id": "85009f4a16158b88",
"metadata": {},
"source": [
"# Boxplot: класс билета vs возраст\n",
"plt.figure(figsize=(10, 6))\n",
"\n",
"# Изменен порядок классов и добавлены цвета\n",
"order = [1, 2, 3]\n",
"sns.boxplot(x='class', y='age', data=df, order=order, palette='Set2')\n",
"\n",
"plt.xlabel('Класс билета (1 - лучший, 3 - худший)', fontsize=12)\n",
"plt.ylabel('Возраст пассажира', fontsize=12)\n",
"plt.title('Распределение возрастов по классам билетов', fontsize=14)\n",
"\n",
"# Добавление средних значений\n",
"for i, class_val in enumerate([1, 2, 3]):\n",
" mean_age = df[df['pclass'] == class_val]['age'].mean()\n",
" plt.text(i, mean_age + 1, f'Ср.: {mean_age:.1f}', ha='center', fontsize=10)\n",
"\n",
"plt.grid(axis='y', alpha=0.3)\n",
"plt.show()\n",
"\n",
"print(\"\\n📊 Статистика по классам:\")\n",
"print(df.groupby('pclass')['age'].agg(['mean', 'median', 'std']).round(1))"
]
},
{
"cell_type": "markdown",
"id": "ea8315adea678f3d",
"metadata": {},
"source": [
"from tqdm import tqdm\n",
"import time\n",
"\n",
"# Симуляция обработки данных с прогресс-баром\n",
"print(\"🔄 Анализ данных пассажиров с прогресс-баром...\")\n",
"\n",
"# Группировка пассажиров и анализ\n",
"results = []\n",
"for ticket_class in tqdm(df['pclass'].unique(), desc=\"Анализ классов\", unit=\"класс\", colour='green'):\n",
" class_data = df[df['pclass'] == ticket_class]\n",
" time.sleep(0.1) # Симуляция вычислений\n",
"\n",
" results.append({\n",
" 'класс': ticket_class,\n",
" 'кол-во_пассажиров': len(class_data),\n",
" 'выживаемость_%': (class_data['survived'].sum() / len(class_data)) * 100,\n",
" 'средний_возраст': class_data['age'].mean(),\n",
" 'средняя_цена_билета': class_data['fare'].mean()\n",
" })\n",
"\n",
"result_df = pd.DataFrame(results)\n",
"print(\"\\n✅ Анализ завершен!\")\n",
"result_df"
]
},
{
"cell_type": "markdown",
"id": "12ede302d3d767ef",
"metadata": {},
"source": [
"# Тепловая карта корреляций\n",
"plt.figure(figsize=(10, 8))\n",
"\n",
"# Выбираем числовые колонки\n",
"numeric_cols = ['survived', 'pclass', 'age', 'sibsp', 'parch', 'fare']\n",
"corr_matrix = df[numeric_cols].corr()\n",
"\n",
"sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0,\n",
" square=True, linewidths=2, fmt='.3f',\n",
" cbar_kws={'label': 'Коэффициент корреляции'})\n",
"plt.title('Матрица корреляции параметров выживаемости', fontsize=14)\n",
"plt.show()\n",
"\n",
"print(\"\\n📈 Ключевые корреляции с выживаемостью:\")\n",
"print(f\" Класс билета: {corr_matrix.loc['survived', 'pclass']:.3f} (отрицательная = лучше класс = выше шанс)\")\n",
"print(f\" Цена билета: {corr_matrix.loc['survived', 'fare']:.3f} (положительная = дороже билет = выше шанс)\")\n",
"print(f\" Возраст: {corr_matrix.loc['survived', 'age']:.3f} (слабая корреляция)\")"
]
},
{
"cell_type": "markdown",
"id": "870930145d19645",
"metadata": {},
"source": [
"# =========================\n",
"# КОД СО ВСЕМИ ГРАФИКАМИ\n",
"# =========================\n",
"\n",
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"# Загрузка данных\n",
"df = sns.load_dataset('titanic')\n",
"\n",
"df['age'] = df['age'].fillna(df['age'].median())\n",
"df['fare'] = df['fare'].fillna(df['fare'].median())\n",
"df['embarked'] = df['embarked'].fillna(df['embarked'].mode()[0])\n",
"\n",
"df = df.drop('deck', axis=1)\n",
"\n",
"print(\"=\" * 60)\n",
"print(\"📊 ПОСТРОЕНИЕ ГРАФИКОВ ДЛЯ АНАЛИЗА ДАННЫХ ТИТАНИКА\")\n",
"print(\"=\" * 60)\n",
"print(f\"✅ Загружено {len(df)} пассажиров\")\n",
"print(f\"✅ Пропуски заполнены\")\n",
"print(f\"✅ Данные готовы к анализу\")\n",
"\n",
"# =================================\n",
"# ГРАФИК 1: ГИСТОГРАММА (Histogram)\n",
"# =================================\n",
"print(\"\\n1⃣ ГИСТОГРАММА - Распределение возрастов пассажиров\")\n",
"\n",
"plt.figure(figsize=(12, 6))\n",
"\n",
"# Разделяем выживших и погибших\n",
"survived = df[df['survived'] == 1]['age'].dropna()\n",
"died = df[df['survived'] == 0]['age'].dropna()\n",
"\n",
"plt.hist(survived, bins=20, alpha=0.7, label='Выжившие', \n",
" color='green', edgecolor='black', linewidth=1.5)\n",
"plt.hist(died, bins=20, alpha=0.7, label='Погибшие', \n",
" color='red', edgecolor='black', linewidth=1.5)\n",
"\n",
"plt.xlabel('Возраст (лет)', fontsize=12, fontweight='bold')\n",
"plt.ylabel('Количество пассажиров', fontsize=12, fontweight='bold')\n",
"plt.title('ГРАФИК 1: Распределение возрастов (Выжившие vs Погибшие)', \n",
" fontsize=14, fontweight='bold')\n",
"plt.axvline(survived.mean(), color='darkgreen', linestyle='--', linewidth=2,\n",
" label=f'Ср. возраст выживших: {survived.mean():.1f}')\n",
"plt.axvline(died.mean(), color='darkred', linestyle='--', linewidth=2,\n",
" label=f'Ср. возраст погибших: {died.mean():.1f}')\n",
"plt.legend(loc='upper right', fontsize=10)\n",
"plt.grid(alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(f\"✅ ГРАФИК 1 построен!\")\n",
"\n",
"# =======================================\n",
"# ГРАФИК 2: ТОЧЕЧНЫЙ ГРАФИК (Scatterplot)\n",
"# =======================================\n",
"print(\"\\n2⃣ ТОЧЕЧНЫЙ ГРАФИК - Возраст vs Стоимость билета\")\n",
"\n",
"plt.figure(figsize=(12, 6))\n",
"\n",
"clean_age = df['age'].dropna()\n",
"clean_fare = df['fare'].dropna()\n",
"\n",
"# Цветовая схема: зеленые - выжившие, красные - погибшие\n",
"colors = df.loc[clean_age.index, 'survived'].map({0: 'red', 1: 'green'})\n",
"\n",
"plt.scatter(clean_age, clean_fare, c=colors, alpha=0.6, s=50, edgecolors='black', linewidth=0.5)\n",
"\n",
"plt.xlabel('Возраст (лет)', fontsize=12, fontweight='bold')\n",
"plt.ylabel('Стоимость билета ($)', fontsize=12, fontweight='bold')\n",
"plt.title('ГРАФИК 2: Зависимость стоимости билета от возраста пассажира', \n",
" fontsize=14, fontweight='bold')\n",
"\n",
"z = np.polyfit(clean_age, clean_fare, 1)\n",
"p = np.poly1d(z)\n",
"x_trend = np.linspace(clean_age.min(), clean_age.max(), 100)\n",
"plt.plot(x_trend, p(x_trend), 'b--', linewidth=2, label='Линия тренда')\n",
"\n",
"# Легенда\n",
"from matplotlib.patches import Patch\n",
"legend_elements = [\n",
" Patch(facecolor='green', alpha=0.6, label='Выжившие'),\n",
" Patch(facecolor='red', alpha=0.6, label='Погибшие'),\n",
" Patch(facecolor='blue', alpha=0.6, label='Линия тренда')\n",
"]\n",
"plt.legend(handles=legend_elements, loc='upper right', fontsize=10)\n",
"plt.grid(alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"correlation = clean_age.corr(clean_fare)\n",
"print(f\"✅ ГРАФИК 2 построен!\")\n",
"print(f\" Корреляция между возрастом и ценой билета: {correlation:.3f}\")\n",
"\n",
"# ============================================\n",
"# ГРАФИК 3: BOXPLOT (Ящик с усами)\n",
"# ============================================\n",
"print(\"\\n3⃣ ЯЩИК С УСАМИ - Распределение цен на билеты по классам\")\n",
"\n",
"plt.figure(figsize=(10, 6))\n",
"\n",
"# Создаем boxplot с кастомными цветами\n",
"boxplot = sns.boxplot(x='pclass', y='fare', data=df, \n",
" palette='Set2', linewidth=2, \n",
" fliersize=5, flierprops=dict(marker='o', markerfacecolor='red', markersize=6))\n",
"\n",
"plt.xlabel('Класс билета (1 - первый/роскошный, 3 - третий/эконом)', fontsize=12, fontweight='bold')\n",
"plt.ylabel('Стоимость билета ($)', fontsize=12, fontweight='bold')\n",
"plt.title('ГРАФИК 3: Распределение стоимости билетов по классам обслуживания', \n",
" fontsize=14, fontweight='bold')\n",
"\n",
"# Добавляем аннотации со средними значениями\n",
"for i, pclass in enumerate([1, 2, 3], 1):\n",
" mean_fare = df[df['pclass'] == pclass]['fare'].mean()\n",
" plt.text(i - 0.8, mean_fare + 10, f'Средняя: ${mean_fare:.0f}', \n",
" fontsize=10, ha='center', bbox=dict(boxstyle=\"round,pad=0.3\", facecolor=\"yellow\", alpha=0.7))\n",
"\n",
"plt.xticks([0, 1, 2], ['1 класс (люкс)', '2 класс (стандарт)', '3 класс (эконом)'], rotation=15)\n",
"plt.grid(axis='y', alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(f\"✅ ГРАФИК 3 построен!\")\n",
"\n",
"# ============================================\n",
"# ГРАФИК 4: ДОПОЛНИТЕЛЬНЫЙ - Тепловая карта (Heatmap)\n",
"# ============================================\n",
"print(\"\\n4⃣ ТЕПЛОВАЯ КАРТА - Корреляция параметров\")\n",
"\n",
"plt.figure(figsize=(10, 8))\n",
"\n",
"# Выбираем числовые параметры\n",
"numeric_cols = ['survived', 'pclass', 'age', 'sibsp', 'parch', 'fare']\n",
"corr_matrix = df[numeric_cols].corr()\n",
"\n",
"# Создаем тепловую карту\n",
"sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, \n",
" square=True, linewidths=2, fmt='.3f',\n",
" cbar_kws={'label': 'Коэффициент корреляции', 'shrink': 0.8},\n",
" annot_kws={'size': 12, 'weight': 'bold'})\n",
"\n",
"plt.title('ГРАФИК 4: Матрица корреляции параметров выживаемости', \n",
" fontsize=14, fontweight='bold')\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"✅ ГРАФИК 4 построен!\")\n",
"\n",
"# ============================================\n",
"# ГРАФИК 5: ДОПОЛНИТЕЛЬНЫЙ - Столбчатая диаграмма\n",
"# ============================================\n",
"print(\"\\n5⃣ СТОЛБЧАТАЯ ДИАГРАММА - Выживаемость по классам\")\n",
"\n",
"plt.figure(figsize=(10, 6))\n",
"\n",
"# Считаем выживаемость по классам\n",
"survival_by_class = df.groupby('pclass')['survived'].mean() * 100\n",
"\n",
"bars = plt.bar([1, 2, 3], survival_by_class.values, \n",
" color=['gold', 'silver', '#CD7F32'], \n",
" edgecolor='black', linewidth=2, alpha=0.8)\n",
"\n",
"# Добавляем значения на столбцы\n",
"for bar, value in zip(bars, survival_by_class.values):\n",
" plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, \n",
" f'{value:.1f}%', ha='center', va='bottom', fontsize=12, fontweight='bold')\n",
"\n",
"plt.xlabel('Класс билета', fontsize=12, fontweight='bold')\n",
"plt.ylabel('Выживаемость (%)', fontsize=12, fontweight='bold')\n",
"plt.title('ГРАФИК 5: Процент выживших пассажиров по классам', \n",
" fontsize=14, fontweight='bold')\n",
"plt.xticks([1, 2, 3], ['1 класс\\n(люкс)', '2 класс\\n(стандарт)', '3 класс\\n(эконом)'])\n",
"plt.ylim(0, 100)\n",
"plt.grid(axis='y', alpha=0.3)\n",
"\n",
"# Добавляем среднюю линию\n",
"plt.axhline(df['survived'].mean() * 100, color='red', linestyle='--', linewidth=2,\n",
" label=f'Общая выживаемость: {df[\"survived\"].mean()*100:.1f}%')\n",
"plt.legend()\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"✅ ГРАФИК 5 построен!\")\n",
"\n",
"# ============================================\n",
"# tqdm демонстрация\n",
"# ============================================\n",
"print(\"\\n\" + \"=\" * 60)\n",
"print(\"🔄 АНАЛИЗ С ПРОГРЕСС-БАРОМ (tqdm)\")\n",
"print(\"=\" * 60)\n",
"\n",
"from tqdm import tqdm\n",
"import time\n",
"\n",
"results = []\n",
"for pclass in tqdm([1, 2, 3], desc=\"Анализ классов\", unit=\"класс\", colour='green'):\n",
" class_data = df[df['pclass'] == pclass]\n",
" time.sleep(0.1) # Симуляция\n",
" \n",
" results.append({\n",
" 'Класс': pclass,\n",
" 'Пассажиров': len(class_data),\n",
" 'Выживаемость %': round((class_data['survived'].sum() / len(class_data)) * 100, 1),\n",
" 'Средняя цена': round(class_data['fare'].mean(), 2)\n",
" })\n",
"\n",
"print(\"\\n✅ Результаты анализа:\")\n",
"result_df = pd.DataFrame(results)\n",
"print(result_df.to_string(index=False))\n",
"\n",
"# ============================================\n",
"# ИТОГИ\n",
"# ============================================\n",
"print(\"\\n\" + \"=\" * 60)\n",
"print(\"📊 ВЫВОДЫ ПО РЕЗУЛЬТАТАМ АНАЛИЗА\")\n",
"print(\"=\" * 60)\n",
"\n",
"print(f\"\"\"\n",
"✅ Основные результаты:\n",
"1. Всего проанализировано: {len(df)} пассажиров\n",
"2. Общая выживаемость: {df['survived'].mean() * 100:.1f}%\n",
"\n",
"✅ Факторы, влияющие на выживаемость:\n",
"3. Класс билета:\n",
" - 1 класс: {(df[df['pclass']==1]['survived'].mean()*100):.1f}%\n",
" - 2 класс: {(df[df['pclass']==2]['survived'].mean()*100):.1f}%\n",
" - 3 класс: {(df[df['pclass']==3]['survived'].mean()*100):.1f}%\n",
"\n",
"4. Возраст: средний возраст выживших ({survived.mean():.1f}) vs погибших ({died.mean():.1f})\n",
"\n",
"5. Цена билета: корреляция с выживаемостью {corr_matrix.loc['survived', 'fare']:.3f}\n",
"\"\"\")\n",
"\n",
"print(\"=\" * 60)\n",
"print(\"✅ АНАЛИЗ УСПЕШНО ЗАВЕРШЕН!\")\n",
"print(\" Построено 5 графиков: Гистограмма, Scatterplot, Boxplot, Heatmap, Bar chart\")\n",
"print(\"=\" * 60)"
]
},
{
"cell_type": "markdown",
"id": "bb76a6b35ff61162",
"metadata": {},
"source": [
"## 📝 ВЫВОДЫ ПО РЕЗУЛЬТАТАМ АНАЛИЗА\n",
"\n",
"### Ключевые результаты:\n",
"1. **Объем данных**: Проанализировано {len(df)} пассажиров\n",
"2. **Общая выживаемость**: {df['survived'].mean()*100:.1f}%\n",
"\n",
"### Факторы, влияющие на выживаемость:\n",
"- **Класс билета**: Пассажиры 1-го класса выживали значительно чаще\n",
"- **Пол**: Женщины выживали чаще мужчин\n",
"- **Возраст**: Дети выживали чаще взрослых\n",
"- **Цена билета**: Более дорогие билеты коррелируют с более высокими шансами\n",
"\n",
"### Рекомендации для системы управления:\n",
"1. Учитывать категории пользователей при приоритизации\n",
"2. Внедрить систему лояльности для постоянных клиентов\n",
"3. Анализировать поведение разных сегментов аудитории\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "ad6c90dab130cf29",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c1457863ccaa86",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.14.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}