Mathew K Analytics

Lesson 52 · Market Research Analytics in Python

Writing Insight-Driven Market Research Summaries in Python

Businesses need to distill large datasets into clear, actionable insights. Market research summaries help decision makers understand customer trends,…

⬇ Download notebookOpen in Colab ↗
pandasNumPyTextBlob

What you'll learn

Data

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

📓 Full notebook

Download .ipynb

Writing Insight-Driven Market Research Summaries#

  • Businesses need to distill large datasets into clear, actionable insights.
  • Market research summaries help decision makers understand customer trends, product opportunities, and key areas to improve.
  • In this lesson, you will use Python to analyze real market research and customer analytics data, then write concise, evidence-backed summaries.
  • You will practice extracting findings about customer satisfaction, campaign effectiveness, and more.
import pandas as pd
import numpy as np
import openml
import warnings
warnings.filterwarnings('ignore')

Market research data and survey analytics concepts#

  • Customer survey datasets contain responses, demographic data, and sometimes open-ended feedback.
  • Responses may use scales (like 1-5 satisfaction, or NPS 0-10), yes/no, or free text answers.
  • Demographic columns (age, region, income) enable segmentation and subgroup analysis.
  • Common beginner mistakes include ignoring missing values, misreading scale directions, or reporting findings without samples or context.
# Beginner Example 1: Load a customer satisfaction survey dataset
dataset = openml.datasets.get_dataset(42178)
satisfaction_df, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(satisfaction_df.shape)
print(satisfaction_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: Calculate the overall churn rate (percentage of customers who left)
churn_rate = satisfaction_df['Churn'].value_counts(normalize=True).get('Yes', 0) * 100
print(f'Churn Rate: {churn_rate:.2f}%')
Churn Rate: 26.54%
# Beginner Example 3: Count the most common contract type
common_contract = satisfaction_df['Contract'].value_counts().idxmax()
print(f'Most common contract type: {common_contract}')
Most common contract type: Month-to-month
# Beginner Example 4: Load a marketing campaign dataset
campaign_dataset = openml.datasets.get_dataset(1461)
campaign_df, _, _, _ = campaign_dataset.get_data(dataset_format='dataframe')
campaign_df.columns = ['age','job','marital','education','default','balance','housing','loan','contact','day','month','duration','campaign','pdays','previous','poutcome','response']
print(campaign_df.shape)
print(campaign_df.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  
# Beginner Example 5: Calculate the response rate for a marketing campaign
response_rate = campaign_df['response'].value_counts(normalize=True).get('2', 0) * 100
print(f'Response Rate: {response_rate:.2f}%')
Response Rate: 11.70%
# Intermediate Example 1: Segment churn rate by contract type
churn_by_contract = satisfaction_df.groupby('Contract')['Churn'].value_counts(normalize=True).unstack().get('Yes', 0) * 100
print('Churn Rate by Contract Type:')
print(churn_by_contract)
Churn Rate by Contract Type:
Contract
Month-to-month    42.709677
One year          11.269518
Two year           2.831858
Name: Yes, dtype: float64
# Intermediate Example 2: Average monthly charges for customers by churn status
avg_charges = satisfaction_df.groupby('Churn')['MonthlyCharges'].mean()
print('Average Monthly Charges grouped by Churn:')
print(avg_charges)
Average Monthly Charges grouped by Churn:
Churn
No     61.265124
Yes    74.441332
Name: MonthlyCharges, dtype: float64
# Intermediate Example 3: Creating a Net Promoter Score (NPS) analysis
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)
})
n_promoters = (nps_df['NPS_Score'] >= 9).sum()
n_detractors = (nps_df['NPS_Score'] <= 6).sum()
total_responses = nps_df.shape[0]
nps = ((n_promoters - n_detractors) / total_responses) * 100
print(f'Net Promoter Score: {nps:.1f}')
Net Promoter Score: -47.6
# Intermediate Example 4: Segment NPS by region
nps_by_region = nps_df.groupby('Region')['NPS_Score'].agg([
    lambda x: (x >= 9).sum(),
    lambda x: (x <= 6).sum(),
    'count'
])
nps_by_region.columns = ['Promoters', 'Detractors', 'Total']
nps_by_region['NPS'] = ((nps_by_region['Promoters'] - nps_by_region['Detractors']) / nps_by_region['Total']) * 100
print(nps_by_region[['NPS']])
              NPS
Region           
East   -52.212389
North  -40.939597
South  -54.545455
West   -44.444444
# Intermediate Example 5: Analyze open-ended customer feedback with sentiment scoring
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'
    ]})
from textblob import TextBlob
feedback_df['Sentiment'] = feedback_df['Feedback'].apply(lambda x: TextBlob(x).sentiment.polarity)
print(feedback_df)
   CustomerID                                  Feedback  Sentiment
