Mathew K Analytics

Lesson 53 · Market Research Analytics in Python

Data Storytelling for Stakeholders: Enhance Market Research Analytics Skills

In this lesson, we will learn how to analyze market research and customer analytics data to tell compelling stories to stakeholders. Understanding and…

⬇ 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

Data Storytelling for Stakeholders#

  • In this lesson, we will learn how to analyze market research and customer analytics data to tell compelling stories to stakeholders.
  • Understanding and communicating insights from customer and market data is critical for making better business decisions.
  • You will gain hands-on experience using real datasets to extract, visualize, and explain key findings to non-technical business audiences.
  • We will practice summarizing customer feedback, segmenting audiences, and creating clear recommendations.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import openml
import warnings
warnings.filterwarnings('ignore')

Market Research Data: Structure and Core Concepts#

  • Customer analytics and market research datasets usually contain responses from surveys, demographic information, and behavioral indicators.
  • Each row is typically a survey response or a customer record.
  • Data can include numeric scores (ex: NPS), categorical ratings (ex: 'Yes'/'No'), and open-ended feedback.
  • It is important to check for missing data, inconsistent responses, and understand how responses are coded.
  • Common mistakes: forgetting to check how missing data is represented, aggregating data incorrectly, or misunderstanding the meaning of scale-based responses.
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  
n_missing = df.isnull().sum().sum()
print(f"Total missing values in the survey dataset: {n_missing}")
Total missing values in the survey dataset: 0
churn_counts = df['Churn'].value_counts()
print("Customer Churn Counts:")
print(churn_counts)
Customer Churn Counts:
Churn
No     5174
Yes    1869
Name: count, dtype: int64
gender_churn = df.groupby('gender')['Churn'].value_counts().unstack()
print(gender_churn)
Churn     No  Yes
gender           
Female  2549  939
Male    2625  930
churn_rate = df['Churn'].value_counts(normalize=True).loc['Yes']
print(f"Overall churn rate: {churn_rate:.2%}")
Overall churn rate: 26.54%
sns.countplot(data=df, x='Churn', hue='Contract')
plt.title('Churn by Contract Type')
plt.ylabel('Number of Customers')
plt.xlabel('Churn')
plt.show()
No description has been provided for this image
monthly_charges = df.groupby('Churn')['MonthlyCharges'].mean()
print("Average monthly charges by churn status:")
print(monthly_charges)
Average monthly charges by churn status:
Churn
No     61.265124
Yes    74.441332
Name: MonthlyCharges, dtype: float64
plt.figure(figsize=(8,4))
sns.boxplot(data=df, x='Churn', y='MonthlyCharges')
plt.title('Monthly Charges: Churned vs Not Churned Customers')
plt.show()
No description has been provided for this image
internet_churn = df.groupby('InternetService')['Churn'].value_counts(normalize=True).unstack()
internet_churn['Churn Rate (%)'] = internet_churn['Yes'] * 100
print(internet_churn[['Churn Rate (%)']])
Churn            Churn Rate (%)
InternetService                
DSL                   18.959108
Fiber optic           41.892765
No                     7.404980
survey_summary = df.describe(include='all')
survey_summary.to_csv('survey_summary.csv')
print('Survey summary statistics saved as survey_summary.csv')
Survey summary statistics saved as survey_summary.csv
campaign_dataset = openml.datasets.get_dataset(1461)
df_campaign, _, _, _ = campaign_dataset.get_data(dataset_format='dataframe')
df_campaign.columns = ['age','job','marital','education','default','balance','housing','loan','contact','day','month','duration','campaign','pdays','previous','poutcome','response']
pivot = pd.crosstab(df_campaign['job'], df_campaign['response'], normalize='index') * 100
pivot = pivot.rename(columns={'yes':'Yes', 'no':'No'}) if 'yes' in pivot.columns else pivot
print(pivot)
response               1          2
job                                
admin.         87.797331  12.202669
blue-collar    92.725031   7.274969
entrepreneur   91.728312   8.271688
housemaid      91.209677   8.790323
management     86.244449  13.755551
retired        77.208481  22.791519
self-employed  88.157061  11.842939
services       91.116996   8.883004
student        71.321962  28.678038
technician     88.943004  11.056996
unemployed     84.497314  15.502686
unknown        88.194444  11.805556
sns.heatmap(pivot, annot=True, fmt='.1f', cmap='coolwarm')
plt.title('Campaign Positive Response Rate by Job Segment (%)')
plt.ylabel('Job')
plt.xlabel('Response')
plt.show()
No description has been provided for this image
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)})
nps_df['NPS_Type'] = np.where(nps_df['NPS_Score'] >= 9, 'Promoter',
                    np.where(nps_df['NPS_Score'] <=6, 'Detractor', 'Passive'))
