<h4 dir="auto">Describe the workflow you want to enable</h4>
<p dir="auto">The following is a proposal for an additional feature_selection method. I would like to propose a simple feature selection function that removes highly correlated feature columns using the Pearson product-moment correlation coefficients. The threshold that needs to be exceeded can be specified by the user.</p>
<h4 dir="auto">Describe your proposed solution</h4>
<p dir="auto">Below is a simplified function showing the essential functionality of the proposed feature:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# input data, 1st and 2nd column highly correlated
X = np.array([[3, 2, 9], [2, 1, 2], [2, 1, 1], [1, 0, 3]]) 

&gt;&gt;&gt; X
array([[3, 2, 9],
       [2, 1, 2],
       [2, 1, 1],
       [1, 0, 3]])

def correlation_selection(X, threshold=.9, rowvar=False):
    &quot;&quot;&quot;Remove highly correlated feature columns.

    Parameters
    -----------

    X : ndarray of shape (n_samples, n_features)

    threshold : float, default=.9

    rowvar: bool, default=False

    Returns
    --------

    X_reduced : ndarray of shape (n_samples, n_features_reduced)

    &quot;&quot;&quot;

    corr = np.absolute(np.corrcoef(X, rowvar=rowvar))
    upper = corr*np.triu(np.ones(corr.shape), k=1).astype(np.bool)
    to_drop = [column for column in range(upper.shape[1]) if any(upper[:,column] &gt;= threshold)]
    X_reduced = np.delete(X, to_drop, axis=1)

    return X_reduced

X_reduced = correlation_selection(X)

# feature matrix after removing correlated columns
&gt;&gt;&gt; X_reduced
array([[3, 9],
       [2, 2],
       [2, 1],
       [1, 3]])"><pre class="notranslate"><span class="pl-c"># input data, 1st and 2nd column highly correlated</span>
<span class="pl-v">X</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">array</span>([[<span class="pl-c1">3</span>, <span class="pl-c1">2</span>, <span class="pl-c1">9</span>], [<span class="pl-c1">2</span>, <span class="pl-c1">1</span>, <span class="pl-c1">2</span>], [<span class="pl-c1">2</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>], [<span class="pl-c1">1</span>, <span class="pl-c1">0</span>, <span class="pl-c1">3</span>]]) 

<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-v">X</span>
<span class="pl-en">array</span>([[<span class="pl-c1">3</span>, <span class="pl-c1">2</span>, <span class="pl-c1">9</span>],
       [<span class="pl-c1">2</span>, <span class="pl-c1">1</span>, <span class="pl-c1">2</span>],
       [<span class="pl-c1">2</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>],
       [<span class="pl-c1">1</span>, <span class="pl-c1">0</span>, <span class="pl-c1">3</span>]])