0           1          Great service and friendly staff     0.5875
1           2  Delivery was slow and packaging was poor    -0.3500
2           3         Excellent quality, will buy again     1.0000
3           4        Customer support needs improvement     0.0000
4           5                      Good value for money     0.7000
# Advanced Example 1: Segment campaign response rates by education
response_by_edu = campaign_df.groupby('education')['response'].value_counts(normalize=True).unstack().get('2', 0) * 100
print(response_by_edu.sort_values(ascending=False))
education
tertiary     15.006390
unknown      13.570275
secondary    10.559435
primary       8.626478
Name: 2, dtype: float64
# Advanced Example 2: Analyze trends in customer retention using synthetic cohort data
np.random.seed(0)
dates = pd.date_range('2021-01-01', periods=24, freq='ME')
cohort_df = pd.DataFrame({
    'CustomerID': np.random.randint(1000, 2000, len(dates)),
    'Signup_Month': dates,
    'Active_Users': np.random.randint(50, 300, len(dates))
})
cohort_df['Month'] = cohort_df['Signup_Month'].dt.strftime('%Y-%m')
monthly_users = cohort_df.groupby('Month')['Active_Users'].sum()
monthly_users.plot(title='Monthly Active Users Trend')
<Axes: title={'center': 'Monthly Active Users Trend'}, xlabel='Month'>
No description has been provided for this image
# Advanced Example 3: Cross-tabulation of churn by contract and payment method
ct = pd.crosstab(satisfaction_df['Contract'], satisfaction_df['PaymentMethod'], values=(satisfaction_df['Churn']=='Yes'), aggfunc='mean').fillna(0)*100
print('Churn % by Contract and Payment Method:')
print(ct.round(1))
Churn % by Contract and Payment Method:
PaymentMethod   Bank transfer (automatic)  Credit card (automatic)  \
Contract                                                             
Month-to-month                       34.1                     32.8   
One year                              9.7                     10.3   
Two year                              3.4                      2.2   

PaymentMethod   Electronic check  Mailed check  
Contract                                        
Month-to-month              53.7          31.6  
One year                    18.4           6.8  
Two year                     7.7           0.8  
# Error Handling Example 1: Detecting missing survey responses
missing_counts = satisfaction_df.isnull().sum()
print('Missing values in each column:')
print(missing_counts[missing_counts > 0])
Missing values in each column:
Series([], dtype: int64)
# Error Handling Example 2: Catching incorrect groupings
try:
    bogus_group = satisfaction_df.groupby('NonexistentColumn')['Churn'].mean()
except Exception as e:
    print('Grouping error:', e)
Grouping error: 'NonexistentColumn'
# Error Handling Example 3: Misinterpreting NPS scale
sample_nps = pd.Series([10,9,8,7,6,5,4,0])
promoters = (sample_nps >= 9).sum()
detractors = (sample_nps <= 6).sum()
nps_score = ((promoters - detractors) / len(sample_nps)) * 100
print('Correct NPS calculation:', nps_score)
Correct NPS calculation: -25.0

Market research best practices and key analytics patterns#

  • Segment your analysis to find differences and opportunities across groups.
  • Use cross-tabs and pivot tables to reveal relationships between variables.
  • Construct composite scores (like NPS, CSAT, loyalty index) for clearer insights.
  • Always check for trends over time to support long-term business decisions.
  • Document all assumptions and data quality issues in your summary.
# Best Practice: Create a segmentation index by combining multiple features
satisfaction_df['HighValue'] = (satisfaction_df['MonthlyCharges'] > 80) & (satisfaction_df['tenure'] > 24)
segment_churn = satisfaction_df.groupby('HighValue')['Churn'].value_counts(normalize=True).unstack().get('Yes', 0) * 100
print('Churn Rate by HighValue segment:')
print(segment_churn)
Churn Rate by HighValue segment:
HighValue
False    28.406467
True     21.277748
Name: Yes, dtype: float64
# Best Practice: Run a trend analysis of campaign response rate over calendar month
campaign_df['month_num'] = pd.to_datetime(campaign_df['month'].astype(str), format='%b').dt.month.fillna(0).astype(int)
monthly_resp = campaign_df.groupby('month_num')['response'].value_counts(normalize=True).unstack().get('2', 0) * 100
monthly_resp = monthly_resp[monthly_resp.index > 0]
monthly_resp.plot(title='Campaign Positive Response Rate by Month')
<Axes: title={'center': 'Campaign Positive Response Rate by Month'}, xlabel='month_num'>
No description has been provided for this image
# End-to-end example: From raw NPS scores to an executive summary
np.random.seed(42)
summary_nps_df = pd.DataFrame({'Region': np.random.choice(['North', 'South', 'East', 'West'], 200),
                                'NPS_Score': np.random.randint(0, 11, 200)})
nps_summary = summary_nps_df.groupby('Region')['NPS_Score'].agg([
    lambda x: (x >= 9).sum(),
    lambda x: (x <= 6).sum(),
    'size'
])
nps_summary.columns = ['Promoters', 'Detractors', 'Total']
nps_summary['NPS'] = ((nps_summary['Promoters'] - nps_summary['Detractors']) / nps_summary['Total']) * 100
top_region = nps_summary['NPS'].idxmax()
bottom_region = nps_summary['NPS'].idxmin()
with open('market_research_summary.txt', 'w') as f:
    for region, row in nps_summary.iterrows():
        f.write(f'Region: {region}, NPS: {row.NPS:.1f}\n')
    f.write(f'Highest NPS: {top_region}\n')
    f.write(f'Lowest NPS: {bottom_region}\n')
print(nps_summary[['NPS']])
              NPS
Region           
East   -66.666667
North  -63.043478
South  -47.826087
West   -48.148148
 

Found this useful?

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