<p dir="auto">By default many model selection tools such as <code class="notranslate">cross_validate</code>, <code class="notranslate">validation_curves</code> and <code class="notranslate">*SearchCV</code> catch exceptions, raise a warning and score the model with <code class="notranslate">nan</code>. This can be a nice behavior, especially for <code class="notranslate">*SearchCV</code> estimators that can explore invalid hyper-parameter combinations.</p>
<p dir="auto">However the warnings can be hidden when they are issued on the stderr of loky workers (python subprocesses), for instance in Jupyter interactive environments:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/89061/124569928-af984100-de46-11eb-80d4-2a10331942eb.png"><img src="https://user-images.githubusercontent.com/89061/124569928-af984100-de46-11eb-80d4-2a10331942eb.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto">This is a big usability bug. I think we should refactor the model selection tools to raise the warning from the main process instead of the workers. This would make it possible to:</p>
<ul dir="auto">
<li>make it possible to display the warning on the main process stderr to avoid hiding those when parallel compution on several Python processes (e.g. with the default loky backend of joblib) or multi-machine (e.g. with the dask or ray cluster backends of joblib).</li>
<li>raise the warning only once (for the first case) to avoid a "wall of warnings"-effect when <code class="notranslate">n_jobs=1</code></li>
<li>inform the user that can set <code class="notranslate">error_score="raise"</code> in the warning message if they want there code to raise an exception instead of gettting <code class="notranslate">nan</code>-valued scores.</li>
</ul>
<p dir="auto">This problem has been causing a lot of confusion to MOOC participants (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="936831338" data-permission-text="Title is private" data-url="https://github.com/INRIA/scikit-learn-mooc/issues/377" data-hovercard-type="issue" data-hovercard-url="/INRIA/scikit-learn-mooc/issues/377/hovercard" href="https://github.com/INRIA/scikit-learn-mooc/issues/377">INRIA/scikit-learn-mooc#377</a>) so it's probably hurting scikit-learn usability significantly.</p>
<h3 dir="auto">Describe the bug</h3>
<p dir="auto">Hello, I am using a sklearn.tree.DecisionTreeRegressor for two tasks where one task is just a 'subsets' of the other and I expect to get the same tree, but I don't.<br>
In the two tasks, the examples are identical, where task2 the feature matrix is duplicated horizontally, i.e. if the feature space in task1 is of dimension 9,  then for task2 it is of dimension 18,  where the first 9 features and the last 9 features are identical.</p>
<p dir="auto">I am running :<br>
<code class="notranslate">stump = DecisionTreeRegressor(max_depth=1, random_state=42)</code></p>
<p dir="auto">TASK 1 INPUT: 9 features</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="[[322.   0. 143. 265.  27.   0.  61.  32.  32.]
 [116.   0.  50.  90.  12.   0.  22.  12.  12.]
 [137.   0.  74. 125.  22.   0.  33.  12.  12.]
 [ 97.   0.  46.  85.  10.   0.  16.  12.  12.]
 [159.   0. 107. 156.  39.   0.  47.  12.  22.]
 [268.   0. 145. 235.  39.   0.  70.  10.  28.]
 [129.   0.  49.  80.  17.   0.  25.   6.   6.]
 [122.   0.  42.  80.   8.   0.  20.  12.  12.]
 [569.   0. 337. 520. 131.   0. 163.   0.   0.]
 [ 76.   0.  30.  60.   4.   0.  13.  10.  10.]
 [120.   0.  54. 100.  12.   0.  22.  12.  12.]
 [245.   0.  74. 145.  10.   0.  36.  18.  21.]
 [109.   0.  49.  90.  11.   0.  21.  10.  10.]
 [126.   0.  80. 120.  28.   0.  37.   6.  14.]
 [140.   0.  76. 120.  22.   0.  37.   6.  17.]]
 "><pre class="notranslate"><code class="notranslate">[[322.   0. 143. 265.  27.   0.  61.  32.  32.]
 [116.   0.  50.  90.  12.   0.  22.  12.  12.]
 [137.   0.  74. 125.  22.   0.  33.  12.  12.]
 [ 97.   0.  46.  85.  10.   0.  16.  12.  12.]
 [159.   0. 107. 156.  39.   0.  47.  12.  22.]
 [268.   0. 145. 235.  39.   0.  70.  10.  28.]
 [129.   0.  49.  80.  17.   0.  25.   6.   6.]
 [122.   0.  42.  80.   8.   0.  20.  12.  12.]
 [569.   0. 337. 520. 131.   0. 163.   0.   0.]
 [ 76.   0.  30.  60.   4.   0.  13.  10.  10.]
 [120.   0.  54. 100.  12.   0.  22.  12.  12.]
 [245.   0.  74. 145.  10.   0.  36.  18.  21.]
 [109.   0.  49.  90.  11.   0.  21.  10.  10.]
 [126.   0.  80. 120.  28.   0.  37.   6.  14.]
 [140.   0.  76. 120.  22.   0.  37.   6.  17.]]
 
