Mathew K Analytics

Lesson 20 · Market Research Analytics in Python

How to Prepare Clean, Analysis-Ready Datasets for Market Research Using Python

In market research and customer analytics, preparing clean datasets is essential before any analysis. Dirty or inconsistent data often leads to misleading…

⬇ 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

Preparing Clean Analysis-Ready Datasets for Market Research#

  • In market research and customer analytics, preparing clean datasets is essential before any analysis.
  • Dirty or inconsistent data often leads to misleading business insights and poor decision-making.
  • In this lesson, you will learn best practices for transforming customer, survey, and campaign datasets into analysis-ready tables.
  • We will focus on filtering, missing data, fixing data types, removing duplicates, segmenting, and preparing aggregated features.
  • By the end, you will be able to take messy customer data to actionable business metrics.
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import openml

What are Market Research Datasets?#

  • In market research, datasets can come from surveys, transaction logs, or campaign responses.
  • Each row usually represents a customer, a transaction, or a survey response.
  • Data can include demographics, satisfaction ratings, open-ended feedback, and calculated scores (like NPS).
  • Beginners often struggle with missing values, wrongly typed columns, and interpreting survey scales.
  • Always check for consistency and validity before analyzing customer or survey data.
# Beginner Example 1: Load a customer satisfaction survey dataset (OpenML)
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  
# Beginner Example 2: Load a bank marketing campaign dataset (OpenML)
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  
# Beginner Example 3: Load an online retail transaction dataset (UCI)
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  
# Beginner Example 4: Creating a synthetic NPS survey results dataset
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
# Beginner Example 5: Importing open-ended customer feedback data
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
# Beginner Example 6: Create customer cohort and retention dataset
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

Intermediate Data Cleaning Steps#

  • Real-world data often has missing values, quirky formats, or inconsistent category labels.
  • You must check column dtypes, handle missing entries, and standardize values.
  • Always look for duplicate records, which can skew your business conclusions.
  • Market research data with inconsistent segment or score labels can block valid comparisons.
  • Explore and repair your data before building any models or dashboards.
# Intermediate Example 1: Detect and summarize missing data (customer satisfaction)
missing_counts = df_cs.isnull().sum()
print('Missing values by column:')
print(missing_counts[missing_counts > 0])
Missing values by column:
Series([], dtype: int64)
# Intermediate Example 2: Check for and drop duplicate rows
print('Number of duplicates:', df_cs.duplicated().sum())
df_cs_clean = df_cs.drop_duplicates()
print('Shape after deduplication:', df_cs_clean.shape)
Number of duplicates: 22
Shape after deduplication: (7021, 20)
# Intermediate Example 3: Convert TotalCharges to float and handle errors
df_cs_clean['TotalCharges'] = pd.to_numeric(df_cs_clean['TotalCharges'], errors='coerce')
print('Total null values after conversion:', df_cs_clean['TotalCharges'].isnull().sum())
Total null values after conversion: 11
# Intermediate Example 4: Standardize category labels (marketing campaign jobs)
print('Unique job values before cleaning:', sorted(df_mkt['job'].unique()))
df_mkt['job'] = df_mkt['job'].str.lower().str.strip()
print('Unique job values after cleaning:', sorted(df_mkt['job'].unique()))
Unique job values before cleaning: ['admin.', 'blue-collar', 'entrepreneur', 'housemaid', 'management', 'retired', 'self-employed', 'services', 'student', 'technician', 'unemployed', 'unknown']
Unique job values after cleaning: ['admin.', 'blue-collar', 'entrepreneur', 'housemaid', 'management', 'retired', 'self-employed', 'services', 'student', 'technician', 'unemployed', 'unknown']
# Intermediate Example 5: Identify negative or out-of-range values
invalid_ages = df_nps[(df_nps['Age'] < 0) | (df_nps['Age'] > 120)]
print('Number of invalid ages:', invalid_ages.shape[0])
Number of invalid ages: 0

Advanced Cleaning and Transformation#

  • For deep business insights, analysis-ready data often needs feature engineering and segmentation.
  • You will commonly aggregate or pivot transaction data, or create new score columns.
  • Business coding frequently means merging, filtering, and reshaping customer/response tables.
  • Validated segment definitions and proper types are crucial for reproducibility.
  • Always record how you transformed data so you and your team can reproduce insights.
# Advanced Example 1: Calculate customer tenure groups
df_cs_clean['tenure_group'] = pd.cut(df_cs_clean['tenure'], bins=[0, 12, 24, 48, 72], labels=['<1yr','1-2yr','2-4yr','4-6yr'])
print(df_cs_clean[['tenure','tenure_group']].head(5))
   tenure tenure_group
0       1         <1yr
1      34        2-4yr
2       2         <1yr
3      45        2-4yr
4       2         <1yr
# Advanced Example 2: Aggregate retail transactions to find total spend per customer
df_retail_cust = df_retail.dropna(subset=['Customer ID'])
df_retail_cust['TotalSpend'] = df_retail_cust['Quantity'] * df_retail_cust['Price']
spend_by_customer = df_retail_cust.groupby('Customer ID')['TotalSpend'].sum().reset_index()
print(spend_by_customer.head())
   Customer ID  TotalSpend
0      12346.0        0.00
1      12347.0     4310.00
2      12348.0     1797.24
3      12349.0     1757.55
4      12350.0      334.40
# Advanced Example 3: Segment NPS respondents
def nps_segment(score):
    if score >= 9:
        return 'Promoter'
    elif score >= 7:
        return 'Passive'
    else:
        return 'Detractor'
