From cecf94809bcc602c6cc8b345275474479c46bead Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Thu, 5 Dec 2024 16:00:47 +0400 Subject: [PATCH] Lec2 --- assets/lec2-split.png | Bin 0 -> 64794 bytes lec2.ipynb | 1187 +++++++++++++++++++++++++++++++++++++++++ poetry.lock | 143 ++++- pyproject.toml | 1 + src/utils.py | 79 +++ 5 files changed, 1409 insertions(+), 1 deletion(-) create mode 100644 assets/lec2-split.png create mode 100644 lec2.ipynb create mode 100644 src/utils.py diff --git a/assets/lec2-split.png b/assets/lec2-split.png new file mode 100644 index 0000000000000000000000000000000000000000..fad816002d2a8e70511aba4576d76473857b6434 GIT binary patch literal 64794 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î$QmZGHB#w01qUA`pU%k2jOw4Y24#HCL)+EJ_{V`M?P6iH^-|| zUr;m_Bafc{g$5W%4tdG{1zqC3~jVfSi81y|ZOn@nt>hLH0h^pdT-g5JCC z153myy6lf;vg-k)-yd1kx)l3^eP0+i7**DFc{lHNwy)OqTqep6hD8|@g5Ld#Wv~7N z&zR<*p`f_8HD>tPq8qe$BnuD?CQ2>N8a!z8pqH!rSb4+t?9D3WP=#XtL;? zd>wei823SGrF;t)J)VN94Wt+D4GGg~DX>Jd0(ryyhzXKyVZ-k8Cnp+l@R&mHuHo~& z9_wnAF&kQm{|7h*9eXi-(TX26jk2Wxhn)5{>hEif+Oa>3Z5msKXc7M%zonHscNURe&8wpF!N5_Y|0c*O&iD7hG(BkZArD^;#{Wc=*X<`(+}5DJSqV} zY%VAXVn1@N`%=!$nBufhLH*aCY9&mANQVgD5r{l1%(XA%z*W!;VGl#$ z&nMndL#znr;dpR*%iXESDUqUH{~)v58nm((a@f?yZ*;#6*04QUz{YNi;u(=Bdfmuc zXmc`)DsUD()1K1mKkZ8aTCe?8YDFUDoQqOX{4krUi}yY^PDHxul{hX+lo0FTZ_-S> z$2+n~-X=qDRq2&^dyWeR+(NN~Hrn&tQbBH;9&He)4>BT-aasW+sQfwsxAs(dY)? z);!{h?&bdf80i)7xV6o|H`Nb8{El0@OFaDl?zs<*KNfcQAI1Q}2_#NcC!hQEpELRc zE9U4cb{W^dE9MS=+5y=S)^<+wkq?0!NGn*r^R&(~Scd#NwSWaqfAKFO-*mB<_E%$) QN5D^BT3M>(g-O8w08bq~IRF3v literal 0 HcmV?d00001 diff --git a/lec2.ipynb b/lec2.ipynb new file mode 100644 index 0000000..361bd66 --- /dev/null +++ b/lec2.ipynb @@ -0,0 +1,1187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Загрузка данных в DataFrame" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Index: 891 entries, 1 to 891\n", + "Data columns (total 11 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 Survived 891 non-null int64 \n", + " 1 Pclass 891 non-null int64 \n", + " 2 Name 891 non-null object \n", + " 3 Sex 891 non-null object \n", + " 4 Age 714 non-null float64\n", + " 5 SibSp 891 non-null int64 \n", + " 6 Parch 891 non-null int64 \n", + " 7 Ticket 891 non-null object \n", + " 8 Fare 891 non-null float64\n", + " 9 Cabin 204 non-null object \n", + " 10 Embarked 889 non-null object \n", + "dtypes: float64(2), int64(4), object(5)\n", + "memory usage: 83.5+ KB\n" + ] + }, + { + "data": { + "text/plain": [ + "(891, 11)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
PassengerId
103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
503Allen, Mr. William Henrymale35.0003734508.0500NaNS
\n", + "
" + ], + "text/plain": [ + " Survived Pclass \\\n", + "PassengerId \n", + "1 0 3 \n", + "2 1 1 \n", + "3 1 3 \n", + "4 1 1 \n", + "5 0 3 \n", + "\n", + " Name Sex Age \\\n", + "PassengerId \n", + "1 Braund, Mr. Owen Harris male 22.0 \n", + "2 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 \n", + "3 Heikkinen, Miss. Laina female 26.0 \n", + "4 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 \n", + "5 Allen, Mr. William Henry male 35.0 \n", + "\n", + " SibSp Parch Ticket Fare Cabin Embarked \n", + "PassengerId \n", + "1 1 0 A/5 21171 7.2500 NaN S \n", + "2 1 0 PC 17599 71.2833 C85 C \n", + "3 0 0 STON/O2. 3101282 7.9250 NaN S \n", + "4 1 0 113803 53.1000 C123 S \n", + "5 0 0 373450 8.0500 NaN S " + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"data/titanic.csv\", index_col=\"PassengerId\")\n", + "\n", + "df.info()\n", + "\n", + "display(df.shape)\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Получение сведений о пропущенных данных" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Типы пропущенных данных:\n", + "- None - представление пустых данных в Python\n", + "- NaN - представление пустых данных в Pandas\n", + "- '' - пустая строка" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Survived 0\n", + "Pclass 0\n", + "Name 0\n", + "Sex 0\n", + "Age 177\n", + "SibSp 0\n", + "Parch 0\n", + "Ticket 0\n", + "Fare 0\n", + "Cabin 687\n", + "Embarked 2\n", + "dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Survived False\n", + "Pclass False\n", + "Name False\n", + "Sex False\n", + "Age True\n", + "SibSp False\n", + "Parch False\n", + "Ticket False\n", + "Fare False\n", + "Cabin True\n", + "Embarked True\n", + "dtype: bool" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Age процент пустых значений: %19.87'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Cabin процент пустых значений: %77.10'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Embarked процент пустых значений: %0.22'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Количество пустых значений признаков\n", + "display(df.isnull().sum())\n", + "display()\n", + "\n", + "# Есть ли пустые значения признаков\n", + "display(df.isnull().any())\n", + "display()\n", + "\n", + "# Процент пустых значений признаков\n", + "for i in df.columns:\n", + " null_rate = df[i].isnull().sum() / len(df) * 100\n", + " if null_rate > 0:\n", + " display(f\"{i} процент пустых значений: %{null_rate:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Заполнение пропущенных данных\n", + "\n", + "https://pythonmldaily.com/posts/pandas-dataframes-search-drop-empty-values\n", + "\n", + "https://scales.arabpsychology.com/stats/how-to-fill-nan-values-with-median-in-pandas/" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(891, 11)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Survived False\n", + "Pclass False\n", + "Name False\n", + "Sex False\n", + "Age False\n", + "SibSp False\n", + "Parch False\n", + "Ticket False\n", + "Fare False\n", + "Cabin False\n", + "Embarked False\n", + "dtype: bool" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedAgeFillNAAgeFillMedian
PassengerId
88702Montvila, Rev. Juozasmale27.00021153613.00NaNS27.027.0
88811Graham, Miss. Margaret Edithfemale19.00011205330.00B42S19.019.0
88903Johnston, Miss. Catherine Helen \"Carrie\"femaleNaN12W./C. 660723.45NaNS0.028.0
89011Behr, Mr. Karl Howellmale26.00011136930.00C148C26.026.0
89103Dooley, Mr. Patrickmale32.0003703767.75NaNQ32.032.0
\n", + "
" + ], + "text/plain": [ + " Survived Pclass Name \\\n", + "PassengerId \n", + "887 0 2 Montvila, Rev. Juozas \n", + "888 1 1 Graham, Miss. Margaret Edith \n", + "889 0 3 Johnston, Miss. Catherine Helen \"Carrie\" \n", + "890 1 1 Behr, Mr. Karl Howell \n", + "891 0 3 Dooley, Mr. Patrick \n", + "\n", + " Sex Age SibSp Parch Ticket Fare Cabin Embarked \\\n", + "PassengerId \n", + "887 male 27.0 0 0 211536 13.00 NaN S \n", + "888 female 19.0 0 0 112053 30.00 B42 S \n", + "889 female NaN 1 2 W./C. 6607 23.45 NaN S \n", + "890 male 26.0 0 0 111369 30.00 C148 C \n", + "891 male 32.0 0 0 370376 7.75 NaN Q \n", + "\n", + " AgeFillNA AgeFillMedian \n", + "PassengerId \n", + "887 27.0 27.0 \n", + "888 19.0 19.0 \n", + "889 0.0 28.0 \n", + "890 26.0 26.0 \n", + "891 32.0 32.0 " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fillna_df = df.fillna(0)\n", + "\n", + "display(fillna_df.shape)\n", + "\n", + "display(fillna_df.isnull().any())\n", + "\n", + "# Замена пустых данных на 0\n", + "df[\"AgeFillNA\"] = df[\"Age\"].fillna(0)\n", + "\n", + "# Замена пустых данных на медиану\n", + "df[\"AgeFillMedian\"] = df[\"Age\"].fillna(df[\"Age\"].median())\n", + "\n", + "df.tail()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedAgeFillNAAgeFillMedianAgeCopy
PassengerId
88702Montvila, Rev. Juozasmale27.00021153613.00NaNS27.027.027.0
88811Graham, Miss. Margaret Edithfemale19.00011205330.00B42S19.019.019.0
88903Johnston, Miss. Catherine Helen \"Carrie\"femaleNaN12W./C. 660723.45NaNS0.028.00.0
89011Behr, Mr. Karl Howellmale26.00011136930.00C148C26.026.026.0
89103Dooley, Mr. Patrickmale32.0003703767.75NaNQ32.032.032.0
\n", + "
" + ], + "text/plain": [ + " Survived Pclass Name \\\n", + "PassengerId \n", + "887 0 2 Montvila, Rev. Juozas \n", + "888 1 1 Graham, Miss. Margaret Edith \n", + "889 0 3 Johnston, Miss. Catherine Helen \"Carrie\" \n", + "890 1 1 Behr, Mr. Karl Howell \n", + "891 0 3 Dooley, Mr. Patrick \n", + "\n", + " Sex Age SibSp Parch Ticket Fare Cabin Embarked \\\n", + "PassengerId \n", + "887 male 27.0 0 0 211536 13.00 NaN S \n", + "888 female 19.0 0 0 112053 30.00 B42 S \n", + "889 female NaN 1 2 W./C. 6607 23.45 NaN S \n", + "890 male 26.0 0 0 111369 30.00 C148 C \n", + "891 male 32.0 0 0 370376 7.75 NaN Q \n", + "\n", + " AgeFillNA AgeFillMedian AgeCopy \n", + "PassengerId \n", + "887 27.0 27.0 27.0 \n", + "888 19.0 19.0 19.0 \n", + "889 0.0 28.0 0.0 \n", + "890 26.0 26.0 26.0 \n", + "891 32.0 32.0 32.0 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[\"AgeCopy\"] = df[\"Age\"]\n", + "\n", + "# Замена данных сразу в DataFrame без копирования\n", + "df.fillna({\"AgeCopy\": 0}, inplace=True)\n", + "\n", + "df.tail()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Удаление наблюдений с пропусками" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(183, 14)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Survived False\n", + "Pclass False\n", + "Name False\n", + "Sex False\n", + "Age False\n", + "SibSp False\n", + "Parch False\n", + "Ticket False\n", + "Fare False\n", + "Cabin False\n", + "Embarked False\n", + "dtype: bool" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dropna_df = df.dropna()\n", + "\n", + "display(dropna_df.shape)\n", + "\n", + "display(fillna_df.isnull().any())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Создание выборок данных\n", + "\n", + "Библиотека scikit-learn\n", + "\n", + "https://scikit-learn.org/stable/index.html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Pclass\n", + "3 491\n", + "1 216\n", + "2 184\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Обучающая выборка: '" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(534, 3)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Pclass\n", + "3 294\n", + "1 130\n", + "2 110\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Контрольная выборка: '" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(178, 3)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Pclass\n", + "3 98\n", + "1 43\n", + "2 37\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Тестовая выборка: '" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(179, 3)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Pclass\n", + "3 99\n", + "1 43\n", + "2 37\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Вывод распределения количества наблюдений по меткам (классам)\n", + "from src.utils import split_stratified_into_train_val_test\n", + "\n", + "\n", + "display(df.Pclass.value_counts())\n", + "display()\n", + "\n", + "data = df[[\"Pclass\", \"Survived\", \"AgeFillMedian\"]].copy()\n", + "\n", + "df_train, df_val, df_test, y_train, y_val, y_test = split_stratified_into_train_val_test(\n", + " data, stratify_colname=\"Pclass\", frac_train=0.60, frac_val=0.20, frac_test=0.20\n", + ")\n", + "\n", + "display(\"Обучающая выборка: \", df_train.shape)\n", + "display(df_train.Pclass.value_counts())\n", + "\n", + "display(\"Контрольная выборка: \", df_val.shape)\n", + "display(df_val.Pclass.value_counts())\n", + "\n", + "display(\"Тестовая выборка: \", df_test.shape)\n", + "display(df_test.Pclass.value_counts())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выборка с избытком (oversampling)\n", + "\n", + "https://www.blog.trainindata.com/oversampling-techniques-for-imbalanced-data/\n", + "\n", + "https://datacrayon.com/machine-learning/class-imbalance-and-oversampling/\n", + "\n", + "Выборка с недостатком (undersampling)\n", + "\n", + "https://machinelearningmastery.com/random-oversampling-and-undersampling-for-imbalanced-classification/\n", + "\n", + "Библиотека imbalanced-learn\n", + "\n", + "https://imbalanced-learn.org/stable/" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Обучающая выборка: '" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(534, 3)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Pclass\n", + "3 294\n", + "1 130\n", + "2 110\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Обучающая выборка после oversampling: '" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(860, 3)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Pclass\n", + "3 294\n", + "2 288\n", + "1 278\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PclassSurvivedAgeFillMedian
03028.000000
13030.000000
21141.000000
33016.000000
43132.000000
............
8552025.000000
8562149.895392
8572049.548159
8582049.410133
8592050.944726
\n", + "