</code></pre></div>
<p dir="auto">TASK 2 INPUT: 18 features where the last 9 features are just duplications of the first 9</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=" [[322.   0. 143. 265.  27.   0.  61.  32.  32. 322.   0. 143. 265.  27.
    0.  61.  32.  32.]
 [116.   0.  50.  90.  12.   0.  22.  12.  12. 116.   0.  50.  90.  12.
    0.  22.  12.  12.]
 [137.   0.  74. 125.  22.   0.  33.  12.  12. 137.   0.  74. 125.  22.
    0.  33.  12.  12.]
 [ 97.   0.  46.  85.  10.   0.  16.  12.  12.  97.   0.  46.  85.  10.
    0.  16.  12.  12.]
 [159.   0. 107. 156.  39.   0.  47.  12.  22. 159.   0. 107. 156.  39.
    0.  47.  12.  22.]
 [268.   0. 145. 235.  39.   0.  70.  10.  28. 268.   0. 145. 235.  39.
    0.  70.  10.  28.]
 [129.   0.  49.  80.  17.   0.  25.   6.   6. 129.   0.  49.  80.  17.
    0.  25.   6.   6.]
 [122.   0.  42.  80.   8.   0.  20.  12.  12. 122.   0.  42.  80.   8.
    0.  20.  12.  12.]
 [569.   0. 337. 520. 131.   0. 163.   0.   0. 569.   0. 337. 520. 131.
    0. 163.   0.   0.]
 [ 76.   0.  30.  60.   4.   0.  13.  10.  10.  76.   0.  30.  60.   4.
    0.  13.  10.  10.]
 [120.   0.  54. 100.  12.   0.  22.  12.  12. 120.   0.  54. 100.  12.
    0.  22.  12.  12.]
 [245.   0.  74. 145.  10.   0.  36.  18.  21. 245.   0.  74. 145.  10.
    0.  36.  18.  21.]
 [109.   0.  49.  90.  11.   0.  21.  10.  10. 109.   0.  49.  90.  11.
    0.  21.  10.  10.]
 [126.   0.  80. 120.  28.   0.  37.   6.  14. 126.   0.  80. 120.  28.
    0.  37.   6.  14.]
 [140.   0.  76. 120.  22.   0.  37.   6.  17. 140.   0.  76. 120.  22.
    0.  37.   6.  17.]]"><pre class="notranslate"><code class="notranslate"> [[322.   0. 143. 265.  27.   0.  61.  32.  32. 322.   0. 143. 265.  27.
    0.  61.  32.  32.]
 [116.   0.  50.  90.  12.   0.  22.  12.  12. 116.   0.  50.  90.  12.
    0.  22.  12.  12.]
 [137.   0.  74. 125.  22.   0.  33.  12.  12. 137.   0.  74. 125.  22.
    0.  33.  12.  12.]
 [ 97.   0.  46.  85.  10.   0.  16.  12.  12.  97.   0.  46.  85.  10.
    0.  16.  12.  12.]
 [159.   0. 107. 156.  39.   0.  47.  12.  22. 159.   0. 107. 156.  39.
    0.  47.  12.  22.]
 [268.   0. 145. 235.  39.   0.  70.  10.  28. 268.   0. 145. 235.  39.
    0.  70.  10.  28.]
 [129.   0.  49.  80.  17.   0.  25.   6.   6. 129.   0.  49.  80.  17.
    0.  25.   6.   6.]
 [122.   0.  42.  80.   8.   0.  20.  12.  12. 122.   0.  42.  80.   8.
    0.  20.  12.  12.]
 [569.   0. 337. 520. 131.   0. 163.   0.   0. 569.   0. 337. 520. 131.
    0. 163.   0.   0.]
 [ 76.   0.  30.  60.   4.   0.  13.  10.  10.  76.   0.  30.  60.   4.
    0.  13.  10.  10.]
 [120.   0.  54. 100.  12.   0.  22.  12.  12. 120.   0.  54. 100.  12.
    0.  22.  12.  12.]
 [245.   0.  74. 145.  10.   0.  36.  18.  21. 245.   0.  74. 145.  10.
    0.  36.  18.  21.]
 [109.   0.  49.  90.  11.   0.  21.  10.  10. 109.   0.  49.  90.  11.
    0.  21.  10.  10.]
 [126.   0.  80. 120.  28.   0.  37.   6.  14. 126.   0.  80. 120.  28.
    0.  37.   6.  14.]
 [140.   0.  76. 120.  22.   0.  37.   6.  17. 140.   0.  76. 120.  22.
    0.  37.   6.  17.]]
