{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Params"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"seed = 42\nnfold = 5","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Imports"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\nfrom sklearn.cluster import KMeans\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import LabelEncoder\nfrom collections import Counter, defaultdict\nfrom sklearn.utils import check_random_state","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv('../input/ranzcr-clip-catheter-line-classification/train.csv')\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Basic EDA"},{"metadata":{"trusted":true},"cell_type":"code","source":"train.nunique()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As we can see, we only have 3255 patients. We want to make sure that each patient's images do not appear in multiple folds to avoid data leakage."},{"metadata":{"trusted":true},"cell_type":"code","source":"train.PatientID.value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Some patient has 172 images and some only has 1."},{"metadata":{},"cell_type":"markdown","source":"## Utils"},{"metadata":{"trusted":true},"cell_type":"code","source":"class RepeatedStratifiedGroupKFold():\n\n    def __init__(self, n_splits=5, n_repeats=1, random_state=None):\n        self.n_splits = n_splits\n        self.n_repeats = n_repeats\n        self.random_state = random_state\n        \n    def split(self, X, y=None, groups=None):\n        k = self.n_splits\n        def eval_y_counts_per_fold(y_counts, fold):\n            y_counts_per_fold[fold] += y_counts\n            std_per_label = []\n            for label in range(labels_num):\n                label_std = np.std(\n                    [y_counts_per_fold[i][label] / y_distr[label] for i in range(k)]\n                )\n                std_per_label.append(label_std)\n            y_counts_per_fold[fold] -= y_counts\n            return np.mean(std_per_label)\n            \n        rnd = check_random_state(self.random_state)\n        for repeat in range(self.n_repeats):\n            labels_num = np.max(y) + 1\n            y_counts_per_group = defaultdict(lambda: np.zeros(labels_num))\n            y_distr = Counter()\n            for label, g in zip(y, groups):\n                y_counts_per_group[g][label] += 1\n                y_distr[label] += 1\n\n            y_counts_per_fold = defaultdict(lambda: np.zeros(labels_num))\n            groups_per_fold = defaultdict(set)\n        \n            groups_and_y_counts = list(y_counts_per_group.items())\n            rnd.shuffle(groups_and_y_counts)\n\n            for g, y_counts in sorted(groups_and_y_counts, key=lambda x: -np.std(x[1])):\n                best_fold = None\n                min_eval = None\n                for i in range(k):\n                    fold_eval = eval_y_counts_per_fold(y_counts, i)\n                    if min_eval is None or fold_eval < min_eval:\n                        min_eval = fold_eval\n                        best_fold = i\n                y_counts_per_fold[best_fold] += y_counts\n                groups_per_fold[best_fold].add(g)\n            \n            all_groups = set(groups)\n            for i in range(k):\n                train_groups = all_groups - groups_per_fold[i]\n                test_groups = groups_per_fold[i]\n\n                train_indices = [i for i, g in enumerate(groups) if g in train_groups]\n                test_indices = [i for i, g in enumerate(groups) if g in test_groups]\n\n                yield train_indices, test_indices","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Split folds\nIdeas:\n1. make sure that the labels are stratified\n2. one patient's images are grouped in one fold"},{"metadata":{"trusted":true},"cell_type":"code","source":"# let's first concat all the labels \n# e.g 00000000010   \ntarget_cols = train.drop(['StudyInstanceUID', 'PatientID'],axis=1).columns.values.tolist()\ntargets = train[target_cols].astype(str)\n# create a new col to store the label\ntrain['combined_tar'] = ''\nfor i in tqdm(range(targets.shape[1])):\n    train['combined_tar'] += targets.iloc[:,i]\n# take a look at it\ntrain.combined_tar.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train['combined_tar'] = LabelEncoder().fit_transform(train['combined_tar'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train['fold'] = -1\nrskf = RepeatedStratifiedGroupKFold(n_splits=nfold, random_state=42)\nfor i, (train_idx, valid_idx) in enumerate(rskf.split(train, train.combined_tar, train.PatientID)): #(df, targets, group)\n    train.loc[valid_idx, 'fold'] = int(i)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Sanity Check\nYou wanna make sure this split makes sense. We can do that by checking the stratification and groups."},{"metadata":{"trusted":true},"cell_type":"code","source":"train.query('fold==0').combined_tar.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.query('fold==1').combined_tar.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.query('fold==2').combined_tar.value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It seems that the label is very nicely stratified. Now let's check groups."},{"metadata":{"trusted":true},"cell_type":"code","source":"np.intersect1d(train.query('fold==0').PatientID.unique(), train.query('fold==1').PatientID.unique())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"np.intersect1d(train.query('fold==1').PatientID.unique(), train.query('fold==2').PatientID.unique())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"np.intersect1d(train.query('fold==2').PatientID.unique(), train.query('fold==3').PatientID.unique())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"No patient has appeared in two folds."},{"metadata":{},"cell_type":"markdown","source":"## Save final CSV"},{"metadata":{"trusted":true},"cell_type":"code","source":"train.drop('combined_tar', axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.drop('combined_tar', axis=1).to_csv('train_folds.csv', index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}