What is R-Squared?
R-squared (R²), also called the coefficient of determination, measures the proportion of variance in the dependent variable that is explained by the independent variables in a regression model.
"R-squared tells you how well your model fits the data—how much of the story your predictors tell."
The Intuition
Total Variation in Y = Explained Variation + Unexplained Variation 100% = R² × 100% + (1 - R²) × 100% R² = 0.85 means: 85% of the variation in Y is explained by your model 15% is due to other factors or random noise
Part 1: Mathematical Foundation
1.1 The Formula
R² = 1 - (SS_res / SS_tot) Where: - SS_res = Sum of Squared Residuals (unexplained variation) - SS_tot = Total Sum of Squares (total variation) SS_res = Σ(y_i - ŷ_i)² SS_tot = Σ(y_i - ȳ)² Therefore: R² = 1 - [Σ(y_i - ŷ_i)² / Σ(y_i - ȳ)²]
1.2 Visualizing R-Squared
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score # Create demonstration data np.random.seed(42) n = 100 # Perfect relationship (R² = 1) x_perfect = np.linspace(0, 10, n) y_perfect = 2 * x_perfect + 1 # Moderate relationship (R² ≈ 0.7) x_moderate = np.linspace(0, 10, n) y_moderate = 2 * x_moderate + 1 + np.random.normal(0, 2, n) # Weak relationship (R² ≈ 0.3) x_weak = np.linspace(0, 10, n) y_weak = 2 * x_weak + 1 + np.random.normal(0, 5, n) # No relationship (R² ≈ 0) x_none = np.linspace(0, 10, n) y_none = np.random.normal(10, 3, n) # Calculate R² for each def calculate_r2(x, y): model = LinearRegression() model.fit(x.reshape(-1, 1), y) y_pred = model.predict(x.reshape(-1, 1)) return r2_score(y, y_pred) r2_perfect = calculate_r2(x_perfect, y_perfect) r2_moderate = calculate_r2(x_moderate, y_moderate) r2_weak = calculate_r2(x_weak, y_weak) r2_none = calculate_r2(x_none, y_none) # Visualize fig, axes = plt.subplots(2, 2, figsize=(12, 10)) datasets = [ (x_perfect, y_perfect, f'Perfect Relationship\nR² = {r2_perfect:.3f}'), (x_moderate, y_moderate, f'Moderate Relationship\nR² = {r2_moderate:.3f}'), (x_weak, y_weak, f'Weak Relationship\nR² = {r2_weak:.3f}'), (x_none, y_none, f'No Relationship\nR² = {r2_none:.3f}') ] for ax, (x, y, title) in zip(axes.flat, datasets): # Fit model model = LinearRegression() model.fit(x.reshape(-1, 1), y) y_pred = model.predict(x.reshape(-1, 1)) # Plot ax.scatter(x, y, alpha=0.6, label='Data') ax.plot(x, y_pred, 'r-', linewidth=2, label='Regression Line') # Add total variation arrows (to mean) mean_y = np.mean(y) ax.axhline(mean_y, color='gray', linestyle='--', alpha=0.5, label=f'Mean: {mean_y:.2f}') # Annotate ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_title(title) ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() 1.3 The Components of R-Squared
def decompose_r2(x, y): """ Decompose R² into its components """ # Fit model model = LinearRegression() model.fit(x.reshape(-1, 1), y) y_pred = model.predict(x.reshape(-1, 1)) # Calculate components y_mean = np.mean(y) # Total Sum of Squares (total variation) ss_tot = np.sum((y - y_mean) ** 2) # Residual Sum of Squares (unexplained variation) ss_res = np.sum((y - y_pred) ** 2) # Explained Sum of Squares (explained variation) ss_exp = np.sum((y_pred - y_mean) ** 2) # R² r2 = 1 - (ss_res / ss_tot) # Create decomposition DataFrame decomposition = pd.DataFrame({ 'Component': ['Total Variation (SS_tot)', 'Explained Variation (SS_exp)', 'Unexplained Variation (SS_res)'], 'Sum of Squares': [ss_tot, ss_exp, ss_res], 'Percentage': [100, (ss_exp/ss_tot)*100, (ss_res/ss_tot)*100] }) print("=== R-SQUARED DECOMPOSITION ===\n") print(decomposition.to_string()) print(f"\nR² = {r2:.4f} = {ss_exp:.2f} / {ss_tot:.2f}") print(f"Interpretation: {r2*100:.1f}% of variation explained") return decomposition # Example x_sample = np.linspace(0, 10, 50) y_sample = 2 * x_sample + 1 + np.random.normal(0, 3, 50) decomp = decompose_r2(x_sample, y_sample) Part 2: R-Squared in Context
2.1 Range and Interpretation
def interpret_r2(r2, context="general"): """ Provide context-specific interpretation of R² """ interpretations = { "general": [ (0.9, 1.0, "Excellent fit - almost all variation explained"), (0.7, 0.9, "Very good fit - strong explanatory power"), (0.5, 0.7, "Moderate fit - reasonable explanatory power"), (0.3, 0.5, "Weak fit - limited explanatory power"), (0.0, 0.3, "Poor fit - little explanatory power") ], "social_sciences": [ (0.3, 1.0, "Good fit (human behavior is hard to predict)"), (0.1, 0.3, "Moderate fit"), (0.0, 0.1, "Weak fit") ], "physical_sciences": [ (0.95, 1.0, "Expected fit (controlled experiments)"), (0.8, 0.95, "Acceptable fit"), (0.0, 0.8, "Poor fit - missing important factors") ], "finance": [ (0.3, 1.0, "Good fit (markets are noisy)"), (0.1, 0.3, "Moderate fit"), (0.0, 0.1, "Typical for noisy financial data") ] } context_interpretations = interpretations.get(context, interpretations["general"]) print(f"R² = {r2:.4f}") print(f"\nInterpretation for {context.replace('_', ' ').title()}:") for lower, upper, desc in context_interpretations: if lower <= r2 <= upper: print(f" → {desc}") break # Additional nuance if r2 < 0: print("\n⚠ Warning: R² < 0 indicates model performs worse than using the mean") elif r2 > 1: print("\n⚠ Warning: R² > 1 is impossible - check your calculations") # Examples interpret_r2(0.92, "physical_sciences") interpret_r2(0.35, "social_sciences") interpret_r2(0.12, "finance") 2.2 R-Squared for Different Model Types
from sklearn.ensemble import RandomForestRegressor from sklearn.svm import SVR from sklearn.neighbors import KNeighborsRegressor def compare_r2_across_models(X, y, test_size=0.3): """ Compare R² across different model types """ from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=test_size, random_state=42 ) models = { 'Linear Regression': LinearRegression(), 'Random Forest': RandomForestRegressor(n_estimators=100, random_state=42), 'SVM': SVR(), 'KNN': KNeighborsRegressor(n_neighbors=5) } results = [] for name, model in models.items(): model.fit(X_train, y_train) y_pred = model.predict(X_test) r2 = r2_score(y_test, y_pred) results.append({ 'Model': name, 'R² (Test)': r2, 'Interpretation': 'Good' if r2 > 0.7 else 'Moderate' if r2 > 0.5 else 'Poor' }) results_df = pd.DataFrame(results).sort_values('R² (Test)', ascending=False) print("=== R² COMPARISON ACROSS MODELS ===\n") print(results_df.to_string()) return results_df Part 3: Adjusted R-Squared
3.1 Why Adjusted R-Squared?
Problem: Adding more predictors always increases R², even if they're useless.
Solution: Adjusted R² penalizes for unnecessary predictors.
3.2 The Formula
Adjusted R² = 1 - [(1 - R²) × (n - 1) / (n - k - 1)] Where: - n = number of observations - k = number of predictors
3.3 Demonstration
def demonstrate_adjusted_r2(n_samples=100): """ Show how adjusted R² penalizes useless predictors """ np.random.seed(42) # Generate true relationship X_true = np.random.randn(n_samples, 2) y = 3 * X_true[:, 0] + 2 * X_true[:, 1] + np.random.randn(n_samples) * 0.5 results = [] # Add useless predictors one by one for n_useless in range(0, 20): # Create features: 2 true + n_useless random noise X_useless = np.random.randn(n_samples, n_useless) X = np.hstack([X_true, X_useless]) # Fit model model = LinearRegression() model.fit(X, y) y_pred = model.predict(X) # Calculate R² and Adjusted R² n = len(y) k = X.shape[1] r2 = r2_score(y, y_pred) adj_r2 = 1 - (1 - r2) * (n - 1) / (n - k - 1) results.append({ 'Predictors': k, 'Useless': n_useless, 'R²': r2, 'Adjusted R²': adj_r2, 'Difference': r2 - adj_r2 }) results_df = pd.DataFrame(results) # Plot fig, ax = plt.subplots(figsize=(10, 6)) ax.plot(results_df['Predictors'], results_df['R²'], 'b-', linewidth=2, label='R² (increases with useless predictors)') ax.plot(results_df['Predictors'], results_df['Adjusted R²'], 'r-', linewidth=2, label='Adjusted R² (penalizes useless predictors)') ax.axvline(x=2, color='gray', linestyle='--', label='True predictors (k=2)') ax.set_xlabel('Number of Predictors (including useless)') ax.set_ylabel('R² / Adjusted R²') ax.set_title('R² vs Adjusted R²: Adding Useless Predictors') ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() print("\n=== EFFECT OF USELESS PREDICTORS ===\n") print(results_df[['Predictors', 'Useless', 'R²', 'Adjusted R²']].tail(10).to_string()) return results_df results_df = demonstrate_adjusted_r2() Part 4: Limitations and Pitfalls
4.1 What R-Squared Doesn't Tell You
def r2_limitations_demo(): """ Demonstrate the limitations of R² """ np.random.seed(42) n = 100 # Create different scenarios scenarios = [] # 1. Non-linear relationship (R² can be misleading) x_nonlinear = np.linspace(-5, 5, n) y_nonlinear = x_nonlinear**2 + np.random.normal(0, 2, n) # 2. Outliers affecting R² x_outliers = np.random.randn(n) y_outliers = 2 * x_outliers + np.random.randn(n) # Add outliers x_outliers = np.append(x_outliers, [5, 6, 7]) y_outliers = np.append(y_outliers, [30, 35, 40]) # 3. Heteroscedasticity (variance changes with X) x_hetero = np.linspace(0, 10, n) y_hetero = 2 * x_hetero + np.random.normal(0, x_hetero, n) # 4. Restricted range (low R² but strong relationship) x_restricted = np.random.uniform(8, 10, n) y_restricted = 2 * x_restricted + np.random.normal(0, 0.5, n) scenarios = [ (x_nonlinear, y_nonlinear, "Non-linear Relationship", "R² may be low even with strong relationship"), (x_outliers, y_outliers, "Outliers Present", "R² heavily influenced by outliers"), (x_hetero, y_hetero, "Heteroscedasticity", "R² doesn't detect varying variance"), (x_restricted, y_restricted, "Restricted Range", "R² low despite strong relationship within range") ] fig, axes = plt.subplots(2, 2, figsize=(12, 10)) for ax, (x, y, title, note) in zip(axes.flat, scenarios): # Fit linear model model = LinearRegression() model.fit(x.reshape(-1, 1), y) y_pred = model.predict(x.reshape(-1, 1)) r2 = r2_score(y, y_pred) # Plot ax.scatter(x, y, alpha=0.6, s=30) ax.plot(x, y_pred, 'r-', linewidth=2) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_title(f'{title}\nR² = {r2:.3f}') ax.text(0.05, 0.95, note, transform=ax.transAxes, fontsize=9, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() r2_limitations_demo() 4.2 Common Misconceptions
def r2_misconceptions(): """ Address common misconceptions about R² """ misconceptions = [ { "Misconception": "High R² means the model is good", "Truth": "High R² can come from overfitting, omitted variable bias, or spurious correlations", "Example": "A model predicting stock prices with R²=0.95 but fails out-of-sample" }, { "Misconception": "Low R² means the model is useless", "Truth": "Low R² can still have statistically significant predictors", "Example": "Medical studies often have R² < 0.3 but identify important risk factors" }, { "Misconception": "R² measures prediction accuracy", "Truth": "R² measures fit, not prediction accuracy", "Example": "A model can have high R² but poor predictions if overfitted" }, { "Misconception": "Adding more variables always improves the model", "Truth": "Adding useless variables increases R² but reduces adjusted R²", "Example": "Including random noise as predictors inflates R² artificially" }, { "Misconception": "R² can be compared across different datasets", "Truth": "R² is dataset-specific and not comparable across different Y variables", "Example": "R² from a model predicting sales vs predicting log(sales) aren't comparable" } ] print("=== COMMON R-SQUARED MISCONCEPTIONS ===\n") for i, mis in enumerate(misconceptions, 1): print(f"{i}. {mis['Misconception']}") print(f" Truth: {mis['Truth']}") print(f" Example: {mis['Example']}\n") # Visual example np.random.seed(42) n = 50 # Create data with high R² but poor predictions x_train = np.linspace(0, 10, n) y_train = 2 * x_train + np.random.normal(0, 0.5, n) # Overfit model (high degree polynomial) from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline x_test = np.linspace(0, 12, 100) # Test beyond training range fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # Good model (simple linear) model_good = LinearRegression() model_good.fit(x_train.reshape(-1, 1), y_train) y_pred_good = model_good.predict(x_test.reshape(-1, 1)) r2_good = r2_score(y_train, model_good.predict(x_train.reshape(-1, 1))) # Overfit model (high polynomial) model_overfit = make_pipeline(PolynomialFeatures(degree=15), LinearRegression()) model_overfit.fit(x_train.reshape(-1, 1), y_train) y_pred_overfit = model_overfit.predict(x_test.reshape(-1, 1)) r2_overfit = r2_score(y_train, model_overfit.predict(x_train.reshape(-1, 1))) axes[0].scatter(x_train, y_train, alpha=0.6, label='Training Data') axes[0].plot(x_test, y_pred_good, 'g-', linewidth=2, label=f'Good Model (R²={r2_good:.3f})') axes[0].set_xlim(0, 12) axes[0].set_title('Good Model: Generalizes Well') axes[0].legend() axes[0].grid(True, alpha=0.3) axes[1].scatter(x_train, y_train, alpha=0.6, label='Training Data') axes[1].plot(x_test, y_pred_overfit, 'r-', linewidth=2, label=f'Overfit Model (R²={r2_overfit:.3f})') axes[1].set_xlim(0, 12) axes[1].set_title('Overfit Model: High R² but Poor Predictions') axes[1].legend() axes[1].grid(True, alpha=0.3) plt.tight_layout() plt.show() r2_misconceptions() Part 5: Advanced R-Squared Concepts
5.1 Out-of-Sample R-Squared
def out_of_sample_r2(X, y, test_size=0.3): """ Calculate R² on test data to assess generalization """ from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=test_size, random_state=42 ) model = LinearRegression() model.fit(X_train, y_train) # In-sample R² y_train_pred = model.predict(X_train) r2_train = r2_score(y_train, y_train_pred) # Out-of-sample R² y_test_pred = model.predict(X_test) r2_test = r2_score(y_test, y_test_pred) # Calculate shrinkage shrinkage = (r2_train - r2_test) / r2_train * 100 if r2_train > 0 else 0 print("=== IN-SAMPLE vs OUT-OF-SAMPLE R² ===\n") print(f"In-sample R² (Training): {r2_train:.4f}") print(f"Out-of-sample R² (Test): {r2_test:.4f}") print(f"Shrinkage: {shrinkage:.1f}%") if r2_test < 0: print("\n⚠ Warning: Test R² < 0 means model performs worse than using the mean") print(" This indicates severe overfitting or wrong model specification") return r2_train, r2_test 5.2 Pseudo R-Squared for Non-Linear Models
def pseudo_r2_logistic(y_true, y_pred_proba): """ Calculate various pseudo R² measures for logistic regression """ from sklearn.metrics import log_loss # Null model (only intercept) p_null = np.mean(y_true) null_log_likelihood = np.sum(y_true * np.log(p_null) + (1 - y_true) * np.log(1 - p_null)) # Full model log-likelihood full_log_likelihood = -log_loss(y_true, y_pred_proba, normalize=False) # McFadden's R² mcfadden_r2 = 1 - (full_log_likelihood / null_log_likelihood) # Cox & Snell R² n = len(y_true) cox_snell = 1 - np.exp((2/n) * (null_log_likelihood - full_log_likelihood)) # Nagelkerke (adjusted Cox & Snell) nagelkerke = cox_snell / (1 - np.exp(2 * null_log_likelihood / n)) results = { 'McFadden R²': mcfadden_r2, 'Cox & Snell R²': cox_snell, 'Nagelkerke R²': nagelkerke } print("=== PSEUDO R-SQUARED FOR LOGISTIC REGRESSION ===\n") for name, value in results.items(): print(f"{name}: {value:.4f}") return results 5.3 Incremental R-Squared (Change in R²)
def incremental_r2(df, target, base_vars, new_vars): """ Calculate the incremental contribution of new variables """ # Model without new variables formula_base = f"{target} ~ {' + '.join(base_vars)}" model_base = smf.ols(formula_base, data=df).fit() # Model with new variables all_vars = base_vars + new_vars formula_full = f"{target} ~ {' + '.join(all_vars)}" model_full = smf.ols(formula_full, data=df).fit() # R² values r2_base = model_base.rsquared r2_full = model_full.rsquared r2_increment = r2_full - r2_base # F-test for significance of increment from statsmodels.stats.anova import anova_lm anova_results = anova_lm(model_base, model_full) print("=== INCREMENTAL R-SQUARED ANALYSIS ===\n") print(f"Base Model R²: {r2_base:.4f}") print(f"Full Model R²: {r2_full:.4f}") print(f"Incremental R²: {r2_increment:.4f}") print(f"Percentage improvement: {(r2_increment/r2_base)*100:.1f}%") print("\nANOVA Test for Improvement:") print(anova_results) return r2_increment, anova_results Part 6: Practical Applications
6.1 Feature Selection Using R-Squared
def r2_based_feature_selection(X, y, threshold=0.01): """ Forward selection based on R² improvement """ from sklearn.feature_selection import f_regression n_features = X.shape[1] selected_features = [] remaining_features = list(range(n_features)) current_r2 = 0 history = [] while remaining_features: best_r2 = current_r2 best_feature = None for feature in remaining_features: # Test adding this feature features_to_test = selected_features + [feature] X_subset = X[:, features_to_test] model = LinearRegression() model.fit(X_subset, y) y_pred = model.predict(X_subset) r2 = r2_score(y, y_pred) if r2 > best_r2 + threshold: best_r2 = r2 best_feature = feature if best_feature is not None: selected_features.append(best_feature) remaining_features.remove(best_feature) history.append({ 'Step': len(selected_features), 'Feature': best_feature, 'R²': best_r2, 'ΔR²': best_r2 - current_r2 }) current_r2 = best_r2 else: break # Create results DataFrame results_df = pd.DataFrame(history) print("=== FORWARD SELECTION BASED ON R² ===\n") print(results_df.to_string()) print(f"\nSelected {len(selected_features)} features") print(f"Final R²: {current_r2:.4f}") return selected_features, results_df 6.2 Model Comparison Framework
def comprehensive_model_comparison(models_dict, X_train, X_test, y_train, y_test): """ Compare multiple models with various R² metrics """ results = [] for model_name, model in models_dict.items(): # Fit model model.fit(X_train, y_train) # Predictions y_train_pred = model.predict(X_train) y_test_pred = model.predict(X_test) # Calculate metrics r2_train = r2_score(y_train, y_train_pred) r2_test = r2_score(y_test, y_test_pred) # Adjusted R² (for linear models only) if hasattr(model, 'coef_'): n = len(y_train) k = X_train.shape[1] adj_r2 = 1 - (1 - r2_train) * (n - 1) / (n - k - 1) else: adj_r2 = np.nan # Overfitting measure overfit_penalty = (r2_train - r2_test) / r2_train if r2_train > 0 else 0 results.append({ 'Model': model_name, 'Train R²': r2_train, 'Test R²': r2_test, 'Adj. R²': adj_r2, 'Overfit Penalty': overfit_penalty, 'Generalization Gap': r2_train - r2_test }) results_df = pd.DataFrame(results).sort_values('Test R²', ascending=False) print("=== COMPREHENSIVE MODEL COMPARISON ===\n") print(results_df.round(4).to_string()) # Visual comparison fig, ax = plt.subplots(figsize=(10, 6)) x = np.arange(len(results_df)) width = 0.35 ax.bar(x - width/2, results_df['Train R²'], width, label='Train R²', alpha=0.8) ax.bar(x + width/2, results_df['Test R²'], width, label='Test R²', alpha=0.8) ax.set_xlabel('Model') ax.set_ylabel('R²') ax.set_title('Model Comparison: Train vs Test R²') ax.set_xticks(x) ax.set_xticklabels(results_df['Model'], rotation=45, ha='right') ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() return results_df Part 7: Reporting R-Squared
7.1 Best Practices for Reporting
def report_r2(r2, adj_r2=None, n=None, k=None, context="general"): """ Generate a professional report of R² results """ print("=== REGRESSION MODEL FIT REPORT ===\n") # Basic R² print(f"R-squared: {r2:.4f}") print(f"Interpretation: {r2*100:.1f}% of the variance in the dependent variable " f"is explained by the model.\n") # Adjusted R² if provided if adj_r2 is not None: print(f"Adjusted R-squared: {adj_r2:.4f}") if n and k: print(f"Penalized for {k} predictors using {n} observations") print(f"Difference: R² - Adj R² = {r2 - adj_r2:.4f}\n") # Context-specific interpretation interpret_r2(r2, context) # Recommendations print("\n--- Recommendations ---") if r2 > 0.9: print("✓ Very strong model fit") print(" → Consider potential overfitting") print(" → Validate with out-of-sample testing") elif r2 > 0.7: print("✓ Strong model fit") print(" → Good explanatory power") elif r2 > 0.5: print("✓ Moderate model fit") print(" → Consider additional predictors if needed") elif r2 > 0.3: print("⚠ Weak model fit") print(" → May need additional variables or non-linear terms") else: print("⚠ Poor model fit") print(" → Model may not be appropriate for this data") print(" → Consider different variables or model types") # Confidence interval for R² (bootstrap) print("\n--- Confidence Interval (Bootstrap) ---") print("Note: R² is a point estimate. Consider reporting confidence intervals.") 7.2 Visual Reporting
def visualize_r2_report(r2, adj_r2=None, model_name="Current Model"): """ Create visual report of R² """ fig, axes = plt.subplots(1, 3, figsize=(15, 4)) # 1. R² gauge chart ax1 = axes[0] # Create a gauge-like bar colors = ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850'] # Determine color based on R² if r2 < 0.3: color = colors[0] status = "Poor" elif r2 < 0.5: color = colors[1] status = "Weak" elif r2 < 0.7: color = colors[2] status = "Moderate" elif r2 < 0.9: color = colors[3] status = "Good" else: color = colors[4] status = "Excellent" # Create horizontal bar ax1.barh([0], [r2], color=color, height=0.3, alpha=0.8) ax1.barh([0], [1], color='lightgray', height=0.3, alpha=0.5) ax1.set_xlim(0, 1) ax1.set_yticks([]) ax1.set_xlabel('R-squared') ax1.set_title(f'{model_name}\nR² = {r2:.3f} ({status})') # Add text annotation ax1.text(r2, 0, f'{r2:.1%}', ha='center', va='center', fontweight='bold') # 2. Explained vs Unexplained pie chart ax2 = axes[1] sizes = [r2 * 100, (1 - r2) * 100] labels = [f'Explained\n{r2:.1%}', f'Unexplained\n{1-r2:.1%}'] colors_pie = ['#2c7bb6', '#d7191c'] ax2.pie(sizes, labels=labels, colors=colors_pie, autopct='%1.1f%%', startangle=90) ax2.set_title('Variance Decomposition') # 3. Adjusted R² comparison (if provided) ax3 = axes[2] if adj_r2 is not None: x = [0, 1] y = [r2, adj_r2] colors_bars = ['#2c7bb6', '#abd9e9'] bars = ax3.bar(x, y, color=colors_bars, alpha=0.8) ax3.set_xticks(x) ax3.set_xticklabels(['R²', 'Adjusted R²']) ax3.set_ylim(0, 1) ax3.set_ylabel('Value') ax3.set_title('R² vs Adjusted R²') # Add value labels for bar, val in zip(bars, y): ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02, f'{val:.3f}', ha='center', fontweight='bold') else: # Show a comparison with a null model ax3.text(0.5, 0.5, 'Adjusted R² not available\n(for non-linear models)', ha='center', va='center', transform=ax3.transAxes, fontsize=10) ax3.set_title('Adjusted R²') ax3.set_xlim(0, 1) ax3.set_ylim(0, 1) plt.tight_layout() plt.show() # Example usage visualize_r2_report(0.823, adj_r2=0.815, model_name="Sales Prediction Model") Summary: R-Squared Quick Reference
# Quick Reference Card r2_quick_reference = { "Definition": "Proportion of variance explained by the model", "Formula": "R² = 1 - (SS_res / SS_tot)", "Range": "0 to 1 (or negative if model is worse than mean)", "Interpretation": { "0.90-1.00": "Excellent - almost all variation explained", "0.70-0.90": "Good - strong explanatory power", "0.50-0.70": "Moderate - reasonable explanatory power", "0.30-0.50": "Weak - limited explanatory power", "0.00-0.30": "Poor - little explanatory power" }, "Key Limitations": [ "Doesn't measure prediction accuracy", "Increases with useless predictors", "Can't be compared across different Y variables", "Doesn't detect non-linearity", "Sensitive to outliers" ], "When to Use": [ "Comparing models on same dataset", "Assessing explanatory power", "Feature selection (with caution)", "Model improvement tracking" ], "When NOT to Use": [ "Comparing models on different datasets", "Assessing prediction performance alone", "When assumptions are violated", "As the only model evaluation metric" ], "Alternatives": [ "Adjusted R² (penalizes complexity)", "Out-of-sample R² (assesses generalization)", "RMSE/MAE (prediction accuracy)", "AIC/BIC (model comparison)", "Pseudo R² (non-linear models)" ] } # Print quick reference print("=== R-SQUARED QUICK REFERENCE ===\n") for category, content in r2_quick_reference.items(): print(f"{category}:") if isinstance(content, dict): for key, value in content.items(): print(f" {key}: {value}") elif isinstance(content, list): for item in content: print(f" • {item}") else: print(f" {content}") print() Key Takeaway: R-squared is a powerful but often misunderstood metric. It tells you how much of the variance in your outcome is explained by your predictors, but it doesn't tell you if your model is good, if your predictions are accurate, or if you've chosen the right variables. Use R-squared as part of a comprehensive evaluation strategy that includes:
- Adjusted R² for model comparison
- Out-of-sample validation for generalization
- Diagnostic plots for assumption checking
- Subject matter expertise for practical significance
- Multiple metrics (RMSE, MAE, AIC, etc.) for a complete picture
Remember: A high R² doesn't guarantee a good model, and a low R² doesn't mean your model is useless. The right interpretation depends on your field, your data, and your specific goals.
Building Blocks of C: A Complete Guide to Functions
Explains how functions work in C programming, including function declaration, definition, parameters, return values, and how functions help organize reusable code.
https://macronepal.com/bash/building-blocks-of-c-a-complete-guide-to-functions/
The Heart of Text Processing: A Complete Guide to Strings in C
Explains how strings are used in C, covering character arrays, string handling functions, and common techniques for text processing tasks.
https://macronepal.com/bash/the-heart-of-text-processing-a-complete-guide-to-strings-in-c-2/
The Cornerstone of Data Organization: A Complete Guide to Arrays in C
Describes how arrays store multiple values in C, including indexing, initialization, and using arrays to manage structured data efficiently.
https://macronepal.com/bash/the-cornerstone-of-data-organization-a-complete-guide-to-arrays-in-c/
Guaranteed Execution: A Complete Guide to the Do-While Loop in C
Explains the do-while loop structure in C, highlighting how it ensures code runs at least once before checking the loop condition.
https://macronepal.com/bash/guaranteed-execution-a-complete-guide-to-the-do-while-loop-in-c/
Mastering Iteration: A Complete Guide to the For Loop in C
Explains how the for loop works in C, including initialization, condition checking, and increment steps for repeated execution of code blocks.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-the-for-loop-in-c/
Mastering Iteration: A Complete Guide to While Loops in C
Explains the while loop structure in C, focusing on condition-based repetition and proper loop control techniques.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-while-loops-in-c/
Beyond If-Else: A Complete Guide to Switch Case in C
Explains how switch-case statements work in C programming, enabling efficient handling of multiple conditional branches.
https://macronepal.com/bash/beyond-if-else-a-complete-guide-to-switch-case-in-c/
Mastering the Fundamentals: A Complete Guide to Arithmetic Operations in C
Explains how arithmetic operators such as addition, subtraction, multiplication, and division work in C, along with operator precedence and usage examples.
https://macronepal.com/bash/mastering-the-fundamentals-a-complete-guide-to-arithmetic-operations-in-c/
Foundation of C Programming: A Complete Guide to Basic Input Output
Explains how input and output functions like printf and scanf work in C, forming the foundation for interacting with users and displaying program results.
https://macronepal.com/bash/foundation-of-c-programming-a-complete-guide-to-basic-input-output/