{
  "id": 411398,
  "title": "4th Place Gold - Single Model RAPIDS cuML SVR!",
  "content": "<h1>AMP®-Parkinson's Disease Progression Prediction</h1>\n<p>Wow, this competition had a close finish at the top. I suspected this would be the case because I believe there was only 1 source of reliable signal (i.e. <code>patient visit dates</code>) and all the tops teams were working hard to extract an extra SMAPE 0.1 from it. The metric SMAPE is a percentage so <code>0.1</code> is actually <code>0.001</code>. That's how close the finish was!</p>\n<p>The final leaderboard is separated into two groups. The top 18 teams have <code>SMAPE &lt;= 62.5</code> and the next teams have <code>SMAPE &gt;= 68.4</code>. I believe the top 18 teams used signal from <code>patient visit dates</code> and the next teams <code>did not</code>.</p>\n<h1>Signal from Protein/Peptide data</h1>\n<p>In this competition, Kaggle provided 227 Protein NXP features and 968 Peptide PeptideAbundance features. That is 1195 features for each of 17 possible visit dates. We only have 248 train patients. And the <code>curse of dimensionality</code> begins when <code>number of features &gt; train samples / 10</code>. In other words we only have enough train data to reasonably train 25 features not 1195 features!</p>\n<p>I did an experiment where i made 1000 columns of random numbers. Using forward feature selection, I found that columns of <code>random numbers</code> would boost GroupKFold CV score <strong>the same amount that protein/peptide features did</strong>. This means that there may be signal hiding in the protein peptide data but it is too weak to detect patterns with only 248 patients (because no protein nor peptide boost CV more than random numbers can).</p>\n<h1>Signal from Patient Visit Dates</h1>\n<p>Next I searched patient visit dates for signal. Many Kagglers overlooked that we can engineer features from patient visit dates. Here are some example features</p>\n<ul>\n<li>when was patient's first blood work measured?</li>\n<li>did patient get blood work at their first doctor visit, yes or no?</li>\n<li>how many times did a patient visit the doctor?</li>\n<li>how long ago was the patient's last visit?</li>\n</ul>\n<p>Etc etc. We can create 100s of features about when a patient visited the doctor and when a patient had blood work done. I quickly noticed the following trend. <strong>Patients who visit the doctor more often have larger UPDR scores</strong>. This is shown in the following 3 plots. The first are patients who visited the doctor a normal number of times. The second are patients who visited 1 standard deviation less than normal. And the last are patients who visited 1 standard deviation more. In each plot, we display the average target value per visit month for these 3 groups of patients:<br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/normal2.png\" alt=\"\"><br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/less2.png\" alt=\"\"><br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/more2.png\" alt=\"\"></p>\n<h1>Feature Engineering</h1>\n<p>The above plots show that there is signal in the <code>patient visit dates</code>. What is the best way to extract this signal? I generated 100s of features and used <code>for-loops</code> with <strong>RAPIDS cuML SVR</strong> to find which features extract the most signal.</p>\n<p>In the end, simple \"booleans\" worked best (and the model \"created its own features internally\"). For each visit month, i created a boolean variable. For example for visit month = 24, i created the following \"boolean\":</p>\n<ul>\n<li><code>v24 = 0</code> if we know that patient did <strong>not</strong> visit on visit month = 24</li>\n<li><code>v24 = 1</code> if we know that patient did visit on visit month = 24</li>\n<li><code>v24 = -1</code> if we <strong>do not</strong> know if patient visited on month = 24</li>\n</ul>\n<p>The reason for the third category is because at each time step of Kaggle's API we are asked to predict <code>0, 6, 12, 24</code> months into the future. So if the current visit month = 12 and we are predicting visit month 36, we do <strong>not</strong> know if the patient visited during visit month = 24.</p>\n<h1>Single Model RAPIDS cuML - 8th Place Gold</h1>\n<p>A single <strong>RAPIDS cuML SVR</strong> model trained with 11 features which are <code>visit_month</code> and <code>v0</code>, <code>v6</code>, <code>v12</code>, <code>v18</code>, <code>v24</code>, <code>v36</code>, <code>v48</code>, <code>v60</code>, <code>v72</code>, <code>v84</code> where the <code>v</code> features are described above achieves <code>CV = 55.5</code> and <code>Public LB = 55.4</code> and <code>Private LB = 60.5</code>. This is 8th place Gold. Using <strong>RAPIDS cuML</strong> was great because it allowed me to experiment dozens of models in minutes!</p>\n<h1>Single Model TensorFlow MLP - 4th Place Gold</h1>\n<p>After I found the above features, i tried different model types. I tried XGBoost with <code>PseudoHuber loss</code>. It's CV was not as good as <strong>RAPIDS cuML SVR</strong>. Next I tried TensorFlow MLP with <code>MeanAbsoluteError</code>. We built an MLP with 10 hidden layers where each hidden layer has 24 units and activation Relu. We used no Dropout and no BatchNormalization. We trained it for 15 epochs with Adam optimizer <code>LR = 1e-3</code> and then 15 epochs <code>LR = 1e-4</code>. This achieves <code>CV = 55.0</code> and <code>Public LB 54.9</code> and <code>Private LB 60.1</code>. This is 4th place Gold.</p>\n<h1>Creating Train Data</h1>\n<p>Our model trained with <code>train_clinical_data.csv</code> only. Creating proper train data for the above features was not trivial. We needed to convert each row from the original train data into <strong>4 new rows</strong>. If the original row was <code>patient_id = 55</code> and <code>visit_month = 24</code>. Then we needed to replace this row with 4 new rows:</p>\n<ul>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=-1, v12=-1, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=-1, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=X24, v&gt;24=-1</li>\n</ul>\n<p>where <code>X1</code>, <code>X6</code>, <code>X12</code>, <code>X24</code> are the values <code>0</code> or <code>1</code> based on whether <code>patient_id=55</code> visited on months 0, 6, 12, 24 in the train data. The 4 new rows are current visit month minus 0, 6, 12, 24. If any of these subtractions are not valid visit months then we don't make that row.</p>\n<h1>4th Place Solution Code</h1>\n<p>I published my 4th place submission code using TensorFlow MLP <a href=\"https://www.kaggle.com/code/cdeotte/4th-place-solution-public-54-9-private-60-1\" target=\"_blank\">here</a>. Enjoy!</p>",
  "comments": [
    {
      "id": 2265148,
      "author_name": "Ankur Limbashia",
      "author_url": "",
      "post_date": "2023-05-19T02:49:38.903000",
      "content": "<p>Very interesting to know how the top 18 teams have used the visit month feature! I wish I had thought the same.<br>\nCongratulations !!!</p>",
      "votes": 7,
      "replies": [
        {
          "id": 2265230,
          "author_name": "Cody_Null",
          "author_url": "",
          "post_date": "2023-05-19T04:56:40.533000",
          "content": "<p>Yes, you can bet I am going to spend more time doing EDA from here on out and that I will be plotting every variable against each other </p>",
          "votes": 7,
          "replies": []
        }
      ]
    },
    {
      "id": 2265544,
      "author_name": "Vincent Schuler",
      "author_url": "",
      "post_date": "2023-05-19T10:25:58.560000",
      "content": "<p>Congratulations and thanks for your review ! </p>\n<p>The idea of the \"v-booleans\" is clever and better than what I used. I'm surprised by the boost given by the MLP compared to XGBoost and SVR !</p>\n<p>Bonus question : how did you manage to get your 52.8 LB score ? (I think I'm not the only one you made feel bad during some weeks 😃)</p>",
      "votes": 3,
      "replies": [
        {
          "id": 2265582,
          "author_name": "Chris Deotte",
          "author_url": "",
          "post_date": "2023-05-19T11:07:53.217000",
          "content": "<p>My best public LB score is actually <code>LB = 51.3</code> but i did not submit it because i didn't want my public LB score to be too good 😃</p>\n<p>From EDA, i saw that approximately 25% of train patients were control patients. I was curious if test data contained control patients or not (because this would affect our model approach). So I probed the first 165 patients out of test data's 384 patients. Among those 165 patients, about 24 were in public test (vs private), and about 8 are control patients. So yes, test data contains control patients too.</p>\n<p>If we submit linear trend public notebook and then multiply the 8 control patients (i discovered) targets by 0.2, we achieve <code>LB = 51.3</code>. </p>",
          "votes": 4,
          "replies": [
            {
              "id": 2265801,
              "author_name": "Chris Miles",
              "author_url": "",
              "post_date": "2023-05-19T14:13:14.727000",
              "content": "<p>Wow that is so cool. Is this somewhat how you went about probing? </p>\n<ul>\n<li>Track the visit patterns of all patients. </li>\n<li>Choose the 165, maybe by lowest patient_id. </li>\n<li>On the last iteration, loop through the 165 patients, and run <br>\n<code>if patient_id_visit_pattern == control_pattern: time.sleep(10 seconds)</code></li>\n<li>Then number_control_patients = run_time_seconds / 10</li>\n</ul>\n<p>Also, can you please share some insight into how you keep track of submission runtime?<br>\nI can't find anywhere on kaggle that tells me how long a submission took. So unless I keep watch on the submission page I don't know when it ended. I suppose I could make a program to watch the html and keep track, but I figured expert LB probers like yourself may have a simpler solution. </p>\n<p>Thanks for another crystal clear explanation of your solution. </p>",
              "votes": 1,
              "replies": []
            },
            {
              "id": 2266304,
              "author_name": "Chris Deotte",
              "author_url": "",
              "post_date": "2023-05-19T22:14:48.240000",
              "content": "<p>Chris, the trick to probe test patients is having a consistent list of patients that has the same ordering each time you run the submit notebook.</p>\n<p>So I would create a NumPy array and add each new patient id that the Kaggle API provides in the order that they arrive in the Kaggle API for-loop and test dataframe. Then each time i run the code, the 384 test patients will be in the same order with same array index.</p>\n<p>To probe a control patient, we just write if <code>patient id</code> has index <code>X</code> in the \"consistent array\" then multiply <code>patient id</code> targets by 0.2. If the public LB score improves then, this patient is both in public test and a control patient. And we try X=0, then X=1, then X=2, etc.</p>",
              "votes": 3,
              "replies": []
            }
          ]
        }
      ]
    },
    {
      "id": 2265257,
      "author_name": "ADAM.",
      "author_url": "",
      "post_date": "2023-05-19T05:27:53.443000",
      "content": "<p>Thanks for sharing. I also added a random column in my lgb models. I found that the CV of features with a random column is much worse. So I tuned parameters to make models to converge(i.e. no much difference between with a random columns and without a random column.)</p>",
      "votes": 3,
      "replies": []
    },
    {
      "id": 2271376,
      "author_name": "Muhammad Ammar Jamshed",
      "author_url": "",
      "post_date": "2023-05-23T20:31:44.383000",
      "content": "<p>This is truly spectacular <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> !<br>\nKeep up your amazing work </p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2271166,
      "author_name": "liuyt49",
      "author_url": "",
      "post_date": "2023-05-23T17:03:01.020000",
      "content": "<p>congratulations, this is Great!</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2270643,
      "author_name": "Gaurav Srivastava",
      "author_url": "",
      "post_date": "2023-05-23T09:45:20.273000",
      "content": "<p>Amazing Work <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> ! Thanks for sharing 😊</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2269266,
      "author_name": "caleb Kemboi",
      "author_url": "",
      "post_date": "2023-05-22T10:43:56.610000",
      "content": "<p>congratulations, this is Great!</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2268774,
      "author_name": "Siyun Yang",
      "author_url": "",
      "post_date": "2023-05-22T02:00:43.080000",
      "content": "<p>Thank you for sharing and congratulations!</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2267029,
      "author_name": "Atuljo",
      "author_url": "",
      "post_date": "2023-05-20T14:31:57.723000",
      "content": "<p>I tried the problem but had no idea how to approach this problem. Your solution is very helpful.</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2266524,
      "author_name": "Jayanth Sai Marreddi",
      "author_url": "",
      "post_date": "2023-05-20T07:06:13.430000",
      "content": "<p>Thank you for sharing learnt a lot, I also question myself why haven't I thought of this , Great Work</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2266382,
      "author_name": "Joel Sathiyendra",
      "author_url": "",
      "post_date": "2023-05-20T01:52:59.140000",
      "content": "<p>I literally had no idea how to approach this problem. Your solution is very insightful. Thank you for sharing <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a>.🎉</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2266017,
      "author_name": "Tisha",
      "author_url": "",
      "post_date": "2023-05-19T16:32:51.403000",
      "content": "<p>Congratulations man!! </p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265793,
      "author_name": "Oscar Aguilar",
      "author_url": "",
      "post_date": "2023-05-19T14:09:11.960000",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> and thanks for sharing your approach👍</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265786,
      "author_name": "Nat",
      "author_url": "",
      "post_date": "2023-05-19T14:06:52.453000",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a>, thank you for sharing your solution with us! It was well written and an awesome learning tool for me. </p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265642,
      "author_name": "Catharis",
      "author_url": "",
      "post_date": "2023-05-19T11:59:50.313000",
      "content": "<p>Congratulations and thanks for sharing your approach!  <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265331,
      "author_name": "steubk",
      "author_url": "",
      "post_date": "2023-05-19T07:04:02.620000",
      "content": "<p>Insightful solution as usual. Thank you for sharing <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> !</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265283,
      "author_name": "Giorgi Goletiani",
      "author_url": "",
      "post_date": "2023-05-19T06:05:35.123000",
      "content": "<p>Congrats! I actually guessed you were only using visit_month because you took the lead quite early on when you had not quite figured out what was scored and what was not based on protein data availability, but I still did not want to believe that protein/peptide data was useless.  <br>\nSimple solution proves to be the best solution once again.</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265190,
      "author_name": "AC",
      "author_url": "",
      "post_date": "2023-05-19T04:06:08.547000",
      "content": "<p>Oh Wow!! Thank you for sharing! This approach is so good! Using random numbers, instead. Congratulations! :) 🙌</p>",
      "votes": 1,
      "replies": [
        {
          "id": 2266408,
          "author_name": "yuan",
          "author_url": "",
          "post_date": "2023-05-20T03:20:18.230000",
          "content": "<p>good job good job</p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 2271504,
      "author_name": "Wang rundong",
      "author_url": "",
      "post_date": "2023-05-24T01:25:09.380000",
      "content": "<p>congratulations！</p>",
      "votes": 2,
      "replies": []
    },
    {
      "id": 2265726,
      "author_name": "moth",
      "author_url": "",
      "post_date": "2023-05-19T13:28:55.657000",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> Bojan must be sad that XGBoost was surpassed by RAPIDS SVR 😂</p>",
      "votes": 2,
      "replies": [
        {
          "id": 2266299,
          "author_name": "Cody_Null",
          "author_url": "",
          "post_date": "2023-05-19T22:06:23.077000",
          "content": "<p>I had thought the same thing haha </p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 2274373,
      "author_name": "Nooruddin Hyderabadwalla, CFA",
      "author_url": "",
      "post_date": "2023-05-25T23:08:06.603000",
      "content": "<p>Very innovative approach here <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 2269872,
      "author_name": "Bogdan Stamenov",
      "author_url": "",
      "post_date": "2023-05-22T19:07:03.530000",
      "content": "<p>Thanks for sharing and congrats</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2266388,
      "author_name": "yang kaiyu36",
      "author_url": "",
      "post_date": "2023-05-20T02:26:24.863000",
      "content": "<p>thank you for sharing</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265791,
      "author_name": "Xiao-Su (Frank) Hu",
      "author_url": "",
      "post_date": "2023-05-19T14:08:09.013000",
      "content": "<p>Thank for sharing!</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2265546,
      "author_name": "Fnoa",
      "author_url": "",
      "post_date": "2023-05-19T10:27:59.100000",
      "content": "<p>Great explanation! Thanks <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 2294299,
      "author_name": "Hossein Ahmadi",
      "author_url": "",
      "post_date": "2023-06-10T00:16:23.160000",
      "content": "<p>Just want to say \"Thank You\".</p>",
      "votes": 0,
      "replies": []
    }
  ],
  "comment_count": 32,
  "messages": [
    {
      "id": 2265139,
      "postDate": "2023-05-19T02:38:05.577Z",
      "content": "<h1>AMP®-Parkinson's Disease Progression Prediction</h1>\n<p>Wow, this competition had a close finish at the top. I suspected this would be the case because I believe there was only 1 source of reliable signal (i.e. <code>patient visit dates</code>) and all the tops teams were working hard to extract an extra SMAPE 0.1 from it. The metric SMAPE is a percentage so <code>0.1</code> is actually <code>0.001</code>. That's how close the finish was!</p>\n<p>The final leaderboard is separated into two groups. The top 18 teams have <code>SMAPE &lt;= 62.5</code> and the next teams have <code>SMAPE &gt;= 68.4</code>. I believe the top 18 teams used signal from <code>patient visit dates</code> and the next teams <code>did not</code>.</p>\n<h1>Signal from Protein/Peptide data</h1>\n<p>In this competition, Kaggle provided 227 Protein NXP features and 968 Peptide PeptideAbundance features. That is 1195 features for each of 17 possible visit dates. We only have 248 train patients. And the <code>curse of dimensionality</code> begins when <code>number of features &gt; train samples / 10</code>. In other words we only have enough train data to reasonably train 25 features not 1195 features!</p>\n<p>I did an experiment where i made 1000 columns of random numbers. Using forward feature selection, I found that columns of <code>random numbers</code> would boost GroupKFold CV score <strong>the same amount that protein/peptide features did</strong>. This means that there may be signal hiding in the protein peptide data but it is too weak to detect patterns with only 248 patients (because no protein nor peptide boost CV more than random numbers can).</p>\n<h1>Signal from Patient Visit Dates</h1>\n<p>Next I searched patient visit dates for signal. Many Kagglers overlooked that we can engineer features from patient visit dates. Here are some example features</p>\n<ul>\n<li>when was patient's first blood work measured?</li>\n<li>did patient get blood work at their first doctor visit, yes or no?</li>\n<li>how many times did a patient visit the doctor?</li>\n<li>how long ago was the patient's last visit?</li>\n</ul>\n<p>Etc etc. We can create 100s of features about when a patient visited the doctor and when a patient had blood work done. I quickly noticed the following trend. <strong>Patients who visit the doctor more often have larger UPDR scores</strong>. This is shown in the following 3 plots. The first are patients who visited the doctor a normal number of times. The second are patients who visited 1 standard deviation less than normal. And the last are patients who visited 1 standard deviation more. In each plot, we display the average target value per visit month for these 3 groups of patients:<br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/normal2.png\" alt=\"\"><br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/less2.png\" alt=\"\"><br>\n<img src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/more2.png\" alt=\"\"></p>\n<h1>Feature Engineering</h1>\n<p>The above plots show that there is signal in the <code>patient visit dates</code>. What is the best way to extract this signal? I generated 100s of features and used <code>for-loops</code> with <strong>RAPIDS cuML SVR</strong> to find which features extract the most signal.</p>\n<p>In the end, simple \"booleans\" worked best (and the model \"created its own features internally\"). For each visit month, i created a boolean variable. For example for visit month = 24, i created the following \"boolean\":</p>\n<ul>\n<li><code>v24 = 0</code> if we know that patient did <strong>not</strong> visit on visit month = 24</li>\n<li><code>v24 = 1</code> if we know that patient did visit on visit month = 24</li>\n<li><code>v24 = -1</code> if we <strong>do not</strong> know if patient visited on month = 24</li>\n</ul>\n<p>The reason for the third category is because at each time step of Kaggle's API we are asked to predict <code>0, 6, 12, 24</code> months into the future. So if the current visit month = 12 and we are predicting visit month 36, we do <strong>not</strong> know if the patient visited during visit month = 24.</p>\n<h1>Single Model RAPIDS cuML - 8th Place Gold</h1>\n<p>A single <strong>RAPIDS cuML SVR</strong> model trained with 11 features which are <code>visit_month</code> and <code>v0</code>, <code>v6</code>, <code>v12</code>, <code>v18</code>, <code>v24</code>, <code>v36</code>, <code>v48</code>, <code>v60</code>, <code>v72</code>, <code>v84</code> where the <code>v</code> features are described above achieves <code>CV = 55.5</code> and <code>Public LB = 55.4</code> and <code>Private LB = 60.5</code>. This is 8th place Gold. Using <strong>RAPIDS cuML</strong> was great because it allowed me to experiment dozens of models in minutes!</p>\n<h1>Single Model TensorFlow MLP - 4th Place Gold</h1>\n<p>After I found the above features, i tried different model types. I tried XGBoost with <code>PseudoHuber loss</code>. It's CV was not as good as <strong>RAPIDS cuML SVR</strong>. Next I tried TensorFlow MLP with <code>MeanAbsoluteError</code>. We built an MLP with 10 hidden layers where each hidden layer has 24 units and activation Relu. We used no Dropout and no BatchNormalization. We trained it for 15 epochs with Adam optimizer <code>LR = 1e-3</code> and then 15 epochs <code>LR = 1e-4</code>. This achieves <code>CV = 55.0</code> and <code>Public LB 54.9</code> and <code>Private LB 60.1</code>. This is 4th place Gold.</p>\n<h1>Creating Train Data</h1>\n<p>Our model trained with <code>train_clinical_data.csv</code> only. Creating proper train data for the above features was not trivial. We needed to convert each row from the original train data into <strong>4 new rows</strong>. If the original row was <code>patient_id = 55</code> and <code>visit_month = 24</code>. Then we needed to replace this row with 4 new rows:</p>\n<ul>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=-1, v12=-1, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=-1, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=-1, v&gt;24=-1</li>\n<li>patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=X24, v&gt;24=-1</li>\n</ul>\n<p>where <code>X1</code>, <code>X6</code>, <code>X12</code>, <code>X24</code> are the values <code>0</code> or <code>1</code> based on whether <code>patient_id=55</code> visited on months 0, 6, 12, 24 in the train data. The 4 new rows are current visit month minus 0, 6, 12, 24. If any of these subtractions are not valid visit months then we don't make that row.</p>\n<h1>4th Place Solution Code</h1>\n<p>I published my 4th place submission code using TensorFlow MLP <a href=\"https://www.kaggle.com/code/cdeotte/4th-place-solution-public-54-9-private-60-1\" target=\"_blank\">here</a>. Enjoy!</p>",
      "rawMarkdown": "#AMP®-Parkinson's Disease Progression Prediction\nWow, this competition had a close finish at the top. I suspected this would be the case because I believe there was only 1 source of reliable signal (i.e. `patient visit dates`) and all the tops teams were working hard to extract an extra SMAPE 0.1 from it. The metric SMAPE is a percentage so `0.1` is actually `0.001`. That's how close the finish was!\n\nThe final leaderboard is separated into two groups. The top 18 teams have `SMAPE <= 62.5` and the next teams have `SMAPE >= 68.4`. I believe the top 18 teams used signal from `patient visit dates` and the next teams `did not`.\n\n# Signal from Protein/Peptide data\nIn this competition, Kaggle provided 227 Protein NXP features and 968 Peptide PeptideAbundance features. That is 1195 features for each of 17 possible visit dates. We only have 248 train patients. And the `curse of dimensionality` begins when `number of features > train samples / 10`. In other words we only have enough train data to reasonably train 25 features not 1195 features!\n\nI did an experiment where i made 1000 columns of random numbers. Using forward feature selection, I found that columns of `random numbers` would boost GroupKFold CV score **the same amount that protein/peptide features did**. This means that there may be signal hiding in the protein peptide data but it is too weak to detect patterns with only 248 patients (because no protein nor peptide boost CV more than random numbers can).\n\n# Signal from Patient Visit Dates\nNext I searched patient visit dates for signal. Many Kagglers overlooked that we can engineer features from patient visit dates. Here are some example features\n* when was patient's first blood work measured?\n* did patient get blood work at their first doctor visit, yes or no?\n* how many times did a patient visit the doctor?\n* how long ago was the patient's last visit?\n\nEtc etc. We can create 100s of features about when a patient visited the doctor and when a patient had blood work done. I quickly noticed the following trend. **Patients who visit the doctor more often have larger UPDR scores**. This is shown in the following 3 plots. The first are patients who visited the doctor a normal number of times. The second are patients who visited 1 standard deviation less than normal. And the last are patients who visited 1 standard deviation more. In each plot, we display the average target value per visit month for these 3 groups of patients:\n![](https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/normal2.png)\n![](https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/less2.png)\n![](https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/May-2023/more2.png)\n\n# Feature Engineering\nThe above plots show that there is signal in the `patient visit dates`. What is the best way to extract this signal? I generated 100s of features and used `for-loops` with **RAPIDS cuML SVR** to find which features extract the most signal.\n\nIn the end, simple \"booleans\" worked best (and the model \"created its own features internally\"). For each visit month, i created a boolean variable. For example for visit month = 24, i created the following \"boolean\":\n* `v24 = 0` if we know that patient did **not** visit on visit month = 24\n* `v24 = 1` if we know that patient did visit on visit month = 24\n* `v24 = -1` if we **do not** know if patient visited on month = 24\n\nThe reason for the third category is because at each time step of Kaggle's API we are asked to predict `0, 6, 12, 24` months into the future. So if the current visit month = 12 and we are predicting visit month 36, we do **not** know if the patient visited during visit month = 24.\n\n# Single Model RAPIDS cuML - 8th Place Gold\nA single **RAPIDS cuML SVR** model trained with 11 features which are `visit_month` and `v0`, `v6`, `v12`, `v18`, `v24`, `v36`, `v48`, `v60`, `v72`, `v84` where the `v` features are described above achieves `CV = 55.5` and `Public LB = 55.4` and `Private LB = 60.5`. This is 8th place Gold. Using **RAPIDS cuML** was great because it allowed me to experiment dozens of models in minutes!\n\n# Single Model TensorFlow MLP - 4th Place Gold\nAfter I found the above features, i tried different model types. I tried XGBoost with `PseudoHuber loss`. It's CV was not as good as **RAPIDS cuML SVR**. Next I tried TensorFlow MLP with `MeanAbsoluteError`. We built an MLP with 10 hidden layers where each hidden layer has 24 units and activation Relu. We used no Dropout and no BatchNormalization. We trained it for 15 epochs with Adam optimizer `LR = 1e-3` and then 15 epochs `LR = 1e-4`. This achieves `CV = 55.0` and `Public LB 54.9` and `Private LB 60.1`. This is 4th place Gold.\n\n# Creating Train Data\nOur model trained with `train_clinical_data.csv` only. Creating proper train data for the above features was not trivial. We needed to convert each row from the original train data into **4 new rows**. If the original row was `patient_id = 55` and `visit_month = 24`. Then we needed to replace this row with 4 new rows:\n* patient_id=55, visit_month=24, And v0=X1, v6=-1, v12=-1, v24=-1, v>24=-1\n* patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=-1, v24=-1, v>24=-1\n* patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=-1, v>24=-1\n* patient_id=55, visit_month=24, And v0=X1, v6=X6, v12=X12, v24=X24, v>24=-1\n\nwhere `X1`, `X6`, `X12`, `X24` are the values `0` or `1` based on whether `patient_id=55` visited on months 0, 6, 12, 24 in the train data. The 4 new rows are current visit month minus 0, 6, 12, 24. If any of these subtractions are not valid visit months then we don't make that row.\n\n# 4th Place Solution Code\nI published my 4th place submission code using TensorFlow MLP [here][1]. Enjoy!\n\n[1]: https://www.kaggle.com/code/cdeotte/4th-place-solution-public-54-9-private-60-1",
      "votes": 150
    },
    {
      "id": 2265148,
      "postDate": "2023-05-19T02:49:38.903Z",
      "content": "<p>Very interesting to know how the top 18 teams have used the visit month feature! I wish I had thought the same.<br>\nCongratulations !!!</p>",
      "rawMarkdown": "Very interesting to know how the top 18 teams have used the visit month feature! I wish I had thought the same.\nCongratulations !!!",
      "votes": 7,
      "replies": [
        {
          "id": 2265230,
          "postDate": "2023-05-19T04:56:40.533Z",
          "content": "<p>Yes, you can bet I am going to spend more time doing EDA from here on out and that I will be plotting every variable against each other </p>",
          "rawMarkdown": "Yes, you can bet I am going to spend more time doing EDA from here on out and that I will be plotting every variable against each other ",
          "votes": 7
        }
      ]
    },
    {
      "id": 2265544,
      "postDate": "2023-05-19T10:25:58.560Z",
      "content": "<p>Congratulations and thanks for your review ! </p>\n<p>The idea of the \"v-booleans\" is clever and better than what I used. I'm surprised by the boost given by the MLP compared to XGBoost and SVR !</p>\n<p>Bonus question : how did you manage to get your 52.8 LB score ? (I think I'm not the only one you made feel bad during some weeks 😃)</p>",
      "rawMarkdown": "Congratulations and thanks for your review ! \n\nThe idea of the \"v-booleans\" is clever and better than what I used. I'm surprised by the boost given by the MLP compared to XGBoost and SVR !\n\nBonus question : how did you manage to get your 52.8 LB score ? (I think I'm not the only one you made feel bad during some weeks 😃)",
      "votes": 3,
      "replies": [
        {
          "id": 2265582,
          "postDate": "2023-05-19T11:07:53.217Z",
          "content": "<p>My best public LB score is actually <code>LB = 51.3</code> but i did not submit it because i didn't want my public LB score to be too good 😃</p>\n<p>From EDA, i saw that approximately 25% of train patients were control patients. I was curious if test data contained control patients or not (because this would affect our model approach). So I probed the first 165 patients out of test data's 384 patients. Among those 165 patients, about 24 were in public test (vs private), and about 8 are control patients. So yes, test data contains control patients too.</p>\n<p>If we submit linear trend public notebook and then multiply the 8 control patients (i discovered) targets by 0.2, we achieve <code>LB = 51.3</code>. </p>",
          "rawMarkdown": "My best public LB score is actually `LB = 51.3` but i did not submit it because i didn't want my public LB score to be too good 😃\n\nFrom EDA, i saw that approximately 25% of train patients were control patients. I was curious if test data contained control patients or not (because this would affect our model approach). So I probed the first 165 patients out of test data's 384 patients. Among those 165 patients, about 24 were in public test (vs private), and about 8 are control patients. So yes, test data contains control patients too.\n\nIf we submit linear trend public notebook and then multiply the 8 control patients (i discovered) targets by 0.2, we achieve `LB = 51.3`. ",
          "votes": 4,
          "replies": [
            {
              "id": 2265801,
              "postDate": "2023-05-19T14:13:14.727Z",
              "content": "<p>Wow that is so cool. Is this somewhat how you went about probing? </p>\n<ul>\n<li>Track the visit patterns of all patients. </li>\n<li>Choose the 165, maybe by lowest patient_id. </li>\n<li>On the last iteration, loop through the 165 patients, and run <br>\n<code>if patient_id_visit_pattern == control_pattern: time.sleep(10 seconds)</code></li>\n<li>Then number_control_patients = run_time_seconds / 10</li>\n</ul>\n<p>Also, can you please share some insight into how you keep track of submission runtime?<br>\nI can't find anywhere on kaggle that tells me how long a submission took. So unless I keep watch on the submission page I don't know when it ended. I suppose I could make a program to watch the html and keep track, but I figured expert LB probers like yourself may have a simpler solution. </p>\n<p>Thanks for another crystal clear explanation of your solution. </p>",
              "rawMarkdown": "Wow that is so cool. Is this somewhat how you went about probing? \n* Track the visit patterns of all patients. \n* Choose the 165, maybe by lowest patient_id. \n* On the last iteration, loop through the 165 patients, and run \n ``` if patient_id_visit_pattern == control_pattern: time.sleep(10 seconds)```\n* Then number_control_patients = run_time_seconds / 10\n\nAlso, can you please share some insight into how you keep track of submission runtime?\nI can't find anywhere on kaggle that tells me how long a submission took. So unless I keep watch on the submission page I don't know when it ended. I suppose I could make a program to watch the html and keep track, but I figured expert LB probers like yourself may have a simpler solution. \n\nThanks for another crystal clear explanation of your solution. ",
              "votes": 1
            },
            {
              "id": 2266304,
              "postDate": "2023-05-19T22:14:48.240Z",
              "content": "<p>Chris, the trick to probe test patients is having a consistent list of patients that has the same ordering each time you run the submit notebook.</p>\n<p>So I would create a NumPy array and add each new patient id that the Kaggle API provides in the order that they arrive in the Kaggle API for-loop and test dataframe. Then each time i run the code, the 384 test patients will be in the same order with same array index.</p>\n<p>To probe a control patient, we just write if <code>patient id</code> has index <code>X</code> in the \"consistent array\" then multiply <code>patient id</code> targets by 0.2. If the public LB score improves then, this patient is both in public test and a control patient. And we try X=0, then X=1, then X=2, etc.</p>",
              "rawMarkdown": "Chris, the trick to probe test patients is having a consistent list of patients that has the same ordering each time you run the submit notebook.\n\nSo I would create a NumPy array and add each new patient id that the Kaggle API provides in the order that they arrive in the Kaggle API for-loop and test dataframe. Then each time i run the code, the 384 test patients will be in the same order with same array index.\n\nTo probe a control patient, we just write if `patient id` has index `X` in the \"consistent array\" then multiply `patient id` targets by 0.2. If the public LB score improves then, this patient is both in public test and a control patient. And we try X=0, then X=1, then X=2, etc.",
              "votes": 3
            }
          ]
        }
      ]
    },
    {
      "id": 2265257,
      "postDate": "2023-05-19T05:27:53.443Z",
      "content": "<p>Thanks for sharing. I also added a random column in my lgb models. I found that the CV of features with a random column is much worse. So I tuned parameters to make models to converge(i.e. no much difference between with a random columns and without a random column.)</p>",
      "rawMarkdown": "Thanks for sharing. I also added a random column in my lgb models. I found that the CV of features with a random column is much worse. So I tuned parameters to make models to converge(i.e. no much difference between with a random columns and without a random column.)",
      "votes": 3
    },
    {
      "id": 2271376,
      "postDate": "2023-05-23T20:31:44.383Z",
      "content": "<p>This is truly spectacular <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> !<br>\nKeep up your amazing work </p>",
      "rawMarkdown": "This is truly spectacular @cdeotte !\nKeep up your amazing work ",
      "votes": 1
    },
    {
      "id": 2271166,
      "postDate": "2023-05-23T17:03:01.020Z",
      "content": "<p>congratulations, this is Great!</p>",
      "rawMarkdown": "congratulations, this is Great!",
      "votes": 1
    },
    {
      "id": 2270643,
      "postDate": "2023-05-23T09:45:20.273Z",
      "content": "<p>Amazing Work <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> ! Thanks for sharing 😊</p>",
      "rawMarkdown": "Amazing Work @cdeotte ! Thanks for sharing 😊",
      "votes": 1
    },
    {
      "id": 2269266,
      "postDate": "2023-05-22T10:43:56.610Z",
      "content": "<p>congratulations, this is Great!</p>",
      "rawMarkdown": "congratulations, this is Great!",
      "votes": 1
    },
    {
      "id": 2268774,
      "postDate": "2023-05-22T02:00:43.080Z",
      "content": "<p>Thank you for sharing and congratulations!</p>",
      "rawMarkdown": "Thank you for sharing and congratulations!",
      "votes": 1
    },
    {
      "id": 2267029,
      "postDate": "2023-05-20T14:31:57.723Z",
      "content": "<p>I tried the problem but had no idea how to approach this problem. Your solution is very helpful.</p>",
      "rawMarkdown": "I tried the problem but had no idea how to approach this problem. Your solution is very helpful.",
      "votes": 1
    },
    {
      "id": 2266524,
      "postDate": "2023-05-20T07:06:13.430Z",
      "content": "<p>Thank you for sharing learnt a lot, I also question myself why haven't I thought of this , Great Work</p>",
      "rawMarkdown": "Thank you for sharing learnt a lot, I also question myself why haven't I thought of this , Great Work",
      "votes": 1
    },
    {
      "id": 2266382,
      "postDate": "2023-05-20T01:52:59.140Z",
      "content": "<p>I literally had no idea how to approach this problem. Your solution is very insightful. Thank you for sharing <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a>.🎉</p>",
      "rawMarkdown": "I literally had no idea how to approach this problem. Your solution is very insightful. Thank you for sharing @cdeotte.🎉",
      "votes": 1
    },
    {
      "id": 2266017,
      "postDate": "2023-05-19T16:32:51.403Z",
      "content": "<p>Congratulations man!! </p>",
      "rawMarkdown": "Congratulations man!! ",
      "votes": 1
    },
    {
      "id": 2265793,
      "postDate": "2023-05-19T14:09:11.960Z",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> and thanks for sharing your approach👍</p>",
      "rawMarkdown": "Congrats @cdeotte and thanks for sharing your approach👍",
      "votes": 1
    },
    {
      "id": 2265786,
      "postDate": "2023-05-19T14:06:52.453Z",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a>, thank you for sharing your solution with us! It was well written and an awesome learning tool for me. </p>",
      "rawMarkdown": "Congrats @cdeotte, thank you for sharing your solution with us! It was well written and an awesome learning tool for me. ",
      "votes": 1
    },
    {
      "id": 2265642,
      "postDate": "2023-05-19T11:59:50.313Z",
      "content": "<p>Congratulations and thanks for sharing your approach!  <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "rawMarkdown": "Congratulations and thanks for sharing your approach!  @cdeotte ",
      "votes": 1
    },
    {
      "id": 2265331,
      "postDate": "2023-05-19T07:04:02.620Z",
      "content": "<p>Insightful solution as usual. Thank you for sharing <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> !</p>",
      "rawMarkdown": "Insightful solution as usual. Thank you for sharing @cdeotte !",
      "votes": 1
    },
    {
      "id": 2265283,
      "postDate": "2023-05-19T06:05:35.123Z",
      "content": "<p>Congrats! I actually guessed you were only using visit_month because you took the lead quite early on when you had not quite figured out what was scored and what was not based on protein data availability, but I still did not want to believe that protein/peptide data was useless.  <br>\nSimple solution proves to be the best solution once again.</p>",
      "rawMarkdown": "Congrats! I actually guessed you were only using visit_month because you took the lead quite early on when you had not quite figured out what was scored and what was not based on protein data availability, but I still did not want to believe that protein/peptide data was useless.  \nSimple solution proves to be the best solution once again.",
      "votes": 1
    },
    {
      "id": 2265190,
      "postDate": "2023-05-19T04:06:08.547Z",
      "content": "<p>Oh Wow!! Thank you for sharing! This approach is so good! Using random numbers, instead. Congratulations! :) 🙌</p>",
      "rawMarkdown": "Oh Wow!! Thank you for sharing! This approach is so good! Using random numbers, instead. Congratulations! :) 🙌",
      "votes": 1,
      "replies": [
        {
          "id": 2266408,
          "postDate": "2023-05-20T03:20:18.230Z",
          "content": "<p>good job good job</p>",
          "rawMarkdown": "good job good job\n",
          "votes": 1
        }
      ]
    },
    {
      "id": 2271504,
      "postDate": "2023-05-24T01:25:09.380Z",
      "content": "<p>congratulations！</p>",
      "rawMarkdown": "congratulations！",
      "votes": 2
    },
    {
      "id": 2265726,
      "postDate": "2023-05-19T13:28:55.657Z",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> Bojan must be sad that XGBoost was surpassed by RAPIDS SVR 😂</p>",
      "rawMarkdown": "Congrats @cdeotte Bojan must be sad that XGBoost was surpassed by RAPIDS SVR 😂",
      "votes": 2,
      "replies": [
        {
          "id": 2266299,
          "postDate": "2023-05-19T22:06:23.077Z",
          "content": "<p>I had thought the same thing haha </p>",
          "rawMarkdown": "I had thought the same thing haha ",
          "votes": 1
        }
      ]
    },
    {
      "id": 2274373,
      "postDate": "2023-05-25T23:08:06.603Z",
      "content": "<p>Very innovative approach here <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "rawMarkdown": "Very innovative approach here @cdeotte "
    },
    {
      "id": 2269872,
      "postDate": "2023-05-22T19:07:03.530Z",
      "content": "<p>Thanks for sharing and congrats</p>",
      "rawMarkdown": "Thanks for sharing and congrats",
      "votes": 1
    },
    {
      "id": 2266388,
      "postDate": "2023-05-20T02:26:24.863Z",
      "content": "<p>thank you for sharing</p>",
      "rawMarkdown": "thank you for sharing",
      "votes": 1
    },
    {
      "id": 2265791,
      "postDate": "2023-05-19T14:08:09.013Z",
      "content": "<p>Thank for sharing!</p>",
      "rawMarkdown": "Thank for sharing!",
      "votes": 1
    },
    {
      "id": 2265546,
      "postDate": "2023-05-19T10:27:59.100Z",
      "content": "<p>Great explanation! Thanks <a href=\"https://www.kaggle.com/cdeotte\" target=\"_blank\">@cdeotte</a> </p>",
      "rawMarkdown": "Great explanation! Thanks @cdeotte ",
      "votes": 1
    },
    {
      "id": 2294299,
      "postDate": "2023-06-10T00:16:23.160Z",
      "content": "<p>Just want to say \"Thank You\".</p>",
      "rawMarkdown": "Just want to say \"Thank You\"."
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-19T14:11:08Z",
  "competition_id": 37794,
  "competition_slug": "amp-parkinsons-disease-progression-prediction",
  "team_id": 10172946,
  "solution_url": "/competitions/amp-parkinsons-disease-progression-prediction/writeups/chris-deotte-4th-place-gold-single-model-rapids-cu"
}