df_nps['NPS_Type'] = df_nps['NPS_Score'].apply(nps_segment)
print(df_nps[['NPS_Score','NPS_Type']].head(10))
   NPS_Score   NPS_Type
0          2  Detractor
1          0  Detractor
2          4  Detractor
3          3  Detractor
4          9   Promoter
5          7    Passive
6          0  Detractor
7          9   Promoter
8          0  Detractor
9          3  Detractor
# Advanced Example 4: Create cohort month from signup dates for retention analysis
df_cohort['CohortMonth'] = df_cohort['Signup_Month'].dt.to_period('M')
print(df_cohort[['Signup_Month','CohortMonth']].head(5))
  Signup_Month CohortMonth
0   2021-01-31     2021-01
1   2021-02-28     2021-02
2   2021-03-31     2021-03
3   2021-04-30     2021-04
4   2021-05-31     2021-05
# Advanced Example 5: Merge NPS and Feedback for richer analytics
df_merged = pd.merge(df_nps, df_feedback, on='CustomerID', how='left')
print(df_merged.head(5))
   CustomerID  Age Region  NPS_Score   NPS_Type  \
0           1   56   West          2  Detractor   
1           2   69  North          0  Detractor   
2           3   46   East          4  Detractor   
3           4   32   West          3  Detractor   
4           5   60   East          9   Promoter   

                                   Feedback  
0          Great service and friendly staff  
1  Delivery was slow and packaging was poor  
2         Excellent quality, will buy again  
3        Customer support needs improvement  
4                      Good value for money  

Error Handling and Debugging in Dataset Preparation#

  • Market research data is rarely perfect.
  • Watch for missing survey item responses, invalid types, and misaggregations.
  • Pay close attention to Likert scales, NPS ranges, and customer segmentation steps.
  • Always confirm the result after each cleaning or transformation.
  • Even small mistakes can lead to incorrect recommendations.
# Debug Example 1: Finding rows with all survey items missing
missing_items = df_cs_clean[df_cs_clean[['MonthlyCharges','TotalCharges','tenure']].isnull().all(axis=1)]
print('Rows with all key items missing:', missing_items.shape[0])
Rows with all key items missing: 0
# Debug Example 2: Aggregating NPS scores by group, check for misgroupings
nps_by_region = df_nps.groupby('Region')['NPS_Score'].mean()
print(nps_by_region)
Region
East     4.504425
North    5.214765
South    4.719008
West     5.025641
Name: NPS_Score, dtype: float64
# Debug Example 3: Checking NPS scoring conformity
not_in_range = df_nps[~df_nps['NPS_Score'].between(0,10)]
print('Out-of-range NPS records:', not_in_range.shape[0])
Out-of-range NPS records: 0

Best Practices for Analysis-Ready Datasets#

  • Always document each cleaning and transformation step clearly.
  • Use segmentation to compare customer groups (e.g., Promoters vs. Detractors).
  • Cross-tabulate categorical data with outcomes to detect patterns.
  • Index construction (such as Customer Value Index) creates new business features.
  • Trend analysis over time clarifies shifts in customer satisfaction.
# Pattern Example 1: Cross-tabulate segment with churn outcome
if 'Churn' in df_cs_clean.columns:
    crosstab = pd.crosstab(df_cs_clean['tenure_group'], df_cs_clean['Churn'])
    print(crosstab)
Churn           No   Yes
tenure_group            
<1yr          1128  1025
1-2yr          730   294
2-4yr         1269   325
4-6yr         2026   213
# Pattern Example 2: Construct a simple customer value index
if 'MonthlyCharges' in df_cs_clean.columns and 'tenure' in df_cs_clean.columns:
    df_cs_clean['CustomerValue'] = df_cs_clean['MonthlyCharges'] * df_cs_clean['tenure']
    print(df_cs_clean[['MonthlyCharges','tenure','CustomerValue']].head())
   MonthlyCharges  tenure  CustomerValue
0           29.85       1          29.85
1           56.95      34        1936.30
2           53.85       2         107.70
3           42.30      45        1903.50
4           70.70       2         141.40
# Pattern Example 3: Analyze trend in average NPS by region
avg_nps_by_region = df_nps.groupby('Region')['NPS_Score'].mean()
print('Average NPS by region:')
print(avg_nps_by_region)
Average NPS by region:
Region
East     4.504425
North    5.214765
South    4.719008
West     5.025641
Name: NPS_Score, dtype: float64
# Pattern Example 4: Visualize active users per cohort month (trend analysis)
import matplotlib.pyplot as plt
plt.figure(figsize=(7,3))
df_cohort.groupby('CohortMonth')['Active_Users'].mean().plot(marker='o')
plt.title('Active Users per CohortMonth')
plt.ylabel('Active Users')
plt.xlabel('CohortMonth')
plt.tight_layout()
plt.show()
No description has been provided for this image
# End-to-End Example: Prepare a pipeline for analysis-ready customer satisfaction data
def prepare_cs(df):
    df = df.drop_duplicates()
    df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
    df = df.dropna(subset=['MonthlyCharges','TotalCharges','tenure'])
    df['tenure_group'] = pd.cut(df['tenure'],bins=[0,12,24,48,72],labels=['<1yr','1-2yr','2-4yr','4-6yr'])
    if 'Churn' in df.columns:
        crosstab = pd.crosstab(df['tenure_group'], df['Churn'])
        print(crosstab)
    else:
        print('Churn column not found!')
prepare_cs(df_cs)
Churn           No   Yes
tenure_group            
<1yr          1128  1025
1-2yr          730   294
2-4yr         1269   325
4-6yr         2026   213
 

Found this useful?

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