Mathew K Analytics

Lesson 28 · Market Research Analytics in Python

Behavioral and Attitudinal Segmentation in Market Research

In this lesson, we will solve the business problem of identifying customer segments based on both behaviors (e.g., purchases, service usage) and attitudes…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Behavioral and Attitudinal Segmentation in Market Research#

  • In this lesson, we will solve the business problem of identifying customer segments based on both behaviors (e.g., purchases, service usage) and attitudes (e.g., satisfaction, loyalty).
  • Behavioral and attitudinal segmentation helps companies target different customer needs, improve experiences, and personalize marketing strategies.
  • By analyzing customer data from various sources, we will extract actionable segments, interpret them, and generate business insights for decision-making.
  • You will use Python to load, explore, and segment real customer datasets using survey responses, transactional behaviors, and customer attitudes.
import pandas as pd
import numpy as np
import openml
import warnings
warnings.filterwarnings('ignore')

What Are Behavioral and Attitudinal Segmentation?#

  • Behavioral segmentation divides customers based on actions like purchasing, website visits, or service usage.
  • Attitudinal segmentation groups customers based on subjective data such as opinions, satisfaction surveys, or brand perception.
  • Survey and transactional datasets may include both types but structure them in different columns or even different files.
  • Beginners often analyze just one dimension, or mislabel attitudes as true behaviors, leading to weak segment definitions.
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  
dataset = openml.datasets.get_dataset(42178)
df_sat, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(df_sat.shape)
print(df_sat[['gender', 'SeniorCitizen', 'MonthlyCharges', 'Churn']].head(3))
(7043, 20)
   gender  SeniorCitizen  MonthlyCharges Churn
0  Female              0           29.85    No
1    Male              0           56.95    No
2    Male              0           53.85   Yes
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
recent_date = df_retail['InvoiceDate'].max()
df_retail['Recency'] = (recent_date - df_retail['InvoiceDate']).dt.days
customer_recency = df_retail.groupby('Customer ID')['Recency'].min().reset_index()
customer_recency['Segment'] = pd.cut(customer_recency['Recency'], bins=[-1,30,90,180,10000], labels=['Active','Warm','Cool','Dormant'])
print(customer_recency.head(5))
   Customer ID  Recency  Segment
0      12346.0      325  Dormant
1      12347.0        1   Active
2      12348.0       74     Warm
3      12349.0       18   Active
4      12350.0      309  Dormant
def nps_category(score):
    if score >= 9:
        return 'Promoter'
    elif score >= 7:
        return 'Passive'
    else:
        return 'Detractor'

df_nps['NPS_Category'] = df_nps['NPS_Score'].apply(nps_category)
print(df_nps[['CustomerID','NPS_Score','NPS_Category']].head(6))
   CustomerID  NPS_Score NPS_Category
0           1          2    Detractor
1           2          0    Detractor
2           3          4    Detractor
3           4          3    Detractor
4           5          9     Promoter
5           6          7      Passive
customer_recency['CustomerID'] = customer_recency['Customer ID']
merged = pd.merge(customer_recency[['CustomerID','Segment']], df_nps[['CustomerID','NPS_Category']], on='CustomerID', how='inner')
crosstab = pd.crosstab(merged['Segment'], merged['NPS_Category'])
print(crosstab)
Empty DataFrame
Columns: []
Index: []
order_counts = df_retail.groupby('Customer ID').size().reset_index(name='Frequency')
order_counts['FreqSegment'] = pd.qcut(order_counts['Frequency'], q=4, labels=['Low','Medium','High','Very High'])
print(order_counts[['Customer ID','Frequency','FreqSegment']].head(6))
   Customer ID  Frequency FreqSegment
0      12346.0          2         Low
1      12347.0        182   Very High
2      12348.0         31      Medium
3      12349.0         73        High
4      12350.0         17         Low
5      12352.0         95        High
churn_rates = df_sat.groupby(['SeniorCitizen','Partner'])['Churn'].value_counts(normalize=True).unstack().fillna(0)
churn_rates = churn_rates[['Yes','No']]
print(churn_rates)
Churn                       Yes        No
SeniorCitizen Partner                    
0             No       0.300130  0.699870
              Yes      0.166490  0.833510
1             No       0.488576  0.511424
              Yes      0.345550  0.654450
customer_profile = pd.merge(order_counts[['Customer ID','FreqSegment']], df_nps[['CustomerID','NPS_Category']], 
                            left_on='Customer ID', right_on='CustomerID', how='inner')
summary = customer_profile.groupby(['FreqSegment','NPS_Category']).size().unstack().fillna(0).astype(int)
print(summary)
Empty DataFrame
Columns: []
Index: []
df_retail['Amount'] = df_retail['Quantity'] * df_retail['Price']
rfm = df_retail.groupby('Customer ID').agg({
    'Recency': 'min',
    'Invoice': 'nunique',
    'Amount': 'sum'
}).rename(columns={'Recency':'Recency','Invoice':'Frequency','Amount':'Monetary'})
rfm['R_Score'] = pd.qcut(rfm['Recency'],q=4,labels=[4,3,2,1]).astype(int)
rfm['F_Score'] = pd.qcut(rfm['Frequency'].rank(method='first'),q=4,labels=[1,2,3,4]).astype(int)
rfm['M_Score'] = pd.qcut(rfm['Monetary'].rank(method='first'),q=4,labels=[1,2,3,4]).astype(int)
rfm['RFM_Segment'] = rfm['R_Score'].astype(str) + rfm['F_Score'].astype(str) + rfm['M_Score'].astype(str)
print(rfm.head(6))
             Recency  Frequency  Monetary  R_Score  F_Score  M_Score  \
