16. Advanced Regression Analysis
16. Advanced Regression Analysis¶
Previous: ANOVA | Next: Generalized Linear Models
Overview¶
This chapter covers advanced topics in Multiple Regression Analysis. We will learn about checking regression assumptions, diagnostic plots, multicollinearity issues, and variable selection methods.
1. Multiple Regression Basics¶
1.1 Model and Estimation¶
import numpy as np
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor
# Generate example data
np.random.seed(42)
n = 200
# Independent variables
X1 = np.random.normal(50, 10, n) # Experience (months)
X2 = np.random.normal(70, 15, n) # Education score
X3 = np.random.normal(30, 5, n) # Age
# Dependent variable (salary): linear relationship + noise
Y = 30000 + 500*X1 + 200*X2 + 100*X3 + np.random.normal(0, 5000, n)
# Create DataFrame
df = pd.DataFrame({
'salary': Y,
'experience': X1,
'education': X2,
'age': X3
})
# Descriptive statistics
print("Data descriptive statistics:")
print(df.describe())
# Correlation matrix
print("\nCorrelation matrix:")
print(df.corr().round(3))
1.2 statsmodels OLS¶
# OLS regression
# Method 1: formula API (R style)
model_formula = smf.ols('salary ~ experience + education + age', data=df).fit()
# Method 2: matrix API
X = df[['experience', 'education', 'age']]
X = sm.add_constant(X) # Add intercept
y = df['salary']
model_matrix = sm.OLS(y, X).fit()
# Print results
print("Regression results:")
print(model_formula.summary())
# Extract key statistics
print("\nKey statistics:")
print(f"R-squared: {model_formula.rsquared:.4f}")
print(f"Adjusted R-squared: {model_formula.rsquared_adj:.4f}")
print(f"F-statistic: {model_formula.fvalue:.2f}")
print(f"F p-value: {model_formula.f_pvalue:.4e}")
print(f"AIC: {model_formula.aic:.2f}")
print(f"BIC: {model_formula.bic:.2f}")
1.3 Interpreting Coefficients¶
# Detailed coefficient analysis
print("Coefficient analysis:")
print("-" * 70)
print(f"{'Variable':<15} {'Coef':<12} {'Std Err':<12} {'t-value':<10} {'p-value':<12} {'95% CI'}")
print("-" * 70)
coef_table = pd.DataFrame({
'coef': model_formula.params,
'std_err': model_formula.bse,
't_value': model_formula.tvalues,
'p_value': model_formula.pvalues,
'ci_lower': model_formula.conf_int()[0],
'ci_upper': model_formula.conf_int()[1]
})
for idx, row in coef_table.iterrows():
ci_str = f"[{row['ci_lower']:.1f}, {row['ci_upper']:.1f}]"
sig = "***" if row['p_value'] < 0.001 else ("**" if row['p_value'] < 0.01 else ("*" if row['p_value'] < 0.05 else ""))
print(f"{idx:<15} {row['coef']:<12.2f} {row['std_err']:<12.2f} {row['t_value']:<10.2f} {row['p_value']:<10.4f}{sig:<2} {ci_str}")
# Standardized coefficients (Beta)
print("\nStandardized coefficients (Beta):")
df_standardized = (df - df.mean()) / df.std()
model_std = smf.ols('salary ~ experience + education + age', data=df_standardized).fit()
for var in ['experience', 'education', 'age']:
print(f" {var}: {model_std.params[var]:.4f}")
2. Checking Regression Assumptions¶
2.1 Assumption Overview¶
# Regression assumptions:
# 1. Linearity: Linear relationship between Y and X
# 2. Independence: Residuals are independent
# 3. Homoscedasticity: Constant variance of residuals
# 4. Normality: Residuals follow normal distribution
# Values for diagnostics
fitted = model_formula.fittedvalues
residuals = model_formula.resid
standardized_residuals = model_formula.get_influence().resid_studentized_internal
2.2 Residual Analysis¶
# Comprehensive diagnostic plots
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 1. Residuals vs Fitted (linearity, homoscedasticity)
axes[0, 0].scatter(fitted, residuals, alpha=0.5)
axes[0, 0].axhline(0, color='red', linestyle='--')
axes[0, 0].set_xlabel('Fitted values')
axes[0, 0].set_ylabel('Residuals')
axes[0, 0].set_title('Residuals vs Fitted (linearity, homoscedasticity)')
# Add Lowess curve
from statsmodels.nonparametric.smoothers_lowess import lowess
z = lowess(residuals, fitted, frac=0.3)
axes[0, 0].plot(z[:, 0], z[:, 1], 'g-', linewidth=2, label='Lowess')
axes[0, 0].legend()
# 2. Q-Q Plot (normality)
stats.probplot(residuals, dist="norm", plot=axes[0, 1])
axes[0, 1].set_title('Q-Q Plot (normality)')
# 3. Scale-Location Plot (homoscedasticity)
sqrt_std_residuals = np.sqrt(np.abs(standardized_residuals))
axes[1, 0].scatter(fitted, sqrt_std_residuals, alpha=0.5)
axes[1, 0].set_xlabel('Fitted values')
axes[1, 0].set_ylabel('√|Standardized residuals|')
axes[1, 0].set_title('Scale-Location (homoscedasticity)')
z = lowess(sqrt_std_residuals, fitted, frac=0.3)
axes[1, 0].plot(z[:, 0], z[:, 1], 'g-', linewidth=2)
# 4. Residual histogram (normality)
axes[1, 1].hist(residuals, bins=30, density=True, alpha=0.7, edgecolor='black')
xmin, xmax = axes[1, 1].get_xlim()
x = np.linspace(xmin, xmax, 100)
axes[1, 1].plot(x, stats.norm.pdf(x, residuals.mean(), residuals.std()),
'r-', linewidth=2, label='Normal distribution')
axes[1, 1].set_xlabel('Residuals')
axes[1, 1].set_ylabel('Density')
axes[1, 1].set_title('Residual histogram (normality)')
axes[1, 1].legend()
plt.tight_layout()
plt.show()
2.3 Normality Tests¶
# Residual normality tests
# Shapiro-Wilk test
stat_shapiro, p_shapiro = stats.shapiro(residuals)
print(f"Shapiro-Wilk test: W = {stat_shapiro:.4f}, p = {p_shapiro:.4f}")
# Kolmogorov-Smirnov test
stat_ks, p_ks = stats.kstest(residuals, 'norm', args=(residuals.mean(), residuals.std()))
print(f"Kolmogorov-Smirnov test: D = {stat_ks:.4f}, p = {p_ks:.4f}")
# Jarque-Bera test
from scipy.stats import jarque_bera
stat_jb, p_jb = jarque_bera(residuals)
print(f"Jarque-Bera test: JB = {stat_jb:.4f}, p = {p_jb:.4f}")
# Skewness and kurtosis
print(f"\nSkewness: {stats.skew(residuals):.4f} (close to 0 is symmetric)")
print(f"Kurtosis: {stats.kurtosis(residuals):.4f} (close to 0 is normal)")
2.4 Homoscedasticity Tests¶
# Breusch-Pagan test
from statsmodels.stats.diagnostic import het_breuschpagan
bp_stat, bp_pvalue, bp_fstat, bp_f_pvalue = het_breuschpagan(residuals, X)
print(f"Breusch-Pagan test:")
print(f" LM statistic: {bp_stat:.4f}")
print(f" LM p-value: {bp_pvalue:.4f}")
print(f" F statistic: {bp_fstat:.4f}")
print(f" F p-value: {bp_f_pvalue:.4f}")
# White test
from statsmodels.stats.diagnostic import het_white
white_stat, white_pvalue, white_fstat, white_f_pvalue = het_white(residuals, X)
print(f"\nWhite test:")
print(f" LM statistic: {white_stat:.4f}")
print(f" LM p-value: {white_pvalue:.4f}")
# Goldfeld-Quandt test
from statsmodels.stats.diagnostic import het_goldfeldquandt
gq_stat, gq_pvalue, gq_alt = het_goldfeldquandt(y, X, alternative='two-sided')
print(f"\nGoldfeld-Quandt test:")
print(f" F statistic: {gq_stat:.4f}")
print(f" p-value: {gq_pvalue:.4f}")
2.5 Independence Test (Autocorrelation)¶
# Durbin-Watson test
from statsmodels.stats.stattools import durbin_watson
dw_stat = durbin_watson(residuals)
print(f"Durbin-Watson test: DW = {dw_stat:.4f}")
print(" DW ≈ 2: no autocorrelation")
print(" DW < 2: positive autocorrelation")
print(" DW > 2: negative autocorrelation")
# Ljung-Box test
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_result = acorr_ljungbox(residuals, lags=[1, 5, 10], return_df=True)
print(f"\nLjung-Box test:")
print(lb_result)
3. Advanced Diagnostic Plots¶
3.1 Leverage and Influence¶
# Influence analysis
influence = model_formula.get_influence()
# Leverage (Hat values)
leverage = influence.hat_matrix_diag
# Cook's Distance
cooks_d = influence.cooks_distance[0]
# DFFITS
dffits = influence.dffits[0]
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 1. Residuals vs Leverage
axes[0, 0].scatter(leverage, standardized_residuals, alpha=0.5)
axes[0, 0].axhline(0, color='red', linestyle='--')
axes[0, 0].set_xlabel('Leverage')
axes[0, 0].set_ylabel('Standardized residuals')
axes[0, 0].set_title('Residuals vs Leverage')
# Cook's D contours
n = len(y)
p = len(model_formula.params)
leverage_range = np.linspace(0.001, max(leverage), 100)
for cook_threshold in [0.5, 1]:
cook_line = np.sqrt(cook_threshold * p * (1 - leverage_range) / leverage_range)
axes[0, 0].plot(leverage_range, cook_line, 'g--', alpha=0.5)
axes[0, 0].plot(leverage_range, -cook_line, 'g--', alpha=0.5)
# 2. Cook's Distance
axes[0, 1].stem(range(len(cooks_d)), cooks_d, markerfmt='o', basefmt=' ')
axes[0, 1].axhline(4/n, color='red', linestyle='--', label=f"4/n = {4/n:.4f}")
axes[0, 1].set_xlabel('Observation index')
axes[0, 1].set_ylabel("Cook's Distance")
axes[0, 1].set_title("Cook's Distance")
axes[0, 1].legend()
# Influential observations
influential = np.where(cooks_d > 4/n)[0]
print(f"Influential observations (Cook's D > 4/n): {influential}")
# 3. Leverage distribution
axes[1, 0].hist(leverage, bins=30, density=True, alpha=0.7, edgecolor='black')
axes[1, 0].axvline(2*p/n, color='red', linestyle='--', label=f'2p/n = {2*p/n:.4f}')
axes[1, 0].set_xlabel('Leverage')
axes[1, 0].set_ylabel('Density')
axes[1, 0].set_title('Leverage distribution')
axes[1, 0].legend()
high_leverage = np.where(leverage > 2*p/n)[0]
print(f"High leverage observations: {len(high_leverage)} cases")
# 4. DFFITS
axes[1, 1].stem(range(len(dffits)), dffits, markerfmt='o', basefmt=' ')
threshold = 2 * np.sqrt(p/n)
axes[1, 1].axhline(threshold, color='red', linestyle='--')
axes[1, 1].axhline(-threshold, color='red', linestyle='--', label=f'±2√(p/n) = ±{threshold:.4f}')
axes[1, 1].set_xlabel('Observation index')
axes[1, 1].set_ylabel('DFFITS')
axes[1, 1].set_title('DFFITS')
axes[1, 1].legend()
plt.tight_layout()
plt.show()
3.2 Partial Regression Plots¶
# Partial Regression Plots (Added Variable Plots)
fig = sm.graphics.plot_partregress_grid(model_formula)
fig.suptitle('Partial Regression Plots', y=1.02)
plt.tight_layout()
plt.show()
# Individual partial regression plots
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for i, var in enumerate(['experience', 'education', 'age']):
sm.graphics.plot_partregress(
'salary', var, ['experience', 'education', 'age'],
data=df, obs_labels=False, ax=axes[i]
)
axes[i].set_title(f'salary ~ {var} | other variables')
plt.tight_layout()
plt.show()
3.3 Component-Component plus Residual (CCPR) Plots¶
# Component-Component plus Residual Plot
fig = sm.graphics.plot_ccpr_grid(model_formula)
fig.suptitle('Component-Component plus Residual Plots (CCPR)', y=1.02)
plt.tight_layout()
plt.show()
4. Multicollinearity¶
4.1 Diagnosing Multicollinearity¶
# Generate data with multicollinearity
np.random.seed(42)
n = 200
X1 = np.random.normal(50, 10, n)
X2 = X1 + np.random.normal(0, 3, n) # High correlation with X1
X3 = np.random.normal(30, 5, n)
Y = 100 + 10*X1 + 5*X2 + 20*X3 + np.random.normal(0, 50, n)
df_multi = pd.DataFrame({
'Y': Y, 'X1': X1, 'X2': X2, 'X3': X3
})
# Correlation matrix
print("Correlation matrix:")
print(df_multi.corr().round(3))
# Regression
model_multi = smf.ols('Y ~ X1 + X2 + X3', data=df_multi).fit()
print("\nModel with multicollinearity:")
print(model_multi.summary().tables[1])
4.2 VIF (Variance Inflation Factor)¶
# Calculate VIF
def calculate_vif(df, features):
"""Calculate VIF"""
X = df[features]
X = sm.add_constant(X)
vif_data = pd.DataFrame()
vif_data['feature'] = features
vif_data['VIF'] = [variance_inflation_factor(X.values, i+1)
for i in range(len(features))]
return vif_data
features = ['X1', 'X2', 'X3']
vif_result = calculate_vif(df_multi, features)
print("VIF (Variance Inflation Factor):")
print(vif_result)
print("\nInterpretation:")
print(" VIF = 1: no multicollinearity")
print(" VIF 1-5: low multicollinearity")
print(" VIF 5-10: moderate multicollinearity")
print(" VIF > 10: high multicollinearity (problem)")
# VIF of original data
features_orig = ['experience', 'education', 'age']
vif_orig = calculate_vif(df, features_orig)
print("\nOriginal data VIF:")
print(vif_orig)
4.3 Addressing Multicollinearity¶
# Solution 1: Remove variable
model_reduced = smf.ols('Y ~ X1 + X3', data=df_multi).fit() # Remove X2
print("Model after variable removal (X2 removed):")
print(model_reduced.summary().tables[1])
# Solution 2: Principal Component Regression (PCR)
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# Standardization
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df_multi[['X1', 'X2', 'X3']])
# PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print("\nPCA results:")
print(f"Explained variance ratio: {pca.explained_variance_ratio_}")
print(f"Cumulative explained variance: {pca.explained_variance_ratio_.cumsum()}")
# PCR model
df_pca = pd.DataFrame(X_pca, columns=['PC1', 'PC2'])
df_pca['Y'] = df_multi['Y']
model_pcr = smf.ols('Y ~ PC1 + PC2', data=df_pca).fit()
print("\nPCR model:")
print(model_pcr.summary().tables[1])
# Solution 3: Ridge regression
from sklearn.linear_model import Ridge
X = df_multi[['X1', 'X2', 'X3']]
y = df_multi['Y']
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
print("\nRidge regression coefficients:")
for name, coef in zip(['X1', 'X2', 'X3'], ridge.coef_):
print(f" {name}: {coef:.4f}")
5. Variable Selection¶
5.1 Forward Selection, Backward Elimination, Stepwise Selection¶
# Generate data with more variables
np.random.seed(42)
n = 200
# Relevant variables
X1 = np.random.normal(50, 10, n) # Important
X2 = np.random.normal(30, 5, n) # Important
X3 = np.random.normal(100, 20, n) # Important
# Irrelevant variables
X4 = np.random.normal(0, 1, n)
X5 = np.random.normal(0, 1, n)
X6 = np.random.normal(0, 1, n)
Y = 10 + 5*X1 + 3*X2 + 2*X3 + np.random.normal(0, 30, n)
df_select = pd.DataFrame({
'Y': Y, 'X1': X1, 'X2': X2, 'X3': X3, 'X4': X4, 'X5': X5, 'X6': X6
})
# Full model
model_full = smf.ols('Y ~ X1 + X2 + X3 + X4 + X5 + X6', data=df_select).fit()
print("Full model:")
print(model_full.summary().tables[1])
def forward_selection(data, response, features, significance_level=0.05):
"""Forward selection"""
selected = []
remaining = features.copy()
while remaining:
best_pvalue = 1
best_feature = None
for feature in remaining:
formula = f'{response} ~ ' + ' + '.join(selected + [feature])
model = smf.ols(formula, data=data).fit()
pvalue = model.pvalues[feature]
if pvalue < best_pvalue:
best_pvalue = pvalue
best_feature = feature
if best_pvalue < significance_level:
selected.append(best_feature)
remaining.remove(best_feature)
print(f"Added: {best_feature} (p = {best_pvalue:.4f})")
else:
break
return selected
def backward_elimination(data, response, features, significance_level=0.05):
"""Backward elimination"""
selected = features.copy()
while selected:
formula = f'{response} ~ ' + ' + '.join(selected)
model = smf.ols(formula, data=data).fit()
# Find least significant variable
max_pvalue = 0
worst_feature = None
for feature in selected:
pvalue = model.pvalues[feature]
if pvalue > max_pvalue:
max_pvalue = pvalue
worst_feature = feature
if max_pvalue > significance_level:
selected.remove(worst_feature)
print(f"Removed: {worst_feature} (p = {max_pvalue:.4f})")
else:
break
return selected
# Forward selection
print("Forward selection:")
print("-" * 40)
features = ['X1', 'X2', 'X3', 'X4', 'X5', 'X6']
selected_forward = forward_selection(df_select, 'Y', features)
print(f"Selected variables: {selected_forward}")
# Backward elimination
print("\nBackward elimination:")
print("-" * 40)
selected_backward = backward_elimination(df_select, 'Y', features)
print(f"Selected variables: {selected_backward}")
5.2 Information Criteria (AIC, BIC)¶
# Compare all possible models (for small variable sets)
from itertools import combinations
def all_possible_models(data, response, features):
"""Compare all possible models"""
results = []
for k in range(1, len(features) + 1):
for subset in combinations(features, k):
formula = f'{response} ~ ' + ' + '.join(subset)
model = smf.ols(formula, data=data).fit()
results.append({
'features': subset,
'n_features': k,
'R2': model.rsquared,
'R2_adj': model.rsquared_adj,
'AIC': model.aic,
'BIC': model.bic
})
return pd.DataFrame(results)
all_models = all_possible_models(df_select, 'Y', features)
# Best model by each criterion
print("Best model by each criterion:")
print("-" * 60)
best_r2_adj = all_models.loc[all_models['R2_adj'].idxmax()]
best_aic = all_models.loc[all_models['AIC'].idxmin()]
best_bic = all_models.loc[all_models['BIC'].idxmin()]
print(f"Best Adjusted R²: {best_r2_adj['features']}")
print(f" R² = {best_r2_adj['R2']:.4f}, Adj R² = {best_r2_adj['R2_adj']:.4f}")
print(f"\nMinimum AIC: {best_aic['features']}")
print(f" AIC = {best_aic['AIC']:.2f}")
print(f"\nMinimum BIC: {best_bic['features']}")
print(f" BIC = {best_bic['BIC']:.2f}")
# Visualization
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# Best model by number of variables
for metric, ax, title in zip(['R2_adj', 'AIC', 'BIC'], axes, ['Adjusted R²', 'AIC', 'BIC']):
for k in range(1, 7):
subset_models = all_models[all_models['n_features'] == k]
if metric == 'R2_adj':
best_val = subset_models[metric].max()
else:
best_val = subset_models[metric].min()
ax.scatter([k], [best_val], s=100)
ax.set_xlabel('Number of variables')
ax.set_ylabel(title)
ax.set_title(f'Number of variables vs {title}')
ax.set_xticks(range(1, 7))
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
5.3 Variable Selection Using Cross-Validation¶
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
# Find optimal model using cross-validation
X_all = df_select[features]
y = df_select['Y']
print("Cross-validation results (5-fold):")
print("-" * 50)
cv_results = []
for k in range(1, len(features) + 1):
best_cv_score = -np.inf
best_subset = None
for subset in combinations(features, k):
X_subset = df_select[list(subset)]
model = LinearRegression()
scores = cross_val_score(model, X_subset, y, cv=5, scoring='r2')
mean_score = scores.mean()
if mean_score > best_cv_score:
best_cv_score = mean_score
best_subset = subset
cv_results.append({
'n_features': k,
'best_features': best_subset,
'cv_r2': best_cv_score
})
print(f"k={k}: {best_subset}, CV R² = {best_cv_score:.4f}")
# Best model
best_cv = max(cv_results, key=lambda x: x['cv_r2'])
print(f"\nBest model: {best_cv['best_features']}")
print(f"CV R² = {best_cv['cv_r2']:.4f}")
6. Practical Example: House Price Prediction¶
6.1 Data Preparation¶
# Simulate house price data
np.random.seed(42)
n = 500
# Generate features
size = np.random.normal(1500, 400, n) # Square feet
bedrooms = np.random.poisson(3, n) + 1
bathrooms = bedrooms * 0.5 + np.random.normal(0, 0.5, n)
bathrooms = np.clip(bathrooms, 1, 5)
age = np.random.exponential(20, n)
distance = np.random.uniform(1, 30, n) # Distance to downtown
# Price (including nonlinear relationships)
price = (100000 + 100*size + 20000*bedrooms + 15000*bathrooms
- 2000*age - 1000*distance
+ 0.02*size*bedrooms # Interaction
+ np.random.normal(0, 30000, n))
price = np.maximum(price, 50000) # Minimum price
df_house = pd.DataFrame({
'price': price,
'size': size,
'bedrooms': bedrooms,
'bathrooms': bathrooms,
'age': age,
'distance': distance
})
print("House data descriptive statistics:")
print(df_house.describe())
6.2 Exploratory Analysis¶
# Correlation analysis
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
for i, var in enumerate(['size', 'bedrooms', 'bathrooms', 'age', 'distance']):
ax = axes[i // 3, i % 3]
ax.scatter(df_house[var], df_house['price'], alpha=0.3)
ax.set_xlabel(var)
ax.set_ylabel('price')
# Regression line
z = np.polyfit(df_house[var], df_house['price'], 1)
p = np.poly1d(z)
ax.plot(sorted(df_house[var]), p(sorted(df_house[var])), 'r-')
# Correlation coefficient
corr = df_house['price'].corr(df_house[var])
ax.set_title(f'r = {corr:.3f}')
# Correlation matrix heatmap
axes[1, 2].set_visible(False)
fig.delaxes(axes[1, 2])
plt.tight_layout()
plt.show()
# Correlation matrix heatmap
plt.figure(figsize=(8, 6))
sns.heatmap(df_house.corr(), annot=True, cmap='RdBu_r', center=0,
fmt='.2f', square=True)
plt.title('Correlation matrix')
plt.tight_layout()
plt.show()
6.3 Model Building and Diagnostics¶
# Build model
model_house = smf.ols('price ~ size + bedrooms + bathrooms + age + distance',
data=df_house).fit()
print("House price regression model:")
print(model_house.summary())
# Diagnostics
print("\nModel diagnostics:")
print("-" * 40)
# VIF
features_house = ['size', 'bedrooms', 'bathrooms', 'age', 'distance']
vif_house = calculate_vif(df_house, features_house)
print("\nVIF:")
print(vif_house)
# Normality test
stat, p = stats.shapiro(model_house.resid[:5000]) # Shapiro limited to 5000
print(f"\nResidual normality (Shapiro): p = {p:.4f}")
# Homoscedasticity test
X_house = sm.add_constant(df_house[features_house])
bp_stat, bp_p, _, _ = het_breuschpagan(model_house.resid, X_house)
print(f"Homoscedasticity (Breusch-Pagan): p = {bp_p:.4f}")
6.4 Model Improvement¶
# Add interaction term
model_improved = smf.ols('price ~ size * bedrooms + bathrooms + age + distance',
data=df_house).fit()
print("Improved model (with interaction):")
print(model_improved.summary().tables[1])
# Compare AIC/BIC
print(f"\nModel comparison:")
print(f"Basic model: AIC = {model_house.aic:.2f}, BIC = {model_house.bic:.2f}")
print(f"Improved model: AIC = {model_improved.aic:.2f}, BIC = {model_improved.bic:.2f}")
# ANOVA model comparison
print("\nANOVA (model comparison):")
print(sm.stats.anova_lm(model_house, model_improved))
Practice Problems¶
Problem 1: Regression Diagnostics¶
Perform regression analysis with the following data and check assumptions.
np.random.seed(42)
X = np.random.normal(0, 1, 100)
Y = 2 + 3*X + np.random.normal(0, 1, 100) * np.abs(X) # Heteroscedasticity
Problem 2: Multicollinearity¶
Diagnose and address multicollinearity in the data below.
np.random.seed(42)
X1 = np.random.normal(0, 1, 100)
X2 = 0.9*X1 + np.random.normal(0, 0.1, 100)
X3 = np.random.normal(0, 1, 100)
Y = 1 + 2*X1 + 3*X3 + np.random.normal(0, 1, 100)
Problem 3: Variable Selection¶
Select optimal variable combination from 5 independent variables. - Apply forward selection, backward elimination, and AIC criteria - Compare results
Summary¶
| Diagnostic Item | Test/Method | Python Function |
|---|---|---|
| Normality | Shapiro-Wilk | stats.shapiro() |
| Homoscedasticity | Breusch-Pagan | het_breuschpagan() |
| Independence | Durbin-Watson | durbin_watson() |
| Influence | Cook's D | get_influence().cooks_distance |
| Multicollinearity | VIF | variance_inflation_factor() |
| Variable selection | AIC/BIC | model.aic, model.bic |