Mathew K Analytics

Lesson 22 · Market Research Analytics in Python

Master Frequency Tables & Percentage Analysis for Market Research Using Python

We will learn how to use frequency tables and percentage analysis to understand survey, customer, and campaign data. These methods help marketers 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

Frequency Tables and Percentage Analysis in Market Research#

  • We will learn how to use frequency tables and percentage analysis to understand survey, customer, and campaign data.
  • These methods help marketers and analysts identify customer traits, product feedback, and market trends for decision making.
  • You will practice creating, interpreting, and communicating frequency statistics that reveal what your customers think or do.
  • By the end, you will be able to turn raw responses into useful, business-driven insights.
import pandas as pd
import numpy as np
import openml
import warnings
warnings.filterwarnings('ignore')

Understanding Survey and Market Data Structure#

  • Market research data often comes from customer surveys, campaign records, or feedback comments.
  • Responses can be numerical (rating scales), categorical (yes, no, male, female), or text (comments).
  • Frequency tables summarize how often each response or group appears.
  • Beginners often forget to handle missing values, mislabel columns, or misinterpret category meanings.
# Beginner Example 1: Frequency table of gender in customer satisfaction survey
dataset = openml.datasets.get_dataset(42178)
df_cs, _, _, _ = dataset.get_data(dataset_format='dataframe')
freq_gender = df_cs['gender'].value_counts()
print('Gender Frequency Table:')
print(freq_gender)
Gender Frequency Table:
gender
Male      3555
Female    3488
Name: count, dtype: int64
# Beginner Example 2: Creating a percentage table for contract types
contract_counts = df_cs['Contract'].value_counts(normalize=True) * 100
print('Contract Type Percentage Table:')
print(contract_counts.round(2))
Contract Type Percentage Table:
Contract
Month-to-month    55.02
Two year          24.07
One year          20.91
Name: proportion, dtype: float64
# Beginner Example 3: Frequency table for churned customers
freq_churn = df_cs['Churn'].value_counts()
print('Customer Churn Frequency Table:')
print(freq_churn)
Customer Churn Frequency Table:
Churn
No     5174
Yes    1869
Name: count, dtype: int64
# Intermediate Example 1: Cross-tabulating gender vs churn
cross_table = pd.crosstab(df_cs['gender'], df_cs['Churn'])
print('Cross-Tab: Gender vs Churn')
print(cross_table)
Cross-Tab: Gender vs Churn
Churn     No  Yes
gender           
Female  2549  939
Male    2625  930
# Intermediate Example 2: Add margins (totals) to crosstab
cross_table_margins = pd.crosstab(df_cs['gender'], df_cs['Churn'], margins=True)
print('Cross-Tab with Totals: Gender vs Churn')
print(cross_table_margins)
Cross-Tab with Totals: Gender vs Churn
Churn     No   Yes   All
gender                  
Female  2549   939  3488
Male    2625   930  3555
All     5174  1869  7043
# Intermediate Example 3: Percentage breakdown by payment method
payment_perc = df_cs['PaymentMethod'].value_counts(normalize=True) * 100
print('Payment Method Percentages:')
print(payment_perc.round(1))
Payment Method Percentages:
PaymentMethod
Electronic check             33.6
Mailed check                 22.9
Bank transfer (automatic)    21.9
Credit card (automatic)      21.6
Name: proportion, dtype: float64
# Intermediate Example 4: Frequency table from synthetic NPS survey data
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)
})
freq_nps = df_nps['NPS_Score'].value_counts().sort_index()
print('NPS Score Frequency Table:')
print(freq_nps)
NPS Score Frequency Table:
NPS_Score
0     52
1     39
2     50
3     45
4     45
5     49
6     43
7     55
8     37
9     43
10    42
Name: count, dtype: int64
# Intermediate Example 5: Percentage analysis of regions in NPS survey
region_perc = df_nps['Region'].value_counts(normalize=True) * 100
print('Region Percentage Table:')
print(region_perc.round(1))
Region Percentage Table:
Region
North    29.8
South    24.2
West     23.4
East     22.6
Name: proportion, dtype: float64
# Advanced Example 1: Frequency table for multi-categorical market campaign responses
dataset = openml.datasets.get_dataset(1461)
df_marketing, _, _, _ = dataset.get_data(dataset_format='dataframe')
df_marketing.columns = ['age','job','marital','education','default','balance','housing','loan','contact','day','month','duration','campaign','pdays','previous','poutcome','response']
freq_response = df_marketing['response'].value_counts()
print('Marketing Campaign Response Frequency:')
print(freq_response)
Marketing Campaign Response Frequency:
response
1    39922
2     5289
Name: count, dtype: int64
# Advanced Example 2: Percentage analysis by education for campaign responders
edu_response = pd.crosstab(df_marketing['education'], df_marketing['response'], normalize='index') * 100
print('Education vs Response Rate (%)')
print(edu_response.round(1))
Education vs Response Rate (%)
response      1     2
education            
primary    91.4   8.6
secondary  89.4  10.6
tertiary   85.0  15.0
unknown    86.4  13.6
# Advanced Example 3: Multi-level frequency table: Marital, Job, Campaign Response
multi_table = pd.crosstab([df_marketing['marital'], df_marketing['job']], df_marketing['response'])
print('Multi-level Frequency Table: Marital Status, Job, Campaign Response')
print(multi_table.head(10))
Multi-level Frequency Table: Marital Status, Job, Campaign Response
response                  1    2
marital  job                    
divorced admin.         660   90
         blue-collar    692   58
         entrepreneur   164   15
         housemaid      166   18
         management     969  142
         retired        304  121
         self-employed  118   22
         services       499   50
         student          5    1
         technician     848   77
