{"cells":[{"metadata":{},"cell_type":"markdown","source":"# RANCR CLiP EDA-Class Imbalance, Patient Overlap\n\n\n### Competition : **RANZCR CLiP - Catheter and Line Position Challenge**\n\n\nOutline : \n\n* Class Count Plot \n* Patient Overlap \n* Class Imbalance \n* Weighted Loss"},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport plotly.graph_objs as go\nimport os\nimport torch \nfrom plotly.offline import iplot\n\npd.options.plotting.backend = \"plotly\"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Class Count Plot"},{"metadata":{"trusted":true},"cell_type":"code","source":"data = pd.read_csv('../input/ranzcr-clip-catheter-line-classification/train.csv')\ndata.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"data.iloc[:,1:12].sum().plot.barh()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Patient Overlap "},{"metadata":{},"cell_type":"markdown","source":"Patient Overlap is the case when train dataset and valid dataset having same patient examples. Simply intersection of train patient and valid patient.\n\nTo Avoid Patient Overlap :\n* Either drop overlapped data from train dataset or valid dataset \n* Either Add overlapped data from train dataset to valid dataset or valid dataset to train dataset"},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"No. of unique patient : {}\".format(len(set(data['PatientID']))))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_split(data):\n    train_df = data.sample(frac = 0.985)\n    valid_df = data.drop(train_df.index)\n    return train_df, valid_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data,valid_data = get_split(data)\nprint(\"Size of train set : {}\".format(len(train_data)))\nprint(\"Size of valid set : {}\".format(len(valid_data)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def check_for_leakage(df1, df2, patient_col):\n    \"\"\"\n    Return True if there any patients are in both df1 and df2.\n\n    Args:\n        df1 (dataframe): dataframe describing first dataset\n        df2 (dataframe): dataframe describing second dataset\n        patient_col (str): string name of column with patient IDs\n    \n    Returns:\n        leakage (bool): True if there is leakage, otherwise False\n    \"\"\"\n    \n    df1_patients_unique = set(df1[patient_col].values)\n    df2_patients_unique = set(df2[patient_col].values)\n    \n    patients_in_both_groups = df1_patients_unique.intersection(df2_patients_unique)\n\n    leakage = len(patients_in_both_groups) > 0 \n    \n    return leakage","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Are patient overlapping ? : {}\".format(check_for_leakage(train_data,valid_data,'PatientID')))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ids_train = train_data.PatientID.values\nids_valid = valid_data.PatientID.values\n\nids_train_set = set(ids_train)\nids_valid_set = set(ids_valid)\n\nprint(\"Unique patient Id in train set : {}\".format(len(ids_train_set)))\nprint(\"Unique patient Id in valid set : {}\".format(len(ids_valid_set)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"patient_overlap = list(ids_train_set.intersection(ids_valid_set))\n\nn_overlap = len(patient_overlap)\nprint(\"No. of Patient IDs in both train and valid set : {}\".format(n_overlap))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_overlap_idxs = []\nvalid_overlap_idxs = []\nfor idx in range(n_overlap):\n    train_overlap_idxs.extend(train_data.index[train_data['PatientID'] == patient_overlap[idx]].tolist())\n    valid_overlap_idxs.extend(valid_data.index[valid_data['PatientID'] == patient_overlap[idx]].tolist())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"add_to_valid = train_data.loc[train_overlap_idxs]\nvalid_data = valid_data.append(add_to_valid)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data = train_data.drop(train_overlap_idxs)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Size of train set : {}\".format(len(train_data)))\nprint(\"Size of valid set : {}\".format(len(valid_data)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_data.iloc[:,1:12].sum().plot.barh()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data.iloc[:,1:12].sum().plot.barh()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Class Imbalance "},{"metadata":{"trusted":true},"cell_type":"code","source":"def compute_class_freqs(labels):\n    \n    labels = np.array(labels)\n    \n    N = labels.shape[0]\n    \n    positive_frequencies = np.sum(labels,axis = 0) / N\n    negative_frequencies = 1 - positive_frequencies\n    \n    return positive_frequencies, negative_frequencies","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"freq_pos, freq_neg = compute_class_freqs(train_data.iloc[:,1:12])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class_names = train_data.iloc[:,1:12].columns","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.DataFrame({\"Class\": class_names, \"Label\": \"Positive\", \"Value\": freq_pos})\ndf = df.append([{\"Class\": class_names[l], \"Label\": \"Negative\", \"Value\": v} for l,v in enumerate(freq_neg)], ignore_index=True)\n\ntrace1 = go.Bar(\n                x = df.iloc[0:11,:].Class,\n                y = df.iloc[0:11,:].Value,\n                name = \"postive\",\n                marker = dict(color = 'rgba(0,0,255, 0.7)',\n                             line=dict(color='rgb(0,0,0)',width=1.5)),\n                text = df.iloc[0:11,:].Label)\n\ntrace2 = go.Bar(\n                x = df.iloc[11:,:].Class,\n                y = df.iloc[11:,:].Value,\n                name = \"negative\",\n                marker = dict(color = 'rgba(255,0,0, 0.7)',\n                             line=dict(color='rgb(0,0,0)',width=1.5)),\n                text = df.iloc[11:,:].Label)\n\n\ndata = [trace1,trace2]\nlayout = go.Layout(barmode = \"group\")\nfig = go.Figure(data = data, layout = layout)\niplot(fig)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Contributions of positive cases is significantly lower than that of the negative ones. However, we want the contributions to be equal. One way of doing this is by multiplying each example from each class by a class-specific weight factor, $pos_{weights}$ and $neg_{weights}$, so that the overall contribution of each class is the same.\n\nTo have this, we want\n\n$$pos_{weights} \\times freq_{p} = neg_{weights} \\times freq_{n},$$\nwhich we can do simply by taking\n\n$$pos_{weights} = freq_{neg}$$$$neg_{weights} = freq_{pos}$$\nThis way, we will be balancing the contribution of positive and negative labels."},{"metadata":{"trusted":true},"cell_type":"code","source":"pos_weights = freq_neg\nneg_weights = freq_pos\npos_contribution = freq_pos * pos_weights \nneg_contribution = freq_neg * neg_weights","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.DataFrame({\"Class\": class_names, \"Label\": \"Positive\", \"Value\": pos_contribution})\ndf = df.append([{\"Class\": class_names[l], \"Label\": \"Negative\", \"Value\": v} for l,v in enumerate(neg_contribution)], ignore_index=True)\n\ntrace1 = go.Bar(\n                x = df.iloc[0:11,:].Class,\n                y = df.iloc[0:11,:].Value,\n                name = \"postive\",\n                marker = dict(color = 'rgba(0,0,255, 0.7)',\n                             line=dict(color='rgb(0,0,0)',width=1.5)),\n                text = df.iloc[0:11,:].Label)\n\ntrace2 = go.Bar(\n                x = df.iloc[11:,:].Class,\n                y = df.iloc[11:,:].Value,\n                name = \"negative\",\n                marker = dict(color = 'rgba(255,0,0, 0.7)',\n                             line=dict(color='rgb(0,0,0)',width=1.5)),\n                text = df.iloc[11:,:].Label)\n\n\ndata = [trace1,trace2]\nlayout = go.Layout(barmode = \"group\")\nfig = go.Figure(data = data, layout = layout)\niplot(fig)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Weighted Loss"},{"metadata":{},"cell_type":"markdown","source":"To Avoid Data imbalance it is good to use weighted loss.\n\n$$\\mathcal{L}_{cross-entropy}(x_i) = -(y_i \\log(f(x_i)) + (1-y_i) \\log(1-f(x_i))),$$\n\n$$\\mathcal{L}_{cross-entropy}^{w}(x) = - (pos_{weights} y \\log(f(x)) + neg_{weights}(1-y) \\log( 1 - f(x) ) ).$$"},{"metadata":{"trusted":true},"cell_type":"code","source":"def weighted_loss(pos_weights,neg_weights,y_pred,y_true,epsilon = 1e-7):\n    \n    \"\"\"\n    Return True if there any patients are in both df1 and df2.\n\n    Args:\n        pos_weights: Negative Frequency of Train Dataset \n        neg_weights: Positive Frequency of Train Dataset \n        y_pred : model output\n        y_true : ground truth\n    \n    Returns:\n        loss : weighted cross-entropy loss\n    \"\"\"\n    \n    loss = 0.0\n    for i in range(len(pos_weights)):\n        loss_pos = -1 * torch.mean(pos_weights[i] * y_true[:,i] * torch.log(y_pred[:,i] + epsilon))\n        loss_neg = -1 * torch.mean(neg_weights[i] * (1-y_true[:,i]) * torch.log((1-y_pred[:,i]) + epsilon))\n        loss += loss_pos + loss_neg\n        \n    return loss","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}