Concatenate custom features with CountVectorizer
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.
Introduction
To combine text features from CountVectorizer with custom numerical features in scikit-learn, use scipy.sparse.hstack() to horizontally stack the sparse matrix from CountVectorizer with your custom feature array. For a cleaner approach, use ColumnTransformer with Pipeline to apply CountVectorizer to text columns and passthrough or transform numerical columns in a single step. This pattern is standard for NLP tasks where text bag-of-words features need to be combined with metadata like document length, sentiment scores, or categorical attributes.
The Problem
CountVectorizer produces a sparse matrix of word counts, but your model also needs non-text features:
Method 1: scipy.sparse.hstack (Direct)
This preserves sparsity, which is critical for large vocabularies where a dense matrix would use too much memory.
Method 2: ColumnTransformer (Recommended)
Use ColumnTransformer for a clean pipeline that handles text and numeric columns together:
Method 3: FeatureUnion
FeatureUnion concatenates outputs from multiple transformers:
Method 4: TfidfVectorizer + Custom Features
TfidfVectorizer is often preferred over CountVectorizer for text classification:
Getting Feature Names
Common Pitfalls
- Using
np.hstackinstead ofscipy.sparse.hstack:np.hstackconverts the sparse matrix to dense, consuming massive memory for large vocabularies (e.g., 50,000 words x 100,000 documents). Always usescipy.sparse.hstackto preserve sparsity. - Not scaling custom features before concatenation:
CountVectorizerproduces counts (0, 1, 2, ...) or TF-IDF scores (0.0-1.0), while custom features may be in a completely different range (e.g., word count 0-500). Without scaling, the model weights are dominated by larger-magnitude features. UseStandardScalerorMinMaxScaleron custom features. - Forgetting to apply the same transformations at prediction time: If you use
hstackmanually, you must apply the samevectorizer.transform()andscaler.transform()at prediction time. APipelinewithColumnTransformerhandles this automatically. - Passing a DataFrame column to
CountVectorizerinColumnTransformeras a list:CountVectorizerexpects a single column (string), not a list of columns. InColumnTransformer, pass the column name as a string ('text'), not a list (['text']). - Mismatching row counts between text features and custom features: If the text array and custom feature array have different numbers of rows,
hstackproduces a cryptic dimension error. Always verify that both arrays have the same number of samples before concatenating.
Summary
- Use
scipy.sparse.hstack([text_features, csr_matrix(custom_features)])for manual concatenation - Use
ColumnTransformerwithPipelinefor production ML pipelines (cleanest approach) - Use
FeatureUnionwhen working with raw text input (no DataFrame) - Scale custom features to match the range of text features before combining
- Preserve sparsity — never convert large sparse matrices to dense arrays
Related reading
- Concatenate two models with tensorflow.keras
- Concept of getter in TensorFlow
- Conditional assignment of tensor values in TensorFlow
- Conditional execution in TensorFlow
- conditional graph in tensorflow and for loop that accesses tensor size
- Configuring Tensorflow to use all CPU's
- Confused about conv2d_transpose
- confused about random_state in decision tree of scikit learn
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.