860 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Pclass Survived AgeFillMedian\n", + "0 3 0 28.000000\n", + "1 3 0 30.000000\n", + "2 1 1 41.000000\n", + "3 3 0 16.000000\n", + "4 3 1 32.000000\n", + ".. ... ... ...\n", + "855 2 0 25.000000\n", + "856 2 1 49.895392\n", + "857 2 0 49.548159\n", + "858 2 0 49.410133\n", + "859 2 0 50.944726\n", + "\n", + "[860 rows x 3 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from imblearn.over_sampling import ADASYN\n", + "\n", + "ada = ADASYN()\n", + "\n", + "display(\"Обучающая выборка: \", df_train.shape)\n", + "display(df_train.Pclass.value_counts())\n", + "\n", + "X_resampled, y_resampled = ada.fit_resample(df_train, df_train[\"Pclass\"]) # type: ignore\n", + "df_train_adasyn = pd.DataFrame(X_resampled)\n", + "\n", + "display(\"Обучающая выборка после oversampling: \", df_train_adasyn.shape)\n", + "display(df_train_adasyn.Pclass.value_counts())\n", + "\n", + "df_train_adasyn" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/poetry.lock b/poetry.lock index 375a1b5..4c0f1c5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -763,6 +763,30 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "imbalanced-learn" +version = "0.12.4" +description = "Toolbox for imbalanced dataset in machine learning." +optional = false +python-versions = "*" +files = [ + {file = "imbalanced-learn-0.12.4.tar.gz", hash = "sha256:8153ba385d296b07d97e0901a2624a86c06b48c94c2f92da3a5354827697b7a3"}, + {file = "imbalanced_learn-0.12.4-py3-none-any.whl", hash = "sha256:d47fc599160d3ea882e712a3a6b02bdd353c1a6436d8d68d41b1922e6ee4a703"}, +] + +[package.dependencies] +joblib = ">=1.1.1" +numpy = ">=1.17.3" +scikit-learn = ">=1.0.2" +scipy = ">=1.5.0" +threadpoolctl = ">=2.0.0" + +[package.extras] +docs = ["keras (>=2.4.3)", "matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.5.0)", "pandas (>=1.0.5)", "pydata-sphinx-theme (>=0.13.3)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.13.0)", "sphinxcontrib-bibtex (>=2.4.1)", "tensorflow (>=2.4.3)"] +examples = ["keras (>=2.4.3)", "matplotlib (>=3.1.2)", "pandas (>=1.0.5)", "seaborn (>=0.9.0)", "tensorflow (>=2.4.3)"] +optional = ["keras (>=2.4.3)", "pandas (>=1.0.5)", "tensorflow (>=2.4.3)"] +tests = ["black (>=23.3.0)", "flake8 (>=3.8.2)", "keras (>=2.4.3)", "mypy (>=1.3.0)", "pandas (>=1.0.5)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "tensorflow (>=2.4.3)"] + [[package]] name = "ipykernel" version = "6.29.5" @@ -903,6 +927,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + [[package]] name = "json5" version = "0.10.0" @@ -2513,6 +2548,101 @@ files = [ {file = "rpds_py-0.22.0.tar.gz", hash = "sha256:32de71c393f126d8203e9815557c7ff4d72ed1ad3aa3f52f6c7938413176750a"}, ] +[[package]] +name = "scikit-learn" +version = "1.5.2" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"}, + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8"}, + {file = "scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1"}, + {file = "scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca64b3089a6d9b9363cd3546f8978229dcbb737aceb2c12144ee3f70f95684b7"}, + {file = "scikit_learn-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:3bed4909ba187aca80580fe2ef370d9180dcf18e621a27c4cf2ef10d279a7efe"}, + {file = "scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "send2trash" version = "1.8.3" @@ -2622,6 +2752,17 @@ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + [[package]] name = "tinycss2" version = "1.4.0" @@ -2791,4 +2932,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "499a93cb5bb093f5378ad4a2e77f4f895221c389934fb4a15e5a5127db419128" +content-hash = "d9de29d5a54172d74c1c4d32cc992d85f9ada806a82846ab228dca34419bba41" diff --git a/pyproject.toml b/pyproject.toml index dd58b4a..012070f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ jupyter = "^1.1.1" numpy = "^2.1.0" pandas = "^2.2.2" matplotlib = "^3.9.2" +imbalanced-learn = "^0.12.3" [build-system] diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..cb8c396 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,79 @@ +from typing import Tuple + +import pandas as pd +from pandas import DataFrame +from sklearn.model_selection import train_test_split + + +def split_stratified_into_train_val_test( + df_input, + stratify_colname="y", + frac_train=0.6, + frac_val=0.15, + frac_test=0.25, + random_state=None, +) -> Tuple[DataFrame, DataFrame, DataFrame, DataFrame, DataFrame, DataFrame]: + """ + Splits a Pandas dataframe into three subsets (train, val, and test) + following fractional ratios provided by the user, where each subset is + stratified by the values in a specific column (that is, each subset has + the same relative frequency of the values in the column). It performs this + splitting by running train_test_split() twice. + + Parameters + ---------- + df_input : Pandas dataframe + Input dataframe to be split. + stratify_colname : str + The name of the column that will be used for stratification. Usually + this column would be for the label. + frac_train : float + frac_val : float + frac_test : float + The ratios with which the dataframe will be split into train, val, and + test data. The values should be expressed as float fractions and should + sum to 1.0. + random_state : int, None, or RandomStateInstance + Value to be passed to train_test_split(). + + Returns + ------- + df_train, df_val, df_test : + Dataframes containing the three splits. + """ + + if frac_train + frac_val + frac_test != 1.0: + raise ValueError( + "fractions %f, %f, %f do not add up to 1.0" + % (frac_train, frac_val, frac_test) + ) + + if stratify_colname not in df_input.columns: + raise ValueError("%s is not a column in the dataframe" % (stratify_colname)) + + X = df_input # Contains all columns. + y = df_input[ + [stratify_colname] + ] # Dataframe of just the column on which to stratify. + + # Split original dataframe into train and temp dataframes. + df_train, df_temp, y_train, y_temp = train_test_split( + X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state + ) + + if frac_val <= 0: + assert len(df_input) == len(df_train) + len(df_temp) + return df_train, pd.DataFrame(), df_temp, y_train, pd.DataFrame(), y_temp + + # Split the temp dataframe into val and test dataframes. + relative_frac_test = frac_test / (frac_val + frac_test) + df_val, df_test, y_val, y_test = train_test_split( + df_temp, + y_temp, + stratify=y_temp, + test_size=relative_frac_test, + random_state=random_state, + ) + + assert len(df_input) == len(df_train) + len(df_val) + len(df_test) + return df_train, df_val, df_test, y_train, y_val, y_test