</code></pre></div>
<p dir="auto">In the first task, the tree chooses feature number 2, with gain 1.1111111111111112, threshold 52.0.<br>
The feature values across the examples in this case are:<br>
<code class="notranslate">    [143.  50.  74.  46. 107. 145.  49.  42. 337.  30.  54.  74.  49.  80. 76.]</code></p>
<p dir="auto">In the second task, the tree chooses feature number 12, with gain 1.1111111111111112, threshold 95.0.<br>
The feature values across the examples in this case are:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="    [265.  90. 125.  85. 156. 235.  80.  80. 520.  60. 100. 145.  90. 120.
 120.]"><pre class="notranslate"><code class="notranslate">    [265.  90. 125.  85. 156. 235.  80.  80. 520.  60. 100. 145.  90. 120.
 120.]
</code></pre></div>
<p dir="auto">I understand that it is possible that there are multiple features that result in the same gain, but the feature number 12 that is selected in the second task, also exists in the first task as feature number 3, so I am trying to understand why it chooses different features for these two tasks, it doesn't make sense.<br>
Is it possible to make this behavior deterministic (for example to make it choose the first feature that has max gain)</p>
<p dir="auto">Thank you.</p>
<h3 dir="auto">Steps/Code to Reproduce</h3>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="data1 = np.array([[322.,   0., 143., 265.,  27.,   0.,  61.,  32.,  32.],
       [116.,   0.,  50.,  90.,  12.,   0.,  22.,  12.,  12.],
       [137.,   0.,  74., 125.,  22.,   0.,  33.,  12.,  12.],
       [ 97.,   0.,  46.,  85.,  10.,   0.,  16.,  12.,  12.],
       [159.,   0., 107., 156.,  39.,   0.,  47.,  12.,  22.],
       [268.,   0., 145., 235.,  39.,   0.,  70.,  10.,  28.],
       [129.,   0.,  49.,  80.,  17.,   0.,  25.,   6.,   6.],
       [122.,   0.,  42.,  80.,   8.,   0.,  20.,  12.,  12.],
       [569.,   0., 337., 520., 131.,   0., 163.,   0.,   0.],
       [ 76.,   0.,  30.,  60.,   4.,   0.,  13.,  10.,  10.],
       [120.,   0.,  54., 100.,  12.,   0.,  22.,  12.,  12.],
       [245.,   0.,  74., 145.,  10.,   0.,  36.,  18.,  21.],
       [109.,   0.,  49.,  90.,  11.,   0.,  21.,  10.,  10.],
       [126.,   0.,  80., 120.,  28.,   0.,  37.,   6.,  14.],
       [140.,   0.,  76., 120.,  22.,   0.,  37.,   6.,  17.]])

