Mathew K Analytics

Lesson 14 · Market Research Analytics in Python

Handling Open-Ended Survey Responses in Python for Market Research

In this lesson, we will learn how to analyze and extract insights from open-ended survey responses. Businesses often ask customers for feedback in their own…

📓 Full notebook

Download .ipynb

Handling Open-Ended Survey Responses#

  • In this lesson, we will learn how to analyze and extract insights from open-ended survey responses.
  • Businesses often ask customers for feedback in their own words, which can reveal hidden opinions and new ideas.
  • We will practice using Python to summarize, categorize, and visualize text feedback for customer analytics.
  • By mastering this, you will help your team turn raw comments into actionable business recommendations.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Understanding Open-Ended Survey Data#

  • Surveys often include both structured questions (like ratings) and free-form text feedback.
  • Open-ended responses allow customers to express detailed opinions, complaints, and suggestions.
  • Business insight depends on extracting patterns from this unstructured text.
  • Beginners sometimes forget to clean, organize, or summarize text feedback, missing important themes.
  • Common errors include analyzing only quantitative data or manually reading comments without summarization tools.
# Beginner Example 1: Create a DataFrame of open-ended customer 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'
                  ]})
print(df.shape)
print(df.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 2: Count the number of feedback responses
feedback_count = df['Feedback'].count()
print('Number of feedback responses:', feedback_count)
Number of feedback responses: 5
# Beginner Example 3: Preview unique words in feedback
all_feedback = ' '.join(df['Feedback'].values)
unique_words = set(all_feedback.lower().split())
print('Sample unique words in feedback:', list(unique_words)[:8])
Sample unique words in feedback: ['excellent', 'buy', 'improvement', 'packaging', 'for', 'was', 'slow', 'and']
# Beginner Example 4: Count keyword mentions (simple case-insensitive search)
keyword = 'service'
df['Has_Service'] = df['Feedback'].str.lower().str.contains(keyword)
print(df[['Feedback','Has_Service']])
                                   Feedback  Has_Service
0          Great service and friendly staff         True
1  Delivery was slow and packaging was poor        False
2         Excellent quality, will buy again        False
3        Customer support needs improvement        False
4                      Good value for money        False

Intermediate Example 1: Loading a Real Customer Satisfaction Survey with Open-Ended Responses#

  • Let us use a more complex, real survey dataset from OpenML to practice larger-scale analysis.
  • This dataset simulates actual customer experience data and includes demographic variables.
import openml
dataset = openml.datasets.get_dataset(42178)
customer_df, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(customer_df.columns.tolist())
print(customer_df.shape)
['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure', 'PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling', 'PaymentMethod', 'MonthlyCharges', 'TotalCharges', 'Churn']
(7043, 20)
# Intermediate Example 2: Simulate adding open-ended text to a large survey
customer_df = customer_df.head(12).copy()
feedbacks = [
    'Friendly service and fast response',
    'Unhelpful support, very disappointed',
    'Quick installation and polite staff',
    'Hold times were too long',
    'Great offers and discounts',
    'Billing was confusing',
    'Received wrong product',
    'App is buggy but staff tried to help',
    'Outstanding experience',
    'Hard to reach support team',
    'Rep did not understand my problem',
    'Will recommend to friends' ]
customer_df['Feedback'] = feedbacks
print(customer_df[['gender','Churn','Feedback']].head())
   gender Churn                              Feedback
0  Female    No    Friendly service and fast response
1    Male    No  Unhelpful support, very disappointed
2    Male   Yes   Quick installation and polite staff
3    Male    No              Hold times were too long
4  Female   Yes            Great offers and discounts
# Intermediate Example 3: Reporting the main complaint themes with keyword flags
themes = ['support', 'billing', 'discount', 'help', 'recommend']
for theme in themes:
    theme_col = f'Has_{theme.capitalize()}'
    customer_df[theme_col] = customer_df['Feedback'].str.lower().str.contains(theme)

print(customer_df[['Feedback'] + [f'Has_{t.capitalize()}' for t in themes]])
                                Feedback  Has_Support  Has_Billing  \
0     Friendly service and fast response        False        False   
1   Unhelpful support, very disappointed         True        False   
2    Quick installation and polite staff        False        False   
3               Hold times were too long        False        False   
4             Great offers and discounts        False        False   
5                  Billing was confusing        False         True   
6                 Received wrong product        False        False   
7   App is buggy but staff tried to help        False        False   
8                 Outstanding experience        False        False   
9             Hard to reach support team         True        False   
10     Rep did not understand my problem        False        False   
11             Will recommend to friends        False        False   

    Has_Discount  Has_Help  Has_Recommend  
0          False     False          False  
1          False      True          False  
2          False     False          False  
3          False     False          False  
4           True     False          False  
5          False     False          False  
6          False     False          False  
7          False      True          False  
8          False     False          False  
9          False     False          False  
10         False     False          False  
11         False     False           True  
# Intermediate Example 4: Calculate frequency of each theme
theme_counts = {theme: customer_df[f'Has_{theme.capitalize()}'].sum() for theme in themes}
print('Number of responses per theme:')
print(theme_counts)
Number of responses per theme:
{'support': np.int64(2), 'billing': np.int64(1), 'discount': np.int64(1), 'help': np.int64(2), 'recommend': np.int64(1)}
# Intermediate Example 5: Visualizing comment themes (bar chart)
import matplotlib.pyplot as plt
plt.bar(theme_counts.keys(), theme_counts.values(), color='royalblue')
plt.title('Frequency of Comment Themes')
plt.xlabel('Theme')
plt.ylabel('Number of Mentions')
plt.show()
No description has been provided for this image

Advanced Example 1: Grouping open-ended feedback by customer churn#

  • Let us see how feedback topics differ between customers who churned and those who stayed.
  • Segmenting comments by outcome reveals actionable retention insights.
# Advanced Example 2: Cross-tabulation of themes by churn status
crosstab = customer_df.groupby('Churn')[[f'Has_{t.capitalize()}' for t in themes]].sum()
print('Theme mentions by churn status:')
print(crosstab)
Theme mentions by churn status:
       Has_Support  Has_Billing  Has_Discount  Has_Help  Has_Recommend
Churn                                                                 
No               2            0             0         2              1
Yes              0            1             1         0              0
# Advanced Example 3: Export categorized feedback to CSV
output_path = 'categorized_feedback.csv'
customer_df[['gender','Churn','Feedback'] + [f'Has_{t.capitalize()}' for t in themes]].to_csv(output_path, index=False)
print('Categorized feedback exported to', output_path)
Categorized feedback exported to categorized_feedback.csv

Error Handling and Debugging Common Issues#

  • Working with open-ended text can lead to missing or badly formatted data.
  • Beginners sometimes forget to check for empty feedback or typo errors in search keywords.
  • Let us practice handling these cases.
# Missing survey responses (blank feedback)
df_with_missing = df.copy()
df_with_missing.loc[2, 'Feedback'] = None
print(df_with_missing)
   CustomerID                                  Feedback  Has_Service
0           1          Great service and friendly staff         True
1           2  Delivery was slow and packaging was poor        False
2           3                                      None        False
3           4        Customer support needs improvement        False
4           5                      Good value for money        False
# Handle missing text safely in keyword search
keyword = 'value'
df_with_missing['Has_Value'] = df_with_missing['Feedback'].fillna('').str.lower().str.contains(keyword)
print(df_with_missing[['Feedback','Has_Value']])
                                   Feedback  Has_Value
0          Great service and friendly staff      False
1  Delivery was slow and packaging was poor      False
2                                      None      False
3        Customer support needs improvement      False
4                      Good value for money       True
# Incorrect grouping: counting feedback by wrong column (example mistake)
try:
    print(df.groupby('NonexistentColumn')['Feedback'].count())
except Exception as e:
    print('Error:', str(e))
Error: 'NonexistentColumn'
# Misinterpreting rating scales or NPS scores (example correction)
nps_df = pd.DataFrame({'CustomerID':[1,2,3,4,5], 'NPS_Score':[10,9,6,4,7]})
nps_df['Promoter'] = nps_df['NPS_Score'] >= 9
nps_df['Detractor'] = nps_df['NPS_Score'] <= 6
nps_df['Passive'] = ~nps_df['Promoter'] & ~nps_df['Detractor']
print(nps_df)
   CustomerID  NPS_Score  Promoter  Detractor  Passive
0           1         10      True      False    False
1           2          9      True      False    False
2           3          6     False       True    False
3           4          4     False       True    False
4           5          7     False      False     True

Best Practices in Open-Ended Survey Analysis#

  • Segment feedback by key customer groups, such as churn status or demographics, to target actions.
  • Use crosstabs to examine theme frequency by segment and surface hidden pain points.
  • Combine index or score analysis with text to tell a complete business story.
  • Look for trends over time by applying the same approach to new survey waves.
# Pattern: Segmentation by demographic variable (gender)
seg_table = customer_df.groupby('gender')[[f'Has_{t.capitalize()}' for t in themes]].sum()
print('Feedback themes by gender:')
print(seg_table)
Feedback themes by gender:
        Has_Support  Has_Billing  Has_Discount  Has_Help  Has_Recommend
gender                                                                 
Female            0            1             1         1              0
Male              2            0             0         1              1
# Pattern: Cross-tabulation of two variables (Churn & Discount theme)
discount_cross = pd.crosstab(customer_df['Churn'], customer_df['Has_Discount'])
print('Churn vs Discount mentions:')
print(discount_cross)
Churn vs Discount mentions:
Has_Discount  False  True 
Churn                     
No                8      0
Yes               3      1
# Pattern: Summarize qualitative and quantitative together
avg_monthly_charges = customer_df.groupby('Has_Billing')['MonthlyCharges'].mean()
print('Average monthly charges, grouped by mentions of billing issues:')
print(avg_monthly_charges)
Average monthly charges, grouped by mentions of billing issues:
Has_Billing
False    54.759091
True     99.650000
Name: MonthlyCharges, dtype: float64
# Pattern: Trend analysis (simulate by reshuffling feedback and grouping by first letter)
customer_df['LetterGroup'] = customer_df['Feedback'].str[0].str.upper()
trend_counts = customer_df.groupby('LetterGroup')[f'Has_Support'].sum()
print('Support theme mentions grouped by first letter of feedback:')
print(trend_counts)
Support theme mentions grouped by first letter of feedback:
LetterGroup
A    0
B    0
F    0
G    0
H    1
O    0
Q    0
R    0
U    1
W    0
Name: Has_Support, dtype: int64

End-to-End Example: From Raw Feedback to Business Recommendation#

  • Suppose the product team receives raw survey comments after a new launch.
  • Let us flag concerns, summarize issues, and make an actionable recommendation.
# Mini business case: QA team reviews negative feedback for top themes
raw_feedback = pd.Series([
    'Support did not resolve my issue',
    'Very happy with new features',
    'Staff was polite but slow',
    'I love the app update',
    'Unclear billing, extra fees appeared',
    'Setup process was easier than expected',
    'App kept crashing for two days',
    'Promo offer did not apply correctly'
])
themes_case = ['support','billing','app','offer']
flags = pd.DataFrame({theme: raw_feedback.str.lower().str.contains(theme) for theme in themes_case})
summary = flags.sum().sort_values(ascending=False)
print('Count of flag mentions in feedback:')
print(summary)
Count of flag mentions in feedback:
app        5
support    1
billing    1
offer      1
dtype: int64
# Forming an actionable business recommendation
top_issue = summary.idxmax()
recommendation = f'Our immediate focus should be addressing {top_issue}-related complaints, as it was mentioned most often.'
print(recommendation)
Our immediate focus should be addressing app-related complaints, as it was mentioned most often.
 

Found this useful?

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