Machine learning / Feature engineering and preprocessing
De-normalized Data for Machine Learning Models in Python
Contents
- Understanding De-normalized Data
- Benefits of De-normalized Data for Model Training
- Preparing De-normalized Data
- Handling Missing Data in De-normalized Structures
- Feature Engineering with De-normalized Data
- Handling Categorical Variables in De-normalized Data
- Scaling Features in De-normalized Data
- Handling Time Series Data in De-normalized Format
- Dealing with High Cardinality in De-normalized Data
- Creating Train-Test Split with De-normalized Data
- Building a Simple Model with De-normalized Data
- Handling Imbalanced Data in De-normalized Datasets
- Optimizing Memory Usage for Large De-normalized Datasets
- Monitoring and Updating De-normalized Data
- Advantages and Disadvantages of De-normalized Data
- Additional Resources
Understanding De-normalized Data
De-normalized data refers to a data structure where redundant information is intentionally added to improve query performance or simplify data handling. This approach contrasts with normalized data, which aims to reduce redundancy.
# Example of normalized vs de-normalized data
# Normalized
users = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
orders = [{'id': 101, 'user_id': 1, 'product': 'Book'},
{'id': 102, 'user_id': 2, 'product': 'Pen'}]
# De-normalized
denorm_orders = [{'id': 101, 'user_id': 1, 'user_name': 'Alice', 'product': 'Book'},
{'id': 102, 'user_id': 2, 'user_name': 'Bob', 'product': 'Pen'}]
Benefits of De-normalized Data for Model Training
De-normalized data can enhance model training by:
- Reducing join operations
- Improving query speed
- Simplifying data access patterns
import pandas as pd
# Creating a de-normalized dataset
data = {
'user_id': [1, 2, 3],
'user_name': ['Alice', 'Bob', 'Charlie'],
'order_id': [101, 102, 103],
'product': ['Book', 'Pen', 'Notebook'],
'category': ['Stationery', 'Stationery', 'Stationery']
}
df = pd.DataFrame(data)
print(df.head())
Preparing De-normalized Data
To prepare de-normalized data:
- Identify related entities
- Combine data from multiple tables
- Add redundant information
import pandas as pd
# Simulating normalized data
users = pd.DataFrame({'user_id': [1, 2, 3], 'user_name': ['Alice', 'Bob', 'Charlie']})
orders = pd.DataFrame({'order_id': [101, 102, 103], 'user_id': [1, 2, 3], 'product': ['Book', 'Pen', 'Notebook']})
categories = pd.DataFrame({'product': ['Book', 'Pen', 'Notebook'], 'category': ['Stationery', 'Stationery', 'Stationery']})
# De-normalizing the data
denormalized = orders.merge(users, on='user_id').merge(categories, on='product')
print(denormalized.head())
Handling Missing Data in De-normalized Structures
De-normalized data may introduce null values. Strategies to handle this:
- Imputation
- Creating placeholder values
- Using appropriate join types
import pandas as pd
import numpy as np
# Creating data with missing values
users = pd.DataFrame({'user_id': [1, 2, 3, 4], 'user_name': ['Alice', 'Bob', 'Charlie', np.nan]})
orders = pd.DataFrame({'order_id': [101, 102, 103], 'user_id': [1, 2, 5], 'product': ['Book', 'Pen', 'Notebook']})
# Outer join to preserve all data
denormalized = orders.merge(users, on='user_id', how='outer')
# Handling missing values
denormalized['user_name'].fillna('Unknown', inplace=True)
denormalized['product'].fillna('No Order', inplace=True)
print(denormalized)
Feature Engineering with De-normalized Data
De-normalized data allows for easier feature engineering:
- Creating aggregate features
- Deriving new features from multiple fields
- Applying transformations across related entities
import pandas as pd
# De-normalized dataset
data = {
'user_id': [1, 1, 2, 2, 3],
'user_name': ['Alice', 'Alice', 'Bob', 'Bob', 'Charlie'],
'order_id': [101, 102, 103, 104, 105],
'product': ['Book', 'Pen', 'Notebook', 'Pencil', 'Eraser'],
'price': [10, 2, 5, 1, 1]
}
df = pd.DataFrame(data)
# Feature engineering
df['total_spent'] = df.groupby('user_id')['price'].transform('sum')
df['order_count'] = df.groupby('user_id')['order_id'].transform('count')
df['avg_order_value'] = df['total_spent'] / df['order_count']
print(df.head())
Handling Categorical Variables in De-normalized Data
De-normalized data often includes categorical variables. Techniques to handle them:
- One-hot encoding
- Label encoding
- Embedding layers for deep learning models
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
# De-normalized dataset
data = {
'user_id': [1, 2, 3],
'user_name': ['Alice', 'Bob', 'Charlie'],
'product': ['Book', 'Pen', 'Notebook'],
'category': ['Stationery', 'Stationery', 'Stationery']
}
df = pd.DataFrame(data)
# One-hot encoding
onehot = OneHotEncoder(sparse=False)
product_encoded = onehot.fit_transform(df[['product']])
product_columns = onehot.get_feature_names(['product'])
# Label encoding
le = LabelEncoder()
df['user_name_encoded'] = le.fit_transform(df['user_name'])
# Combining encoded features
encoded_df = pd.concat([df, pd.DataFrame(product_encoded, columns=product_columns)], axis=1)
print(encoded_df)
Scaling Features in De-normalized Data
Scaling is crucial for many machine learning algorithms. Common techniques:
- Standard Scaling
- Min-Max Scaling
- Robust Scaling for outlier-sensitive data
import pandas as pd
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# De-normalized dataset
data = {
'user_id': [1, 2, 3],
'user_name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'total_spent': [100, 250, 50]
}
df = pd.DataFrame(data)
# Standard Scaling
scaler = StandardScaler()
df[['age_scaled', 'total_spent_scaled']] = scaler.fit_transform(df[['age', 'total_spent']])
# Min-Max Scaling
minmax_scaler = MinMaxScaler()
df[['age_minmax', 'total_spent_minmax']] = minmax_scaler.fit_transform(df[['age', 'total_spent']])
print(df)
Handling Time Series Data in De-normalized Format
De-normalized time series data can include:
- Timestamp features
- Lagged variables
- Rolling statistics
import pandas as pd
import numpy as np
# Creating a time series dataset
dates = pd.date_range(start='2023-01-01', end='2023-01-10', freq='D')
data = {
'date': dates,
'user_id': np.random.choice([1, 2, 3], size=len(dates)),
'sales': np.random.randint(10, 100, size=len(dates))
}
df = pd.DataFrame(data)
# Adding time-based features
df['day_of_week'] = df['date'].dt.dayofweek
df['month'] = df['date'].dt.month
# Creating lagged features
df['sales_lag1'] = df.groupby('user_id')['sales'].shift(1)
# Adding rolling statistics
df['sales_rolling_mean'] = df.groupby('user_id')['sales'].rolling(window=3).mean().reset_index(0, drop=True)
print(df)
Dealing with High Cardinality in De-normalized Data
High cardinality features are common in de-normalized data. Strategies to handle them:
- Frequency encoding
- Target encoding
- Dimensionality reduction techniques
import pandas as pd
from category_encoders import TargetEncoder
from sklearn.preprocessing import LabelEncoder
# De-normalized dataset with high cardinality
data = {
'user_id': range(1, 101),
'product_id': np.random.choice(range(1, 1001), size=100),
'category': np.random.choice(['A', 'B', 'C', 'D', 'E'], size=100),
'sales': np.random.randint(10, 1000, size=100)
}
df = pd.DataFrame(data)
# Frequency encoding
fe = df['product_id'].value_counts(normalize=True)
df['product_id_freq'] = df['product_id'].map(fe)
# Target encoding
te = TargetEncoder()
df['category_target_encoded'] = te.fit_transform(df['category'], df['sales'])
# Label encoding for high cardinality feature
le = LabelEncoder()
df['product_id_label'] = le.fit_transform(df['product_id'])
print(df.head())
Creating Train-Test Split with De-normalized Data
When splitting de-normalized data:
- Ensure no data leakage
- Maintain the integrity of related records
- Consider temporal aspects if applicable
import pandas as pd
from sklearn.model_selection import train_test_split
# De-normalized dataset
data = {
'user_id': range(1, 101),
'user_name': [f'User_{i}' for i in range(1, 101)],
'product_id': np.random.choice(range(1, 21), size=100),
'purchase_amount': np.random.randint(10, 1000, size=100)
}
df = pd.DataFrame(data)
# Splitting the data
train, test = train_test_split(df, test_size=0.2, stratify=df['user_id'])
# Checking the distribution of users in train and test sets
print("Train set user distribution:")
print(train['user_id'].value_counts().head())
print("\nTest set user distribution:")
print(test['user_id'].value_counts().head())
Building a Simple Model with De-normalized Data
Using de-normalized data to train a basic model:
- Prepare features and target
- Choose an appropriate algorithm
- Train and evaluate the model
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import numpy as np
# De-normalized dataset
data = {
'user_id': range(1, 101),
'user_age': np.random.randint(18, 70, size=100),
'product_id': np.random.choice(range(1, 21), size=100),
'category': np.random.choice(['A', 'B', 'C'], size=100),
'purchase_amount': np.random.randint(10, 1000, size=100)
}
df = pd.DataFrame(data)
# Prepare features and target
X = pd.get_dummies(df.drop('purchase_amount', axis=1), columns=['category'])
y = df['purchase_amount']
# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train a Random Forest model
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# Make predictions and evaluate
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"Mean Squared Error: {mse}")
Handling Imbalanced Data in De-normalized Datasets
De-normalized data can exacerbate class imbalance. Techniques to address this:
- Oversampling minority class
- Undersampling majority class
- Synthetic data generation (SMOTE)
import pandas as pd
import numpy as np
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
# Creating an imbalanced dataset
data = {
'user_id': range(1, 1001),
'age': np.random.randint(18, 70, size=1000),
'purchase_amount': np.random.randint(10, 1000, size=1000),
'churn': np.random.choice([0, 1], size=1000, p=[0.9, 0.1]) # 10% churn rate
}
df = pd.DataFrame(data)
# Prepare features and target
X = df[['age', 'purchase_amount']]
y = df['churn']
# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Apply SMOTE
smote = SMOTE(random_state=42)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)
# Train a model on the resampled data
model = RandomForestClassifier(random_state=42)
model.fit(X_train_resampled, y_train_resampled)
# Evaluate the model
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
Optimizing Memory Usage for Large De-normalized Datasets
Large de-normalized datasets can consume significant memory. Strategies to optimize:
- Use appropriate data types
- Compress string columns
- Utilize chunking for processing
import pandas as pd
import numpy as np
# Function to generate a large dataset
def generate_large_dataset(n_rows):
return pd.DataFrame({
'id': range(n_rows),
'category': np.random.choice(['A', 'B', 'C', 'D'], size=n_rows),
'value': np.random.rand(n_rows),
'timestamp': pd.date_range(start='2023-01-01', periods=n_rows, freq='S')
})
# Generate a dataset
df = generate_large_dataset(1_000_000)
# Check initial memory usage
print(f"Initial memory usage: {df.memory_usage().sum() / 1e6:.2f} MB")
# Optimize data types
df['id'] = df['id'].astype('int32')
df['category'] = df['category'].astype('category')
df['value'] = df['value'].astype('float32')
# Check optimized memory usage
print(f"Optimized memory usage: {df.memory_usage().sum() / 1e6:.2f} MB")
# Demonstrate chunking for processing
chunk_size = 100_000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
# Process each chunk
processed_chunk = chunk.groupby('category')['value'].mean()
print(processed_chunk)
Monitoring and Updating De-normalized Data
De-normalized data requires regular maintenance:
- Set up data quality checks
- Implement update mechanisms
- Version control for schema changes
import pandas as pd
from datetime import datetime
# Simulating a de-normalized dataset
data = {
'user_id': [1, 2, 3],
'user_name': ['Alice', 'Bob', 'Charlie'],
'last_purchase_date': ['2023-01-15', '2023-02-20', '2023-03-10'],
'total_purchases': [5, 3, 8]
}
df = pd.DataFrame(data)
# Data quality check
def check_data_quality(df):
assert df['user_id'].is_unique, "Duplicate user IDs found"
assert df['total_purchases'].min() >= 0, "Negative purchase counts found"
# Update mechanism
def update_user_purchase(df, user_id, new_purchase_date, purchase_count):
user_index = df.index[df['user_id'] == user_id].tolist()[0]
df.at[user_index, 'last_purchase_date'] = new_purchase_date
df.at[user_index, 'total_purchases'] += purchase_count
# Perform updates
update_user_purchase(df, 2, '2023-04-01', 1)
check_data_quality(df)
print(df)
Advantages and Disadvantages of De-normalized Data
Advantages:
- Faster query performance
- Simplified data access
- Reduced need for joins
Disadvantages:
- Data redundancy
- Increased storage requirements
- Potential for data inconsistency
import pandas as pd
# Normalized data
users = pd.DataFrame({
'user_id': [1, 2],
'name': ['Alice', 'Bob']
})
orders = pd.DataFrame({
'order_id': [101, 102, 103],
'user_id': [1, 1, 2],
'product': ['Book', 'Pen', 'Notebook']
})
# De-normalized data
denormalized = pd.DataFrame({
'order_id': [101, 102, 103],
'user_id': [1, 1, 2],
'name': ['Alice', 'Alice', 'Bob'],
'product': ['Book', 'Pen', 'Notebook']
})
# Comparison of query performance
%time result_norm = pd.merge(orders, users, on='user_id')
%time result_denorm = denormalized[['order_id', 'name', 'product']]
print("Normalized data shape:", result_norm.shape)
print("De-normalized data shape:", result_denorm.shape)
Additional Resources
- "Designing Data-Intensive Applications" by Martin Kleppmann ArXiv: https://arxiv.org/abs/2005.05497
- "Database Systems: The Complete Book" by Hector Garcia-Molina, Jeffrey D. Ullman, and Jennifer Widom
- "Machine Learning Design Patterns" by Valliappa Lakshmanan, Sara Robinson, and Michael Munn ArXiv: https://arxiv.org/abs/2008.00104
These resources provide in-depth information on data modeling, database design, and machine learning practices related to data preparation and management.