Mathew K Analytics

Lesson 37 · Market Research Analytics in Python

Measuring Customer Satisfaction with Market Research Data

In this lesson, we will learn how to analyze customer satisfaction survey data for actionable business insights. Customer satisfaction is crucial for…

⬇ 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

Measuring Customer Satisfaction with Market Research Data#

  • In this lesson, we will learn how to analyze customer satisfaction survey data for actionable business insights.
  • Customer satisfaction is crucial for building loyalty, improving retention, and informing marketing strategies.
  • By the end, you will know how to measure, visualize, and segment satisfaction scores using real datasets.
  • We will solve problems relevant to customer analytics and market research teams.
import openml
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

Understanding Customer Satisfaction Data#

  • Most customer satisfaction datasets use structured responses from surveys.
  • Columns often include demographics (like age, gender), service indicators, and satisfaction scores (like NPS or Likert ratings).
  • Be careful: missing data, incorrectly coded scales, or reversed scales are common mistakes for beginners.
  • Always verify what each score means before analyzing, and watch out for leading or biased survey questions.
# BEGINNER EXAMPLE 1: Load a real customer satisfaction survey dataset
dataset = openml.datasets.get_dataset(42178)
df, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(df.shape)
print(df.head(3))
(7043, 20)
   gender  SeniorCitizen Partner Dependents  tenure PhoneService  \
0  Female              0     Yes         No       1           No   
1    Male              0      No         No      34          Yes   
2    Male              0      No         No       2          Yes   

      MultipleLines InternetService OnlineSecurity OnlineBackup  \
0  No phone service             DSL             No          Yes   
1                No             DSL            Yes           No   
2                No             DSL            Yes          Yes   

  DeviceProtection TechSupport StreamingTV StreamingMovies        Contract  \
0               No          No          No              No  Month-to-month   
1              Yes          No          No              No        One year   
2               No          No          No              No  Month-to-month   

  PaperlessBilling     PaymentMethod  MonthlyCharges TotalCharges Churn  
0              Yes  Electronic check           29.85        29.85    No  
1               No      Mailed check           56.95       1889.5    No  
2              Yes      Mailed check           53.85       108.15   Yes  
# BEGINNER EXAMPLE 2: Check for missing values in survey responses
missing = df.isnull().sum()
print(missing[missing > 0])
Series([], dtype: int64)
# BEGINNER EXAMPLE 3: Summarize basic demographic information
print(df['gender'].value_counts())
print(df['SeniorCitizen'].value_counts())
gender
Male      3555
Female    3488
Name: count, dtype: int64
SeniorCitizen
0    5901
1    1142
Name: count, dtype: int64
# BEGINNER EXAMPLE 4: Visualize satisfaction scores (churn as proxy)
sns.countplot(data=df, x='Churn')
plt.title('Customer Churn as Satisfaction Indicator')
plt.xlabel('Customer Churned?')
plt.ylabel('Count')
plt.show()
No description has been provided for this image
# BEGINNER EXAMPLE 5: Calculate and print churn rate
churn_rate = df['Churn'].value_counts(normalize=True)['Yes'] * 100
print(f'Overall churn rate: {churn_rate:.2f}%')
Overall churn rate: 26.54%
# BEGINNER EXAMPLE 6: Load a synthetic Net Promoter Score (NPS) survey dataset
np.random.seed(42)
nps_df = 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_df.head(3))
   CustomerID  Age Region  NPS_Score
0           1   56   West          2
1           2   69  North          0
2           3   46   East          4
# INTERMEDIATE EXAMPLE 1: Visualize the NPS score distribution
sns.histplot(nps_df['NPS_Score'], bins=11, kde=False)
plt.title('NPS Score Distribution')
plt.xlabel('NPS Score (0-10)')
plt.ylabel('Number of Responses')
plt.show()
No description has been provided for this image
# INTERMEDIATE EXAMPLE 2: Calculate NPS according to standard formula
total = len(nps_df)
promoters = len(nps_df[nps_df['NPS_Score'] >= 9])
detractors = len(nps_df[nps_df['NPS_Score'] <= 6])
nps = ((promoters - detractors) / total) * 100
print(f'Net Promoter Score (NPS): {nps:.1f}')
Net Promoter Score (NPS): -47.6
# INTERMEDIATE EXAMPLE 3: Segment NPS by region
region_nps = nps_df.groupby('Region').apply(
    lambda d: ((len(d[d['NPS_Score'] >= 9]) - len(d[d['NPS_Score'] <= 6])) / len(d)) * 100
)
print(region_nps)
Region
East    -52.212389
North   -40.939597
South   -54.545455
West    -44.444444
dtype: float64
# INTERMEDIATE EXAMPLE 4: Visualize regional NPS differences
region_nps.plot(kind='bar', color='skyblue')
plt.title('Regional Net Promoter Score (NPS)')
plt.xlabel('Region')
plt.ylabel('NPS')
plt.ylim(-100, 100)
plt.show()
No description has been provided for this image
# INTERMEDIATE EXAMPLE 5: Load and preview open-ended customer feedback data
feedback_df = pd.DataFrame({
    'CustomerID':[1,2,3,4,5],
    'Feedback':['Great service and friendly staff',
                'Delivery was slow and packaging was poor',
                'Excellent quality, will buy again',
                'Customer support needs improvement',
                'Good value for money']
})
print(feedback_df.head())
   CustomerID                                  Feedback
