Post

Tabular Data Preprocessing and Post-Processing: Complete Guide

Tabular Data Preprocessing and Post-Processing: Complete Guide

Guide

Table of Contents

Introduction

Data preprocessing and post-processing are critical stages in the data pipeline. While preprocessing prepares raw data for analysis and modeling, post-processing refines and validates the output. Together, they ensure data quality, consistency, and reliability throughout your entire data workflow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Raw Data
   │
   ▼
┌─────────────────────────┐
│   PREPROCESSING         │
│  (Data Cleaning)        │
└─────────────────────────┘
   │
   ▼
┌─────────────────────────┐
│   Processing/Analysis   │
│  (Models, Queries)      │
└─────────────────────────┘
   │
   ▼
┌─────────────────────────┐
│   POST-PROCESSING       │
│  (Validation, Mapping)  │
└─────────────────────────┘
   │
   ▼
Insights & Results

Part 1: Data Preprocessing

Data preprocessing is the process of cleaning, transforming, and organizing raw data into a format suitable for analysis, modeling, or machine learning.

Why preprosessing is important?

  • Quality Issues: Raw data often contains errors, missing values, and inconsistencies
  • Format Mismatch: Data may be in incompatible formats or units
  • Outliers: Extreme values can skew analysis results
  • Performance: Clean data improves model accuracy and speed
  • Reliability: Ensures reproducible results

Key Preprocessing Steps

This section outlines the essential steps involved in preparing tabular data for analysis and modeling. Each step addresses a specific challenge, such as missing values, outliers, or inconsistent formats, to ensure your data is clean, reliable, and ready for downstream tasks. Following these best practices helps build robust machine learning pipelines and improves the quality of the results.

1. Data Collection & Understanding

Before any transformation, you must understand the data’s semantics.

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
import numpy as np

# Load data
df = pd.read_csv('raw_data.csv')

# Understand the data
print(df.head())
print(df.info())
print(df.describe())
print(df.isnull().sum())

Key questions

  • What does each column represent?
  • Are units consistent?
  • Is missingness random or meaningful?
  • Is there temporal or label leakage?

Note: Please refer to the “Advanced Feature Engineering: Complete Guide” post for more details on data leakage in machine learning.

2. Handling Missing Values

Identifying Missing Data:

1
2
3
4
5
6
7
8
# Check for missing values
missing_data = df.isnull().sum()
print(missing_data)

# Visualize missing data
import matplotlib.pyplot as plt
missing_data.plot(kind='bar')
plt.show()

Strategies for Handling Missing Values:

Strategy Use Case Example
Drop Few missing values Remove rows with NaN
Mean/Median Numerical data Fill with central tendency
Forward Fill Time series Use previous value
Backward Fill Time series Use next value
Interpolation Continuous data Estimate intermediate values
Domain Knowledge Critical data Fill with expert input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Method 1: Drop rows with missing values
df_cleaned = df.dropna()

# Method 2: Fill with mean (numerical)
df['age'].fillna(df['age'].mean(), inplace=True)

# Method 3: Fill with forward fill (time series)
df['temperature'].fillna(method='ffill', inplace=True)

# Method 4: Interpolation
df['stock_price'].interpolate(method='linear', inplace=True)

# Method 5: Fill with specific value
df['category'].fillna('Unknown', inplace=True)

Note: add a missing-indicator feature when missingness is informative.

3. Handling Outliers

Detecting Outliers:

Outliers can be errors or rare but valid events. Methods include, Z-score, IQR, Isolation Forest, Domain constraints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scipy.stats as stats

# Method 1: Z-score (values > 3 are outliers)
z_scores = np.abs(stats.zscore(df['salary']))
outliers_zscore = z_scores > 3

# Method 2: IQR (Interquartile Range)
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
outliers_iqr = (df['salary'] < (Q1 - 1.5 * IQR)) | (df['salary'] > (Q3 + 1.5 * IQR))

