Mathew K Analytics

Lesson 4 · Market Research Analytics in Python

Market Research Workflow Using Python: Step-by-Step Training Guide

In this lesson, we will solve real-world market research and customer analytics problems using Python. We will work with genuine business datasets such as…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Market Research Workflow Using Python#

  • In this lesson, we will solve real-world market research and customer analytics problems using Python.
  • We will work with genuine business datasets such as customer satisfaction surveys, campaign data, retail transactions, NPS responses, and customer feedback.
  • Understanding and analyzing customer data helps businesses uncover insights, retain customers, and design more effective products and services.
  • By the end, you will be able to import survey data, perform key analyses, spot mistakes, and deliver insights, metrics, or recommendations that matter.
import pandas as pd
import numpy as np
import openml
import warnings
warnings.filterwarnings('ignore')

Core Market Research Concepts#

  • Market research datasets often contain structured responses: multiple-choice, numeric ratings, text comments, demographic info, purchase history, and more.
  • Customer satisfaction, NPS, and campaign datasets capture how different segments respond to products or services.
  • Beginners often forget to check for missing or inconsistent responses, or misinterpret scales (e.g., thinking a high Likert score is always good).
  • Text feedback is common but must be analyzed differently than numeric data.
  • Segmentation and trend analysis are crucial for turning data into business decisions.
dataset = openml.datasets.get_dataset(42178)
df_cs, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(df_cs.shape)
print(df_cs.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  
dataset = openml.datasets.get_dataset(1461)
df_mkt, _, _, _ = dataset.get_data(dataset_format='dataframe')
df_mkt.columns = ['age','job','marital','education','default','balance','housing','loan','contact','day','month','duration','campaign','pdays','previous','poutcome','response']
print(df_mkt.shape)
print(df_mkt.head(3))
(45211, 17)
   age           job  marital  education default  balance housing loan  \
0   58    management  married   tertiary      no   2143.0     yes   no   
1   44    technician   single  secondary      no     29.0     yes   no   
2   33  entrepreneur  married  secondary      no      2.0     yes  yes   

   contact  day month  duration  campaign  pdays  previous poutcome response  
0  unknown    5   may     261.0         1   -1.0       0.0  unknown        1  
1  unknown    5   may     151.0         1   -1.0       0.0  unknown        1  
2  unknown    5   may      76.0         1   -1.0       0.0  unknown        1  
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')
df_retail['InvoiceDate'] = pd.to_datetime(df_retail['InvoiceDate'])
print(df_retail.shape)
print(df_retail.head(3))
(541910, 8)
  Invoice StockCode                         Description  Quantity  \
0  536365    85123A  WHITE HANGING HEART T-LIGHT HOLDER         6   
1  536365     71053                 WHITE METAL LANTERN         6   
2  536365    84406B      CREAM CUPID HEARTS COAT HANGER         8   

          InvoiceDate  Price  Customer ID         Country  
0 2010-12-01 08:26:00   2.55      17850.0  United Kingdom  
1 2010-12-01 08:26:00   3.39      17850.0  United Kingdom  
2 2010-12-01 08:26:00   2.75      17850.0  United Kingdom  
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(df_nps.shape)
print(df_nps.head(3))
(500, 4)
   CustomerID  Age Region  NPS_Score
0           1   56   West          2
1           2   69  North          0
2           3   46   East          4
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.shape)
print(df_feedback.head(3))
(5, 2)
   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
np.random.seed(0)
dates = pd.date_range('2021-01-01', periods=24, freq='ME')
df_cohort = pd.DataFrame({'CustomerID': np.random.randint(1000,2000,len(dates)), 'Signup_Month': dates, 'Active_Users': np.random.randint(50,300,len(dates))})
print(df_cohort.shape)
print(df_cohort.head(3))
(24, 3)
   CustomerID Signup_Month  Active_Users
0        1684   2021-01-31           138
1        1559   2021-02-28           131
2        1629   2021-03-31           215

