{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":81933,"databundleVersionId":9643020,"sourceType":"competition"},{"sourceId":7453542,"sourceType":"datasetVersion","datasetId":921302},{"sourceId":9633761,"sourceType":"datasetVersion","datasetId":5875194},{"sourceId":9842977,"sourceType":"datasetVersion","datasetId":6038653}],"dockerImageVersionId":30775,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# dummy cell for starting","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">1/ Introduction</p>\n![image demonstrating problematic Internet usage](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRv6vi_VmC1Be3chdU8QE7DI8lCvVef0x-4-Q&s)","metadata":{}},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n- The main aim of the competition is to use our training data to predict **sii** or **Severity Impairment Index**, which is a standard measure of Problematic Internet Use (PIU).\n- The training data comprises 3,960 records of children and young people with 81 columns (not including the ID column).\n- Of particular importance in the data are results of the **Parent-Child Internet Addiction Test (PCIAT)**.\n- The target is actually derived from the field PCIAT-PCIAT_Total (scored out of 100).\n- We can therefore choose to predict the PCIAT Total and convert this to sii (making this a regression problem) or stick with sii (making this a classification problem).\n- The test data is really just formatted sample data. The actual test data of about 3,800 instances is hidden.\n- In the sample data none of the 22 PCIAT fields are available (in addition to the target feature). Hence the sample data format has 58 columns compared to 81 in the train data.\n- In 1,224 records in the train data the sii target and all the PCIAT columns are missing - presumably because not available.\n- Overall there are > 1,000,000 missing values in the train data.\n- Only 2,736 records have a target, the rest are missing.\n- 996 of the young people also have sensor data from a worn device which measures gross motor activity.","metadata":{}},{"cell_type":"code","source":"# Install modules without Internet access is this\n!pip -q install /kaggle/input/pytorchtabnet/pytorch_tabnet-4.1.0-py3-none-any.whl\n!pip -q install --no-index --find-links /kaggle/input/skorch-wheels skorch","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np, pandas as pd, os\nfrom sklearn.model_selection import cross_val_score, StratifiedKFold\nfrom pytorch_tabnet.tab_model import TabNetRegressor\nfrom xgboost import XGBRegressor\nfrom lightgbm import LGBMRegressor\nfrom catboost import CatBoostRegressor\nfrom skorch import NeuralNetRegressor\nimport skorch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Dataset\n#from torch import torch\nimport plotly.express as px, seaborn as sns, matplotlib.pyplot as plt\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.compose import ColumnTransformer\nimport plotly.express as px, seaborn as sns, matplotlib.pyplot as plt\nsns.set_style('darkgrid')\nfrom sklearn.metrics import make_scorer, cohen_kappa_score\nfrom sklearn.preprocessing import MinMaxScaler\nimport warnings\nwarnings.simplefilter('ignore')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">3/ Import data</p>","metadata":{}},{"cell_type":"code","source":"path = '../input/child-mind-institute-problematic-internet-use/'\n\ntrain = pd.read_csv(path + 'train.csv', index_col = 'id')\nprint(\"The train data has the shape: \",train.shape)\ntest = pd.read_csv(path + 'test.csv', index_col = 'id')\nprint(\"The test data has the shape: \",test.shape)\nprint(\"\")\nprint(\"Total number of missing training values: \", train.isna().sum().sum())\n\npath = '../input/piu-bp-children/'\nBP_boys_table = pd.read_csv (path + 'BP_boys_table.csv', header=None, names=['age', 'description', \\\n                            'value_1', 'value_2', 'value_3', 'value_4', 'value_5', 'value_6', 'value_7'])\nBP_girls_table = pd.read_csv (path + 'BP_girls_table.csv', header=None, names=['age', 'description', \\\n                            'value_1', 'value_2', 'value_3', 'value_4', 'value_5', 'value_6', 'value_7'])\nBP_girls_table.head(5)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Import blood pressure tables into a dictionary\ndef import_BP_table (df):\n    # First fill the first column with the corresponding age\n    for index, row in df.iterrows():\n        if pd.notna(row['age']):\n            last_age_seen = row['age']\n        else:\n            df.loc[index,'age'] = last_age_seen\n    ages = (df['age'].drop_duplicates().sort_values().to_list())\n    value_cols_names = ['value_' + str(i) for i in range (1,8)]\n    filters = ['Prehypertension', 'Stage_1_HTN', 'Stage_2_HTN']\n    BP_table = {}\n    for age in ages:\n        Selected_age_df = df[df['age'] == age]\n        Length_df = Selected_age_df [Selected_age_df ['description'] == 'Length']\n        Lengths_array = Length_df [value_cols_names].to_numpy()\n        BP_values_df = Selected_age_df [Selected_age_df ['description'].isin (filters)]\n        BP_values_array = BP_values_df [value_cols_names].to_numpy()\n        BP_table [age] = (Lengths_array, BP_values_array)\n    return BP_table\n \nBP_tables = {'boys': import_BP_table (BP_boys_table), \\\n             'girls': import_BP_table (BP_girls_table)}\nprint (BP_tables ['girls'])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">4/ Exploratory data analysis","metadata":{}},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\nThe features as described in the data dictionary can be grouped in the following categories:\n\n* Demographics - Information about age and sex of participants.\n* Internet Use - Number of hours of using computer/internet per day.\n* Children's Global Assessment Scale - Numeric scale used by mental health clinicians to rate the general functioning of youths under the age of 18.\n* Physical Measures - Collection of blood pressure, heart rate, height, weight and waist, and hip measurements.\n* FitnessGram Vitals and Treadmill - Measurements of cardiovascular fitness assessed using the NHANES treadmill protocol.\n* FitnessGram Child - Health related physical fitness assessment measuring five different parameters including ae robic capacity, muscular strength, muscular endurance, flexibility, and body composition.\n* Bio-electric Impedance Analysis - Measure of key body composition elements, including BMI, fat, muscle, and water content.\n* Physical Activity Questionnaire - Information about children's participation in vigorous activities over the last 7 days.\n* Sleep Disturbance Scale - Scale to categorize sleep disorders in children.\n* Actigraphy - Objective measure of ecological physical activity through a research-grade biotracker.\n* Season - for each set of measurements there is a 'season' feature which gives the season of the year when the measurements were carried out. These are the only predictive categorical features in the dataset and can be easily preprocessed.\n* As mentioned there are 22 PCIAT features. These comprise answers to 20 questions (each marked out of 5), the total score and 'season' when the test was carried out.\n* The sii target is derived from the total PCIAT score:\n    - 0-30 gives sii = 0\n    - 31-49 gives sii = 1\n    - 50-79 gives sii = 2\n    - 80-100 gives sii = 3. \n* We drop all the PCIAT features from the dataset except the PCIAT Total feature which can be used as a regression target.","metadata":{}},{"cell_type":"code","source":"PCIAT_cols = [val for val in train.columns[train.columns.str.contains('PCIAT')]]\nprint('Number of PCIAT features = ' , len(PCIAT_cols))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = px.scatter(train, x = 'PCIAT-PCIAT_Total', color = 'sii', marginal_x=\"box\", title = 'PCIAT Total')\nfig = fig.update_layout(yaxis_title=\"\")\nfig.update_yaxes(showticklabels=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train[train['PCIAT-PCIAT_Total']<=30].sii.value_counts())\nprint(train[(train['PCIAT-PCIAT_Total']>30) \n    & (train['PCIAT-PCIAT_Total']<50)].sii.value_counts())\nprint(train[(train['PCIAT-PCIAT_Total']>=50) \n    & (train['PCIAT-PCIAT_Total']<80)].sii.value_counts())\nprint(train[train['PCIAT-PCIAT_Total']>=80].sii.value_counts())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.sii.value_counts()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Elementary function for calculating the sii without corrections\ndef calculate_sii (PCIAT_Total):\n    PCIAT_Total = int (round (PCIAT_Total, 0))\n    if PCIAT_Total <= 30:\n        return 0\n    elif 31 <= PCIAT_Total <= 49:\n        return 1\n    elif 50 <= PCIAT_Total <= 79:\n        return 2\n    elif PCIAT_Total >= 80:\n        return 3\n    return np.nan","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# In case of incomplete PCIAT answer set, extrapolate the total score for two or less missing answers\nPCIAT_cols = [val for val in train.columns[train.columns.str.contains('PCIAT')]]\nPCIAT_cols.remove('PCIAT-PCIAT_Total')\nPCIAT_cols.remove( 'PCIAT-Season')\nnan_list = []\nfor index, row in train[PCIAT_cols].iterrows():\n    nan_occurances = 0\n    for col_name in PCIAT_cols:\n        if pd.isna(row[col_name]): \n            nan_occurances += 1\n    if nan_occurances > 0 and nan_occurances <= 2:\n        # linearly extrapolate the calculated value for PCIAT_Total in case of one or two missing answers\n        recalculated_total = int (round ((20/(20 - nan_occurances)) * train.loc[index, 'PCIAT-PCIAT_Total'], 0))\n        train.loc[index, 'PCIAT-PCIAT_Total'] = recalculated_total\n        print (index, recalculated_total)\n    elif nan_occurances > 0:\n        nan_list.append (index)\nprint (len (nan_list))\ntrain.loc[nan_list].filter (like='PCIAT_')\n# Delete those rows from the data frame with nan\ntrain = train.drop(nan_list)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verify the value of sii and adjust if applicable\nfor index, row in train[train['PCIAT-PCIAT_Total'].notna()].iterrows():\n    calculated_value = calculate_sii (row['PCIAT-PCIAT_Total'])\n    if row['sii'] != calculated_value:\n        print (index, calculated_value,row['PCIAT-PCIAT_Total'])\n        train.loc[index, 'sii'] = calculated_value","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Remove rows without sii (not useful for training)\nprint (len (train))\ntrain = train.dropna(subset='sii')\nprint (len (train))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate data frame with statistics for all remaining columns\nif 'train_statistics' in globals():\n    del train_statistics\nselected_cols = list (train.columns.values.tolist())\nfor col in selected_cols:\n    col_stats = train[col].describe()\n    if 'train_statistics' in globals():\n        train_statistics = pd.concat ([train_statistics, col_stats], axis=1)\n    else:\n        train_statistics = col_stats\ntrain_statistics = train_statistics.transpose()\ntrain_statistics.rename(columns={'50%': 'median'}, inplace = True)\ntrain_statistics","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Manually select features for further analysis\nselection = ['SDS-SDS_Total_T', 'SDS-SDS_Total_Raw', 'Fitness_Endurance-Max_Stage']\ntrain[selection].hist(figsize=(10,10), grid = True, color = 'chocolate')\nplt.tight_layout()\nN = len(selection)\ncols = 2\nrows = int ( (N - N % cols) / cols + 1)\n\nfig, axs = plt.subplots(rows, cols, figsize=(30, 60))\ncounter = 0\nfor s in selection:\n    r = int (counter / cols)\n    c = counter % cols\n    sns.scatterplot(y=train['PCIAT-PCIAT_Total'], x=train[s], ax=axs[r,c]).set (title=s)\n    counter += 1\n# remove unused axes\nfor ax in axs.flat[N:]:\n    ax.remove()\nplt.show() ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* From the scatterplot it becomes clearer that SDS-SDS_Total_Raw and \n    SDS-SDS_Total_T contain the same data, wherein\n    SDS-SDS_Total_T simply is a normalised version of SDS-SDS_Total_Raw","metadata":{}},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">4/ Feature engineering</p>","metadata":{}},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* Blood pressure is highly age dependent, therefore using absolute systolic blood pressure values is not useful\n* Instead, we will use BP tables to assess whether the child has high blood pressure or not\n* The data are derived from [A pocket guide to Blood Pressure Measurement in Children](https://www.nhlbi.nih.gov/files/docs/bp_child_pocket.pdf)","metadata":{}},{"cell_type":"code","source":"def BP_table_lookup (systolic, gender, age, height):\n    # Returns classification for paediatric blood pressure as follows:\n    # 0: Normal blood pressure\n    # 1: Prehypertension\n    # 2: Stage 1 hypertension\n    # 3: Stage 2 hypertension\n\n    # Determine first the column number which corresponds to the given height\n    for j in range (0,7):\n        if BP_tables[gender][age][0][0,j] >= height: \n            break\n    # Next, compare the blood pressure values in the column with the given value for the systolic BP\n    for i in range (0,4):\n        if i < 3:\n            if BP_tables[gender][age][1][i,j] > systolic:\n                break\n    return i\ntable_id_map = {0: 'boys', 1: 'girls'}\nprint (BP_table_lookup (134, 'girls', 13, 151))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* I conjecture that the outside weather has an impact on a child's Internet behaviour\n* According to the documentation, the PCIAT questionnaires have been conducted in New York (state)\n* Therefore, I have used some weather statistics for New York (City): temperature in degrees Celsius and daily hours of sunshine\n* The statistical data have been derived from [this website](https://www.metoffice.gov.uk/weather/travel/holiday-weather/americas/usa/newyork)","metadata":{}},{"cell_type":"code","source":"# Statistics for New York City, retrieved from https://www.metoffice.gov.uk/weather/travel/holiday-weather/americas/usa/newyork\nSeason = {'Mar': 1, 'Apr': 1, 'May': 1, 'Jun': 2, 'Jul': 2, 'Aug': 2, \\\n          'Sep': 3, 'Oct': 3, 'Nov': 3, 'Jan': 4, 'Feb': 4, 'Dec': 4}\nMax_temp = {'Mar': 9.5 , 'Apr': 16.0 , 'May': 21.4 , 'Jun': 26.7 , 'Jul': 29.9 , 'Aug': 29.0 , \\\n            'Sep': 24.9 , 'Oct': 18.5 , 'Nov': 12.4 , 'Jan': 4.0 , 'Feb': 5.2 , 'Dec': 6.7 }\nSunshine = {'Mar': 7, 'Apr': 8, 'May': 9, 'Jun': 10, 'Jul': 10, 'Aug': 9, \\\n            'Sep': 8, 'Oct': 7, 'Nov': 6, 'Jan': 5, 'Feb': 6, 'Dec': 5}\nAvg_temp_season = [0.0, 0.0, 0.0, 0.0]\nAvg_sunshine_season = [0, 0, 0, 0]\nfor month in Season.keys():\n    Avg_temp_season [Season[month] - 1] += Max_temp [month] / 3\n    Avg_sunshine_season [Season[month] - 1] += Sunshine [month] / 3\nseason_index = dict(zip([\"Spring\", \"Summer\", \"Fall\", \"Winter\"], [0, 1, 2, 3]))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering(df):\n    # Many of the raw features have little significance (in particular for the Body Impedance Analysis), therefore we will\n    # engineer some more meaningful derived features\n    df['BMI_Age'] = df['Physical-BMI'] * df['Basic_Demos-Age']\n\n    df['Internet_Hours_Age'] = df['PreInt_EduHx-computerinternet_hoursday'] * df['Basic_Demos-Age']\n\n    df['BMI_Internet_Hours'] = df['Physical-BMI'] * df['PreInt_EduHx-computerinternet_hoursday']\n\n    df['BMR_Weight'] = df['BIA-BIA_BMR'] / df['Physical-Weight']\n\n    df['DEE_Weight'] = df['BIA-BIA_DEE'] / df['Physical-Weight']\n\n    df['SMM_Height'] = df['BIA-BIA_SMM'] / df['Physical-Height']\n\n    df['Muscle_to_Fat'] = df['BIA-BIA_SMM'] / df['BIA-BIA_FMI']\n\n    df['Hydration_Status'] = df['BIA-BIA_TBW'] / df['Physical-Weight']\n    \n    # First, add two new columns\n    df['PreInt_EduHx-AvgTemp'] = np.nan\n    df['PreInt_EduHx-AvgSuns'] = np.nan\n    for index, row in df[ (df['PreInt_EduHx-computerinternet_hoursday'].notna()) & (df['PreInt_EduHx-Season'].notna())].iterrows():\n        season_name = row['PreInt_EduHx-Season']\n        df.loc[index, 'PreInt_EduHx-AvgTemp'] = Avg_temp_season [season_index [season_name]]\n        df.loc[index, 'PreInt_EduHx-AvgSuns'] = Avg_sunshine_season [season_index [season_name]]\n    \n    season_cols = [col for col in df.columns if 'Season' in col]\n    df = df.drop(season_cols, axis=1) \n\n    # Merge FitnessGram Child columns into one single column    \n    fgc_cols = [col for col in df.columns if ('FGC' in col and '_Zone' in col)]\n    # Create a new column for FitnessGram Child total\n    df['FGC_total'] = np.nan\n    for index, row in df[df[fgc_cols].notna()].iterrows():\n        fgc_total = 0\n        for col in fgc_cols:\n            if df[col].min() > 0:\n                fgc_total += row[col] - 1\n            else:\n                fgc_total += row[col]\n        df.loc[index, 'FGC_total'] = fgc_total\n    # Remove outliers\n    df.loc[\n        (df['Fitness_Endurance-Max_Stage'].notna()) & \n        (df['Fitness_Endurance-Time_Mins'].isna() | \n         df['Fitness_Endurance-Time_Sec'].isna()), ['Fitness_Endurance-Max_Stage', 'Fitness_Endurance-Time_Sec', \\\n                                                       'Fitness_Endurance-Time_Mins'] \n    ] = np.nan\n    \n    # Fitness_Endurance-Time minutes and seconds are additive, merge into a single column\n    for index, row in df[df['Fitness_Endurance-Time_Sec'].notna()].iterrows():\n        df.loc[index, 'Fitness_Endurance-Time_Sec'] = row['Fitness_Endurance-Time_Mins'] * 60 + \\\n                            row['Fitness_Endurance-Time_Sec']\n    \n    # PAQ_C_Total and PAQ_A_Total contain the same information for children and adults respectively\n    # Merge both columns into a new column\n    df['PAQ_Total'] = np.nan\n    for index, row in df[ (df['PAQ_C-PAQ_C_Total'].notna()) | (df['PAQ_A-PAQ_A_Total'].notna())].iterrows():\n        if pd.notnull(row['PAQ_C-PAQ_C_Total']):\n            df.loc[index, 'PAQ_Total'] = row['PAQ_C-PAQ_C_Total']\n        else:\n            df.loc[index, 'PAQ_Total'] = row['PAQ_A-PAQ_A_Total']\n    \n    # Create a new column for BP category initialised with NaN\n    df['Physical-BP_category'] = np.nan\n    for index, row in df[df['Physical-Systolic_BP'].notna()].iterrows():\n        table_id = table_id_map[row['Basic_Demos-Sex']]\n        # Convert height in inches to a height in centimetres\n        height_cm = round (2.54 * row['Physical-Height'], 0)\n        systolic = row['Physical-Systolic_BP']\n        age = row['Basic_Demos-Age']\n        age = 17 if age > 17 else age\n        BP_category = BP_table_lookup (systolic, table_id, age, height_cm)\n        df.loc[index, 'Physical-BP_category'] = BP_category\n            \n    return df\ntrain = feature_engineering (train)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate data frame with statistics for all remaining columns\nif 'train_statistics' in globals():\n    del train_statistics\nselected_cols = list (train.columns.values.tolist())\nfor col in selected_cols:\n    col_stats = train[col].describe()\n    if 'train_statistics' in globals():\n        train_statistics = pd.concat ([train_statistics, col_stats], axis=1)\n    else:\n        train_statistics = col_stats\ntrain_statistics = train_statistics.transpose()\ntrain_statistics.rename(columns={'50%': 'median'}, inplace = True)\ntrain_statistics","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Manually select features for further analysis\nselection = ['Fitness_Endurance-Time_Sec', 'Physical-BP_category']\ntrain[selection].hist(figsize=(10,10), grid = True, color = 'chocolate')\nplt.tight_layout()\nN = len(selection)\ncols = 2\nrows = int ( (N - N % cols) / cols + 1)\n\nfig, axs = plt.subplots(rows, cols, figsize=(30, 60))\ncounter = 0\nfor s in selection:\n    r = int (counter / cols)\n    c = counter % cols\n    sns.scatterplot(y=train['PCIAT-PCIAT_Total'], x=train[s], ax=axs[r,c]).set (title=s)\n    counter += 1\n# remove unused axes\nfor ax in axs.flat[N:]:\n    ax.remove()\nplt.show() ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* From the scatterplot it becomes clearer that SDS-SDS_Total_Raw and \n    SDS-SDS_Total_T contain the same data, wherein\n    SDS-SDS_Total_T simply is a normalised version of SDS-SDS_Total_Raw","metadata":{}},{"cell_type":"code","source":"train = train.drop(columns = 'SDS-SDS_Total_Raw') ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">6/ Feature selection</p>","metadata":{}},{"cell_type":"code","source":"# List all available features to select from\nprint (train.columns)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"raw_features = ['Basic_Demos-Age', 'Basic_Demos-Sex', 'CGAS-CGAS_Score', 'Physical-BMI',  \\\n                'Physical-Height', 'Physical-Weight', 'Physical-Waist_Circumference',  'Physical-Diastolic_BP', \\\n                'Physical-HeartRate', 'Physical-Systolic_BP',  'Fitness_Endurance-Max_Stage', \\\n                'Fitness_Endurance-Time_Sec', 'FGC-FGC_CU', 'FGC-FGC_CU_Zone', 'FGC-FGC_GSND', 'FGC-FGC_GSND_Zone', \\\n                'FGC-FGC_GSD', 'FGC-FGC_GSD_Zone', 'FGC-FGC_PU', 'FGC-FGC_PU_Zone', 'FGC-FGC_SRL', 'FGC-FGC_SRL_Zone', \\\n                'FGC-FGC_SRR', 'FGC-FGC_SRR_Zone', 'FGC-FGC_TL', 'FGC-FGC_TL_Zone', 'BIA-BIA_Activity_Level_num', \\\n                'BIA-BIA_BMC', 'BIA-BIA_BMR', 'BIA-BIA_DEE', 'BIA-BIA_ECW', 'BIA-BIA_FFM', 'BIA-BIA_FFMI', \\\n                'BIA-BIA_FMI', 'BIA-BIA_Fat', 'BIA-BIA_Frame_num', 'BIA-BIA_ICW', 'BIA-BIA_LDM', 'BIA-BIA_LST', \\\n                'BIA-BIA_SMM', 'BIA-BIA_TBW', 'PAQ_Total', 'SDS-SDS_Total_T']\nengineered_features = ['Basic_Demos-Age', 'Basic_Demos-Sex', 'CGAS-CGAS_Score', 'Physical-BMI', \\\n       'Physical-Height', 'Physical-Weight', 'Physical-Waist_Circumference', \\\n       'Physical-HeartRate', 'Fitness_Endurance-Max_Stage', \\\n       'Fitness_Endurance-Time_Sec', 'SDS-SDS_Total_T', 'PreInt_EduHx-computerinternet_hoursday', \\\n       'PAQ_Total', 'PreInt_EduHx-AvgTemp', 'PreInt_EduHx-AvgSuns', 'BIA-BIA_Activity_Level_num', \\\n       'FGC_total', 'Physical-BP_category', 'BMI_Age','Internet_Hours_Age','BMI_Internet_Hours', \\\n       'BMR_Weight', 'DEE_Weight', 'SMM_Height', 'Muscle_to_Fat', 'Hydration_Status']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">6/ Ensemble regression models</p>","metadata":{}},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* First, we will use multiple Gradient Boosting models and use PCIAT-PCIAT_Total as the target.\n* We tweak the quadratic kappa function to convert PCIAT total scores to sii categories, which gives a better cross-validation result.","metadata":{"execution":{"iopub.status.busy":"2024-10-01T12:35:10.605389Z","iopub.execute_input":"2024-10-01T12:35:10.605904Z","iopub.status.idle":"2024-10-01T12:35:10.613456Z","shell.execute_reply.started":"2024-10-01T12:35:10.605858Z","shell.execute_reply":"2024-10-01T12:35:10.612056Z"}}},{"cell_type":"code","source":"# We start with a set of engineered features\nX = train[engineered_features]\ny = train['PCIAT-PCIAT_Total']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert(scores):\n    bins = np.zeros_like(scores)\n    bins[scores <= 30] = 0\n    bins[(scores > 30) & (scores < 50)] = 1\n    bins[(scores >= 50) & (scores < 80)] = 2\n    bins[scores >= 80] = 3\n    return bins","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def quadratic_kappa(y_true, y_pred):\n    y_true_cat = convert(y_true)\n    y_pred_cat = convert(y_pred)\n    return cohen_kappa_score(y_true_cat, y_pred_cat, weights='quadratic')\n\nkappa_scorer = make_scorer(quadratic_kappa, greater_is_better=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N_SPLITS = 6 # Results in a 84/16 division between training and validation data\nRANDOM_STATE = 42\nSEED = 42\nskf = StratifiedKFold(n_splits=N_SPLITS, shuffle=True, random_state=RANDOM_STATE)\n# Model parameters for LightGBM\nParams = {\n    'learning_rate': 0.039,\n    'max_depth': 12,\n    'num_leaves': 478,\n    'min_data_in_leaf': 13,\n    'feature_fraction': 0.893,\n    'bagging_fraction': 0.784,\n    'bagging_freq': 4,\n    'lambda_l1': 10,  # Increased from 6.59\n    'lambda_l2': 0.01,  # Increased from 2.68e-06\n    'device': 'gpu'\n}\n\n# XGBoost parameters\nXGB_Params = {\n    'learning_rate': 0.075,\n    'max_depth': 6,\n    'n_estimators': 400,\n    'subsample': 0.8,\n    'colsample_bytree': 0.8,\n    'reg_alpha': 1,  # Increased from 0.1\n    'reg_lambda': 5,  # Increased from 1\n    'random_state': SEED,\n    'tree_method': 'gpu_hist',\n}\n\nCatBoost_Params = {\n    'learning_rate': 0.05,\n    'depth': 6,\n    'iterations': 200,\n    'random_seed': SEED,\n    'verbose': 0,\n    'l2_leaf_reg': 10,  # Increase this value\n    'task_type': 'GPU'\n}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create model instances\n\nLight = LGBMRegressor(**Params, random_state=SEED, verbose=-1, n_estimators=300)\n\nXGB_Model = XGBRegressor(**XGB_Params)\n\nCatBoost_Model = CatBoostRegressor(**CatBoost_Params)\n\nModels = [Light, XGB_Model, CatBoost_Model]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for model in Models:\n    scores = cross_val_score(model, X, y, cv=skf, scoring=kappa_scorer)\n    print(\"Quadratic Cohen's Kappa Scores:\", scores)\n    print(\"Mean Quadratic Cohen's Kappa:\", np.mean(scores))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Now use a set of raw features for comparison\nX = train[raw_features]\ny = train['PCIAT-PCIAT_Total']\n# Reinitialise model object\nXGB_Model = XGBRegressor(**XGB_Params)\n# Retrain model\nmodel = XGB_Model\nscores = cross_val_score(model, X, y, cv=skf, scoring=kappa_scorer)\nprint(\"Quadratic Cohen's Kappa Scores:\", scores)\nprint(\"Mean Quadratic Cohen's Kappa:\", np.mean(scores))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n\n* One can observe all the different models based on ensemble trees roughly produce the same results.\n* When we compare the results for the XGB model for both engineered features and raw features, we can see a significant\nimprovement for the engineered features.\n* Let's now take a closer look at the feature importance.","metadata":{}},{"cell_type":"code","source":"model = XGB_Model\nmodel.fit(X,y)\nfeature_imp = pd.Series(model.feature_importances_,index=X.columns).sort_values(ascending=False)\nfeature_imp\nsns.barplot(x=feature_imp, y=feature_imp.index)\nplt.xlabel('Feature Importance Score')\nplt.title(\"Feature Importances\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">6/ Neural network models: Tabnet","metadata":{}},{"cell_type":"code","source":"# New: TabNet \n\nfrom sklearn.base import BaseEstimator, RegressorMixin\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.model_selection import train_test_split\nfrom pytorch_tabnet.callbacks import Callback\nimport os\nimport torch\nfrom pytorch_tabnet.callbacks import Callback\n\nclass TabNetWrapper(BaseEstimator, RegressorMixin):\n    def __init__(self, **kwargs):\n        self.model = TabNetRegressor(**kwargs)\n        self.kwargs = kwargs\n        self.imputer = SimpleImputer(strategy='median')\n        self.best_model_path = 'best_tabnet_model.pt'\n        \n    def fit(self, X, y):\n        # Handle missing values\n        X_imputed = self.imputer.fit_transform(X)\n        \n        if hasattr(y, 'values'):\n            y = y.values\n            \n        # Create internal validation set\n        X_train, X_valid, y_train, y_valid = train_test_split(\n            X_imputed, \n            y, \n            test_size=0.2,\n            random_state=42\n        )\n        \n        # Train TabNet model\n        history = self.model.fit(\n            X_train=X_train,\n            y_train=y_train.reshape(-1, 1),\n            eval_set=[(X_valid, y_valid.reshape(-1, 1))],\n            eval_name=['valid'],\n            eval_metric=['mse'],\n            max_epochs=500,\n            patience=50,\n            batch_size=1024,\n            virtual_batch_size=128,\n            num_workers=0,\n            drop_last=False,\n            callbacks=[\n                TabNetPretrainedModelCheckpoint(\n                    filepath=self.best_model_path,\n                    monitor='valid_mse',\n                    mode='min',\n                    save_best_only=True,\n                    verbose=True\n                )\n            ]\n        )\n        \n        # Load the best model\n        if os.path.exists(self.best_model_path):\n            self.model.load_model(self.best_model_path)\n            os.remove(self.best_model_path)  # Remove temporary file\n        \n        return self\n    \n    def predict(self, X):\n        X_imputed = self.imputer.transform(X)\n        return self.model.predict(X_imputed).flatten()\n    \n    def __deepcopy__(self, memo):\n        # Add deepcopy support for scikit-learn\n        cls = self.__class__\n        result = cls.__new__(cls)\n        memo[id(self)] = result\n        for k, v in self.__dict__.items():\n            setattr(result, k, deepcopy(v, memo))\n        return result\n\n# TabNet hyperparameters\nTabNet_Params = {\n    'n_d': 64,              # Width of the decision prediction layer\n    'n_a': 64,              # Width of the attention embedding for each step\n    'n_steps': 5,           # Number of steps in the architecture\n    'gamma': 1.5,           # Coefficient for feature selection regularization\n    'n_independent': 2,     # Number of independent GLU layer in each GLU block\n    'n_shared': 2,          # Number of shared GLU layer in each GLU block\n    'lambda_sparse': 1e-4,  # Sparsity regularization\n    'optimizer_fn': torch.optim.Adam,\n    'optimizer_params': dict(lr=2e-2, weight_decay=1e-5),\n    'mask_type': 'entmax',\n    'scheduler_params': dict(mode=\"min\", patience=10, min_lr=1e-5, factor=0.5),\n    'scheduler_fn': torch.optim.lr_scheduler.ReduceLROnPlateau,\n    'verbose': 1,\n    'device_name': 'cuda' if torch.cuda.is_available() else 'cpu'\n}\n\nclass TabNetPretrainedModelCheckpoint(Callback):\n    def __init__(self, filepath, monitor='val_loss', mode='min', \n                 save_best_only=True, verbose=1):\n        super().__init__()  # Initialize parent class\n        self.filepath = filepath\n        self.monitor = monitor\n        self.mode = mode\n        self.save_best_only = save_best_only\n        self.verbose = verbose\n        self.best = float('inf') if mode == 'min' else -float('inf')\n        \n    def on_train_begin(self, logs=None):\n        self.model = self.trainer  # Use trainer itself as model\n        \n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        current = logs.get(self.monitor)\n        if current is None:\n            return\n        \n        # Check if current metric is better than best\n        if (self.mode == 'min' and current < self.best) or \\\n           (self.mode == 'max' and current > self.best):\n            if self.verbose:\n                print(f'\\nEpoch {epoch}: {self.monitor} improved from {self.best:.4f} to {current:.4f}')\n            self.best = current\n            if self.save_best_only:\n                self.model.save_model(self.filepath)  # Save the entire model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TabNet hyperparameters\nTabNet_Params = {\n    'n_d': 64,              # Width of the decision prediction layer\n    'n_a': 64,              # Width of the attention embedding for each step\n    'n_steps': 5,           # Number of steps in the architecture\n    'gamma': 1.5,           # Coefficient for feature selection regularization\n    'n_independent': 2,     # Number of independent GLU layer in each GLU block\n    'n_shared': 2,          # Number of shared GLU layer in each GLU block\n    'lambda_sparse': 1e-4,  # Sparsity regularization\n    'optimizer_fn': torch.optim.Adam,\n    'optimizer_params': dict(lr=2e-2, weight_decay=1e-5),\n    'mask_type': 'entmax',\n    'scheduler_params': dict(mode=\"min\", patience=10, min_lr=1e-5, factor=0.5),\n    'scheduler_fn': torch.optim.lr_scheduler.ReduceLROnPlateau,\n    'verbose': 0,\n    'device_name': 'cuda' if torch.cuda.is_available() else 'cpu'\n}\n# Create model instances\nTabNet_Model = TabNetWrapper(**TabNet_Params) # New\n\nscores = cross_val_score(TabNet_Model, X, y, cv=skf, scoring=kappa_scorer)\nprint(\"Quadratic Cohen's Kappa Scores:\", scores)\nprint(\"Mean Quadratic Cohen's Kappa:\", np.mean(scores))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">7/ Neural network models: Custom neural network</p>","metadata":{}},{"cell_type":"code","source":"use_total = True\nif use_total:\n    output_cols = ['PCIAT-PCIAT_Total']\nelse:\n    output_cols = list (train.filter (like='PCIAT_').columns.tolist())\n    output_cols.remove('PCIAT-PCIAT_Total')\nprint (output_cols)\nselection = engineered_features\nX = train[selection]\nN_inputs = len (selection)\ny = train[output_cols]\nN_outputs = len (output_cols)\nprint (f\"{N_inputs} input features and {N_outputs} output features\")\n# Impute column median for missing values\nimputer = SimpleImputer(strategy=\"median\")\nct = ColumnTransformer(\n    [(\"imputer\",imputer, selection)],\n    remainder=\"passthrough\"\n    ) .set_output(transform=\"pandas\")\nX = ct.fit_transform(X)\n\n# Splitting \ntrain_X, val_X, train_y, val_y = train_test_split(X, y, \n                      test_size = 0.15, random_state = 123)\nprint (X[X.isna().any(axis=1)])\nprint (y[y.isna().any(axis=1)])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustomNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.layers = nn.Sequential(\n            nn.Linear(N_inputs, 24),\n            nn.ReLU(),\n            nn.Linear(24, 24),\n            nn.ReLU(),\n            nn.Linear(24, N_outputs)\n        )\n\n    def forward(self, x):\n        return self.layers(x)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustomNeuralNetRegressor (NeuralNetRegressor):\n    def __init__(\n            self,\n            module,\n            *args,\n            criterion=torch.nn.MSELoss,\n            **kwargs\n    ):\n        super(CustomNeuralNetRegressor, self).__init__(\n            module,\n            *args,\n            criterion=criterion,\n            **kwargs\n        )\n            \n    def predict(self, X):\n        y_pred = super().predict (X)\n        y_pred_total =  np.sum (y_pred, axis=1)\n        return y_pred_total\n    \n    def fit (self, X, y):\n        # nX, ny = X.to_numpy(np.float32), y.to_numpy(np.float32)\n        nX, ny = X, y\n        return super().fit (nX, ny) ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# create the skorch wrapper\nnn_model = CustomNeuralNetRegressor (\n    CustomNN, \n    criterion=nn.MSELoss(),\n    optimizer=optim.Adam,\n    lr=0.02,\n    max_epochs=20,\n    batch_size=1,\n    train_split=skorch.dataset.ValidSplit(0.15)\n)\nnX, ny = X.to_numpy(np.float32), y.to_numpy(np.float32)  \nnn_model.fit(nX, ny)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scores = cross_val_score(nn_model, nX, ny, cv=skf, scoring=kappa_scorer)\nprint(\"Quadratic Cohen's Kappa Scores:\", scores)\nprint(\"Mean Quadratic Cohen's Kappa:\", np.mean(scores))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:15px; background-color:chocolate; font-family:arial; font-weight:bold; color:white; font-size:100%; letter-spacing: 2px; text-align:left; border-radius: 10px 10px\">6/ Submission</p>","metadata":{}},{"cell_type":"markdown","source":" <div style=\"border-radius:12px; border:chocolate solid; padding: 15px; background-color: #f6f5f5; font-size:120%; text-align:left\">\n    \n* So we now have two models, a classifier model and a regression model.\n* Since the latter gives a better CV score, we will submit this to get a benchmark LB score.","metadata":{}},{"cell_type":"code","source":"# Apply all transformations also to the test set\ntest = feature_engineering (test)\n# Filter only selected columns\ntest = test[engineered_features]\nprint (test.columns)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = XGB_model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.fit(X,y)\npreds = model.predict(test)\npreds = convert(preds) # convert raw scores to sii categories if using regressor\npreds = pd.Series(preds)\npreds.index = test.index\npreds.to_csv('submission.csv')","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}