Using C# and Azure Text Analytics Sentiment Analysis To Respond To Customer Complaints
I’m a dev. I just make apps. I only deal with customers when something breaks and my support teams can’t fix it. They’re just an annoyance, right? Wrong. I’m going to show you how Azure Cognitive Services Text Analytics Sentiment Analysis can help you retain customers and look good while you’re doing it.
Anyone who has worked in a customer service related field will tell you that it’s far less expensive to keep an existing customer happy than it is to win a new customer to your product or service. This is an unavoidable fact and yet so many companies get it wrong. They put little effort into their existing customers until it is too late. Another fact is that all of these customers have friends and social media accounts of their own. And they will share that experience, be it good or bad, to the detriment or glorious success of your company. One lost existing customer is 100 customers who will never even give your product a try. One mad customer that you’ve made happy and kept around is 10 potentially new customers who will be willing to give you a try. (Yeah, I know, people complain far more than they praise. We’re funny like that.)
Take my own experience for instance. I was a long time customer of AT&T U-Verse. I was happy with everything except the constantly increasing prices. A few years in I was paying 5 times per month what I had paid the first year, and for essentially the same levels of service. I called and they were unwilling to budge on price. So I switched to Spectrum. I’m getting 6x the internet speed and twice the TV channels for 1/3 the monthly price I was paying AT&T. Now I’ve started getting ads from AT&T inviting me to come back, offering me deal after deal. Guess what? It ain’t gonna happen. And I’ve told at least 10 other people that I left U-Verse behind and why. Word spreads.
Another example is Amazon. The Amazon site allows sellers and manufacturers to respond to customer reviews on products that Amazon sells. Some manufacturers are extremely pro-active. Others never, ever, ever respond, even to the worst reviews. I’m much more likely to purchase a product where I know the manufacturer is anxious to make me happy than I am one where they never respond. This is even more true for a company I’ve heard little to nothing about.
It’s simple. Companies that respond quickly to resolve customer issues and complaints survive and thrive. And the faster a company resolves a customer complaint, the happier that customer will be. And the first step to responding is to identify the complaint.
Identifying Negative Customer Feedback
Before you can respond to negative feedback, you have to be aware that it exists. It can come from any number of places, especially in this age of social media. And even once you have aggregated all your feedback, how do you identify and prioritize that feedback so you can address complaints and hopefully prevent a customer from being lost. Once again Azure Text Analytics comes to the rescue.
In my previous blog post, I discussed two of the three Azure Text Analytics services, Language Identification and Keyword Analysis. This time I’ll cover the third of the Text APIs, Sentiment Analysis. Simply put, the Sentiment Analysis API will allow you to pass in a piece of text, along with the identified language and an ID string and receive a score from 0 to 1 indicating how postive or negative the text was. A score close to 0 is extremely negative and a score close to 1 is extremely positive. Using this score you can quickly parse out and prioritize the extremely negative feedback and try to turn a bad experience into a win.
I covered in the previous post how to make calls to the API using REST and the C# NuGet package (which is a simplified wrapper for the REST calls). I won’t re-cover both examples here and the code examples will be using the C# NuGet package. If you wish to know how to make calls using REST in another language, check out the Azure documentation here.
Example Application
For this example, I will create a simple C# console application. It will simulate customer feedback from various sources that has been aggregated into our analyzer. As each message “arrives”, it will be parsed and sent to Azure for analysis and scored. The results will be ranked and output to the console. For this example, time is also a factor. As such, the score of negative messages will receive a time based modifier such that older messages will see their score adjusted more towards zero than newer messages. For this demonstration, we will term any message with a score <= 0.3 as “negative”. And for every 15 minutes old the message is, we will take 10% off (i.e. multiply by 0.9).
We will use a simple class to keep track of our needed info locally. Something like the following should suffice:
public class CustomerFeedback {
public Guid Id { get; set; }
public DateTime ReceivedTime { get; set; } //artificially age how old the message is
public string Name { get; set; } //generic customer name
public string Feedback { get; set; } //text of the feedback to send to Azure
public string Language { get; set; } //We'll assume 'en' for all records for this demo
public float SentimentScore { get; set; } //the score received from Text Analytics
public float ModifiedScore { get; set; } //our modified final score based on record age
}
For this demo I grabbed about a hundred random reviews of a Microsoft Wireless X-Box Controller for PC off of Amazon. It should provide a good random selection of customer sentiment. The device was rated around 3.8 stars overall. For the purpose of this demo though, we only want the text reviews. For this demo, we are assuming all reviews are in the English language and use the code “en” for all records. However, Sentiment Analysis is currently available for English, French, Portuguese (Portugal), and Spanish. It is in preview for Danish, Dutch, Finnish, German, Greek, Italian, Norwegian, Polish, Russian, Swedish and Turkish.
Here’s the code I used to set everything up for my local data:
for (var x = 1; x <= 100; x++)
{
var name = "Customer {x}";
//generic customer name assigned
var receivedTime = DateTime.Now.Subtract(TimeSpan.FromMinutes(_rand.Next(600)));
//artificially age the review
var feedback = GetFeedback(x);
//grab a review from the list. The list order was randomized on initialization
//now create our local record to track
var newFeed = new CustomerFeedback() {
Feedback = feedback, Id = Guid.NewGuid(), Language = "en", ModifiedScore = 1.0f, Name = name, ReceivedTime = receivedTime, SentimentScore = 1.0f };
Feedback.Add(newFeed);
}
The final part is retrieving the results from Azure Cognitive Services Text Analytics Sentiment Analysis API (say that 5 times fast). Once more the code for that is fairly straightforward and easy to implement. Using the NuGet package we simply create our client, create a request, add the documents to that request, make our call and then process the results. As with other Text Analytics calls, you make submit up to 1000 documents with each request and each document can be no more than 5000 characters.
Here’s a sample code block for making the call.
//create our client
var client = new SentimentClient("<YOUR_API_KEY>")
{
Url = "https://<YOUR_DATA_CENTER>.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment"
};
//create the request
var request = new SentimentRequest {Documents = new List<IDocument>()};
foreach (var customerFeedback in Feedback)
{
//add each request document
request.Documents.Add(new SentimentDocument()
{
Id=customerFeedback.Id.ToString(), Language = customerFeedback.Language, Text = customerFeedback.Feedback
});
}
//check our reqests are valid before sending
request.Validate();
//send the request to Azure Cognitive Services Text Analytics Sentiment Analysis
var response = client.GetSentiment(request);
//report errors
if (response.Errors.Any())
{
foreach (var responseError in response.Errors)
{
Console.WriteLine("Error: {responseError.Message}"); } }
//iterate responses and process scores
foreach (var sentimentDocumentResult in response.Documents) {
var originalFeedback = Feedback.FirstOrDefault(x => x.Id.ToString() == sentimentDocumentResult.Id);
if (originalFeedback != null) {
originalFeedback.SentimentScore = sentimentDocumentResult.Score;
originalFeedback.ModifiedScore = sentimentDocumentResult.Score;
//modify score - 10% for every 15 minutes we artificially aged the record
var age = DateTime.Now.Subtract(originalFeedback.ReceivedTime).Minutes;
while (age < 15) { originalFeedback.ModifiedScore = originalFeedback.ModifiedScore * 0.9f;
age = age - 15;
}
}
}
//now report negative responses in our calculated priority order
foreach (var customerFeedback in Feedback.Where(x=>x.ModifiedScore <= 0.3).OrderBy(x=>x.ModifiedScore)) { Console.WriteLine("Customer Name: {customerFeedback.Name}");
Console.WriteLine("Sentiment Score: {customerFeedback.SentimentScore} -- Modified Score: {customerFeedback.ModifiedScore} -- Received Time: {customerFeedback.ReceivedTime.ToShortTimeString()} ({DateTime.Now.Subtract(customerFeedback.ReceivedTime).Minutes} minutes ago)");
Console.WriteLine(customerFeedback.Feedback);
Console.WriteLine("-------------------------------------------------------------------------------"); Console.WriteLine("-------------------------------------------------------------------------------");
}
Final Result
And that’s it! We’ve got our prioritized list. It’s time for your sales and support teams to get out there and rescue those customer relationships that you’re in danger of losing. And if you can pitch it to your bosses as a way to spot and retain at-risk customers, you’ll look like a star and hopefully get the funding for a new LOB app that lets you play around with new tech and APIs. We’re devs. We all love to play with the new toys, especially when the boss is paying!