Mathew K Analytics

Lesson 8 · Market Research Analytics in Python

Numerical Analysis with NumPy for Market Data Training

Learn how to use NumPy to analyze real-world market research and customer analytics datasets. We focus on basic and advanced numerical techniques to uncover…

⬇ 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

Numerical Analysis with NumPy for Market Data#

  • Learn how to use NumPy to analyze real-world market research and customer analytics datasets.
  • We focus on basic and advanced numerical techniques to uncover business insights.
  • You will analyze customer satisfaction, survey scores, and campaign performance using Python.
  • These skills help companies make data-driven decisions, improve products, and understand market trends.
  • By the end, you will summarize, aggregate, and visualize customer data to answer real business questions.
import pandas as pd
import numpy as np
import openml
import warnings
warnings.filterwarnings('ignore')

Understanding Market Data and Surveys#

  • Market research data can include customer responses, satisfaction scores, or sales information.
  • Datasets often have columns for demographic data, survey scores, and purchase behavior.
  • Poor data handling may include misclassifying survey scales, missing values, or incorrect aggregations.
  • Beginners often forget to validate data types or miss handling negative responses correctly.
# Load the Customer Satisfaction dataset from OpenML
dataset = openml.datasets.get_dataset(42178)
df = dataset.get_data(dataset_format='dataframe')[0]
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  
# Beginner: Calculate the mean tenure of customers
mean_tenure = df['tenure'].mean()
print(f'Average customer tenure: {mean_tenure:.2f} months')
Average customer tenure: 32.37 months
# Beginner: Find the proportion of customers who churned
churn_rate = (df['Churn'] == 'Yes').mean()
print(f'Churn rate: {churn_rate:.2%}')
Churn rate: 26.54%
# Beginner: Count male and female customers using NumPy
vals, counts = np.unique(df['gender'], return_counts=True)
for v, c in zip(vals, counts):
    print(f'{v}: {c}')
Female: 3488
Male: 3555
# Intermediate: Create a NumPy array of MonthlyCharges and calculate statistics
charges = df['MonthlyCharges'].values
print('Mean:', np.mean(charges))
print('Std Dev:', np.std(charges))
print('Min:', np.min(charges), 'Max:', np.max(charges))
Mean: 64.76169246059918
Std Dev: 30.087910854936975
Min: 18.25 Max: 118.75
# Intermediate: Find correlation between tenure and monthly charges
corr = np.corrcoef(df['tenure'], df['MonthlyCharges'])[0,1]
print(f'Correlation between tenure and monthly charges: {corr:.2f}')
Correlation between tenure and monthly charges: 0.25
# Intermediate: Calculate average TotalCharges for churned vs. non-churned customers
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
churned = df[df['Churn'] == 'Yes']['TotalCharges']
not_churned = df[df['Churn'] == 'No']['TotalCharges']
print('Avg TotalCharges (Churned):', np.nanmean(churned))
print('Avg TotalCharges (Not Churned):', np.nanmean(not_churned))
Avg TotalCharges (Churned): 1531.7960941680042
Avg TotalCharges (Not Churned): 2555.344141003293
# Advanced: Use NumPy to create bins of MonthlyCharges
bins = np.arange(0, df['MonthlyCharges'].max()+10, 10)
labels = [f'{int(b)}-{int(b+10)}' for b in bins[:-1]]
df['ChargeBin'] = pd.cut(df['MonthlyCharges'], bins=bins, labels=labels)
charge_counts = df['ChargeBin'].value_counts().sort_index()
print(charge_counts)
ChargeBin
0-10         0
10-20      656
20-30      997
30-40      185
40-50      461
50-60      619
60-70      542
70-80      917
80-90      927
90-100     837
100-110    687
110-120    215
Name: count, dtype: int64
# Advanced: Calculate the churn rate for each charge bin
churn_by_bin = df.groupby('ChargeBin')['Churn'].apply(lambda x: (x == 'Yes').mean())
print(churn_by_bin)
ChargeBin
0-10            NaN
10-20      0.088415
20-30      0.104313
30-40      0.281081
40-50      0.318872
50-60      0.208401
60-70      0.206642
70-80      0.393675
80-90      0.362460
90-100     0.378734
100-110    0.327511
110-120    0.130233
Name: Churn, dtype: float64
# Advanced: Calculate average tenure for each contract type using NumPy
contract_types = df['Contract'].unique()
for ctype in contract_types:
    avg_tenure = np.mean(df[df['Contract']==ctype]['tenure'])
    print(f'{ctype}: {avg_tenure:.1f} months')
Month-to-month: 18.0 months
One year: 42.0 months
Two year: 56.7 months
# Load a synthetic NPS survey dataset for practice
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.head(3))
   CustomerID  Age Region  NPS_Score
