Mathew K Analytics

Lesson 35 · Market Research Analytics in Python

Correlation and Key Driver Analysis in Market Research

We are going to learn how to identify what drives customer satisfaction and business results using actual survey and behavioral data. Businesses need to…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Correlation and Key Driver Analysis in Market Research#

  • We are going to learn how to identify what drives customer satisfaction and business results using actual survey and behavioral data.
  • Businesses need to know which factors (age, service quality, pricing, digital features) most affect their Net Promoter Score or customer retention.
  • This lesson will show you how to find correlation patterns and uncover the variables that truly move the needle.
  • Learners will move from data setup through simple correlations to advanced key driver regression, debugging, and best practices.
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import openml
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import pearsonr, spearmanr

What Key Driver and Correlation Analysis Means in Customer Analytics#

  • In market research, companies gather data with surveys and business systems to understand why customers act or feel a certain way.
  • Each row in a customer dataset is usually one person or one transaction, with columns representing survey answers (like satisfaction) and demographics (like age or gender).
  • Beginners often forget to check for missing data, mix up means and correlations, or ignore non-numeric survey responses.
  • Proper correlation and key driver analysis spot which variables are related or even predictive of important outcomes, like NPS scores or churn.
# Example 1: Load a simple NPS survey dataset
np.random.seed(42)
df_nps = pd.DataFrame({'CustomerID': range(1,501),
                      'Age': np.random.randint(18,70,500),
                      'Region': np.random.choice(['North','South','East','West'],500),
                      'NPS_Score': np.random.randint(0,11,500)})
print('NPS dataset shape:', df_nps.shape)
print(df_nps.head(3))
NPS dataset shape: (500, 4)
   CustomerID  Age Region  NPS_Score
0           1   56   West          2
1           2   69  North          0
2           3   46   East          4
# Example 2: Calculate average NPS by region
avg_nps_region = df_nps.groupby('Region')['NPS_Score'].mean()
print('Average NPS by region:')
print(avg_nps_region)
Average NPS by region:
Region
East     4.504425
North    5.214765
South    4.719008
West     5.025641
Name: NPS_Score, dtype: float64
# Example 3: Visualizing NPS Score Distribution
sns.histplot(df_nps['NPS_Score'], bins=11, kde=True, color='blue')
plt.title('Distribution of NPS Scores')
plt.xlabel('NPS Score')
plt.ylabel('Count')
plt.show()
No description has been provided for this image
# Example 4: Load real customer satisfaction survey data from OpenML
dataset = openml.datasets.get_dataset(42178)
df_cs, _, _, _ = dataset.get_data(dataset_format='dataframe')
print('Shape:', df_cs.shape)
df_cs.head(3)
Shape: (7043, 20)
gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges Churn
0 Female 0 Yes No 1 No No phone service DSL No Yes No No No No Month-to-month Yes Electronic check 29.85 29.85 No
1 Male 0 No No 34 Yes No DSL Yes No Yes No No No One year No Mailed check 56.95 1889.5 No
2 Male 0 No No 2 Yes No DSL Yes Yes No No No No Month-to-month Yes Mailed check 53.85 108.15 Yes
# Example 5: Checking for missing customer feedback data
missing_values = df_cs.isnull().sum()
print(missing_values[missing_values > 0])
Series([], dtype: int64)
# Example: Calculate pairwise correlation safely after cleaning
num_columns = ['SeniorCitizen', 'tenure', 'MonthlyCharges', 'TotalCharges']
df_cs['TotalCharges'] = (
    df_cs['TotalCharges']
    .replace(' ', np.nan)
)
df_cs['TotalCharges'] = pd.to_numeric(
    df_cs['TotalCharges'],
    errors='coerce'
)
correlation_matrix = df_cs[num_columns].corr()
print('Correlation matrix:')
print(correlation_matrix)
Correlation matrix:
                SeniorCitizen    tenure  MonthlyCharges  TotalCharges