<span class="pl-k">def</span> <span class="pl-en">correlation_selection</span>(<span class="pl-v">X</span>, <span class="pl-s1">threshold</span><span class="pl-c1">=</span><span class="pl-c1">.9</span>, <span class="pl-s1">rowvar</span><span class="pl-c1">=</span><span class="pl-c1">False</span>):
    <span class="pl-s">"""Remove highly correlated feature columns.</span>
<span class="pl-s"></span>
<span class="pl-s">    Parameters</span>
<span class="pl-s">    -----------</span>
<span class="pl-s"></span>
<span class="pl-s">    X : ndarray of shape (n_samples, n_features)</span>
<span class="pl-s"></span>
<span class="pl-s">    threshold : float, default=.9</span>
<span class="pl-s"></span>
<span class="pl-s">    rowvar: bool, default=False</span>
<span class="pl-s"></span>
<span class="pl-s">    Returns</span>
<span class="pl-s">    --------</span>
<span class="pl-s"></span>
<span class="pl-s">    X_reduced : ndarray of shape (n_samples, n_features_reduced)</span>
<span class="pl-s"></span>
<span class="pl-s">    """</span>

    <span class="pl-s1">corr</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">absolute</span>(<span class="pl-s1">np</span>.<span class="pl-en">corrcoef</span>(<span class="pl-v">X</span>, <span class="pl-s1">rowvar</span><span class="pl-c1">=</span><span class="pl-s1">rowvar</span>))
    <span class="pl-s1">upper</span> <span class="pl-c1">=</span> <span class="pl-s1">corr</span><span class="pl-c1">*</span><span class="pl-s1">np</span>.<span class="pl-en">triu</span>(<span class="pl-s1">np</span>.<span class="pl-en">ones</span>(<span class="pl-s1">corr</span>.<span class="pl-s1">shape</span>), <span class="pl-s1">k</span><span class="pl-c1">=</span><span class="pl-c1">1</span>).<span class="pl-en">astype</span>(<span class="pl-s1">np</span>.<span class="pl-s1">bool</span>)
    <span class="pl-s1">to_drop</span> <span class="pl-c1">=</span> [<span class="pl-s1">column</span> <span class="pl-k">for</span> <span class="pl-s1">column</span> <span class="pl-c1">in</span> <span class="pl-en">range</span>(<span class="pl-s1">upper</span>.<span class="pl-s1">shape</span>[<span class="pl-c1">1</span>]) <span class="pl-k">if</span> <span class="pl-en">any</span>(<span class="pl-s1">upper</span>[:,<span class="pl-s1">column</span>] <span class="pl-c1">&gt;=</span> <span class="pl-s1">threshold</span>)]
    <span class="pl-v">X_reduced</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">delete</span>(<span class="pl-v">X</span>, <span class="pl-s1">to_drop</span>, <span class="pl-s1">axis</span><span class="pl-c1">=</span><span class="pl-c1">1</span>)

    <span class="pl-k">return</span> <span class="pl-v">X_reduced</span>

<span class="pl-v">X_reduced</span> <span class="pl-c1">=</span> <span class="pl-en">correlation_selection</span>(<span class="pl-v">X</span>)

<span class="pl-c"># feature matrix after removing correlated columns</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-v">X_reduced</span>
<span class="pl-en">array</span>([[<span class="pl-c1">3</span>, <span class="pl-c1">9</span>],
       [<span class="pl-c1">2</span>, <span class="pl-c1">2</span>],
       [<span class="pl-c1">2</span>, <span class="pl-c1">1</span>],
       [<span class="pl-c1">1</span>, <span class="pl-c1">3</span>]])</pre></div>
<p dir="auto">I am updating some code in <code class="notranslate">imbalanced-learn</code> to use the estimator tag. In some way, I was able to add a new entry in the <code class="notranslate">_DEFAULT_TAGS</code> and use the implementation of <code class="notranslate">_safe_tags</code>.</p>
<p dir="auto">I have the following use case:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="_DEFAULT_TAGS = {'sample_indices': False}

class BaseClass:
    ...
    def _more_tags(self):
        return {'sample_indices': True}

class SpecialClass(BaseClass):
    ...
    def _more_tags(self):
        tags = super()._more_tags()
        tags['sample_indices'] = False
        return tags"><pre class="notranslate"><span class="pl-s1">_DEFAULT_TAGS</span> <span class="pl-c1">=</span> {<span class="pl-s">'sample_indices'</span>: <span class="pl-c1">False</span>}

<span class="pl-k">class</span> <span class="pl-v">BaseClass</span>:
    ...
    <span class="pl-k">def</span> <span class="pl-en">_more_tags</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">return</span> {<span class="pl-s">'sample_indices'</span>: <span class="pl-c1">True</span>}

<span class="pl-k">class</span> <span class="pl-v">SpecialClass</span>(<span class="pl-v">BaseClass</span>):
    ...
    <span class="pl-k">def</span> <span class="pl-en">_more_tags</span>(<span class="pl-s1">self</span>):
        <span class="pl-s1">tags</span> <span class="pl-c1">=</span> <span class="pl-en">super</span>().<span class="pl-en">_more_tags</span>()
        <span class="pl-s1">tags</span>[<span class="pl-s">'sample_indices'</span>] <span class="pl-c1">=</span> <span class="pl-c1">False</span>
        <span class="pl-k">return</span> <span class="pl-s1">tags</span></pre></div>