labels = np.array([[0], [0], [1], [0], [0], [1], [0], [0], [1], [0], [1], [1], [0], [0], [0]])"><pre class="notranslate"><code class="notranslate">data1 = np.array([[322.,   0., 143., 265.,  27.,   0.,  61.,  32.,  32.],
       [116.,   0.,  50.,  90.,  12.,   0.,  22.,  12.,  12.],
       [137.,   0.,  74., 125.,  22.,   0.,  33.,  12.,  12.],
       [ 97.,   0.,  46.,  85.,  10.,   0.,  16.,  12.,  12.],
       [159.,   0., 107., 156.,  39.,   0.,  47.,  12.,  22.],
       [268.,   0., 145., 235.,  39.,   0.,  70.,  10.,  28.],
       [129.,   0.,  49.,  80.,  17.,   0.,  25.,   6.,   6.],
       [122.,   0.,  42.,  80.,   8.,   0.,  20.,  12.,  12.],
       [569.,   0., 337., 520., 131.,   0., 163.,   0.,   0.],
       [ 76.,   0.,  30.,  60.,   4.,   0.,  13.,  10.,  10.],
       [120.,   0.,  54., 100.,  12.,   0.,  22.,  12.,  12.],
       [245.,   0.,  74., 145.,  10.,   0.,  36.,  18.,  21.],
       [109.,   0.,  49.,  90.,  11.,   0.,  21.,  10.,  10.],
       [126.,   0.,  80., 120.,  28.,   0.,  37.,   6.,  14.],
       [140.,   0.,  76., 120.,  22.,   0.,  37.,   6.,  17.]])

labels = np.array([[0], [0], [1], [0], [0], [1], [0], [0], [1], [0], [1], [1], [0], [0], [0]])
</code></pre></div>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="stump1 = DecisionTreeRegressor(max_depth=1, random_state=42)
stump1.fit(data1, labels)
feature_index_1 = stump.tree_.feature[0]"><pre class="notranslate"><code class="notranslate">stump1 = DecisionTreeRegressor(max_depth=1, random_state=42)
stump1.fit(data1, labels)
feature_index_1 = stump.tree_.feature[0]
</code></pre></div>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="data2 =np.array([[322.,   0., 143., 265.,  27.,   0.,  61.,  32.,  32., 322.,   0.,
        143., 265.,  27.,   0.,  61.,  32.,  32.],
       [116.,   0.,  50.,  90.,  12.,   0.,  22.,  12.,  12., 116.,   0.,
         50.,  90.,  12.,   0.,  22.,  12.,  12.],
       [137.,   0.,  74., 125.,  22.,   0.,  33.,  12.,  12., 137.,   0.,
         74., 125.,  22.,   0.,  33.,  12.,  12.],
       [ 97.,   0.,  46.,  85.,  10.,   0.,  16.,  12.,  12.,  97.,   0.,
         46.,  85.,  10.,   0.,  16.,  12.,  12.],
       [159.,   0., 107., 156.,  39.,   0.,  47.,  12.,  22., 159.,   0.,
        107., 156.,  39.,   0.,  47.,  12.,  22.],
       [268.,   0., 145., 235.,  39.,   0.,  70.,  10.,  28., 268.,   0.,
        145., 235.,  39.,   0.,  70.,  10.,  28.],
       [129.,   0.,  49.,  80.,  17.,   0.,  25.,   6.,   6., 129.,   0.,
         49.,  80.,  17.,   0.,  25.,   6.,   6.],
       [122.,   0.,  42.,  80.,   8.,   0.,  20.,  12.,  12., 122.,   0.,
         42.,  80.,   8.,   0.,  20.,  12.,  12.],
       [569.,   0., 337., 520., 131.,   0., 163.,   0.,   0., 569.,   0.,
        337., 520., 131.,   0., 163.,   0.,   0.],
       [ 76.,   0.,  30.,  60.,   4.,   0.,  13.,  10.,  10.,  76.,   0.,
         30.,  60.,   4.,   0.,  13.,  10.,  10.],
       [120.,   0.,  54., 100.,  12.,   0.,  22.,  12.,  12., 120.,   0.,
         54., 100.,  12.,   0.,  22.,  12.,  12.],
       [245.,   0.,  74., 145.,  10.,   0.,  36.,  18.,  21., 245.,   0.,
         74., 145.,  10.,   0.,  36.,  18.,  21.],
       [109.,   0.,  49.,  90.,  11.,   0.,  21.,  10.,  10., 109.,   0.,
         49.,  90.,  11.,   0.,  21.,  10.,  10.],
       [126.,   0.,  80., 120.,  28.,   0.,  37.,   6.,  14., 126.,   0.,
         80., 120.,  28.,   0.,  37.,   6.,  14.],
       [140.,   0.,  76., 120.,  22.,   0.,  37.,   6.,  17., 140.,   0.,
         76., 120.,  22.,   0.,  37.,   6.,  17.]])