Beginner Example 1: Count Churned vs. Retained Customers#

  • Identifying churned customers is vital for retention strategy.
  • We use the customer satisfaction dataset to count how many left vs. stayed.
churn_counts = df_cs['Churn'].value_counts()
print('Churn breakdown:')
print(churn_counts)
Churn breakdown:
Churn
No     5174
Yes    1869
Name: count, dtype: int64

Beginner Example 2: Basic Summary Statistics for NPS#

  • NPS scores range from 0 (least likely to recommend) to 10 (most likely).
  • High average NPS indicates stronger customer advocacy.
nps_mean = df_nps['NPS_Score'].mean()
nps_std = df_nps['NPS_Score'].std()
print(f'Average NPS: {nps_mean:.2f}')
print(f'Standard deviation of NPS: {nps_std:.2f}')
Average NPS: 4.89
Standard deviation of NPS: 3.14

Beginner Example 3: Frequency Table for Internet Service Types#

  • Segmenting service types reveals what options customers choose most.
  • This helps design future offers and communications.
value_freq = df_cs['InternetService'].value_counts()
print('Frequency of Internet Service Types:')
print(value_freq)
Frequency of Internet Service Types:
InternetService
Fiber optic    3096
DSL            2421
No             1526
Name: count, dtype: int64

Intermediate Example 1: NPS by Region Segmentation#

  • Breaking down NPS by geography spots regional strengths and weaknesses.
  • You can target support or marketing by location.
nps_region = df_nps.groupby('Region')['NPS_Score'].mean()
print('Average NPS by Region:')
print(nps_region)
Average NPS by Region:
Region
East     4.504425
North    5.214765
South    4.719008
West     5.025641
Name: NPS_Score, dtype: float64

Intermediate Example 2: Monthly Customer Retention Trend#

  • Tracking active users over time shows retention performance.
  • It helps reveal effects of product changes or campaigns.
trend = df_cohort.set_index('Signup_Month')['Active_Users']
print('Monthly Active Users:')
print(trend)
Monthly Active Users:
Signup_Month
2021-01-31    138
2021-02-28    131
2021-03-31    215
2021-04-30     75
2021-05-31    127
2021-06-30    122
2021-07-31     59
2021-08-31    198
2021-09-30    165
2021-10-31    258
2021-11-30    293
2021-12-31    247
2022-01-31    129
2022-02-28    225
2022-03-31    242
2022-04-30    132
2022-05-31    149
2022-06-30    266
2022-07-31    227
2022-08-31    293
2022-09-30     79
2022-10-31    197
2022-11-30    197
2022-12-31    192
Name: Active_Users, dtype: int32

Intermediate Example 3: Campaign Response Analysis#

  • Understanding who responds to marketing campaigns increases ROI.
  • We measure mean account balance by response in the campaign data.
mean_balance = df_mkt.groupby('response')['balance'].mean()
print('Average Balance by Campaign Response:')
print(mean_balance)
Average Balance by Campaign Response:
response
1    1303.714969
2    1804.267915
Name: balance, dtype: float64

Advanced Example 1: Constructing NPS Categories#

  • You must segment NPS: 0-6 = Detractor, 7-8 = Passive, 9-10 = Promoter.
  • This is the industry standard for NPS reporting.
bins = [0,6,8,10]
labels = ['Detractor','Passive','Promoter']
df_nps['NPS_Category'] = pd.cut(df_nps['NPS_Score'], bins=[-1,6,8,10], labels=labels)
cat_counts = df_nps['NPS_Category'].value_counts()
print('Counts by NPS Category:')
print(cat_counts)
Counts by NPS Category:
NPS_Category
Detractor    323
Passive       92
Promoter      85
Name: count, dtype: int64

Advanced Example 2: Open-Ended Feedback Sentiment Keyword Search#

  • Customers describe good and bad experiences in their own words.
  • Finding keywords related to issues or praise helps prioritize business actions.
keyword = 'improvement'
hits = df_feedback['Feedback'].str.lower().str.contains(keyword)
print(f'Customers mentioning "{keyword}":')
print(df_feedback[hits])
Customers mentioning "improvement":
   CustomerID                            Feedback