# Method 3: Isolation Forest (ML-based)
from sklearn.ensemble import IsolationForest
iso_forest = IsolationForest(contamination=0.1)
outliers_if = iso_forest.fit_predict(df[['salary']]) == -1

Handling Outliers:

1
2
3
4
5
6
7
8
# Option 1: Remove outliers
df_clean = df[~outliers_iqr]

# Option 2: Cap outliers
df['salary'] = df['salary'].clip(lower=lower_bound, upper=upper_bound)

# Option 3: Transform (log scale)
df['salary_log'] = np.log(df['salary'])

4. Data Type Conversion

1
2
3
4
5
6
7
8
# Convert to appropriate data types
df['age'] = df['age'].astype('int32')
df['hire_date'] = pd.to_datetime(df['hire_date'])
df['is_active'] = df['is_active'].astype('bool')
df['category'] = df['category'].astype('category')

# Verify conversions
print(df.dtypes)

5. Handling Duplicates

1
2
3
4
5
6
7
8
9
10
11
12
# Identify duplicates
duplicates = df.duplicated()
print(f"Duplicate rows: {duplicates.sum()}")

# Remove duplicates
df_clean = df.drop_duplicates()

# Remove duplicates based on specific columns
df_clean = df.drop_duplicates(subset=['email', 'phone'])

# Keep first/last occurrence
df_clean = df.drop_duplicates(keep='first')

6. Text Data Cleaning

1
2
3
4
5
6
7
8
9
10
11
12
13
# Convert to lowercase
df['text'] = df['text'].str.lower()

# Remove special characters
df['text'] = df['text'].str.replace(r'[^\w\s]', '', regex=True)

# Remove whitespace
df['text'] = df['text'].str.strip()

# Remove stopwords (NLP)
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
df['text'] = df['text'].apply(lambda x: ' '.join([w for w in x.split() if w not in stop_words]))

7. Feature Scaling

Feature scaling is the process of adjusting the range and distribution of numerical features so that they are on a comparable scale. This step is important because many machine learning algorithms perform better when input features have similar magnitudes, preventing features with larger values from dominating the learning process. Common scaling techniques include standardization (z-score scaling) and normalization (min-max scaling).

Standardization (Z-score scaling):

This technique transforms features to have mean 0 and standard deviation 1. Use this when your data follows a normal distribution or for algorithms sensitive to feature magnitude.

Formula:

\[Z = \frac{X - \mu}{\sigma}\]

Where:

  • $X$ = original value
  • $\mu$ = mean of the feature
  • $\sigma$ = standard deviation of the feature

Min-Max Normalization (Max-min scaling):

This technique scales values to a fixed range, typically [0, 1] (or sometimes [-1,1]). Use this when you need bounded values or when the scale of features matters.

Formula: \(X_{normalized} = \frac{X - X_{min}}{X_{max} - X_{min}}\)

Where:

  • $X$ = original value
  • $X_{min}$ = minimum value in the feature
  • $X_{max}$ = maximum value in the feature

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Standardization (Z-score normalization)
scaler_std = StandardScaler()
df['salary_scaled'] = scaler_std.fit_transform(df[['salary']])

# Normalization (Min-Max scaling 0-1)
scaler_minmax = MinMaxScaler()
df['salary_normalized'] = scaler_minmax.fit_transform(df[['salary']])

# Log scaling (for skewed data)
df['salary_log'] = np.log1p(df['salary'])

Note:

  • Normalization rescales features to a fixed range, such as [0, 1], making it useful when bounded values are required.
  • Standardization, on the other hand, transforms features to have zero mean and unit variance, resulting in an unbounded range. Choose the method that best fits your data and modeling needs.

8. Encoding Categorical Variables

Large category spaces can dominate models. So we need to apply encoding methods:

Encoding Method Typical Use Case
One-hot Low cardinality categorical features
Frequency encoding Medium/high cardinality, simple models
Target encoding High cardinality, supervised learning
Hashing trick Very high cardinality, memory efficiency
Learned embeddings Deep learning, complex relationships
1
2
3
4
5
6
7
8
9
10
11
# One-Hot Encoding
df_encoded = pd.get_dummies(df, columns=['department', 'location'])