0           1   56   West          2
1           2   69  North          0
2           3   46   East          4
# Beginner: Calculate mean NPS score overall
print('Mean NPS score:', np.mean(df_nps['NPS_Score']))
Mean NPS score: 4.89
# Intermediate: Calculate the percentage of Promoters, Passives, and Detractors
promoters = (df_nps['NPS_Score'] >= 9).mean()
passives = ((df_nps['NPS_Score'] >= 7) & (df_nps['NPS_Score'] <= 8)).mean()
detractors = (df_nps['NPS_Score'] <= 6).mean()
print(f'Promoters: {promoters:.2%}')
print(f'Passives: {passives:.2%}')
print(f'Detractors: {detractors:.2%}')
Promoters: 17.00%
Passives: 18.40%
Detractors: 64.60%
# Intermediate: Calculate NPS by Region using NumPy
regions = df_nps['Region'].unique()
for region in regions:
    scores = df_nps[df_nps['Region']==region]['NPS_Score']
    promoters = np.sum(scores >= 9)
    detractors = np.sum(scores <= 6)
    n = len(scores)
    nps = (promoters - detractors) / n * 100
    print(f'Region: {region}, NPS: {nps:.1f}')
Region: West, NPS: -44.4
Region: North, NPS: -40.9
Region: East, NPS: -52.2
Region: South, NPS: -54.5
# Advanced: Detect missing values in the NPS dataset
missing = df_nps.isnull().sum()
print('Missing values per column:')
print(missing)
Missing values per column:
CustomerID    0
Age           0
Region        0
NPS_Score     0
dtype: int64
# Error Handling: What happens on missing survey responses?
df_nps_missing = df_nps.copy()
df_nps_missing.loc[0:4, 'NPS_Score'] = np.nan
try:
    mean_nps = np.mean(df_nps_missing['NPS_Score'])
    print('Mean NPS with missing:', mean_nps)
except Exception as e:
    print('Error:', e)
Mean NPS with missing: 4.903030303030303
# Error Handling: Clean and impute (fill) missing values with the mean score
df_nps_filled = df_nps_missing.copy()
mean_score = np.nanmean(df_nps_filled['NPS_Score'])
df_nps_filled['NPS_Score'] = df_nps_filled['NPS_Score'].fillna(mean_score)
print(df_nps_filled['NPS_Score'].head(6))
0    4.90303
1    4.90303
2    4.90303
3    4.90303
4    4.90303
5    7.00000
Name: NPS_Score, dtype: float64
# Error Handling: Incorrect aggregation (mean instead of sum for sales)
sales = np.array([20, 30, 50, np.nan, 10])
try:
    print('Wrong total sales:', np.mean(sales))
    print('Correct total sales:', np.nansum(sales))
except Exception as e:
    print('Error:', e)
Wrong total sales: nan
Correct total sales: 110.0
# Best Practice: Segment customers by tenure group
bins = [0, 12, 24, 36, 48, 60, 72]
labels = ['<1y','1-2y','2-3y','3-4y','4-5y','5y+']
df['TenureGroup'] = pd.cut(df['tenure'], bins=bins, labels=labels, right=False)
print(df['TenureGroup'].value_counts().sort_index())
TenureGroup
<1y     2069
1-2y    1047
2-3y     876
3-4y     748
4-5y     820
5y+     1121
Name: count, dtype: int64
# Best Practice: Cross-tabulate churn by tenure group
ctab = pd.crosstab(df['TenureGroup'], df['Churn'], normalize='index')
print(ctab)
Churn              No       Yes
TenureGroup                    
<1y          0.517158  0.482842
1-2y         0.704871  0.295129
2-3y         0.779680  0.220320
3-4y         0.804813  0.195187
4-5y         0.850000  0.150000
5y+          0.917038  0.082962
# Best Practice: Customer Retention Index (CRI) calculation
df['Retained'] = (df['Churn'] == 'No').astype(int)
cri = df.groupby('TenureGroup')['Retained'].mean() * 100
cri = cri.round(1)
print('Customer Retention Index by tenure group (%):')
print(cri)
Customer Retention Index by tenure group (%):
TenureGroup
<1y     51.7
1-2y    70.5
2-3y    78.0
3-4y    80.5
4-5y    85.0
5y+     91.7
Name: Retained, dtype: float64
# Best Practice: Monthly trend analysis of NPS score
df_nps['Month'] = np.random.choice(range(1,13), size=len(df_nps))
monthly_nps = df_nps.groupby('Month')['NPS_Score'].mean()
print('Average NPS score per month:')
print(monthly_nps.sort_index())
Average NPS score per month:
Month
1     5.000000
2     5.956522
3     4.707317
4     4.588235
5     4.804878
6     4.914286
7     5.702703
8     4.357143
9     4.829787
10    4.023256
11    5.019608
12    4.714286
Name: NPS_Score, dtype: float64
# End-to-end Example: Recommend action by identifying highest-churn segment
churn_by_segment = df.groupby('TenureGroup')['Churn'].apply(lambda x: (x == 'Yes').mean()).sort_values(ascending=False)
print('Churn rate by tenure group:')
print(churn_by_segment)
worst_segment = churn_by_segment.idxmax()
print(f'Recommend immediate retention offers for: {worst_segment}')
Churn rate by tenure group:
TenureGroup
<1y     0.482842
1-2y    0.295129
2-3y    0.220320
3-4y    0.195187
4-5y    0.150000
5y+     0.082962
Name: Churn, dtype: float64
Recommend immediate retention offers for: <1y
 

Found this useful?

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