Customer ID                                                            
12346.0          325          2      0.00        1        2        1   
12347.0            1          7   4310.00        4        4        4   
12348.0           74          4   1797.24        2        3        4   
12349.0           18          1   1757.55        3        1        4   
12350.0          309          1    334.40        1        1        2   
12352.0           35         11   1545.41        3        4        3   

            RFM_Segment  
Customer ID              
12346.0             121  
12347.0             444  
12348.0             234  
12349.0             314  
12350.0             112  
12352.0             343  
import pandas as pd
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']
})
# Simple sentiment assignment using keywords
def simple_sentiment(text):
    if any(w in text.lower() for w in ['poor','slow','needs improvement']):
        return 'Negative'
    if any(w in text.lower() for w in ['great','excellent','good']):
        return 'Positive'
    return 'Neutral'
df_feedback['Sentiment'] = df_feedback['Feedback'].apply(simple_sentiment)
print(df_feedback)
   CustomerID                                  Feedback Sentiment
0           1          Great service and friendly staff  Positive
1           2  Delivery was slow and packaging was poor  Negative
2           3         Excellent quality, will buy again  Positive
3           4        Customer support needs improvement  Negative
4           5                      Good value for money  Positive
df_nps_missing = df_nps.copy()
df_nps_missing.loc[[3,8,15], 'NPS_Score'] = np.nan
missing_count = df_nps_missing['NPS_Score'].isna().sum()
print(f'Missing NPS scores: {missing_count}')
mean_score = df_nps_missing['NPS_Score'].mean()
print(f'Mean NPS (with missing): {mean_score:.2f}')
mean_score_filled = df_nps_missing['NPS_Score'].fillna(df_nps_missing['NPS_Score'].mean()).mean()
print(f'Mean NPS (after filling): {mean_score_filled:.2f}')
Missing NPS scores: 3
Mean NPS (with missing): 4.91
Mean NPS (after filling): 4.91
try:
    bad_group = df_retail.groupby('Country')['Recency'].mean()
    print('Mean recency by country:', bad_group.head())
except Exception as e:
    print('Error:', e)
Mean recency by country: Country
Australia    183.492454
Austria      133.503741
Bahrain      224.894737
Belgium      150.413243
Brazil       238.000000
Name: Recency, dtype: float64
sample_scores = [5,6,7,8,9,10]
cats = [nps_category(x) for x in sample_scores]
for s,c in zip(sample_scores, cats):
    print(f'Score {s}: Segment {c}')
Score 5: Segment Detractor
Score 6: Segment Detractor
Score 7: Segment Passive
Score 8: Segment Passive
Score 9: Segment Promoter
Score 10: Segment Promoter
freq_att = pd.crosstab(order_counts['FreqSegment'], df_nps['NPS_Category'])
print(freq_att)
NPS_Category  Detractor  Passive  Promoter
FreqSegment                               
Low                  67       18        19
Medium               90       29        22
High                 78       21        31
Very High            88       24        13
region_nps_avg = df_nps.groupby('Region')['NPS_Score'].mean().sort_values()
print(region_nps_avg)
Region
East     4.504425
South    4.719008
West     5.025641
North    5.214765
Name: NPS_Score, dtype: float64
np.random.seed(42)
monthly_nps = pd.DataFrame({
    'Month': pd.date_range('2022-01-01', periods=12, freq='MS'),
    'NPS_Score': np.random.randint(5,11,12)
})
monthly_nps['NPS_Category'] = monthly_nps['NPS_Score'].apply(nps_category)
print(monthly_nps)
        Month  NPS_Score NPS_Category
0  2022-01-01          8      Passive
1  2022-02-01          9     Promoter
2  2022-03-01          7      Passive
3  2022-04-01          9     Promoter
4  2022-05-01          9     Promoter
5  2022-06-01          6    Detractor
6  2022-07-01          7      Passive
7  2022-08-01          7      Passive
8  2022-09-01          7      Passive
9  2022-10-01          9     Promoter
10 2022-11-01          8      Passive
11 2022-12-01          7      Passive
# End-to-end: Identify at-risk high-value customers with negative attitudes
risky_customers = rfm[(rfm['R_Score'] <= 2) & (rfm['M_Score'] >= 3)].reset_index()
risky_merged = pd.merge(risky_customers, df_nps[['CustomerID','NPS_Category']], 
                       left_on='Customer ID', right_on='CustomerID', how='inner')
risky_segment = risky_merged[risky_merged['NPS_Category'] == 'Detractor']
print('Number of at-risk high-value Detractors:', risky_segment.shape[0])
if not risky_segment.empty:
    print(risky_segment[['Customer ID','Recency','Monetary','NPS_Category']].head(10))
Number of at-risk high-value Detractors: 0
output_file = 'at_risk_high_value_detractors.csv'
risky_segment[['Customer ID', 'Recency', 'Monetary', 'NPS_Category']].to_csv(output_file, index=False)
print(f'Saved: {output_file}')
Saved: at_risk_high_value_detractors.csv
 

Found this useful?

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