0           1          Great service and friendly staff
1           2  Delivery was slow and packaging was poor
2           3         Excellent quality, will buy again
3           4        Customer support needs improvement
4           5                      Good value for money
# INTERMEDIATE EXAMPLE 6: Simple sentiment identification
feedback_df['Sentiment'] = feedback_df['Feedback'].str.contains('great|excellent|good|friendly|buy again', case=False).map({True: 'Positive', False: 'Negative'})
print(feedback_df[['Feedback', 'Sentiment']])
                                   Feedback Sentiment
0          Great service and friendly staff  Positive
1  Delivery was slow and packaging was poor  Negative
2         Excellent quality, will buy again  Positive
3        Customer support needs improvement  Negative
4                      Good value for money  Positive
# ADVANCED EXAMPLE 1: Cross-tabulation between Churn and Internet Service
ct = pd.crosstab(df['InternetService'], df['Churn'], normalize='index') * 100
print(ct)
Churn                   No        Yes
InternetService                      
DSL              81.040892  18.959108
Fiber optic      58.107235  41.892765
No               92.595020   7.404980
# ADVANCED EXAMPLE 2: Monthly churn trend analysis
df['SignupMonth'] = (df['tenure'] // 1).astype(int)
monthly_churn = df.groupby('SignupMonth')['Churn'].apply(lambda x: (x=='Yes').mean() * 100)
monthly_churn.plot(kind='line', marker='o')
plt.title('Monthly Churn Rate Trend')
plt.xlabel('Month Since Signup')
plt.ylabel('Churn Rate (%)')
plt.show()
No description has been provided for this image
# ADVANCED EXAMPLE 3: Create a Customer Satisfaction Index
satisfaction_index = df[['MonthlyCharges', 'tenure']].apply(lambda x: (x['MonthlyCharges']/x['tenure']) if x['tenure'] else x['MonthlyCharges'], axis=1)
df['SatisfactionIndex'] = satisfaction_index
print(df[['MonthlyCharges', 'tenure', 'SatisfactionIndex']].head(3))
   MonthlyCharges  tenure  SatisfactionIndex
0           29.85       1             29.850
1           56.95      34              1.675
2           53.85       2             26.925
# Median Imputation for Numeric Columns
df_filled = df.copy()
num_cols = ['TotalCharges', 'MonthlyCharges']
df_filled[num_cols] = df_filled[num_cols].apply(
    pd.to_numeric, errors='coerce'
)
df_filled[num_cols] = df_filled[num_cols].fillna(
    df_filled[num_cols].median()
)
print(df_filled[num_cols].isnull().sum())
TotalCharges      0
MonthlyCharges    0
dtype: int64
# ERROR HANDLING 2: Guard against grouping errors
try:
    result = df.groupby('Gender').size()
except KeyError:
    print('Check column spelling: Should be gender (lowercase), not Gender.')
Check column spelling: Should be gender (lowercase), not Gender.
# ERROR HANDLING 3: Watch out for misinterpreted NPS scale
def recode_nps(score):
    try:
        s = int(score)
        if s > 10 or s < 0:
            return np.nan
        else:
            return s
    except:
        return np.nan
nps_df['NPS_Score'] = nps_df['NPS_Score'].apply(recode_nps)
print(nps_df['NPS_Score'].isnull().sum())
0

Best Practices for Customer Satisfaction Analytics#

  • Segment and compare satisfaction by key demographics or behaviors.
  • Use cross-tabulation for quick two-way comparisons (e.g., product vs. churn).
  • Always standardize rating scales so everyone is measured on the same terms.
  • Trend analysis over time highlights critical moments in the customer journey.
  • Build indices when simple single-question scores are not enough.
# PATTERN EXAMPLE 1: Customer segmentation by contract type
seg_ct = pd.crosstab(df['Contract'], df['Churn'], normalize='index') * 100
print(seg_ct)
Churn                  No        Yes
Contract                            
Month-to-month  57.290323  42.709677
One year        88.730482  11.269518
Two year        97.168142   2.831858
# PATTERN EXAMPLE 2: Index construction for advanced benchmarking
df['ChargeIndex'] = df['MonthlyCharges']/df['MonthlyCharges'].max()
df['TenureIndex'] = df['tenure']/df['tenure'].max()
print(df[['ChargeIndex', 'TenureIndex']].head(3))
   ChargeIndex  TenureIndex
0     0.251368     0.013889
1     0.479579     0.472222
2     0.453474     0.027778
# FULL MARKET RESEARCH PROBLEM: From survey data to a clear recommendation
dataset = openml.datasets.get_dataset(42178)
df, _, _, _ = dataset.get_data(dataset_format='dataframe')
df['MonthlyCharges'] = df['MonthlyCharges'].fillna(df['MonthlyCharges'].median())
nps_proxy = (df['Churn'].map({'No': 10, 'Yes': 0}))
high_value = df['MonthlyCharges'] > df['MonthlyCharges'].median()
promoters_pct = (nps_proxy[high_value] >= 9).sum() / high_value.sum() * 100
print(f'Percentage of high-value customers who are promoters: {promoters_pct:.2f}%')
Percentage of high-value customers who are promoters: 64.81%
 

Found this useful?

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