SeniorCitizen        1.000000  0.016567        0.220173      0.102411
tenure               0.016567  1.000000        0.247900      0.825880
MonthlyCharges       0.220173  0.247900        1.000000      0.651065
TotalCharges         0.102411  0.825880        0.651065      1.000000
# Example 7: Visualize correlation heatmap
plt.figure(figsize=(6,5))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Correlation Heatmap: Customer Satisfaction Variables')
plt.show()
No description has been provided for this image
# Example 8: Correlation between age and NPS score
corr_pearson, pval = pearsonr(df_nps['Age'], df_nps['NPS_Score'])
print(f'Pearson correlation between Age and NPS_Score: {corr_pearson:.2f}, p-value: {pval:.3f}')
Pearson correlation between Age and NPS_Score: 0.04, p-value: 0.381
# Example 9: Spearman correlation of TotalCharges and tenure (capture non-linear effects)
corr_spearman, pval_s = spearmanr(df_cs['TotalCharges'], df_cs['tenure'])
print(f'Spearman correlation between TotalCharges and Tenure: {corr_spearman:.2f}, p-value: {pval_s:.3f}')
Spearman correlation between TotalCharges and Tenure: nan, p-value: nan
# Example 10: Scatter plot of MonthlyCharges vs. TotalCharges to spot relationship shape
plt.scatter(df_cs['MonthlyCharges'], df_cs['TotalCharges'], alpha=0.3)
plt.title('Monthly Charges vs. Total Charges')
plt.xlabel('Monthly Charges')
plt.ylabel('Total Charges')
plt.show()
No description has been provided for this image
# Example 11: Segmenting NPS Scores by customer age group
df_nps['AgeGroup'] = pd.cut(df_nps['Age'], bins=[17,30,45,60,70], labels=['18-30','31-45','46-60','61-70'])
grouped_nps = df_nps.groupby('AgeGroup')['NPS_Score'].mean()
print('Average NPS by Age Group:')
print(grouped_nps)
Average NPS by Age Group:
AgeGroup
18-30    4.839286
31-45    4.795918
46-60    4.711409
61-70    5.391304
Name: NPS_Score, dtype: float64
# Example 12: Key Driver Analysis - simple OLS regression
import statsmodels.api as sm
X = df_cs[['MonthlyCharges', 'tenure', 'SeniorCitizen']].fillna(0)
y = df_cs['TotalCharges'].fillna(0)
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
print(model.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:           TotalCharges   R-squared:                       0.895
Model:                            OLS   Adj. R-squared:                  0.895
Method:                 Least Squares   F-statistic:                 2.001e+04
Date:                Sat, 14 Feb 2026   Prob (F-statistic):               0.00
Time:                        11:12:31   Log-Likelihood:                -56470.
No. Observations:                7043   AIC:                         1.129e+05
Df Residuals:                    7039   BIC:                         1.130e+05
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
==================================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
----------------------------------------------------------------------------------
const          -2156.8175     21.948    -98.267      0.000   -2199.843   -2113.792
MonthlyCharges    36.0734      0.308    117.113      0.000      35.470      36.677
tenure            65.3200      0.368    177.415      0.000      64.598      66.042
SeniorCitizen    -87.0037     24.363     -3.571      0.000    -134.762     -39.246
==============================================================================
Omnibus:                       50.062   Durbin-Watson:                   1.999
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               39.860
Skew:                           0.106   Prob(JB):                     2.21e-09
Kurtosis:                       2.698   Cond. No.                         220.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Multiple Linear Regression with Categorical Variables (Statsmodels)
import statsmodels.api as sm
X_cat = pd.get_dummies(
    df_cs[['gender', 'Contract', 'InternetService']].fillna('Unknown'),
    drop_first=True
)
X_full = pd.concat([X, X_cat], axis=1)
X_full, y = X_full.align(y, join='inner', axis=0)
X_full = X_full.fillna(0)
y = y.fillna(0)
X_full = X_full.astype(float)
y = y.astype(float)
X_full = sm.add_constant(X_full)
model_cat = sm.OLS(y, X_full).fit()
print(model_cat.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:           TotalCharges   R-squared:                       0.900
Model:                            OLS   Adj. R-squared:                  0.900
Method:                 Least Squares   F-statistic:                     7912.
Date:                Sat, 14 Feb 2026   Prob (F-statistic):               0.00
Time:                        11:14:24   Log-Likelihood:                -56300.
No. Observations:                7043   AIC:                         1.126e+05
Df Residuals:                    7034   BIC:                         1.127e+05
Df Model:                           8                                         
Covariance Type:            nonrobust                                         
===============================================================================================
                                  coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------------------
const                       -2774.8806     44.425    -62.462      0.000   -2861.967   -2687.794
MonthlyCharges                 48.8151      0.794     61.462      0.000      47.258      50.372
tenure                         62.8760      0.524    120.078      0.000      61.850      63.902
SeniorCitizen                 -31.1690     24.223     -1.287      0.198     -78.654      16.316
gender_Male                    17.1664     17.100      1.004      0.315     -16.354      50.687
Contract_One year              58.7411     26.172      2.244      0.025       7.436     110.046
Contract_Two year            -111.4008     31.171     -3.574      0.000    -172.506     -50.296
InternetService_Fiber optic  -551.1300     34.132    -16.147      0.000    -618.040    -484.220
InternetService_No            512.6801     38.449     13.334      0.000     437.309     588.052
==============================================================================
Omnibus:                       40.719   Durbin-Watson:                   2.017
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               31.638
Skew:                           0.076   Prob(JB):                     1.35e-07
Kurtosis:                       2.709   Cond. No.                         564.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Example 14: Key Driver Analysis on NPS using logistic regression
from sklearn.linear_model import LogisticRegression
df_nps['Promoter'] = (df_nps['NPS_Score'] >= 9).astype(int)
X_demo = pd.get_dummies(df_nps[['AgeGroup', 'Region']], drop_first=True)
y_nps = df_nps['Promoter']
model_lr = LogisticRegression().fit(X_demo, y_nps)
coefs = pd.Series(model_lr.coef_[0], index=X_demo.columns)
print('Logistic Regression Coefficients (drivers of high NPS):')
print(coefs.sort_values(ascending=False))
Logistic Regression Coefficients (drivers of high NPS):
Region_West       0.405989
Region_North      0.110466
AgeGroup_61-70    0.110022
AgeGroup_46-60    0.052039
Region_South     -0.003241
AgeGroup_31-45   -0.160670
dtype: float64
# Example 15: Online Retail - correlation of quantity purchased and price
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df_retail = pd.read_excel(url, sheet_name='Year 2010-2011')
corr_qty_price, _ = pearsonr(df_retail['Quantity'], df_retail['Price'])
print(f'Correlation between Quantity and Price: {corr_qty_price:.2f}')
Correlation between Quantity and Price: -0.00
# Example 16: Multi-variable correlation - Heatmap for select retail variables
retail_cols = ['Quantity','Price']
retail_corr = df_retail[retail_cols].corr()
sns.heatmap(retail_corr, annot=True, cmap='YlGnBu')
plt.title('Correlation in Retail Data')
plt.show()
No description has been provided for this image
# Example 17: Constructing a composite satisfaction index (normalize and average)
df_cs['Tenure_Norm'] = (df_cs['tenure'] - df_cs['tenure'].min()) / (df_cs['tenure'].max() - df_cs['tenure'].min())
df_cs['Monthly_Norm'] = (df_cs['MonthlyCharges'] - df_cs['MonthlyCharges'].min()) / (df_cs['MonthlyCharges'].max() - df_cs['MonthlyCharges'].min())
df_cs['SatisfactionIndex'] = (df_cs['Tenure_Norm'] + (1 - df_cs['Monthly_Norm'])) / 2
print(df_cs[['Tenure_Norm','Monthly_Norm','SatisfactionIndex']].head())
   Tenure_Norm  Monthly_Norm  SatisfactionIndex
0     0.013889      0.115423           0.449233
1     0.472222      0.385075           0.543574
2     0.027778      0.354229           0.336774
3     0.625000      0.239303           0.692848
4     0.027778      0.521891           0.252944
# Example 18: Trend analysis of NPS over simulated time periods
date_rng = pd.date_range('2023-01-01', periods=500, freq='D')
df_nps['ResponseDate'] = np.random.choice(date_rng, 500)
df_nps['Period'] = df_nps['ResponseDate'].dt.to_period('M')
trend = df_nps.groupby('Period')['NPS_Score'].mean()
trend.plot(marker='o')
plt.title('Monthly NPS Trend')
plt.ylabel('Average NPS')
plt.xlabel('Period')
plt.show()
No description has been provided for this image
# Example 19: Handling missing NPS survey responses
df_nps_missing = df_nps.copy()
df_nps_missing.loc[np.random.choice(df_nps_missing.index, 20, replace=False), 'NPS_Score'] = np.nan
missing_count = df_nps_missing['NPS_Score'].isnull().sum()
print(f'NPS responses missing: {missing_count}')
print('Imputing with mean NPS score:')
df_nps_missing['NPS_Score'] = df_nps_missing['NPS_Score'].fillna(df_nps_missing['NPS_Score'].mean())
print(f'Missing after imputation: {df_nps_missing['NPS_Score'].isnull().sum()}')
NPS responses missing: 20
Imputing with mean NPS score:
Missing after imputation: 0
# Example 20: Debugging incorrect groupings (using wrong column type)
try:
    grouped = df_nps.groupby('NPS_Score')['Region'].mean()
except Exception as e:
    print('Error:', e)
    print('You cannot calculate the mean of a categorical variable like Region.')
Error: agg function failed [how->mean,dtype->object]
You cannot calculate the mean of a categorical variable like Region.
# Example 21: Debugging Likert scale misinterpretation
likert_map = {'Strongly disagree':1, 'Disagree':2, 'Neutral':3, 'Agree':4, 'Strongly agree':5}
df_likert = pd.DataFrame({'Satisfaction': ['Agree','Neutral','Strongly agree','Disagree','Strongly disagree']})
df_likert['Satisfaction_Score'] = df_likert['Satisfaction'].map(likert_map)
print(df_likert)
        Satisfaction  Satisfaction_Score
0              Agree                   4
1            Neutral                   3
2     Strongly agree                   5
3           Disagree                   2
4  Strongly disagree                   1

Market Research Best Practices: Segmentation, Cross-tabs, Indexes, and Trends#

  • Carefully segment your customer or survey data before looking for key drivers; this makes insights specific and actionable.
  • Use cross-tabulations to explore relationships between categorical and numeric or categorical variables.
  • Build indexes by aggregating important metrics (like satisfaction and tenure) for holistic business insights.
  • Always check for trends over time so you can act early if customer attitudes shift.
# End-to-end: Identify the strongest driver of churn from real customer data
df_cs['TotalCharges'] = pd.to_numeric(df_cs['TotalCharges'], errors='coerce')
df_cs = df_cs.dropna(subset=['MonthlyCharges','tenure','TotalCharges','Churn'])
df_cs['Churn_Flag'] = df_cs['Churn'].map({'Yes':1, 'No':0})
X = df_cs[['MonthlyCharges','tenure','TotalCharges']]
y = df_cs['Churn_Flag']
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=200).fit(X, y)
drivers = pd.Series(model.coef_[0], index=X.columns).sort_values(key=abs, ascending=False)
print('Churn Key Drivers:')
print(drivers)
Churn Key Drivers:
tenure           -0.067113
MonthlyCharges    0.030200
TotalCharges      0.000145
dtype: float64
 

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.