3           4  Customer support needs improvement
missing = df_cs.isnull().sum()
print('Missing survey responses per column:')
print(missing[missing > 0])
Missing survey responses per column:
Series([], dtype: int64)
grouped = df_mkt.groupby('job')['balance'].sum()
print('Total balance by job:')
print(grouped)
incorrect_total = grouped.sum()
dataset_total = df_mkt['balance'].sum()
print(f'Check: Grouped total = {incorrect_total}, Actual total = {dataset_total}')
Total balance by job:
job
admin.            5873423.0
blue-collar      10499141.0
entrepreneur      2262426.0
housemaid         1726570.0
management       16680288.0
retired           4492263.0
self-employed     2602146.0
services          4141904.0
student           1302001.0
technician        9516246.0
unemployed        1982835.0
unknown            510439.0
Name: balance, dtype: float64
Check: Grouped total = 61589682.0, Actual total = 61589682.0
nps_likert = [0,1,2,3,4,5,6,7,8,9,10]
for val in nps_likert:
    if val <= 6:
        category = 'Detractor'
    elif val <= 8:
        category = 'Passive'
    else:
        category = 'Promoter'
    print(f'NPS {val}: {category}')
NPS 0: Detractor
NPS 1: Detractor
NPS 2: Detractor
NPS 3: Detractor
NPS 4: Detractor
NPS 5: Detractor
NPS 6: Detractor
NPS 7: Passive
NPS 8: Passive
NPS 9: Promoter
NPS 10: Promoter

Best Practices: Segment, Cross-Tab, and Track Trends#

  • Always check for missing data and results that make business sense.
  • Segment by region, age, or service type to uncover hidden opportunities.
  • Cross-tabulate customer demographics with behavior for deeper insights.
  • Build simple indexes like Customer Satisfaction or NPS consistently.
  • Track key metrics (like retention or NPS) monthly to diagnose change.
ctab = pd.crosstab(df_cs['SeniorCitizen'], df_cs['InternetService'])
print('Cross-tabulation: Senior Citizen vs. Internet Service')
print(ctab)
Cross-tabulation: Senior Citizen vs. Internet Service
InternetService   DSL  Fiber optic    No
SeniorCitizen                           
0                2162         2265  1474
1                 259          831    52
segmentation = df_nps.groupby(['Region', 'NPS_Category']).size().unstack(fill_value=0)
print('NPS Segmentation by Region:')
print(segmentation)
NPS Segmentation by Region:
NPS_Category  Detractor  Passive  Promoter
Region                                    
East                 76       20        17
North                86       38        25
South                84       19        18
West                 77       15        25
monthly_charges_trend = df_cs.groupby('tenure')['MonthlyCharges'].mean()
print('Average Monthly Charges by Tenure:')
print(monthly_charges_trend.head())
Average Monthly Charges by Tenure:
tenure
0    41.418182
1    50.485808
2    57.206303
3    58.015000
4    57.432670
Name: MonthlyCharges, dtype: float64

End-to-End Mini Project: Identify At-Risk Customer Segment#

  • Goal: Use survey and behavior data to spot a customer group with high churn risk.
  • Step 1: Find all senior citizens with fiber optic internet who have churned.
  • Step 2: Count and recommend a next step for the business.
  • This is a classic real-world task every market research analyst performs.
at_risk = df_cs[(df_cs['SeniorCitizen'] == 1) & (df_cs['InternetService'] == 'Fiber optic') & (df_cs['Churn'] == 'Yes')]
count_risk = at_risk.shape[0]
print(f'Number of at-risk senior fiber optic customers who churned: {count_risk}')
if count_risk > 0:
    print('Action Recommendation: Launch a retention campaign or survey for these customers.')
else:
    print('No at-risk customers found in this segment.')
Number of at-risk senior fiber optic customers who churned: 393
Action Recommendation: Launch a retention campaign or survey for these customers.
 

Found this useful?

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