# Label Encoding
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['department_encoded'] = le.fit_transform(df['department'])

# Target Encoding (for high-cardinality)
target_encoding = df.groupby('city')['purchase_amount'].mean()
df['city_encoded'] = df['city'].map(target_encoding)

9. Feature Engineering

1
2
3
4
5
6
7
8
9
10
11
12
13
# Create new features from existing ones
df['age_squared'] = df['age'] ** 2
df['salary_per_age'] = df['salary'] / df['age']

# Extract date components
df['hire_year'] = pd.to_datetime(df['hire_date']).dt.year
df['hire_month'] = pd.to_datetime(df['hire_date']).dt.month
df['hire_day_of_week'] = pd.to_datetime(df['hire_date']).dt.day_name()

# Polynomial features
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2)
features_poly = poly.fit_transform(df[['age', 'salary']])

10. Feature Selection

Reduces noise and improves interpretability.

Feature Selection Methods

Method Description/Examples
Filter Variance threshold, mutual information
Embedded L1 regularization, tree-based feature importance
Dimensionality reduction PCA, autoencoders

Part 2: Post-Processing

Post-processing is the stage where raw output from models or analysis is refined, validated, and transformed into actionable insights.

Why is post-processing important?

  • Model Output Refinement: Converts raw predictions into interpretable results
  • Quality Assurance: Validates output against expected ranges
  • Format Conversion: Transforms data into required formats
  • Error Detection: Identifies anomalies in output
  • Business Logic: Applies domain-specific rules

Key Post-Processing Steps

1. Output Validation

1
2
3
4
5
6
7
8
9
10
11
12
# Validate predictions are in expected range
predictions = model.predict(X_test)

# Check if predictions are within bounds
min_expected = 0
max_expected = 100

invalid_predictions = (predictions < min_expected) | (predictions > max_expected)
print(f"Invalid predictions: {invalid_predictions.sum()}")

# Clip predictions to valid range
predictions_valid = np.clip(predictions, min_expected, max_expected)

2. Confidence Score Filtering

1
2
3
4
5
6
7
8
9
10
11
12
# For classification models
predictions, confidence = model.predict_with_confidence(X_test)

# Keep only high-confidence predictions
confidence_threshold = 0.85
high_confidence_mask = confidence >= confidence_threshold

confident_predictions = predictions[high_confidence_mask]
low_confidence_indices = np.where(confidence < confidence_threshold)[0]

print(f"Predictions with confidence > {confidence_threshold}: {len(confident_predictions)}")
print(f"Low confidence predictions: {len(low_confidence_indices)}")

3. Anomaly Detection in Output

1
2
3
4
5
6
7
8
9
10
11
from sklearn.ensemble import IsolationForest

# Detect anomalies in predictions
iso_forest = IsolationForest(contamination=0.05)
anomaly_mask = iso_forest.fit_predict(predictions.reshape(-1, 1)) == -1

print(f"Anomalies detected: {anomaly_mask.sum()}")

# Separate anomalies for review
anomalies = predictions[anomaly_mask]
normal_predictions = predictions[~anomaly_mask]

4. Rounding & Formatting

1
2
3
4
5
6
7
8
9
# Round numerical outputs
predictions_rounded = np.round(predictions, 2)

# Format for display
predictions_formatted = [f"${pred:.2f}" for pred in predictions]

# Convert probabilities to percentages
probabilities = model.predict_proba(X_test)
percentages = (probabilities * 100).astype(int)

5. Aggregation & Summarization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Aggregate predictions
results_df = pd.DataFrame({
    'id': X_test.index,
    'prediction': predictions,
    'confidence': confidence,
    'category': pd.cut(predictions, bins=[0, 33, 66, 100], 
                       labels=['Low', 'Medium', 'High'])
})

# Summary statistics
summary = results_df.groupby('category').agg({
    'prediction': ['mean', 'std', 'min', 'max'],
    'confidence': 'mean'
})

print(summary)

