Repository: autogluon/autogluon

Issue:
[BUG] KeyError: "1 required columns are missing from the provided dataset to transform using CategoryFeatureGenerator. Missing columns: ['Product_Description']"
[please refer to my colab file to see the bug in details:
https://colab.research.google.com/drive/1IbQeeavwM7wNSghw0B2kWBsE-RHHHUMU?usp=sharing]
======================================================================
Bug description:

I am refering multimal tutorials to train a dataset with only text features.(refering page: https://auto.gluon.ai/stable/tutorials/tabular_prediction/tabular-multimodal-text-others.html)

here are my settings:
from autogluon.tabular import TabularPredictor
predictor = TabularPredictor(label='Sentiment', path='./model',problem_type='multiclass',eval_metric="f1_micro",verbosity=4)
predictor.fit(train_df,hyperparameters='multimodal', time_limit=None,presets=['best_quality'],auto_stack=True,refit_full=True,set_best_to_refit_full=True,keep_only_best=True,save_space=True,num_bag_sets=1, num_gpus=1)

While training is going well, it seems that testing meets bugs:
KeyError: "1 required columns are missing from the provided dataset to transform using CategoryFeatureGenerator. Missing columns: ['Product_Description']"
here ['Product_Description']" is the feature of testing set. However, I promise the testing set include this feature.

Can anyone give a hand?


Discussion:
Besides, this bug also seems strange:

Fitting model: TextPredictor_BAG_L1 ...
	Dropped 262 of 263 features.
	Warning: Exception caused TextPredictor_BAG_L1 to fail during training... Skipping this model.
		module 'PIL.Image' has no attribute 'Resampling'

how can PIL.Image influence TextPredictor...

Resolving patch:
diff --git a/features/src/autogluon/features/generators/abstract.py b/features/src/autogluon/features/generators/abstract.py
--- a/features/src/autogluon/features/generators/abstract.py
+++ b/features/src/autogluon/features/generators/abstract.py
@@ -168,7 +168,8 @@ def __init__(
             self._post_generators.append(DropDuplicatesFeatureGenerator(post_drop_duplicates=False))
         if name_prefix or name_suffix:
             from .rename import RenameFeatureGenerator
-            self._post_generators.append(RenameFeatureGenerator(name_prefix=name_prefix, name_suffix=name_suffix, inplace=True))
+            # inplace=False required to avoid altering outer context: refer to https://github.com/autogluon/autogluon/issues/2688
+            self._post_generators.append(RenameFeatureGenerator(name_prefix=name_prefix, name_suffix=name_suffix, inplace=False))
 
         if self._post_generators:
             if not self.get_tags().get('allow_post_generators', True):
@@ -333,7 +334,8 @@ def transform(self, X: DataFrame) -> DataFrame:
                 if col not in X.columns:
                     missing_cols.append(col)
             raise KeyError(f'{len(missing_cols)} required columns are missing from the provided dataset to transform using {self.__class__.__name__}. '
-                           f'Missing columns: {missing_cols}')
+                           f'{len(missing_cols)} missing columns: {missing_cols} | '
+                           f'{len(list(X.columns))} available columns: {list(X.columns)}')
         if self._pre_astype_generator:
             X = self._pre_astype_generator.transform(X)
         X_out = self._transform(X)


Test patch:
diff --git a/features/tests/features/generators/test_auto_ml_pipeline.py b/features/tests/features/generators/test_auto_ml_pipeline.py
--- a/features/tests/features/generators/test_auto_ml_pipeline.py
+++ b/features/tests/features/generators/test_auto_ml_pipeline.py
@@ -111,3 +111,139 @@ def test_auto_ml_pipeline_feature_generator(generator_helper, data_helper):
 
     # text_ngram checks
     assert expected_output_data_feat_total == list(output_data['__nlp__._total_'].values)
+
+
+def test_auto_ml_pipeline_feature_generator_raw_text(generator_helper, data_helper):
+    # Given
+    input_data = data_helper.generate_multi_feature_full()
+
+    toy_vectorizer = CountVectorizer(min_df=2, ngram_range=(1, 3), max_features=10, dtype=np.uint8)
+
+    generator = AutoMLPipelineFeatureGenerator(enable_raw_text_features=True, vectorizer=toy_vectorizer)
+
+    for generator_stage in generator.generators:
+        for generator_inner in generator_stage:
+            if isinstance(generator_inner, TextNgramFeatureGenerator):
+                # Necessary in test to avoid CI non-deterministically pruning ngram counts.
+                generator_inner.max_memory_ratio = None
+
+    expected_feature_metadata_in_full = {
+        ('category', ()): ['cat'],
+        ('datetime', ()): ['datetime'],
+        ('float', ()): ['float'],
+        ('int', ()): ['int'],
+        ('object', ()): ['obj'],
+        ('object', ('datetime_as_object',)): ['datetime_as_object'],
+        ('object', ('text',)): ['text']
+    }
+
+    expected_feature_metadata_full = {
+        ('category', ()): ['obj', 'cat'],
+        ('float', ()): ['float'],
+        ('int', ()): ['int'],
+        ('int', ('binned', 'text_special')): [
+            'text.char_count',
+            'text.word_count',
+            'text.lower_ratio',
+            'text.special_ratio',
+            'text.symbol_ratio. '
+        ],
+        ('int', ('datetime_as_int',)): [
+            'datetime',
+            'datetime.year',
+            'datetime.month',
+            'datetime.day',
+            'datetime.dayofweek',
+            'datetime_as_object',
+            'datetime_as_object.year',
+            'datetime_as_object.month',
+            'datetime_as_object.day',
+            'datetime_as_object.dayofweek'
+        ],
+        ('int', ('text_ngram',)): [
+            '__nlp__.breaks',
+            '__nlp__.end',
+            '__nlp__.end of',
+            '__nlp__.end of the',
+            '__nlp__.of',
+            '__nlp__.sentence',
+            '__nlp__.sentence breaks',
+            '__nlp__.the',
+            '__nlp__.the end',
+            '__nlp__.world',
+            '__nlp__._total_'
+        ],
+        ('object', ('text',)): [
+            'text_raw_text'
+        ],
+    }
+
+    # When
+    output_data = generator_helper.fit_transform_assert(
+        input_data=input_data,
+        generator=generator,
+        expected_feature_metadata_in_full=expected_feature_metadata_in_full,
+        expected_feature_metadata_full=expected_feature_metadata_full,
+    )
+
+    assert list(input_data['text'].values) == list(output_data['text_raw_text'].values)
+
+
+def test_auto_ml_pipeline_feature_generator_only_raw_text(generator_helper, data_helper):
+    """
+    Specifically tests when only text columns are provided.
+    This verifies the edge-case bug in v0.6.2 from https://github.com/autogluon/autogluon/issues/2688 is not present.
+    """
+
+    # Given
+    input_data = data_helper.generate_text_feature().to_frame('text')
+
+    toy_vectorizer = CountVectorizer(min_df=2, ngram_range=(1, 3), max_features=10, dtype=np.uint8)
+
+    generator = AutoMLPipelineFeatureGenerator(enable_raw_text_features=True, vectorizer=toy_vectorizer)
+
+    for generator_stage in generator.generators:
+        for generator_inner in generator_stage:
+            if isinstance(generator_inner, TextNgramFeatureGenerator):
+                # Necessary in test to avoid CI non-deterministically pruning ngram counts.
+                generator_inner.max_memory_ratio = None
+
+    expected_feature_metadata_in_full = {
+        ('object', ('text',)): ['text']
+    }
+
+    expected_feature_metadata_full = {
+        ('int', ('binned', 'text_special')): [
+            'text.char_count',
+            'text.word_count',
+            'text.lower_ratio',
+            'text.special_ratio',
+            'text.symbol_ratio. '
+        ],
+        ('int', ('text_ngram',)): [
+            '__nlp__.breaks',
+            '__nlp__.end',
+            '__nlp__.end of',
+            '__nlp__.end of the',
+            '__nlp__.of',
+            '__nlp__.sentence',
+            '__nlp__.sentence breaks',
+            '__nlp__.the',
+            '__nlp__.the end',
+            '__nlp__.world',
+            '__nlp__._total_'
+        ],
+        ('object', ('text',)): [
+            'text_raw_text'
+        ],
+    }
+
+    # When
+    output_data = generator_helper.fit_transform_assert(
+        input_data=input_data,
+        generator=generator,
+        expected_feature_metadata_in_full=expected_feature_metadata_in_full,
+        expected_feature_metadata_full=expected_feature_metadata_full,
+    )
+
+    assert list(input_data['text'].values) == list(output_data['text_raw_text'].values)