nps_summary = nps_df['NPS_Type'].value_counts(normalize=True) * 100
nps_score = nps_summary['Promoter'] - nps_summary['Detractor']
print('NPS Summary (%) by Type:')
print(nps_summary)
print(f'Overall Net Promoter Score (NPS): {nps_score:.1f}')
NPS Summary (%) by Type:
NPS_Type
Detractor    63.0
Passive      19.0
Promoter     18.0
Name: proportion, dtype: float64
Overall Net Promoter Score (NPS): -45.0
region_nps = nps_df.groupby('Region')['NPS_Type'].value_counts(normalize=True).unstack().fillna(0) * 100
region_nps['NPS'] = region_nps['Promoter'] - region_nps['Detractor']
print(region_nps[['Promoter','Passive','Detractor','NPS']])
NPS_Type   Promoter    Passive  Detractor        NPS
Region                                              
East      15.652174  15.652174  68.695652 -53.043478
North     16.535433  21.259843  62.204724 -45.669291
South     21.698113  22.641509  55.660377 -33.962264
West      18.421053  17.105263  64.473684 -46.052632
missing_nps = nps_df['NPS_Score'].isnull().sum()
print(f"Number of missing NPS scores: {missing_nps}")
Number of missing NPS scores: 0
try:
    bad_group = nps_df.groupby('Region')['NPS_Scorez'].mean()
except Exception as e:
    print('Error grouping by wrong column name:', e)
Error grouping by wrong column name: 'Column not found: NPS_Scorez'
if set(nps_df['NPS_Score'].unique()) - set(range(0,11)):
    print("Warning: Some NPS scores fall outside the 0-10 range!")
else:
    print("All NPS scores are within the valid 0-10 range.")
All NPS scores are within the valid 0-10 range.
segmentation = df.groupby(['SeniorCitizen', 'Churn']).size().unstack()
print('Customer Segmentation by Senior Citizen Status and Churn:')
print(segmentation)
Customer Segmentation by Senior Citizen Status and Churn:
Churn            No   Yes
SeniorCitizen            
0              4508  1393
1               666   476
cross_tab = pd.crosstab(df['gender'], df['Contract'])
print('Cross-tabulation: Gender vs. Contract Type')
print(cross_tab)
Cross-tabulation: Gender vs. Contract Type
Contract  Month-to-month  One year  Two year
gender                                      
Female              1925       718       845
Male                1950       755       850
df['Price_Category'] = pd.cut(df['MonthlyCharges'], bins=[0,40,70,150], labels=['Low','Mid','High'])
price_churn = df.groupby('Price_Category')['Churn'].value_counts(normalize=True).unstack().fillna(0) * 100
print(price_churn)
Churn                  No        Yes
Price_Category                      
Low             88.356910  11.643090
Mid             76.078915  23.921085
High            64.638571  35.361429
trend = df.groupby('tenure')['Churn'].value_counts(normalize=True).unstack().fillna(0)['Yes'] * 100
plt.figure(figsize=(8,4))
plt.plot(trend.index, trend.values)
plt.title('Churn Rate over Customer Tenure')
plt.xlabel('Tenure (months)')
plt.ylabel('Churn Rate (%)')
plt.show()
No description has been provided for this image
# 1. Load open-ended customer feedback
df_feedback = 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(df_feedback)
   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
# 2. Simple keyword extraction
keywords = pd.Series(' '.join(df_feedback['Feedback']).lower().split()).value_counts().head(5)
print('Most common feedback keywords:')
print(keywords)
Most common feedback keywords:
and         2
was         2
great       1
service     1
friendly    1
Name: count, dtype: int64
# 3. Write an executive summary
summary = 'Key customer themes: Delivery speed and packaging need improvement, while service and quality are praised. Customers value support and good prices.'
with open('customer_feedback_summary.txt', 'w') as f:
    f.write(summary)
print('Summary saved as customer_feedback_summary.txt')
Summary saved as customer_feedback_summary.txt
 

Found this useful?

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