6. Applying Business Rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Example: Credit scoring post-processing
def post_process_credit_score(predictions, applicant_history):
    # Adjust score based on business rules
    adjusted_scores = predictions.copy()
    
    # Penalty for previous defaults
    adjusted_scores[applicant_history['defaults'] > 0] -= 20
    
    # Bonus for long credit history
    adjusted_scores[applicant_history['years_active'] > 10] += 10
    
    # Ensure scores stay within bounds
    adjusted_scores = np.clip(adjusted_scores, 300, 850)
    
    return adjusted_scores

processed_scores = post_process_credit_score(predictions, applicant_history)

7. Error Handling & Fallback Logic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def safe_prediction(model, X, default_value=None):
    """Safe prediction with error handling"""
    try:
        predictions = model.predict(X)
        
        # Validate output
        if np.any(np.isnan(predictions)) or np.any(np.isinf(predictions)):
            if default_value is not None:
                return np.full_like(predictions, default_value)
            else:
                raise ValueError("Invalid predictions")
        
        return predictions
        
    except Exception as e:
        print(f"Prediction error: {e}")
        if default_value is not None:
            return np.full(len(X), default_value)
        else:
            raise

# Usage
predictions = safe_prediction(model, X_test, default_value=0.5)

8. Output Formatting & Export

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Convert to DataFrame for export
results = pd.DataFrame({
    'customer_id': X_test.index,
    'prediction': predictions_rounded,
    'confidence': confidence_scores,
    'processed_date': pd.Timestamp.now()
})

# Export in different formats
results.to_csv('predictions.csv', index=False)
results.to_json('predictions.json', orient='records')
results.to_excel('predictions.xlsx', index=False)

# Database export
from sqlalchemy import create_engine
engine = create_engine('postgresql://user:password@localhost/db')
results.to_sql('predictions', engine, if_exists='append', index=False)

Complete Workflow Example

The following example demonstrates a full end-to-end workflow for tabular data, covering all major steps from preprocessing to post-processing. It shows how to clean and transform raw data, handle missing values and outliers, scale features, encode categorical variables, train a machine learning model, and refine the model’s predictions for final output. This practical pipeline can be adapted to a wide range of real-world data science projects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor

# ============ PREPROCESSING ============
# Load data
df = pd.read_csv('raw_data.csv')

# Handle missing values
df['age'].fillna(df['age'].median(), inplace=True)
df['salary'].fillna(df['salary'].mean(), inplace=True)

# Remove duplicates
df = df.drop_duplicates()

# Handle outliers
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
df = df[(df['salary'] >= Q1 - 1.5*IQR) & (df['salary'] <= Q3 + 1.5*IQR)]

# Scale features
scaler = StandardScaler()
df[['age', 'experience']] = scaler.fit_transform(df[['age', 'experience']])

# Encode categorical
df = pd.get_dummies(df, columns=['department'])

# ============ MODELING ============
X = df.drop('salary_target', axis=1)
y = df['salary_target']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model = RandomForestRegressor()
model.fit(X_train, y_train)

# ============ POST-PROCESSING ============
# Make predictions
predictions = model.predict(X_test)

# Validate predictions
predictions = np.clip(predictions, 0, None)  # No negative salaries

# Round to 2 decimals
predictions = np.round(predictions, 2)

# Create results DataFrame
results = pd.DataFrame({
    'actual': y_test.values,
    'predicted': predictions,
    'error': y_test.values - predictions,
    'error_percent': ((y_test.values - predictions) / y_test.values * 100).round(2)
})

# Export
results.to_csv('predictions_final.csv', index=False)
print(results.head())

Comparison: Preprocessing vs Post-Processing

Aspect Preprocessing Post-Processing
When Before analysis/modeling After analysis/modeling
Input Raw, messy data Model outputs/results
Output Clean, formatted data Refined, validated results
Focus Data quality Output quality
Tools Pandas, NumPy Scikit-learn, domain logic
Example Remove missing values Round predictions

Resources

This post is licensed under CC BY 4.0 by the author.