1. Learning Objectives
By the end of this lesson, you will be able to:
-
Understand the curse of dimensionality and its impact on financial models.
-
Apply PCA, Kernel PCA, and t-SNE for dimensionality reduction.
-
Implement feature selection methods (filter, wrapper, embedded) for financial datasets.
-
Use mutual information and correlation analysis for feature selection.
-
Apply Lasso regression for automatic feature selection.
-
Implement Recursive Feature Elimination (RFE) for model-based feature selection.
-
Understand the tradeoff between interpretability and performance in feature selection.
2. The Curse of Dimensionality – Why Feature Reduction Matters
In high-dimensional spaces, data becomes sparse, and traditional statistical methods fail.
2.1 Key Issues:
-
Data Sparsity:Â The number of data points needed grows exponentially with dimensionality.
-
Distance Concentration:Â In high dimensions, all pairwise distances become similar, making nearest-neighbour methods ineffective.
-
Overfitting:Â With many features, models can fit the noise rather than the signal.
-
Computational Cost:Â Training time increases with the number of features.
Financial Implication:Â Financial datasets often have thousands of features (e.g., all S&P 500 returns, technical indicators). Dimensionality reduction is mandatory.
2.2 The Practical Rule
If you have N observations, you should have at most N/10 features. With 10 years of daily data (N ≈ 2500), you should use at most 250 features.
3. Linear Dimensionality Reduction – PCA Revisited
PCA finds the directions of maximum variance in the data.
3.1 Explained Variance Ratio
The proportion of variance explained by the first k components:EV_k = (Σ_{i=1}^{k} λ_i) / (Σ_{i=1}^{p} λ_i)
Rule of Thumb:Â Keep enough components to explain 80-95% of the variance.
def pca_feature_reduction(X, variance_threshold=0.95):
"""
Reduce dimensionality using PCA with variance threshold.
"""
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# Standardise
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# PCA
pca = PCA(n_components=variance_threshold) # n_components can be a float for variance threshold
X_pca = pca.fit_transform(X_scaled)
print(f"Original features: {X.shape[1]}")
print(f"Reduced features: {X_pca.shape[1]}")
print(f"Explained variance: {pca.explained_variance_ratio_.sum():.4f}")
return X_pca, pca, scaler
3.2 Factor Model Construction with PCA
def factor_model_pca(returns, n_factors=5):
"""
Build a factor model using PCA.
"""
# PCA
pca = PCA(n_components=n_factors)
factors = pca.fit_transform(returns)
# Loadings
loadings = pca.components_.T
# Explained variance
evr = pca.explained_variance_ratio_
# Create results
factor_df = pd.DataFrame(
factors,
index=returns.index,
columns=[f'F_{i+1}' for i in range(n_factors)]
)
loading_df = pd.DataFrame(
loadings,
index=returns.columns,
columns=[f'Loading_{i+1}' for i in range(n_factors)]
)
print(f"Total explained variance: {evr.sum():.4f}")
return factor_df, loading_df, pca
4. Non-Linear Dimensionality Reduction
4.1 t-SNE (t-Distributed Stochastic Neighbor Embedding)
t-SNE is a non-linear technique that preserves local structure. It is excellent for visualisation but cannot be used for live predictions (transductive).
Mathematical Formulation:
-
Compute pairwise similarities in high-dimensional space using a Gaussian kernel:
p_{j|i} = exp(-||x_i - x_j||² / (2σ_i²)) / Σ_{k≠i} exp(-||x_i - x_k||² / (2σ_i²)) -
Compute pairwise similarities in low-dimensional space using a Student-t kernel:
q_{ij} = (1 + ||y_i - y_j||²)^{-1} / Σ_{k≠l} (1 + ||y_k - y_l||²)^{-1} -
Minimise the KL divergence betweenÂ
p andÂq.
from sklearn.manifold import TSNE
def tsne_visualisation(X, n_components=2, perplexity=30, random_state=42):
"""
Visualise high-dimensional data using t-SNE.
"""
# Standardise
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# t-SNE
tsne = TSNE(
n_components=n_components,
perplexity=perplexity,
learning_rate='auto',
init='pca',
random_state=random_state
)
X_tsne = tsne.fit_transform(X_scaled)
return X_tsne, tsne
4.2 UMAP (Uniform Manifold Approximation and Projection)
UMAP is faster than t-SNE and preserves global structure better. It is also more stable and can be used for inductive learning (with a trained transformer).
import umap
def umap_visualisation(X, n_components=2, n_neighbors=15, min_dist=0.1):
"""
Visualise high-dimensional data using UMAP.
"""
# Standardise
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# UMAP
reducer = umap.UMAP(
n_components=n_components,
n_neighbors=n_neighbors,
min_dist=min_dist,
random_state=42
)
X_umap = reducer.fit_transform(X_scaled)
return X_umap, reducer, scaler
4.3 Financial Application – Market Regime Visualisation
def regime_visualisation(returns, regime_labels):
"""
Visualise market regimes using t-SNE or UMAP.
"""
# Extract rolling features
features = pd.DataFrame(index=returns.index)
features['Volatility'] = returns.rolling(60).std()
features['Skewness'] = returns.rolling(60).skew()
features['Kurtosis'] = returns.rolling(60).kurt()
features['Mean_Return'] = returns.rolling(60).mean()
features = features.dropna()
# Align with regime labels
regime_labels_aligned = regime_labels[regime_labels.index.isin(features.index)]
# UMAP
X_umap, reducer, scaler = umap_visualisation(features)
# Plot
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 8))
scatter = plt.scatter(
X_umap[:, 0], X_umap[:, 1],
c=regime_labels_aligned.values,
cmap='viridis',
alpha=0.6
)
plt.colorbar(scatter, label='Regime')
plt.title('Market Regime Visualisation with UMAP')
plt.xlabel('UMAP Component 1')
plt.ylabel('UMAP Component 2')
plt.grid(True, alpha=0.3)
plt.show()
return X_umap
5. Feature Selection Methods
5.1 Filter Methods (Statistical)
Select features based on statistical measures independent of the model.
Correlation-Based Selection:
def correlation_feature_selection(X, y, threshold=0.3):
"""
Select features with high correlation to the target.
"""
correlations = pd.DataFrame({
'feature': X.columns,
'correlation': [np.corrcoef(X[col], y)[0, 1] for col in X.columns]
})
selected = correlations[np.abs(correlations['correlation']) > threshold]
return selected.sort_values('correlation', ascending=False)
Mutual Information Selection:
from sklearn.feature_selection import mutual_info_classif, mutual_info_regression
def mutual_information_selection(X, y, model_type='classification', k=20):
"""
Select top k features using mutual information.
"""
if model_type == 'classification':
mi = mutual_info_classif(X, y)
else:
mi = mutual_info_regression(X, y)
mi_df = pd.DataFrame({
'feature': X.columns,
'mi_score': mi
}).sort_values('mi_score', ascending=False)
selected_features = mi_df.head(k)['feature'].tolist()
return selected_features, mi_df
5.2 Wrapper Methods (Model-Based)
Recursive Feature Elimination (RFE):
RFE recursively removes the least important features based on the model’s coefficients or feature importance.
from sklearn.feature_selection import RFE, RFECV
def recursive_feature_elimination(model, X, y, n_features_to_select=20):
"""
Select features using Recursive Feature Elimination.
"""
rfe = RFE(
estimator=model,
n_features_to_select=n_features_to_select,
step=1,
verbose=1
)
rfe.fit(X, y)
# Get selected features
selected_indices = rfe.get_support(indices=True)
selected_features = X.columns[selected_indices].tolist()
# Feature ranking
ranking_df = pd.DataFrame({
'feature': X.columns,
'ranking': rfe.ranking_,
'selected': rfe.support_
}).sort_values('ranking')
return selected_features, ranking_df, rfe
5.3 Embedded Methods (Regularisation)
Lasso Regression for Feature Selection:
Lasso has L1 regularisation, which drives coefficients to exactly zero.
from sklearn.linear_model import LassoCV
def lasso_feature_selection(X, y, cv=5):
"""
Select features using Lasso regression with cross-validation.
"""
# Lasso with cross-validation
lasso = LassoCV(
cv=cv,
random_state=42,
n_alphas=100,
max_iter=10000
)
lasso.fit(X, y)
# Get coefficients
coef_df = pd.DataFrame({
'feature': X.columns,
'coefficient': lasso.coef_
})
# Features with non-zero coefficients
selected_features = coef_df[np.abs(coef_df['coefficient']) > 0]['feature'].tolist()
print(f"Selected {len(selected_features)} features out of {X.shape[1]}")
return selected_features, coef_df, lasso
5.4 Tree-Based Feature Importance
Random Forest and XGBoost provide built-in feature importance.
def tree_feature_importance(model, X, feature_names, top_k=20):
"""
Extract feature importance from tree-based model.
"""
importance_df = pd.DataFrame({
'feature': feature_names,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
selected_features = importance_df.head(top_k)['feature'].tolist()
return selected_features, importance_df
6. Feature Engineering – Creating Predictive Features
6.1 Interaction Features
Financial relationships are often non-linear and interactive.
def create_interaction_features(X, top_k=5):
"""
Create interaction features for top features.
"""
# Select top features by variance
variances = X.var()
top_features = variances.nlargest(top_k).index.tolist()
# Create interactions
for i, f1 in enumerate(top_features):
for f2 in top_features[i+1:]:
X[f'{f1}_x_{f2}'] = X[f1] * X[f2]
return X
6.2 Polynomial Features
from sklearn.preprocessing import PolynomialFeatures
def create_polynomial_features(X, degree=2, interaction_only=True):
"""
Create polynomial features.
"""
poly = PolynomialFeatures(
degree=degree,
interaction_only=interaction_only,
include_bias=False
)
X_poly = poly.fit_transform(X)
# Get feature names
feature_names = poly.get_feature_names_out(X.columns)
return X_poly, feature_names, poly
6.3 Lagged Features for Time Series
def create_lagged_features(X, lags=[1, 2, 3, 5, 10]):
"""
Create lagged features for time series.
"""
X_lagged = X.copy()
for lag in lags:
for col in X.columns:
X_lagged[f'{col}_lag_{lag}'] = X[col].shift(lag)
return X_lagged
7. Summary for the AI Practitioner
-
Curse of Dimensionality:Â More features is not better. Use feature reduction to avoid overfitting.
-
PCAÂ is the workhorse for linear dimensionality reduction. Use it to extract latent factors from returns.
-
t-SNE and UMAPÂ are for visualisation only. They cannot be used for live predictions.
-
Mutual information captures non-linear dependencies. Use it for feature selection.
-
Lasso performs automatic feature selection through L1 regularisation.
-
RFEÂ selects features based on model importance. Works with any model that provides feature importance or coefficients.
-
Tree-based importance is fast and interpretable. Use it with Random Forest or XGBoost.
-
Feature engineering (interactions, polynomials, lags) can significantly improve model performance.
Â