labels = np.array([[0], [0], [1], [0], [0], [1], [0], [0], [1], [0], [1], [1], [0], [0], [0]])"><pre class="notranslate"><code class="notranslate">data2 =np.array([[322.,   0., 143., 265.,  27.,   0.,  61.,  32.,  32., 322.,   0.,
        143., 265.,  27.,   0.,  61.,  32.,  32.],
       [116.,   0.,  50.,  90.,  12.,   0.,  22.,  12.,  12., 116.,   0.,
         50.,  90.,  12.,   0.,  22.,  12.,  12.],
       [137.,   0.,  74., 125.,  22.,   0.,  33.,  12.,  12., 137.,   0.,
         74., 125.,  22.,   0.,  33.,  12.,  12.],
       [ 97.,   0.,  46.,  85.,  10.,   0.,  16.,  12.,  12.,  97.,   0.,
         46.,  85.,  10.,   0.,  16.,  12.,  12.],
       [159.,   0., 107., 156.,  39.,   0.,  47.,  12.,  22., 159.,   0.,
        107., 156.,  39.,   0.,  47.,  12.,  22.],
       [268.,   0., 145., 235.,  39.,   0.,  70.,  10.,  28., 268.,   0.,
        145., 235.,  39.,   0.,  70.,  10.,  28.],
       [129.,   0.,  49.,  80.,  17.,   0.,  25.,   6.,   6., 129.,   0.,
         49.,  80.,  17.,   0.,  25.,   6.,   6.],
       [122.,   0.,  42.,  80.,   8.,   0.,  20.,  12.,  12., 122.,   0.,
         42.,  80.,   8.,   0.,  20.,  12.,  12.],
       [569.,   0., 337., 520., 131.,   0., 163.,   0.,   0., 569.,   0.,
        337., 520., 131.,   0., 163.,   0.,   0.],
       [ 76.,   0.,  30.,  60.,   4.,   0.,  13.,  10.,  10.,  76.,   0.,
         30.,  60.,   4.,   0.,  13.,  10.,  10.],
       [120.,   0.,  54., 100.,  12.,   0.,  22.,  12.,  12., 120.,   0.,
         54., 100.,  12.,   0.,  22.,  12.,  12.],
       [245.,   0.,  74., 145.,  10.,   0.,  36.,  18.,  21., 245.,   0.,
         74., 145.,  10.,   0.,  36.,  18.,  21.],
       [109.,   0.,  49.,  90.,  11.,   0.,  21.,  10.,  10., 109.,   0.,
         49.,  90.,  11.,   0.,  21.,  10.,  10.],
       [126.,   0.,  80., 120.,  28.,   0.,  37.,   6.,  14., 126.,   0.,
         80., 120.,  28.,   0.,  37.,   6.,  14.],
       [140.,   0.,  76., 120.,  22.,   0.,  37.,   6.,  17., 140.,   0.,
         76., 120.,  22.,   0.,  37.,   6.,  17.]])

labels = np.array([[0], [0], [1], [0], [0], [1], [0], [0], [1], [0], [1], [1], [0], [0], [0]])
</code></pre></div>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="stump2 = DecisionTreeRegressor(max_depth=1, random_state=42)
stump2.fit(data2, labels)
feature_index_2 = stump.tree_.feature[0]"><pre class="notranslate"><code class="notranslate">stump2 = DecisionTreeRegressor(max_depth=1, random_state=42)
stump2.fit(data2, labels)
feature_index_2 = stump.tree_.feature[0]
</code></pre></div>
<h3 dir="auto">Expected Results</h3>
<p dir="auto">I expect the model to choose the same feature over these two tasks. i.e.<br>
feature_index_2 == feature_index_1</p>
<h3 dir="auto">Actual Results</h3>
<p dir="auto">The model chooses different features:<br>
feature_index_1 = 2<br>
feature_index_2 = 12</p>
<h3 dir="auto">Versions</h3>
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Python dependencies:
      sklearn: 1.1.1
          pip: 22.1.2
   setuptools: 63.4.1
        numpy: 1.21.5
        scipy: 1.7.3
       Cython: None
       pandas: 1.4.1
   matplotlib: 3.5.1
       joblib: 1.1.0
threadpoolctl: 2.2.0"><pre class="notranslate">Python dependencies:
      sklearn: 1.1.1
          pip: 22.1.2
   setuptools: 63.4.1
        numpy: 1.21.5
        scipy: 1.7.3
       Cython: None
       pandas: 1.4.1
   matplotlib: 3.5.1
       joblib: 1.1.0
threadpoolctl: 2.2.0</pre></div>