Automate Customer Feedback Analysis with Python and NLP
You have 847 customer survey responses sitting in a spreadsheet. Your boss wants insights by tomorrow morning: What are customers happy about? What's frustrating them? What themes keep appearing? Which issues are urgent?
You could read all 847 responses manually. That's 6-8 hours of mind-numbing work, and you'll inevitably miss patterns. Or you could skim them and rely on gut feeling, risking confirmation bias and missed insights.
There's a better way.
What if Python could read all 847 responses in 60 seconds, automatically categorize them by sentiment and theme, identify your top 10 issues, and generate a executive summary with quotes and recommendations?
That's exactly what we're building today.
What You'll Build
By the end of this tutorial, you'll have a Python system that:
- Imports feedback from CSV, Excel, Google Forms, or surveys
- Analyzes sentiment (positive, negative, neutral) for each response
- Extracts key themes using NLP clustering
- Identifies urgent issues based on language patterns
- Generates word clouds showing common topics
- Creates executive reports with insights and recommendations
- Tracks trends over time to see if issues are improving
Time savings: 6-8 hours of manual analysis β 5 minutes automated
Why Automate Feedback Analysis?
Manual feedback analysis has serious problems:
Scale issues:
- Reading 100+ responses is exhausting
- Patterns across thousands of responses are invisible
- Quarterly feedback takes weeks to analyze
Bias problems:
- You remember negative feedback disproportionately
- Confirmation bias: You see what you expect
- Recency bias: Recent feedback overshadows old
Consistency issues:
- Different people categorize differently
- Subjective interpretation varies
- No standardized metrics
According to a 2025 customer experience study, companies that analyze feedback within 48 hours have 3.2x higher customer retention. But 68% of companies take over 2 weeks to analyze survey resultsβby then, the insights are stale and opportunities are lost.
Prerequisites
- Python 3.8 or higher
- Basic Python knowledge
- Customer feedback data (CSV, Excel, or API)
Step 1: Set Up Your Environment
Install required libraries:
1pip install pandas numpy matplotlib wordcloud textblob spacy scikit-learn openai python-dotenv openpyxl
Install spaCy language model:
1python -m spacy download en_core_web_sm
Create project structure:
feedback_analyzer/ βββ main.py βββ sentiment_analyzer.py βββ theme_extractor.py βββ report_generator.py βββ data/ β βββ feedback.csv βββ output/ β βββ reports/ β βββ visualizations/ βββ .env
Step 2: Import and Clean Feedback Data
Create main.py:
1import pandas as pd2import re3from datetime import datetime4from pathlib import Path56class FeedbackLoader:7 def __init__(self, data_path):8 self.data_path = data_path910 def load_data(self, source_type='csv'):11 """Load feedback from various sources"""12 if source_type == 'csv':13 return self._load_csv()14 elif source_type == 'excel':15 return self._load_excel()16 elif source_type == 'google_forms':17 return self._load_google_forms()18 else:19 raise ValueError(f"Unsupported source type: {source_type}")2021 def _load_csv(self):22 """Load from CSV file"""23 df = pd.read_csv(self.data_path)24 return self._clean_data(df)2526 def _load_excel(self):27 """Load from Excel file"""28 df = pd.read_excel(self.data_path)29 return self._clean_data(df)3031 def _load_google_forms(self):32 """Load from Google Forms (requires API setup)"""33 # Implementation using Google Sheets API34 # For this tutorial, we'll use CSV export from Forms35 pass3637 def _clean_data(self, df):38 """Clean and standardize feedback data"""39 # Ensure required columns exist40 required_cols = ['feedback', 'date']4142 # Try to identify feedback column (flexible naming)43 feedback_col = None44 for col in df.columns:45 col_lower = col.lower()46 if any(term in col_lower for term in ['feedback', 'comment', 'response', 'review', 'text']):47 feedback_col = col48 break4950 if not feedback_col:51 raise ValueError("No feedback column found. Expected column with 'feedback', 'comment', or 'response' in name.")5253 # Rename to standard name54 df = df.rename(columns={feedback_col: 'feedback'})5556 # Handle date column57 date_col = None58 for col in df.columns:59 if any(term in col.lower() for term in ['date', 'timestamp', 'time', 'submitted']):60 date_col = col61 break6263 if date_col and date_col != 'date':64 df = df.rename(columns={date_col: 'date'})65 elif 'date' not in df.columns:66 # Add current date if no date column67 df['date'] = datetime.now().strftime('%Y-%m-%d')6869 # Clean feedback text70 df['feedback'] = df['feedback'].astype(str)71 df['feedback'] = df['feedback'].apply(self._clean_text)7273 # Remove empty feedback74 df = df[df['feedback'].str.len() > 10] # At least 10 characters7576 # Add unique ID77 df['id'] = range(1, len(df) + 1)7879 return df8081 def _clean_text(self, text):82 """Clean individual feedback text"""83 if pd.isna(text) or text == 'nan':84 return ""8586 # Remove extra whitespace87 text = ' '.join(text.split())8889 # Remove URLs90 text = re.sub(r'http\S+|www\S+', '', text)9192 # Remove email addresses93 text = re.sub(r'\S+@\S+', '', text)9495 # Remove special characters (keep punctuation for sentiment)96 text = re.sub(r'[^\w\s.,!?-]', '', text)9798 return text.strip()99100101# Example usage102if __name__ == "__main__":103 loader = FeedbackLoader('data/feedback.csv')104 df = loader.load_data('csv')105106 print(f"Loaded {len(df)} feedback responses")107 print(f"\nSample feedback:")108 print(df[['id', 'date', 'feedback']].head())
Sample feedback data structure (feedback.csv):
1date,customer_name,feedback,rating22026-01-01,John Doe,"Love the product! Fast shipping and great quality.",532026-01-02,Jane Smith,"Disappointed with customer service. Had to wait 2 hours on hold.",242026-01-03,Bob Johnson,"Good product but setup instructions were confusing.",3
Step 3: Build the Sentiment Analyzer
Create sentiment_analyzer.py:
1from textblob import TextBlob2import spacy3from typing import Dict, List45nlp = spacy.load('en_core_web_sm')67class SentimentAnalyzer:8 def __init__(self):9 self.sentiment_labels = {10 'positive': (0.1, 1.0),11 'neutral': (-0.1, 0.1),12 'negative': (-1.0, -0.1)13 }1415 def analyze_sentiment(self, text: str) -> Dict:16 """Analyze sentiment of feedback text"""17 # Use TextBlob for basic sentiment18 blob = TextBlob(text)19 polarity = blob.sentiment.polarity # -1 (negative) to 1 (positive)20 subjectivity = blob.sentiment.subjectivity # 0 (objective) to 1 (subjective)2122 # Classify sentiment23 sentiment_label = self._classify_sentiment(polarity)2425 # Extract entities (people, products, features mentioned)26 doc = nlp(text)27 entities = [ent.text for ent in doc.ents if ent.label_ in ['PRODUCT', 'ORG', 'PERSON']]2829 return {30 'polarity': polarity,31 'subjectivity': subjectivity,32 'sentiment': sentiment_label,33 'entities': entities,34 'is_urgent': self._check_urgency(text, polarity)35 }3637 def _classify_sentiment(self, polarity: float) -> str:38 """Classify polarity score into sentiment label"""39 for label, (min_val, max_val) in self.sentiment_labels.items():40 if min_val <= polarity <= max_val:41 return label42 return 'neutral'4344 def _check_urgency(self, text: str, polarity: float) -> bool:45 """Detect if feedback requires urgent attention"""46 urgent_keywords = [47 'angry', 'furious', 'terrible', 'horrible', 'worst',48 'lawsuit', 'lawyer', 'refund', 'cancel', 'unacceptable',49 'disappointed', 'frustrated', 'failed', 'broken', 'dangerous'50 ]5152 text_lower = text.lower()5354 # Check for urgent keywords55 has_urgent_keyword = any(keyword in text_lower for keyword in urgent_keywords)5657 # Check for extreme negative sentiment58 is_very_negative = polarity < -0.55960 return has_urgent_keyword or is_very_negative6162 def analyze_batch(self, feedback_list: List[str]) -> List[Dict]:63 """Analyze sentiment for multiple feedback entries"""64 results = []6566 for i, text in enumerate(feedback_list):67 try:68 result = self.analyze_sentiment(text)69 result['index'] = i70 results.append(result)71 except Exception as e:72 print(f"Error analyzing feedback {i}: {e}")73 results.append({74 'index': i,75 'polarity': 0,76 'subjectivity': 0,77 'sentiment': 'error',78 'entities': [],79 'is_urgent': False80 })8182 return results838485# Example usage86if __name__ == "__main__":87 analyzer = SentimentAnalyzer()8889 test_feedback = [90 "I absolutely love this product! Best purchase ever.",91 "The service was okay, nothing special.",92 "Terrible experience. I want my money back immediately."93 ]9495 results = analyzer.analyze_batch(test_feedback)9697 for i, result in enumerate(results):98 print(f"\nFeedback {i+1}:")99 print(f" Sentiment: {result['sentiment']}")100 print(f" Polarity: {result['polarity']:.2f}")101 print(f" Urgent: {result['is_urgent']}")
Step 4: Extract Themes and Topics
Create theme_extractor.py:
1from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer2from sklearn.decomposition import LatentDirichletAllocation3from sklearn.cluster import KMeans4import numpy as np5from collections import Counter6import re78class ThemeExtractor:9 def __init__(self, n_themes=5):10 self.n_themes = n_themes11 self.vectorizer = TfidfVectorizer(12 max_features=100,13 stop_words='english',14 ngram_range=(1, 2) # Include bigrams15 )1617 def extract_themes(self, feedback_list: List[str]) -> Dict:18 """Extract main themes from feedback using topic modeling"""1920 if len(feedback_list) < self.n_themes:21 return {'error': 'Not enough feedback for theme extraction'}2223 # Create document-term matrix24 doc_term_matrix = self.vectorizer.fit_transform(feedback_list)2526 # Topic modeling with LDA27 lda = LatentDirichletAllocation(28 n_components=self.n_themes,29 random_state=42,30 max_iter=2031 )32 lda.fit(doc_term_matrix)3334 # Extract top words for each theme35 feature_names = self.vectorizer.get_feature_names_out()36 themes = []3738 for topic_idx, topic in enumerate(lda.components_):39 top_words_idx = topic.argsort()[-10:][::-1]40 top_words = [feature_names[i] for i in top_words_idx]4142 # Generate theme name from top words43 theme_name = self._generate_theme_name(top_words)4445 themes.append({46 'id': topic_idx + 1,47 'name': theme_name,48 'keywords': top_words[:5],49 'weight': float(topic.sum())50 })5152 # Sort themes by weight53 themes.sort(key=lambda x: x['weight'], reverse=True)5455 return {56 'themes': themes,57 'n_feedback': len(feedback_list)58 }5960 def _generate_theme_name(self, top_words: List[str]) -> str:61 """Generate human-readable theme name from top words"""62 # Simple heuristic: use most common nouns/adjectives63 theme_keywords = {64 ('product', 'quality', 'item'): 'Product Quality',65 ('shipping', 'delivery', 'arrived'): 'Shipping & Delivery',66 ('service', 'support', 'help'): 'Customer Service',67 ('price', 'cost', 'expensive', 'cheap'): 'Pricing',68 ('website', 'app', 'interface'): 'User Experience',69 ('easy', 'simple', 'complicated'): 'Ease of Use',70 ('problem', 'issue', 'bug', 'error'): 'Technical Issues',71 ('refund', 'return', 'exchange'): 'Returns & Refunds',72 ('recommend', 'love', 'amazing'): 'Recommendations',73 ('disappointed', 'frustrated', 'angry'): 'Complaints'74 }7576 top_words_set = set(word.lower() for word in top_words)7778 for keywords, name in theme_keywords.items():79 if any(keyword in top_words_set for keyword in keywords):80 return name8182 # Fallback: capitalize first two words83 return ' & '.join(top_words[:2]).title()8485 def extract_keywords(self, feedback_list: List[str], top_n=20) -> List[tuple]:86 """Extract most common keywords/phrases"""87 # Combine all feedback88 all_text = ' '.join(feedback_list)8990 # Extract n-grams91 vectorizer = CountVectorizer(92 ngram_range=(1, 3),93 stop_words='english',94 max_features=top_n95 )9697 word_count_matrix = vectorizer.fit_transform(feedback_list)98 word_counts = word_count_matrix.sum(axis=0)99100 # Get keywords and counts101 keywords = vectorizer.get_feature_names_out()102 counts = np.asarray(word_counts).flatten()103104 # Sort by frequency105 keyword_freq = sorted(zip(keywords, counts), key=lambda x: x[1], reverse=True)106107 return keyword_freq[:top_n]108109 def categorize_feedback(self, feedback_list: List[str], categories: Dict[str, List[str]]) -> Dict:110 """Categorize feedback based on predefined categories and keywords"""111112 results = {category: [] for category in categories.keys()}113 results['uncategorized'] = []114115 for idx, text in enumerate(feedback_list):116 text_lower = text.lower()117 categorized = False118119 for category, keywords in categories.items():120 if any(keyword in text_lower for keyword in keywords):121 results[category].append(idx)122 categorized = True123 break124125 if not categorized:126 results['uncategorized'].append(idx)127128 # Calculate percentages129 total = len(feedback_list)130 summary = {131 category: {132 'count': len(indices),133 'percentage': (len(indices) / total * 100) if total > 0 else 0,134 'indices': indices135 }136 for category, indices in results.items()137 }138139 return summary140141142# Example usage143if __name__ == "__main__":144 extractor = ThemeExtractor(n_themes=5)145146 sample_feedback = [147 "Great product, fast shipping!",148 "Customer service was helpful and responsive",149 "The item arrived damaged, very disappointed",150 "Easy to use, works as expected",151 "Website is confusing, hard to find what I need",152 "Love the quality, will buy again",153 "Shipping took too long, 3 weeks delay",154 "Support team solved my issue quickly",155 "Expensive but worth the price",156 "Product broke after one week"157 ]158159 # Extract themes160 themes_result = extractor.extract_themes(sample_feedback)161 print("Themes found:")162 for theme in themes_result['themes']:163 print(f" {theme['name']}: {', '.join(theme['keywords'])}")164165 # Extract keywords166 keywords = extractor.extract_keywords(sample_feedback, top_n=10)167 print("\nTop keywords:")168 for keyword, count in keywords:169 print(f" {keyword}: {count}")170171 # Categorize feedback172 categories = {173 'Product Quality': ['product', 'quality', 'broke', 'damaged'],174 'Shipping': ['shipping', 'delivery', 'arrived'],175 'Customer Service': ['service', 'support', 'help'],176 'Price': ['expensive', 'cheap', 'price', 'cost']177 }178179 categorization = extractor.categorize_feedback(sample_feedback, categories)180 print("\nCategorization:")181 for category, data in categorization.items():182 print(f" {category}: {data['count']} ({data['percentage']:.1f}%)")
Step 5: Generate Visual Reports
Create report_generator.py:
1import pandas as pd2import matplotlib.pyplot as plt3from wordcloud import WordCloud4import seaborn as sns5from pathlib import Path6from datetime import datetime78class ReportGenerator:9 def __init__(self, output_dir='output/'):10 self.output_dir = Path(output_dir)11 self.output_dir.mkdir(exist_ok=True)1213 # Set style14 sns.set_style("whitegrid")15 plt.rcParams['figure.figsize'] = (12, 6)1617 def generate_word_cloud(self, text: str, title: str, output_filename: str):18 """Generate word cloud visualization"""19 wordcloud = WordCloud(20 width=800,21 height=400,22 background_color='white',23 colormap='viridis',24 max_words=10025 ).generate(text)2627 plt.figure(figsize=(12, 6))28 plt.imshow(wordcloud, interpolation='bilinear')29 plt.axis('off')30 plt.title(title, fontsize=16, fontweight='bold')31 plt.tight_layout()3233 output_path = self.output_dir / 'visualizations' / output_filename34 output_path.parent.mkdir(exist_ok=True)35 plt.savefig(output_path, dpi=300, bbox_inches='tight')36 plt.close()3738 return output_path3940 def generate_sentiment_distribution(self, sentiment_counts: Dict, output_filename: str):41 """Generate sentiment distribution chart"""42 sentiments = list(sentiment_counts.keys())43 counts = list(sentiment_counts.values())4445 colors = {46 'positive': '#2ecc71',47 'neutral': '#f39c12',48 'negative': '#e74c3c'49 }5051 bar_colors = [colors.get(s, '#95a5a6') for s in sentiments]5253 plt.figure(figsize=(10, 6))54 bars = plt.bar(sentiments, counts, color=bar_colors, alpha=0.8)5556 # Add value labels on bars57 for bar in bars:58 height = bar.get_height()59 plt.text(bar.get_x() + bar.get_width()/2., height,60 f'{int(height)}',61 ha='center', va='bottom', fontsize=12, fontweight='bold')6263 plt.title('Sentiment Distribution', fontsize=16, fontweight='bold')64 plt.xlabel('Sentiment', fontsize=12)65 plt.ylabel('Number of Responses', fontsize=12)66 plt.tight_layout()6768 output_path = self.output_dir / 'visualizations' / output_filename69 plt.savefig(output_path, dpi=300, bbox_inches='tight')70 plt.close()7172 return output_path7374 def generate_theme_breakdown(self, themes: List[Dict], output_filename: str):75 """Generate theme breakdown chart"""76 theme_names = [theme['name'] for theme in themes]77 weights = [theme['weight'] for theme in themes]7879 plt.figure(figsize=(12, 6))80 bars = plt.barh(theme_names, weights, color='#3498db', alpha=0.8)8182 # Add value labels83 for i, bar in enumerate(bars):84 width = bar.get_width()85 plt.text(width, bar.get_y() + bar.get_height()/2.,86 f'{weights[i]:.1f}',87 ha='left', va='center', fontsize=10)8889 plt.title('Top Themes in Customer Feedback', fontsize=16, fontweight='bold')90 plt.xlabel('Theme Weight', fontsize=12)91 plt.tight_layout()9293 output_path = self.output_dir / 'visualizations' / output_filename94 plt.savefig(output_path, dpi=300, bbox_inches='tight')95 plt.close()9697 return output_path9899 def generate_executive_summary(self, analysis_results: Dict, output_filename: str):100 """Generate executive summary report"""101 report_path = self.output_dir / 'reports' / output_filename102 report_path.parent.mkdir(exist_ok=True)103104 with open(report_path, 'w', encoding='utf-8') as f:105 f.write("=" * 80 + "\n")106 f.write(" " * 20 + "CUSTOMER FEEDBACK ANALYSIS REPORT\n")107 f.write(" " * 25 + f"{datetime.now().strftime('%B %d, %Y')}\n")108 f.write("=" * 80 + "\n\n")109110 # Overview111 f.write("EXECUTIVE SUMMARY\n")112 f.write("-" * 80 + "\n")113 f.write(f"Total Responses Analyzed: {analysis_results['total_responses']}\n")114 f.write(f"Analysis Period: {analysis_results.get('date_range', 'All time')}\n\n")115116 # Sentiment breakdown117 f.write("SENTIMENT BREAKDOWN\n")118 f.write("-" * 80 + "\n")119 sentiment_data = analysis_results['sentiment']120 for sentiment, count in sentiment_data.items():121 percentage = (count / analysis_results['total_responses'] * 100)122 f.write(f" {sentiment.capitalize()}: {count} ({percentage:.1f}%)\n")123 f.write("\n")124125 # Urgent issues126 urgent_count = analysis_results.get('urgent_count', 0)127 if urgent_count > 0:128 f.write("β οΈ URGENT ATTENTION REQUIRED\n")129 f.write("-" * 80 + "\n")130 f.write(f"{urgent_count} responses require immediate attention.\n\n")131132 urgent_feedback = analysis_results.get('urgent_feedback', [])133 for i, feedback in enumerate(urgent_feedback[:5], 1):134 f.write(f"{i}. \"{feedback['text'][:200]}...\"\n")135 f.write(f" Sentiment: {feedback['sentiment']} | Urgency Score: {feedback['urgency']:.2f}\n\n")136137 # Top themes138 f.write("TOP THEMES\n")139 f.write("-" * 80 + "\n")140 themes = analysis_results.get('themes', [])141 for i, theme in enumerate(themes[:5], 1):142 f.write(f"{i}. {theme['name']}\n")143 f.write(f" Keywords: {', '.join(theme['keywords'])}\n")144 f.write(f" Frequency: {theme['weight']:.1f}\n\n")145146 # Key insights147 f.write("KEY INSIGHTS\n")148 f.write("-" * 80 + "\n")149 insights = analysis_results.get('insights', [])150 for i, insight in enumerate(insights, 1):151 f.write(f"{i}. {insight}\n")152 f.write("\n")153154 # Recommendations155 f.write("RECOMMENDATIONS\n")156 f.write("-" * 80 + "\n")157 recommendations = analysis_results.get('recommendations', [])158 for i, rec in enumerate(recommendations, 1):159 f.write(f"{i}. {rec}\n")160161 f.write("\n" + "=" * 80 + "\n")162 f.write("End of Report\n")163164 return report_path165166167# Example usage168if __name__ == "__main__":169 generator = ReportGenerator()170171 # Sample data172 sentiment_counts = {173 'positive': 450,174 'neutral': 250,175 'negative': 147176 }177178 themes = [179 {'name': 'Product Quality', 'keywords': ['quality', 'durable', 'well-made'], 'weight': 45.2},180 {'name': 'Customer Service', 'keywords': ['support', 'helpful', 'responsive'], 'weight': 38.7},181 {'name': 'Shipping', 'keywords': ['delivery', 'fast', 'arrived'], 'weight': 32.1},182 ]183184 # Generate visualizations185 generator.generate_sentiment_distribution(sentiment_counts, 'sentiment_dist.png')186 generator.generate_theme_breakdown(themes, 'themes.png')
Step 6: Putting It All Together
Update main.py with complete workflow:
1from feedback_loader import FeedbackLoader2from sentiment_analyzer import SentimentAnalyzer3from theme_extractor import ThemeExtractor4from report_generator import ReportGenerator5import pandas as pd6from datetime import datetime78def analyze_customer_feedback(data_path, output_dir='output/'):9 """Complete feedback analysis workflow"""1011 print("=" * 60)12 print("CUSTOMER FEEDBACK ANALYSIS SYSTEM")13 print("=" * 60)14 print()1516 # Step 1: Load data17 print("[1/5] Loading feedback data...")18 loader = FeedbackLoader(data_path)19 df = loader.load_data('csv')20 print(f"β Loaded {len(df)} responses\n")2122 # Step 2: Analyze sentiment23 print("[2/5] Analyzing sentiment...")24 analyzer = SentimentAnalyzer()25 sentiment_results = analyzer.analyze_batch(df['feedback'].tolist())2627 # Add results to dataframe28 df['polarity'] = [r['polarity'] for r in sentiment_results]29 df['sentiment'] = [r['sentiment'] for r in sentiment_results]30 df['is_urgent'] = [r['is_urgent'] for r in sentiment_results]3132 sentiment_counts = df['sentiment'].value_counts().to_dict()33 urgent_count = df['is_urgent'].sum()3435 print(f"β Sentiment breakdown:")36 for sentiment, count in sentiment_counts.items():37 print(f" {sentiment.capitalize()}: {count}")38 print(f"β Urgent responses: {urgent_count}\n")3940 # Step 3: Extract themes41 print("[3/5] Extracting themes...")42 extractor = ThemeExtractor(n_themes=5)43 themes_result = extractor.extract_themes(df['feedback'].tolist())44 themes = themes_result.get('themes', [])4546 print(f"β Found {len(themes)} main themes:")47 for theme in themes[:3]:48 print(f" - {theme['name']}")49 print()5051 # Step 4: Generate visualizations52 print("[4/5] Generating visualizations...")53 generator = ReportGenerator(output_dir)5455 # Word cloud56 all_feedback_text = ' '.join(df['feedback'].tolist())57 generator.generate_word_cloud(58 all_feedback_text,59 'Customer Feedback Word Cloud',60 'wordcloud.png'61 )6263 # Sentiment distribution64 generator.generate_sentiment_distribution(65 sentiment_counts,66 'sentiment_distribution.png'67 )6869 # Theme breakdown70 generator.generate_theme_breakdown(71 themes,72 'themes_breakdown.png'73 )7475 print("β Visualizations created\n")7677 # Step 5: Generate report78 print("[5/5] Generating executive summary...")7980 # Get urgent feedback examples81 urgent_feedback = df[df['is_urgent'] == True][['feedback', 'sentiment', 'polarity']].head(10)82 urgent_examples = [83 {84 'text': row['feedback'],85 'sentiment': row['sentiment'],86 'urgency': abs(row['polarity'])87 }88 for _, row in urgent_feedback.iterrows()89 ]9091 # Generate insights92 insights = generate_insights(df, themes, sentiment_counts)9394 # Generate recommendations95 recommendations = generate_recommendations(df, themes, sentiment_counts, urgent_count)9697 analysis_results = {98 'total_responses': len(df),99 'sentiment': sentiment_counts,100 'urgent_count': urgent_count,101 'urgent_feedback': urgent_examples,102 'themes': themes,103 'insights': insights,104 'recommendations': recommendations,105 'date_range': f"{df['date'].min()} to {df['date'].max()}"106 }107108 report_path = generator.generate_executive_summary(109 analysis_results,110 f'feedback_report_{datetime.now().strftime("%Y%m%d")}.txt'111 )112113 print(f"β Report generated: {report_path}\n")114115 # Save detailed data116 output_csv = Path(output_dir) / 'reports' / f'detailed_analysis_{datetime.now().strftime("%Y%m%d")}.csv'117 df.to_csv(output_csv, index=False)118 print(f"β Detailed data exported: {output_csv}\n")119120 print("=" * 60)121 print("ANALYSIS COMPLETE")122 print("=" * 60)123124 return analysis_results125126127def generate_insights(df, themes, sentiment_counts):128 """Generate key insights from analysis"""129 insights = []130131 total = len(df)132 positive_pct = (sentiment_counts.get('positive', 0) / total * 100)133 negative_pct = (sentiment_counts.get('negative', 0) / total * 100)134135 # Sentiment insights136 if positive_pct > 70:137 insights.append(f"Overall sentiment is highly positive ({positive_pct:.1f}%), indicating strong customer satisfaction.")138 elif negative_pct > 30:139 insights.append(f"Significant negative sentiment detected ({negative_pct:.1f}%), requiring immediate attention.")140141 # Theme insights142 if themes:143 top_theme = themes[0]144 insights.append(f"'{top_theme['name']}' is the most discussed topic, mentioned in approximately {top_theme['weight']:.0f} responses.")145146 # Urgency insight147 urgent_count = df['is_urgent'].sum()148 if urgent_count > 0:149 insights.append(f"{urgent_count} responses contain urgent language requiring immediate follow-up.")150151 return insights152153154def generate_recommendations(df, themes, sentiment_counts, urgent_count):155 """Generate actionable recommendations"""156 recommendations = []157158 # Urgent issues159 if urgent_count > 0:160 recommendations.append(f"PRIORITY: Address {urgent_count} urgent customer issues within 24 hours.")161162 # Negative sentiment163 negative_pct = (sentiment_counts.get('negative', 0) / len(df) * 100)164 if negative_pct > 20:165 recommendations.append("Implement customer retention campaign targeting dissatisfied customers.")166167 # Theme-based recommendations168 for theme in themes[:3]:169 if 'service' in theme['name'].lower():170 recommendations.append("Review customer service processes and consider additional training for support team.")171 elif 'shipping' in theme['name'].lower():172 recommendations.append("Evaluate shipping partnerships and delivery times to improve logistics.")173 elif 'product' in theme['name'].lower():174 recommendations.append("Conduct product quality review and gather detailed feedback on specific issues.")175176 return recommendations177178179# Run analysis180if __name__ == "__main__":181 results = analyze_customer_feedback('data/feedback.csv')
Step 7: Advanced Features
Feature 1: Trend Analysis Over Time
Track how sentiment changes month-over-month:
1def analyze_trends(df):2 """Analyze sentiment trends over time"""3 df['month'] = pd.to_datetime(df['date']).dt.to_period('M')45 monthly_sentiment = df.groupby(['month', 'sentiment']).size().unstack(fill_value=0)67 # Calculate percentage positive8 monthly_sentiment['positive_pct'] = (9 monthly_sentiment['positive'] / monthly_sentiment.sum(axis=1) * 10010 )1112 return monthly_sentiment
Feature 2: AI-Powered Insights with GPT
Use OpenAI to generate human-like insights:
1import openai2import os34def generate_ai_insights(feedback_sample, themes):5 """Use GPT to generate insights"""6 openai.api_key = os.getenv('OPENAI_API_KEY')78 prompt = f"""9 Analyze this customer feedback and provide 3 key insights:1011 Sample feedback:12 {feedback_sample[:1000]}1314 Main themes:15 {', '.join([t['name'] for t in themes])}1617 Provide actionable business insights in bullet points.18 """1920 response = openai.ChatCompletion.create(21 model="gpt-3.5-turbo",22 messages=[23 {"role": "system", "content": "You are a customer experience analyst."},24 {"role": "user", "content": prompt}25 ],26 temperature=0.7,27 max_tokens=30028 )2930 return response.choices[0].message.content
Feature 3: Alert System
Send alerts for urgent issues:
1def send_urgent_alert(urgent_feedback):2 """Send email/Slack alert for urgent issues"""3 # Example using email4 import smtplib5 from email.mime.text import MIMEText67 if len(urgent_feedback) == 0:8 return910 message = f"""11 URGENT: {len(urgent_feedback)} critical customer feedback items detected.1213 Immediate action required for:14 {urgent_feedback[['feedback', 'sentiment']].to_string()}1516 Please review and respond within 24 hours.17 """1819 # Send email (configure SMTP settings)20 # send_email(to='support@company.com', subject='Urgent Customer Feedback', body=message)
Best Practices
1. Regular Analysis Schedule
Run analysis:
- Daily: For high-volume businesses with urgent issues
- Weekly: For moderate feedback volume
- Monthly: For comprehensive trend analysis
2. Feedback Quality
Ensure feedback is actionable:
- Ask specific questions in surveys
- Provide open-ended text fields
- Include context (product, transaction date, etc.)
3. Act on Insights
Analysis is worthless without action:
- Assign urgent issues to support team immediately
- Track response time and resolution rate
- Follow up with customers who had negative experiences
4. Iterate on Categories
Refine your theme categories over time:
- Add new categories as products/services evolve
- Update keywords based on actual feedback language
- Remove irrelevant categories
Frequently Asked Questions
Can this analyze feedback in languages other than English?
Yes! Replace spaCy model with language-specific models (e.g., es_core_web_sm for Spanish). TextBlob supports multiple languages with translation.
How accurate is sentiment analysis? Typically 70-85% accurate. For critical decisions, review flagged items manually. Sarcasm and context can confuse algorithms.
What if I have 100,000+ responses? Use data sampling or distributed processing (Apache Spark, Dask). Analyze most recent 10K for trends, full dataset for deep insights quarterly.
Can I integrate this with Zendesk/Salesforce? Yes! Both have APIs. Replace CSV import with API calls to fetch tickets/cases automatically.
How do I handle multilingual feedback?
Detect language first (using langdetect library), then route to appropriate language model or translate to English before analysis.
What about emojis and internet slang?
Preprocess to convert emojis to text (π β "happy") and expand slang ("gr8" β "great") using libraries like emoji and custom dictionaries.
Related articles: AI Email Assistant: Reduce Inbox Time by 70%, Automate Sales Pipeline with CRM Workflows, Python Automate Outlook Calendar Scheduling
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