<p dir="auto">For some reason, all estimators inheriting from the <code class="notranslate">BaseClass</code> would have a <code class="notranslate">sample_indices</code> tag to <code class="notranslate">True</code>. All but not for one class where I would like to overwrite the tag. Here, I made use of the <code class="notranslate">super</code> class to give the trick that I think could solve the following issue.</p>
<p dir="auto">Because we are overwriting the <code class="notranslate">sample_indices</code>, <code class="notranslate">_get_tag</code> is failing due to what is currently considered as an inconsistent update of the tags dictionary:</p>
<div class="highlight highlight-text-python-traceback notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
&lt;ipython-input-31-5558b1aebfa8&gt; in &lt;module&gt;
----&gt; 1 _safe_tags(xx, 'sample_indices')

~/Documents/packages/scikit-learn/sklearn/utils/estimator_checks.py in _safe_tags(estimator, key)
     68     if hasattr(estimator, &quot;_get_tags&quot;):
     69         if key is not None:
---&gt; 70             return estimator._get_tags().get(key, _DEFAULT_TAGS[key])
     71         tags = estimator._get_tags()
     72         return {key: tags.get(key, _DEFAULT_TAGS[key])

~/Documents/packages/scikit-learn/sklearn/base.py in _get_tags(self)
    320         if hasattr(self, '_more_tags'):
    321             more_tags = self._more_tags()
--&gt; 322             collected_tags = _update_if_consistent(collected_tags, more_tags)
    323         tags = _DEFAULT_TAGS.copy()
    324         tags.update(collected_tags)

~/Documents/packages/scikit-learn/sklearn/base.py in _update_if_consistent(dict1, dict2)
    132         if dict1[key] != dict2[key]:
    133             raise TypeError(&quot;Inconsistent values for tag {}: {} != {}&quot;.format(
--&gt; 134                 key, dict1[key], dict2[key]
    135             ))
    136     dict1.update(dict2)

TypeError: Inconsistent values for tag sample_indices: True != False"><pre class="notranslate">---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
&lt;ipython-input-31-5558b1aebfa8&gt; in &lt;module&gt;
----&gt; 1 _safe_tags(xx, 'sample_indices')

~/Documents/packages/scikit-learn/sklearn/utils/estimator_checks.py in _safe_tags(estimator, key)
     <span class="pl-c1">68</span>     <span class="pl-k">if</span> <span class="pl-c1">hasattr</span>(estimator, <span class="pl-s"><span class="pl-pds">"</span>_get_tags<span class="pl-pds">"</span></span>):
     <span class="pl-c1">69</span>         <span class="pl-k">if</span> key <span class="pl-k">is</span> <span class="pl-k">not</span> <span class="pl-c1">None</span>:
---&gt; 70             return estimator._get_tags().get(key, _DEFAULT_TAGS[key])
     <span class="pl-c1">71</span>         tags <span class="pl-k">=</span> estimator._get_tags()
     <span class="pl-c1">72</span>         <span class="pl-k">return</span> {key: tags.get(key, <span class="pl-c1">_DEFAULT_TAGS</span>[key])

~/Documents/packages/scikit-learn/sklearn/base.py in _get_tags(self)
    <span class="pl-c1">320</span>         <span class="pl-k">if</span> <span class="pl-c1">hasattr</span>(<span class="pl-c1">self</span>, <span class="pl-s"><span class="pl-pds">'</span>_more_tags<span class="pl-pds">'</span></span>):
    <span class="pl-c1">321</span>             more_tags <span class="pl-k">=</span> <span class="pl-c1">self</span>._more_tags()
--&gt; 322             collected_tags = _update_if_consistent(collected_tags, more_tags)
    <span class="pl-c1">323</span>         tags <span class="pl-k">=</span> <span class="pl-c1">_DEFAULT_TAGS</span>.copy()
    <span class="pl-c1">324</span>         tags.update(collected_tags)

~/Documents/packages/scikit-learn/sklearn/base.py in _update_if_consistent(dict1, dict2)
    <span class="pl-c1">132</span>         <span class="pl-k">if</span> dict1[key] <span class="pl-k">!=</span> dict2[key]:
    <span class="pl-c1">133</span>             <span class="pl-k">raise</span> <span class="pl-c1">TypeError</span>(<span class="pl-s"><span class="pl-pds">"</span>Inconsistent values for tag <span class="pl-c1">{}</span>: <span class="pl-c1">{}</span> != <span class="pl-c1">{}</span><span class="pl-pds">"</span></span>.format(
--&gt; 134                 key, dict1[key], dict2[key]
    <span class="pl-c1">135</span>             ))
    <span class="pl-c1">136</span>     dict1.update(dict2)

<span class="pl-en">TypeError</span>: <span class="pl-s">Inconsistent values for tag sample_indices: True != False</span></pre></div>
<p dir="auto">Without the call of <code class="notranslate">super()</code>, I find this rule quite meaningful and the error raising a good thing. However, I think that we could lift this rule in case where we call <code class="notranslate">super()</code> in <code class="notranslate">self._more_tag</code>. By calling <code class="notranslate">super()</code>, one should be aware of overwriting the base-class default tag.</p>
<p dir="auto">Thus, by introspecting <code class="notranslate">self._more_tag</code> and check that <code class="notranslate">super()</code> is called (e.g. using <code class="notranslate">'super' in inspect.getclosurevars(self._more_tags).builtins</code>), we could still allow to update the tags.</p>
<p dir="auto"><a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/amueller/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/amueller">@amueller</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/rth/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/rth">@rth</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/jnothman/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/jnothman">@jnothman</a> Do you think that it is use case and a solution which make sense. I still have the option to add a <code class="notranslate">_more_tag</code> to each classes but this is a lot of duplicated code then.</p>
<p dir="auto">So the changes would be something like the following:</p>
<div class="highlight highlight-source-diff notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="-def _update_if_consistent(dict1, dict2):
+def _update_if_consistent(dict1, dict2, force=False):
     common_keys = set(dict1.keys()).intersection(dict2.keys())
-    for key in common_keys:
-        if dict1[key] != dict2[key]:
-            raise TypeError(&quot;Inconsistent values for tag {}: {} != {}&quot;.format(
-                key, dict1[key], dict2[key]
-            ))
+    if not force:
+        for key in common_keys:
+            if dict1[key] != dict2[key]:
+                raise TypeError(&quot;Inconsistent values for tag {}: {} != {}&quot;
+                                .format(key, dict1[key], dict2[key]))
     dict1.update(dict2)
     return dict1
 
@@ -319,7 +319,10 @@ class BaseEstimator:
                                                        more_tags)
         if hasattr(self, '_more_tags'):
             more_tags = self._more_tags()
-            collected_tags = _update_if_consistent(collected_tags, more_tags)
+            force = 'super' in inspect.getclosurevars(self._more_tags).builtins
+            collected_tags = _update_if_consistent(
+                collected_tags, more_tags, force=force
+            )
         tags = _DEFAULT_TAGS.copy()
         tags.update(collected_tags)
         return tags"><pre class="notranslate"><span class="pl-md"><span class="pl-md">-</span>def _update_if_consistent(dict1, dict2):</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>def _update_if_consistent(dict1, dict2, force=False):</span>
     common_keys = set(dict1.keys()).intersection(dict2.keys())
<span class="pl-md"><span class="pl-md">-</span>    for key in common_keys:</span>
<span class="pl-md"><span class="pl-md">-</span>        if dict1[key] != dict2[key]:</span>
<span class="pl-md"><span class="pl-md">-</span>            raise TypeError("Inconsistent values for tag {}: {} != {}".format(</span>
<span class="pl-md"><span class="pl-md">-</span>                key, dict1[key], dict2[key]</span>
<span class="pl-md"><span class="pl-md">-</span>            ))</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>    if not force:</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>        for key in common_keys:</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>            if dict1[key] != dict2[key]:</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>                raise TypeError("Inconsistent values for tag {}: {} != {}"</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>                                .format(key, dict1[key], dict2[key]))</span>
     dict1.update(dict2)
     return dict1
 
<span class="pl-mdr">@@ -319,7 +319,10 @@</span> class BaseEstimator:
                                                        more_tags)
         if hasattr(self, '_more_tags'):
             more_tags = self._more_tags()
<span class="pl-md"><span class="pl-md">-</span>            collected_tags = _update_if_consistent(collected_tags, more_tags)</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>            force = 'super' in inspect.getclosurevars(self._more_tags).builtins</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>            collected_tags = _update_if_consistent(</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>                collected_tags, more_tags, force=force</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>            )</span>
         tags = _DEFAULT_TAGS.copy()
         tags.update(collected_tags)
         return tags</pre></div>