<p dir="auto">The imputer should have an option to add a binary indicator feature (for each possibly missing feature) that indicate whether a feature was imputed or not (0 or 1).<br>
In applications, missingness is often informative.</p>
<h4 dir="auto">Describe the workflow you want to enable</h4>
<p dir="auto">It would be nice if the <code class="notranslate">ColumnTransformer</code> would <code class="notranslate">get_feature_names</code> from even transformers that don't implement <code class="notranslate">get_feature_names</code>, and used the full API of <code class="notranslate">get_feature_names</code> in transformers where it has been implemented.</p>
<h4 dir="auto">Describe your proposed solution</h4>
<h5 dir="auto">Current:</h5>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    def get_feature_names(self):
        &quot;&quot;&quot;Get feature names from all transformers.
        Returns
        -------
        feature_names : list of strings
            Names of the features produced by transform.
        &quot;&quot;&quot;
        check_is_fitted(self)
        feature_names = []
        for name, trans, column, _ in self._iter(fitted=True):
            if trans == 'drop' or (
                    hasattr(column, '__len__') and not len(column)):
                continue
            if trans == 'passthrough':
                if hasattr(self, '_df_columns'):
                    if ((not isinstance(column, slice))
                            and all(isinstance(col, str) for col in column)):
                        feature_names.extend(column)
                    else:
                        feature_names.extend(self._df_columns[column])
                else:
                    indices = np.arange(self._n_features)
                    feature_names.extend(['x%d' % i for i in indices[column]])
                continue
            if not hasattr(trans, 'get_feature_names'):
                raise AttributeError(&quot;Transformer %s (type %s) does not &quot;
                                     &quot;provide get_feature_names.&quot;
                                     % (str(name), type(trans).__name__))
            feature_names.extend([name + &quot;__&quot; + f for f in
                                  trans.get_feature_names()])
        return feature_names"><pre class="notranslate">    <span class="pl-k">def</span> <span class="pl-en">get_feature_names</span>(<span class="pl-s1">self</span>):
        <span class="pl-s">"""Get feature names from all transformers.</span>
<span class="pl-s">        Returns</span>
<span class="pl-s">        -------</span>
<span class="pl-s">        feature_names : list of strings</span>
<span class="pl-s">            Names of the features produced by transform.</span>
<span class="pl-s">        """</span>
        <span class="pl-en">check_is_fitted</span>(<span class="pl-s1">self</span>)
        <span class="pl-s1">feature_names</span> <span class="pl-c1">=</span> []
        <span class="pl-k">for</span> <span class="pl-s1">name</span>, <span class="pl-s1">trans</span>, <span class="pl-s1">column</span>, <span class="pl-s1">_</span> <span class="pl-c1">in</span> <span class="pl-s1">self</span>.<span class="pl-en">_iter</span>(<span class="pl-s1">fitted</span><span class="pl-c1">=</span><span class="pl-c1">True</span>):
            <span class="pl-k">if</span> <span class="pl-s1">trans</span> <span class="pl-c1">==</span> <span class="pl-s">'drop'</span> <span class="pl-c1">or</span> (
                    <span class="pl-en">hasattr</span>(<span class="pl-s1">column</span>, <span class="pl-s">'__len__'</span>) <span class="pl-c1">and</span> <span class="pl-c1">not</span> <span class="pl-en">len</span>(<span class="pl-s1">column</span>)):
                <span class="pl-k">continue</span>
            <span class="pl-k">if</span> <span class="pl-s1">trans</span> <span class="pl-c1">==</span> <span class="pl-s">'passthrough'</span>:
                <span class="pl-k">if</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">self</span>, <span class="pl-s">'_df_columns'</span>):
                    <span class="pl-k">if</span> ((<span class="pl-c1">not</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">column</span>, <span class="pl-s1">slice</span>))
                            <span class="pl-c1">and</span> <span class="pl-en">all</span>(<span class="pl-en">isinstance</span>(<span class="pl-s1">col</span>, <span class="pl-s1">str</span>) <span class="pl-k">for</span> <span class="pl-s1">col</span> <span class="pl-c1">in</span> <span class="pl-s1">column</span>)):
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(<span class="pl-s1">column</span>)
                    <span class="pl-k">else</span>:
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(<span class="pl-s1">self</span>.<span class="pl-s1">_df_columns</span>[<span class="pl-s1">column</span>])
                <span class="pl-k">else</span>:
                    <span class="pl-s1">indices</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">arange</span>(<span class="pl-s1">self</span>.<span class="pl-s1">_n_features</span>)
                    <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>([<span class="pl-s">'x%d'</span> <span class="pl-c1">%</span> <span class="pl-s1">i</span> <span class="pl-k">for</span> <span class="pl-s1">i</span> <span class="pl-c1">in</span> <span class="pl-s1">indices</span>[<span class="pl-s1">column</span>]])
                <span class="pl-k">continue</span>
            <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">trans</span>, <span class="pl-s">'get_feature_names'</span>):
                <span class="pl-k">raise</span> <span class="pl-v">AttributeError</span>(<span class="pl-s">"Transformer %s (type %s) does not "</span>
                                     <span class="pl-s">"provide get_feature_names."</span>
                                     <span class="pl-c1">%</span> (<span class="pl-en">str</span>(<span class="pl-s1">name</span>), <span class="pl-en">type</span>(<span class="pl-s1">trans</span>).<span class="pl-s1">__name__</span>))
            <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>([<span class="pl-s1">name</span> <span class="pl-c1">+</span> <span class="pl-s">"__"</span> <span class="pl-c1">+</span> <span class="pl-s1">f</span> <span class="pl-k">for</span> <span class="pl-s1">f</span> <span class="pl-c1">in</span>
                                  <span class="pl-s1">trans</span>.<span class="pl-en">get_feature_names</span>()])
        <span class="pl-k">return</span> <span class="pl-s1">feature_names</span></pre></div>
<p dir="auto">If a transformer does not implement get_feature_names, it simply raises an error.</p>
<p dir="auto">If a transformer DOES implement get_feature_names, the ColumnTransformer ignores part of that API (ignoring fitted column names, using instead an integer column index).</p>
<h5 dir="auto">Proposed Solution:</h5>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    def get_feature_names(self):
        from sklearn.utils.validation import check_is_fitted
        from numpy import arange
        &quot;&quot;&quot;Get feature names from all transformers.
        Returns
        -------
        feature_names : list of strings
            Names of the features produced by transform.
        &quot;&quot;&quot;
        check_is_fitted(self)
        feature_names = []
        for name, trans, column, _ in self._iter(fitted=True):
            if trans == 'drop' or (
                    hasattr(column, '__len__') and not len(column)):
                continue
            if trans == 'passthrough':
                if hasattr(self, '_df_columns'):
                    if ((not isinstance(column, slice))
                            and all(isinstance(col, str) for col in column)):
                        feature_names.extend(column)
                    else:
                        feature_names.extend(self._df_columns[column])
                else:
                    indices = arange(self._n_features)
                    feature_names.extend(['x%d' % i for i in indices[column]])
                continue
            if not hasattr(trans, 'get_feature_names'):
                # ADDED SECTION A
                if hasattr(self, '_df_columns'):
                    if ((not isinstance(column, slice))
                            and all(isinstance(col, str) for col in column)):
                        feature_names.extend(f'{name}_{col}' for col in column)
                    else:
                        feature_names.extend(
                            f'{name}_{col}'
                            for col in self._df_columns[column]
                            )
                else:
                    indices = arange(self._n_features)
                    feature_names.extend(['x%d' % i for i in indices[column]])
                continue
                # END SECTION A
            # ADDED SECTION B
            gfn_args = inspect.getfullargspec(trans.get_feature_names).args
            args_to_send = []
            if ('input_features' in gfn_args) and \
                    not isinstance(column, slice):
                args_to_send = [column]
            feature_names.extend([name + &quot;__&quot; + f for f in
                                  trans.get_feature_names(*args_to_send)])
            # END SECTION B
        return feature_names"><pre class="notranslate">    <span class="pl-k">def</span> <span class="pl-en">get_feature_names</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">from</span> <span class="pl-s1">sklearn</span>.<span class="pl-s1">utils</span>.<span class="pl-s1">validation</span> <span class="pl-k">import</span> <span class="pl-s1">check_is_fitted</span>
        <span class="pl-k">from</span> <span class="pl-s1">numpy</span> <span class="pl-k">import</span> <span class="pl-s1">arange</span>
        <span class="pl-s">"""Get feature names from all transformers.</span>
<span class="pl-s">        Returns</span>
<span class="pl-s">        -------</span>
<span class="pl-s">        feature_names : list of strings</span>
<span class="pl-s">            Names of the features produced by transform.</span>
<span class="pl-s">        """</span>
        <span class="pl-en">check_is_fitted</span>(<span class="pl-s1">self</span>)
        <span class="pl-s1">feature_names</span> <span class="pl-c1">=</span> []
        <span class="pl-k">for</span> <span class="pl-s1">name</span>, <span class="pl-s1">trans</span>, <span class="pl-s1">column</span>, <span class="pl-s1">_</span> <span class="pl-c1">in</span> <span class="pl-s1">self</span>.<span class="pl-en">_iter</span>(<span class="pl-s1">fitted</span><span class="pl-c1">=</span><span class="pl-c1">True</span>):
            <span class="pl-k">if</span> <span class="pl-s1">trans</span> <span class="pl-c1">==</span> <span class="pl-s">'drop'</span> <span class="pl-c1">or</span> (
                    <span class="pl-en">hasattr</span>(<span class="pl-s1">column</span>, <span class="pl-s">'__len__'</span>) <span class="pl-c1">and</span> <span class="pl-c1">not</span> <span class="pl-en">len</span>(<span class="pl-s1">column</span>)):
                <span class="pl-k">continue</span>
            <span class="pl-k">if</span> <span class="pl-s1">trans</span> <span class="pl-c1">==</span> <span class="pl-s">'passthrough'</span>:
                <span class="pl-k">if</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">self</span>, <span class="pl-s">'_df_columns'</span>):
                    <span class="pl-k">if</span> ((<span class="pl-c1">not</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">column</span>, <span class="pl-s1">slice</span>))
                            <span class="pl-c1">and</span> <span class="pl-en">all</span>(<span class="pl-en">isinstance</span>(<span class="pl-s1">col</span>, <span class="pl-s1">str</span>) <span class="pl-k">for</span> <span class="pl-s1">col</span> <span class="pl-c1">in</span> <span class="pl-s1">column</span>)):
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(<span class="pl-s1">column</span>)
                    <span class="pl-k">else</span>:
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(<span class="pl-s1">self</span>.<span class="pl-s1">_df_columns</span>[<span class="pl-s1">column</span>])
                <span class="pl-k">else</span>:
                    <span class="pl-s1">indices</span> <span class="pl-c1">=</span> <span class="pl-en">arange</span>(<span class="pl-s1">self</span>.<span class="pl-s1">_n_features</span>)
                    <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>([<span class="pl-s">'x%d'</span> <span class="pl-c1">%</span> <span class="pl-s1">i</span> <span class="pl-k">for</span> <span class="pl-s1">i</span> <span class="pl-c1">in</span> <span class="pl-s1">indices</span>[<span class="pl-s1">column</span>]])
                <span class="pl-k">continue</span>
            <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">trans</span>, <span class="pl-s">'get_feature_names'</span>):
                <span class="pl-c"># ADDED SECTION A</span>
                <span class="pl-k">if</span> <span class="pl-en">hasattr</span>(<span class="pl-s1">self</span>, <span class="pl-s">'_df_columns'</span>):
                    <span class="pl-k">if</span> ((<span class="pl-c1">not</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">column</span>, <span class="pl-s1">slice</span>))
                            <span class="pl-c1">and</span> <span class="pl-en">all</span>(<span class="pl-en">isinstance</span>(<span class="pl-s1">col</span>, <span class="pl-s1">str</span>) <span class="pl-k">for</span> <span class="pl-s1">col</span> <span class="pl-c1">in</span> <span class="pl-s1">column</span>)):
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(<span class="pl-s">f'<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">name</span><span class="pl-kos">}</span></span>_<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">col</span><span class="pl-kos">}</span></span>'</span> <span class="pl-k">for</span> <span class="pl-s1">col</span> <span class="pl-c1">in</span> <span class="pl-s1">column</span>)
                    <span class="pl-k">else</span>:
                        <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>(
                            <span class="pl-s">f'<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">name</span><span class="pl-kos">}</span></span>_<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">col</span><span class="pl-kos">}</span></span>'</span>
                            <span class="pl-k">for</span> <span class="pl-s1">col</span> <span class="pl-c1">in</span> <span class="pl-s1">self</span>.<span class="pl-s1">_df_columns</span>[<span class="pl-s1">column</span>]
                            )
                <span class="pl-k">else</span>:
                    <span class="pl-s1">indices</span> <span class="pl-c1">=</span> <span class="pl-en">arange</span>(<span class="pl-s1">self</span>.<span class="pl-s1">_n_features</span>)
                    <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>([<span class="pl-s">'x%d'</span> <span class="pl-c1">%</span> <span class="pl-s1">i</span> <span class="pl-k">for</span> <span class="pl-s1">i</span> <span class="pl-c1">in</span> <span class="pl-s1">indices</span>[<span class="pl-s1">column</span>]])
                <span class="pl-k">continue</span>
                <span class="pl-c"># END SECTION A</span>
            <span class="pl-c"># ADDED SECTION B</span>
            <span class="pl-s1">gfn_args</span> <span class="pl-c1">=</span> <span class="pl-s1">inspect</span>.<span class="pl-en">getfullargspec</span>(<span class="pl-s1">trans</span>.<span class="pl-s1">get_feature_names</span>).<span class="pl-s1">args</span>
            <span class="pl-s1">args_to_send</span> <span class="pl-c1">=</span> []
            <span class="pl-k">if</span> (<span class="pl-s">'input_features'</span> <span class="pl-c1">in</span> <span class="pl-s1">gfn_args</span>) <span class="pl-c1">and</span> \
                    <span class="pl-c1">not</span> <span class="pl-en">isinstance</span>(<span class="pl-s1">column</span>, <span class="pl-s1">slice</span>):
                <span class="pl-s1">args_to_send</span> <span class="pl-c1">=</span> [<span class="pl-s1">column</span>]
            <span class="pl-s1">feature_names</span>.<span class="pl-en">extend</span>([<span class="pl-s1">name</span> <span class="pl-c1">+</span> <span class="pl-s">"__"</span> <span class="pl-c1">+</span> <span class="pl-s1">f</span> <span class="pl-k">for</span> <span class="pl-s1">f</span> <span class="pl-c1">in</span>
                                  <span class="pl-s1">trans</span>.<span class="pl-en">get_feature_names</span>(<span class="pl-c1">*</span><span class="pl-s1">args_to_send</span>)])
            <span class="pl-c"># END SECTION B</span>
        <span class="pl-k">return</span> <span class="pl-s1">feature_names</span></pre></div>
<h6 dir="auto">Section A adds:</h6>
<p dir="auto"><code class="notranslate">&lt;&lt;transformer name&gt;&gt;_&lt;&lt;column&gt;&gt;</code> for each transformer that doesn't implement <code class="notranslate">get_feature_names</code></p>
<h6 dir="auto">Section A removes:</h6>
<p dir="auto">Raising an error</p>
<h6 dir="auto">Section B adds:</h6>
<p dir="auto"><code class="notranslate">&lt;&lt;transformer name&gt;&gt;__&lt;&lt;output of get_feature_names&gt;&gt;</code> for the transformer by sending in the column names it received at fit.</p>
<p dir="auto">So - if the transformer doesn't implement <code class="notranslate">get_feature_names</code>, we either return the column names (in the case of a 1:1 transformation), or an integer index.<br>
If the transformer DOES implement <code class="notranslate">get_feature_names</code>, we try to get the original feature names that were fed in, and use them to get more descriptive feature names from each transformer.<br>
If that isn't possible, we fall back to the original behavior.</p>
<h4 dir="auto">Describe alternatives you've considered, if relevant</h4>
<p dir="auto">The alternative is to stay with what it is. But I think this is a valuable addition.</p>
<h4 dir="auto">Additional context</h4>
<p dir="auto">I know I haven't considered every eventuality, which is why there is not a pull request associated with this feature request. But I do think I'm close, and I would welcome any input.</p>