# Advanced Example 4: Trend analysismonthly percentage of positive campaign responses
df_marketing['month'] = df_marketing['month'].astype(str)
monthly_responses = df_marketing[df_marketing['response']=='yes']['month'].value_counts()
total_per_month = df_marketing['month'].value_counts()
percentage_positive = (monthly_responses / total_per_month * 100).sort_index()
print('Monthly Percentage of Yes Responses:')
print(percentage_positive.round(2))
Monthly Percentage of Yes Responses:
month
apr   NaN
aug   NaN
dec   NaN
feb   NaN
jan   NaN
jul   NaN
jun   NaN
mar   NaN
may   NaN
nov   NaN
oct   NaN
sep   NaN
Name: count, dtype: float64
# Error Handling Example 1: Detecting and handling missing survey responses
missing_contract = df_cs['Contract'].isnull().sum()
print(f'Total missing Contract responses: {missing_contract}')
if missing_contract > 0:
    # Optionally: fill or remove missing values
    df_cs = df_cs.dropna(subset=['Contract'])
    print('Missing Contract responses have been removed.')
Total missing Contract responses: 0
# Error Handling Example 2: Grouping issueaccidentally grouping on incorrect key
try:
    bad_group = df_cs.groupby('CustomerID')['gender'].value_counts()
    print('Bad grouping result:')
    print(bad_group.head())
except Exception as e:
    print('Grouping error:', e)
Grouping error: 'CustomerID'
# Error Handling Example 3: Misinterpreting NPS scalepercentages for Promoters, Passives, Detractors
nps_counts = df_nps['NPS_Score'].value_counts().sort_index()
nps_labels = pd.cut(df_nps['NPS_Score'], bins=[-1,6,8,10], labels=['Detractor','Passive','Promoter'])
category_perc = nps_labels.value_counts(normalize=True) * 100
print('NPS Categories (%):')
print(category_perc.round(1))
NPS Categories (%):
NPS_Score
Detractor    64.6
Passive      18.4
Promoter     17.0
Name: proportion, dtype: float64

Best Practices in Frequency Analysis#

  • Always segment your data by relevant business categories (e.g., gender, contract, region).
  • Use cross-tabulations to reveal hidden patterns between multiple factors.
  • Build simple indices like NPS for quick executive insights.
  • Monitor trends over time to catch issues or opportunities early.
  • Clean and document your categoriesaccuracy here means accurate business guidance.
# Pattern Example: Segmentation on payment method in customer satisfaction data
segmented = df_cs.groupby('PaymentMethod')['Churn'].value_counts().unstack().fillna(0)
print('Churn Segmentation by Payment Method:')
print(segmented)
Churn Segmentation by Payment Method:
Churn                        No   Yes
PaymentMethod                        
Bank transfer (automatic)  1286   258
Credit card (automatic)    1290   232
Electronic check           1294  1071
Mailed check               1304   308
# Pattern Example: Cross-tab on contract and tenure to monitor customer retention
df_cs['tenure_bin'] = pd.cut(df_cs['tenure'], bins=[0,12,24,36,48,60,df_cs['tenure'].max()], labels=['0-12','13-24','25-36','37-48','49-60','60+'])
contract_tenure = pd.crosstab(df_cs['Contract'], df_cs['tenure_bin'])
print('Contract vs Tenure Cross-Tab:')
print(contract_tenure)
Contract vs Tenure Cross-Tab:
tenure_bin      0-12  13-24  25-36  37-48  49-60  60+
Contract                                             
Month-to-month  1994    737    486    316    234  108
One year         123    197    250    268    321  313
Two year          58     90     96    178    277  986

End-to-End Example: From Survey Data to Actionable Recommendation#

  • Suppose marketing just ran a survey and wants to know: Which region has the highest promoter rate?
  • We will analyze NPS survey data by region and report which region is most likely to recommend the company.
  • Steps: Categorize NPS responses, create a region breakdown, and highlight the top region for promoters.
# Step 1: Label NPS categories and count promoters per region
df_nps['Category'] = pd.cut(df_nps['NPS_Score'], bins=[-1,6,8,10], labels=['Detractor','Passive','Promoter'])
region_promoters = df_nps[df_nps['Category']=='Promoter']['Region'].value_counts()
region_total = df_nps['Region'].value_counts()
region_promoter_rate = (region_promoters / region_total * 100).fillna(0)
best_region = region_promoter_rate.idxmax()
print('Promoter Percentage by Region:')
print(region_promoter_rate.round(1))
print(f'Highest promoter rate: {best_region}')
Promoter Percentage by Region:
Region
East     15.0
North    16.8
South    14.9
West     21.4
Name: count, dtype: float64
Highest promoter rate: West
 

Found this useful?

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