Predicting sentiment from product reviews

In this notebook, you will use product review data from Amazon.com to predict whether the sentiments about a product (from its reviews) are positive or negative.

  • Use DataFrames to do some feature engineering
  • Train a logistic regression model to predict the sentiment of product reviews.
  • Inspect the weights (coefficients) of a trained logistic regression model.
  • Make a prediction (both class and probability) of sentiment for a new product review.
  • Given the logistic regression weights, predictors and ground truth labels, write a function to compute the accuracy of the model.
  • Inspect the coefficients of the logistic regression model and interpret their meanings.
  • Compare multiple logistic regression models.

Importing Libraries


In [69]:
import os
import zipfile
import string
import numpy as np
import pandas as pd
from sklearn import linear_model
from sklearn.feature_extraction.text import CountVectorizer
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('darkgrid')
%matplotlib inline

Unzipping files with Amazon Baby Products Reviews

The dataset consists of baby product reviews from Amazon.com.


In [70]:
# Put files in current direction into a list
files_list = [f for f in os.listdir('.') if os.path.isfile(f)]

In [71]:
# Filename of unzipped file
unzipped_file = 'amazon_baby.csv'

In [72]:
# If upzipped file not in files_list, unzip the file
if unzipped_file not in files_list:
    zip_file = unzipped_file + '.zip'
    unzipping = zipfile.ZipFile(zip_file)
    unzipping.extractall()
    unzipping.close

Loading the products data

The dataset is loaded into a Pandas DataFrame called products.


In [73]:
products = pd.read_csv("amazon_baby.csv")

Now, let us see a preview of what the dataset looks like.


In [74]:
products.head()


Out[74]:
name review rating
0 Planetwise Flannel Wipes These flannel wipes are OK, but in my opinion ... 3
1 Planetwise Wipe Pouch it came early and was not disappointed. i love... 5
2 Annas Dream Full Quilt with 2 Shams Very soft and comfortable and warmer than it l... 5
3 Stop Pacifier Sucking without tears with Thumb... This is a product well worth the purchase. I ... 5
4 Stop Pacifier Sucking without tears with Thumb... All of my kids have cried non-stop when I trie... 5

Performing text cleaning

Let us explore a specific example of a baby product.


In [75]:
products.ix[1]


Out[75]:
name                                  Planetwise Wipe Pouch
review    it came early and was not disappointed. i love...
rating                                                    5
Name: 1, dtype: object

Now, we will perform 2 simple data transformations:

  1. Remove punctuation using Python's built-in string functionality.
  2. Transform the reviews into word-counts.

Aside. In this notebook, we remove all punctuations for the sake of simplicity. A smarter approach to punctuations would preserve phrases such as "I'd", "would've", "hadn't" and so forth. See this page for an example of smart handling of punctuations.

Before removing the punctuation from the strings in the review column, we will fall all NA values with empty string.


In [76]:
products["review"] = products["review"].fillna("")

Below, we are removing all the punctuation from the strings in the review column and saving the result into a new column in the dataframe.


In [77]:
products["review_clean"] = products["review"].str.translate(None, string.punctuation)

Extract sentiments

We will ignore all reviews with rating = 3, since they tend to have a neutral sentiment.


In [78]:
products = products[products['rating'] != 3]
len(products)


Out[78]:
166752

Now, we will assign reviews with a rating of 4 or higher to be positive reviews, while the ones with rating of 2 or lower are negative. For the sentiment column, we use +1 for the positive class label and -1 for the negative class label.

Below, we are create a function we will applyi to the "ratings" column of the dataframe to determine if the review is positive or negative.


In [79]:
def sent_func(x):
    # If rating is >=4, return a positive sentiment (+1)
    if x>=4:
        return 1
    # Else, return a negative sentiment (-1)
    else:
        return -1

Creating a "sentiment" column by applying the sent_func to the "rating" column in the dataframe.


In [80]:
products['sentiment'] = products['rating'].apply(sent_func)

In [81]:
products.ix[20:22]


Out[81]:
name review rating review_clean sentiment
20 Nature's Lullabies Second Year Sticker Calendar I had a hard time finding a second year calend... 5 I had a hard time finding a second year calend... 1
21 Nature's Lullabies Second Year Sticker Calendar I only purchased a second-year calendar for my... 2 I only purchased a secondyear calendar for my ... -1
22 Nature's Lullabies Second Year Sticker Calendar I LOVE this calendar for recording events of m... 5 I LOVE this calendar for recording events of m... 1

Now, we can see that the dataset contains an extra column called sentiment which is either positive (+1) or negative (-1).

Split data into training and test sets

Let's perform a train/test split with 80% of the data in the training set and 20% of the data in the test set.

Loading the indicies for the train and test data and putting them in a list


In [82]:
with open('module-2-assignment-train-idx.txt', 'r') as train_file:
    ind_list_train = map(int,train_file.read().split(','))

In [83]:
with open('module-2-assignment-test-idx.txt', 'r') as test_file:
    ind_list_test = map(int,test_file.read().split(','))

Using the indicies of the train and test data to create the train and test datasets.


In [84]:
train_data = products.iloc[ind_list_train,:]
test_data = products.iloc[ind_list_test,:]

In [85]:
print len(train_data)
print len(test_data)


133416
33336

Build the word count vector for each review

We will now compute the word count for each word that appears in the reviews. A vector consisting of word counts is often referred to as bag-of-word features. Since most words occur in only a few reviews, word count vectors are sparse. For this reason, scikit-learn and many other tools use sparse matrices to store a collection of word count vectors. Refer to appropriate manuals to produce sparse word count vectors. General steps for extracting word count vectors are as follows:

  • Learn a vocabulary (set of all words) from the training data. Only the words that show up in the training data will be considered for feature extraction.
  • Compute the occurrences of the words in each review and collect them into a row vector.
  • Build a sparse matrix where each row is the word count vector for the corresponding review. Call this matrix train_matrix.
  • Using the same mapping between words and columns, convert the test data into a sparse matrix test_matrix.

The following cell uses CountVectorizer in scikit-learn. Notice the token_pattern argument in the constructor.


In [86]:
# Use this token pattern to keep single-letter words
vectorizer = CountVectorizer(token_pattern=r'\b\w+\b')
# First, learn vocabulary from the training data and assign columns to words
# Then convert the training data into a sparse matrix
train_matrix = vectorizer.fit_transform(train_data['review_clean'])
# Second, convert the test data into a sparse matrix, using the same word-column mapping
test_matrix = vectorizer.transform(test_data['review_clean'])

Train a sentiment classifier with logistic regression

We will now use logistic regression to create a sentiment classifier on the training data. This model will use the column word_count as a feature and the column sentiment as the target.

Note: This line may take 1-2 minutes.

Creating an instance of the LogisticRegression class


In [87]:
logreg = linear_model.LogisticRegression()

Using the fit method to train the classifier. This model should use the sparse word count matrix (train_matrix) as features and the column sentiment of train_data as the target. Use the default values for other parameters. Call this model sentiment_model.


In [88]:
sentiment_model = logreg.fit(train_matrix, train_data["sentiment"])

Putting all the weights from the model into a numpy array.


In [89]:
weights_list = list(sentiment_model.intercept_) + list(sentiment_model.coef_.flatten())
weights_sent_model = np.array(weights_list, dtype = np.double)
print len(weights_sent_model)


121713

There are a total of 121713 coefficients in the model. Recall from the lecture that positive weights $w_j$ correspond to weights that cause positive sentiment, while negative weights correspond to negative sentiment.

Quiz question: How many weights are >= 0?


In [90]:
num_positive_weights = len(weights_sent_model[weights_sent_model >= 0.0])
num_negative_weights = len(weights_sent_model[weights_sent_model < 0.0])

print "Number of positive weights: %i" % num_positive_weights
print "Number of negative weights: %i" % num_negative_weights


Number of positive weights: 85915
Number of negative weights: 35798

Making predictions with logistic regression

Now that a model is trained, we can make predictions on the test data. In this section, we will explore this in the context of 3 examples in the test dataset. We refer to this set of 3 examples as the sample_test_data.


In [91]:
sample_test_data = test_data.ix[[59,71,91]]
print sample_test_data['rating']
sample_test_data


59    5
71    2
91    1
Name: rating, dtype: int64
Out[91]:
name review rating review_clean sentiment
59 Our Baby Girl Memory Book Absolutely love it and all of the Scripture in... 5 Absolutely love it and all of the Scripture in... 1
71 Wall Decor Removable Decal Sticker - Colorful ... Would not purchase again or recommend. The dec... 2 Would not purchase again or recommend The deca... -1
91 New Style Trailing Cherry Blossom Tree Decal R... Was so excited to get this product for my baby... 1 Was so excited to get this product for my baby... -1

Let's dig deeper into the first row of the sample_test_data. Here's the full review:


In [92]:
sample_test_data['review'].ix[59]


Out[92]:
'Absolutely love it and all of the Scripture in it.  I purchased the Baby Boy version for my grandson when he was born and my daughter-in-law was thrilled to receive the same book again.'

That review seems pretty positive.

Now, let's see what the next row of the sample_test_data looks like. As we could guess from the sentiment (-1), the review is quite negative.


In [93]:
sample_test_data['review'].ix[71]


Out[93]:
'Would not purchase again or recommend. The decals were thick almost plastic like and were coming off the wall as I was applying them! The would NOT stick! Literally stayed stuck for about 5 minutes then started peeling off.'

We will now make a class prediction for the sample_test_data. The sentiment_model should predict +1 if the sentiment is positive and -1 if the sentiment is negative. Recall from the lecture that the score (sometimes called margin) for the logistic regression model is defined as:

$$ \mbox{score}_i = \mathbf{w}^T h(\mathbf{x}_i) $$

where $h(\mathbf{x}_i)$ represents the features for example $i$. We will write some code to obtain the scores . For each row, the score (or margin) is a number in the range [-inf, inf].


In [94]:
sample_test_matrix = vectorizer.transform(sample_test_data['review_clean'])
scores = sentiment_model.decision_function(sample_test_matrix)
print scores


[  5.61129645  -3.14942405 -10.42641065]

Predicting sentiment

These scores can be used to make class predictions as follows:

$$ \hat{y} = \left\{ \begin{array}{ll} +1 & \mathbf{w}^T h(\mathbf{x}_i) > 0 \\ -1 & \mathbf{w}^T h(\mathbf{x}_i) \leq 0 \\ \end{array} \right. $$

Using scores, write code to calculate $\hat{y}$, the class predictions:


In [95]:
pred_sent_test_data = []
for val in scores:
    if val>0:
        pred_sent_test_data.append(1)
    else:
        pred_sent_test_data.append(-1)
print pred_sent_test_data


[1, -1, -1]

Checkpoint: Run the following code to verify that the class predictions obtained by your calculations are the same as that obtained from Scikit-Learn.


In [96]:
print "Class predictions according to Scikit-Learn:" 
print sentiment_model.predict(sample_test_matrix)


Class predictions according to Scikit-Learn:
[ 1 -1 -1]

Probability predictions

Recall from the lectures that we can also calculate the probability predictions from the scores using: $$ P(y_i = +1 | \mathbf{x}_i,\mathbf{w}) = \frac{1}{1 + \exp(-\mathbf{w}^T h(\mathbf{x}_i))}. $$

Using the variable scores calculated previously, write code to calculate the probability that a sentiment is positive using the above formula. For each row, the probabilities should be a number in the range [0, 1].


In [97]:
prob_pos_score = 1.0/(1.0 + np.exp(-scores))
prob_pos_score


Out[97]:
array([  9.96356994e-01,   4.11139780e-02,   2.96383837e-05])

Checkpoint: Make sure your probability predictions match the ones obtained from Scikit-Learn.


In [98]:
print "Class predictions according to Scikit-Learn:" 
print sentiment_model.predict_proba(sample_test_matrix)[:,1]


Class predictions according to Scikit-Learn:
[  9.96356994e-01   4.11139780e-02   2.96383837e-05]

Quiz Question: Of the three data points in sample_test_data, which one (first, second, or third) has the lowest probability of being classified as a positive review?

The 3rd data point has the lowest probability of being positive

Find the most positive (and negative) review

We now turn to examining the full test dataset, test_data.

Using the sentiment_model, find the 40 reviews in the entire test_data with the highest probability of being classified as a positive review. We refer to these as the "most positive reviews."

To calculate these top-40 reviews, use the following steps:

  1. Make probability predictions on test_data using the sentiment_model.
  2. Sort the data according to those predictions and pick the top 40.

Computing the scores with the sentiment_model decision function and then calculating the probability that y = +1


In [99]:
scores_test_data = sentiment_model.decision_function(test_matrix)
prob_test_data = 1.0/(1.0 + np.exp(-scores_test_data))

To find the 40 most positive and the 40 most negative values, we will create a list of tuples with the entries (probability, index). We will then sort the list and will be able to extract the indicies corresponding to each entry.


In [100]:
# List of indicies in the test data
ind_vals_test_data = test_data.index.values
# Empty list that will be filled with the tuples (probability, index)
score_label_lst_test = len(scores_test_data)*[-1]

Filling the list of tuples with the (probability, index) values


In [101]:
for i in range(len(scores_test_data)):
    score_label_lst_test[i] = (prob_test_data[i],ind_vals_test_data[i])

Sorting the list with the entries (probability, index)


In [102]:
score_label_lst_test.sort()

Extracting the top 40 positive reviews and the top 40 negative reviews


In [103]:
top_40_pos_test_rev = score_label_lst_test[-40:]
top_40_neg_test_rev = score_label_lst_test[0:40]

Getting the indicies of the top 40 positive reviews.


In [104]:
ind_top_40_pos_test = 40*[-1]
for i,val in enumerate(top_40_pos_test_rev):
    ind_top_40_pos_test[i] = val[1]

Getting the indicies of the top 40 negative reviews.


In [105]:
ind_top_40_neg_test = 40*[-1]
for i,val in enumerate(top_40_neg_test_rev):
    ind_top_40_neg_test[i] = val[1]

Quiz Question: Which of the following products are represented in the 40 most positive reviews? [multiple choice]


In [106]:
test_data.ix[ind_top_40_pos_test]["name"]


Out[106]:
8841      Peg Perego Primo Viaggio Car Seat / Infant Car...
172085                  Stokke Scoot Stroller - Light Green
49749     Peg Perego Aria Light Weight One Hand Fold Str...
154622    Rainy Day Indoor Playground toddler swing to b...
42907             Prince Lionheart bebePOD Plus, Watermelon
178750    Joovy Groove Ultralight Umbrella Stroller, Cha...
119618                 Quinny 2012 Buzz Stroller, Rebel Red
70808                 Skip Hop Studio Diaper Bag, Black Dot
2985                            BABYBJORN Potty Chair - Red
51417     The First Years True Fit Convertible Car Seat,...
26833     Lilly Gold Sit 'n' Stroll 5 in 1 Car Seat and ...
115731            Emily Green 6&quot; Bowl, Sunshine Safari
99692                             Dr. Brown's Bottle Warmer
13168         Roundabout Convertible Car Seat - Grey Wicker
166827    Britax Boulevard 70-G3 Convertible Car Seat Se...
14008         Stork Craft Beatrice Combo Tower Chest, White
59276                      Britax Frontier Booster Car Seat
121668    Door Monkey, Childproof Door Lock &amp; Pinch ...
80881        Delta Universal 6 Drawer Dresser, Black Cherry
172351    Phil &amp; Teds Navigator Buggy Golden Kiwi Fr...
147996    Baby Jogger City Mini GT Double Stroller, Shad...
182089    Summer Infant Wide View Digital Color Video Mo...
22586        Britax Decathlon Convertible Car Seat, Tiffany
165593    Ikea 36 Pcs Kalas Kids Plastic BPA Free Flatwa...
50315            P'Kolino Silly Soft Seating in Tias, Green
52631     Evenflo X Sport Plus Convenience Stroller - Ch...
66059          Evenflo 6 Pack Classic Glass Bottle, 4-Ounce
80155     Simple Wishes Hands-Free Breastpump Bra, Pink,...
87017       Baby Einstein Around The World Discovery Center
97325     Freemie Hands-Free Concealable Breast Pump Col...
100166    Infantino Wrap and Tie Baby Carrier, Black Blu...
114796    Fisher-Price Cradle 'N Swing,  My Little Snuga...
119182    Roan Rocco Classic Pram Stroller 2-in-1 with B...
133651                    Britax 2012 B-Agile Stroller, Red
137034           Graco Pack 'n Play Element Playard - Flint
140816           Diono RadianRXT Convertible Car Seat, Plum
147949    Baby Jogger City Mini GT Single Stroller, Shad...
168081    Buttons Cloth Diaper Cover - One Size - 8 Colo...
168697    Graco FastAction Fold Jogger Click Connect Str...
180646        Mamas &amp; Papas 2014 Urbo2 Stroller - Black
Name: name, dtype: object

Quiz Question: Which of the following products are represented in the 20 most negative reviews? [multiple choice]


In [107]:
test_data.ix[ind_top_40_neg_test]["name"]


Out[107]:
16042           Fisher-Price Ocean Wonders Aquarium Bouncer
120209    Levana Safe N'See Digital Video Baby Monitor w...
77072        Safety 1st Exchangeable Tip 3 in 1 Thermometer
48694     Adiri BPA Free Natural Nurser Ultimate Bottle ...
155287    VTech Communications Safe &amp; Sounds Full Co...
94560     The First Years True Choice P400 Premium Digit...
53207                   Safety 1st High-Def Digital Monitor
81332                 Cloth Diaper Sprayer--styles may vary
113995    Motorola Digital Video Baby Monitor with Room ...
10677                     Philips AVENT Newborn Starter Set
9915           Cosco Alpha Omega Elite Convertible Car Seat
59546                Ellaroo Mei Tai Baby Carrier - Hershey
75994            Peg-Perego Tatamia High Chair, White Latte
172090    Belkin WeMo Wi-Fi Baby Monitor for Apple iPhon...
40079     Chicco Cortina KeyFit 30 Travel System in Adve...
149987                     NUK Cook-n-Blend Baby Food Maker
154878    VTech Communications Safe &amp; Sound Digital ...
1116                  Safety 1st Deluxe 4-in-1 Bath Station
83234         Thirsties Hemp Inserts 2 Pack, Small 6-18 Lbs
31741                Regalo My Cot Portable Bed, Royal Blue
176046         Baby Trend Inertia Infant Car Seat - Horizon
105055                  Angelcare Baby Sound Monitor, White
95420          One Step Ahead Hide-Away Extra Long Bed Rail
116391    Keekaroo Height Right High Chair, Infant Inser...
153488         Kidz Delight Smooth Touch Tablet, Fun N Play
83571     Nuby Natural Touch Silicone Travel Infa Feeder...
96572      Baby Jogger Summit XC Double Stroller, Red/Black
139654    Fisher-Price Discover 'n Grow Take-Along Play ...
99594     Valco Baby Tri-mode Twin Stroller EX- Hot Choc...
66354     Levana BABYVIEW20 Interference Free Digital Wi...
17089     Playtex Nurser With Drop-Ins Liner, 4 Ounce, C...
140418    Jolly Jumper Arctic Sneak A Peek Infant Car Se...
82324     Cloud b Gentle Giraffe On The Go Travel Sound ...
172823    Ergobaby Performance Collection Charcoal Grey ...
54267              Pearhead Babyprints Keepsake, Year-Round
76000            Peg-Perego Tatamia High Chair, White Latte
148431    Munchkin Nursery Projector and Sound System, W...
1020                  Safety 1st Deluxe 4-in-1 Bath Station
122105             Baby Trend Encore Travel System-Columbia
26194                            Sunshine Kids Travel - Bag
Name: name, dtype: object

Compute accuracy of the classifier

We will now evaluate the accuracy of the trained classifer. Recall that the accuracy is given by

$$ \mbox{accuracy} = \frac{\mbox{# correctly classified examples}}{\mbox{# total examples}} $$

This can be computed as follows:

  • Step 1: Use the trained model to compute class predictions
  • Step 2: Count the number of data points when the predicted class labels match the ground truth labels (called true_labels below).
  • Step 3: Divide the total number of correct predictions by the total number of data points in the dataset.

Complete the function below to compute the classification accuracy:


In [108]:
def get_classification_accuracy(model, data, true_labels):
    
    # Constructing the wordcount vector
    data_matrix = vectorizer.transform(data['review_clean'])
    
    # Getting the predictions
    preds_data = model.predict(data_matrix)
    
    # Computing the number of correctly classified examples and the total examples
    n_correct = float(np.sum(preds_data == true_labels.values))
    n_total = float(len(preds_data))

    # Computing the accuracy by dividing number of 
    #correctly classified examples by total number of examples
    accuracy = n_correct/n_total
    
    return accuracy

Now, let's compute the classification accuracy of the sentiment_model on the test_data.


In [109]:
acc_sent_mod_test = get_classification_accuracy(sentiment_model, test_data, test_data['sentiment'])
print acc_sent_mod_test


0.932145428366

Quiz Question: What is the accuracy of the sentiment_model on the test_data? Round your answer to 2 decimal places (e.g. 0.76).


In [110]:
print "Accuracy on Test Data: %.2f" %(acc_sent_mod_test)


Accuracy on Test Data: 0.93

Quiz Question: Does a higher accuracy value on the training_data always imply that the classifier is better?

No, you may be overfitting.

Now, computing the accuracy of the sentiment model on the training data for a future quiz question.


In [111]:
acc_sent_mod_train = get_classification_accuracy(sentiment_model, train_data, train_data['sentiment'])
print acc_sent_mod_train


0.96789740361

Finding the weights of significant words for the sentiment_model.

In this section, we will find the weights of significant words for the sentiment_model.

Creating a vocab list. The vocab list constains all the words used for the sentiment_model


In [112]:
vocab = vectorizer.get_feature_names()
print len(vocab)


121712

Creating a list of the significant words in the utf-8 format


In [113]:
un_sig_words = [u'love', u'great', u'easy', u'old', u'little', u'perfect', u'loves', 
                u'well', u'able', u'car', u'broke', u'less', u'even', u'waste', u'disappointed', 
                u'work', u'product', u'money', u'would', u'return']

Creating a list that will store all the indicies where the significant words appear in the vocab list.


In [114]:
ind_vocab_sig_words = []

Finding the index where each significant word appears.


In [115]:
for word in un_sig_words:
    ind_vocab_sig_words.append(vocab.index(word))

Creating an empty list that will store the weights of the significant words. Then, using the index to find the weight for each signigicant word.


In [116]:
ws_sent_mod_sig_words = []
for ind in ind_vocab_sig_words:
    ws_sent_mod_sig_words.append(sentiment_model.coef_.flatten()[ind])

Creating a series that will store the weights of the significant words and displaying this Series.


In [117]:
ws_sent_mod_ser = pd.Series(data=ws_sent_mod_sig_words, index=un_sig_words)
ws_sent_mod_ser


Out[117]:
love            1.574856
great           1.228180
easy            1.358323
old             0.052743
little          0.637940
perfect         1.862897
loves           1.518146
well            0.540171
able            0.390223
car             0.124075
broke          -1.392661
less           -0.277272
even           -0.464528
waste          -1.992954
disappointed   -2.195897
work           -0.460691
product        -0.191460
money          -0.784845
would          -0.288619
return         -1.654747
dtype: float64

Learn another classifier with fewer words

There were a lot of words in the model we trained above. We will now train a simpler logistic regression model using only a subet of words that occur in the reviews. For this assignment, we selected a 20 words to work with. These are:


In [118]:
significant_words = ['love', 'great', 'easy', 'old', 'little', 'perfect', 'loves', 
      'well', 'able', 'car', 'broke', 'less', 'even', 'waste', 'disappointed', 
      'work', 'product', 'money', 'would', 'return']

In [119]:
len(significant_words)


Out[119]:
20

Compute a new set of word count vectors using only these words. The CountVectorizer class has a parameter that lets you limit the choice of words when building word count vectors:


In [120]:
vectorizer_word_subset = CountVectorizer(vocabulary=significant_words) # limit to 20 words
train_matrix_word_subset = vectorizer_word_subset.fit_transform(train_data['review_clean'])
test_matrix_word_subset = vectorizer_word_subset.transform(test_data['review_clean'])

Train a logistic regression model on a subset of data

We will now build a classifier with word_count_subset as the feature and sentiment as the target.

Creating an instance of the LogisticRegression class. Using the fit method to train the classifier. This model should use the sparse word count matrix (train_matrix) as features and the column sentiment of train_data as the target. Use the default values for other parameters. Call this model simple_model.


In [121]:
log_reg = linear_model.LogisticRegression()
simple_model = logreg.fit(train_matrix_word_subset, train_data["sentiment"])

Getting the weights for the 20 significant words from the simple_model


In [122]:
ws_simp_model = list(simple_model.coef_.flatten())

Putting the weights in a Series with the words corresponding to the weights as the index.


In [123]:
ws_simp_mod_ser = pd.Series(data=ws_simp_model, index=significant_words)
ws_simp_mod_ser


Out[123]:
love            1.363690
great           0.944000
easy            1.192538
old             0.085513
little          0.520186
perfect         1.509812
loves           1.673074
well            0.503760
able            0.190909
car             0.058855
broke          -1.651576
less           -0.209563
even           -0.511380
waste          -2.033699
disappointed   -2.348298
work           -0.621169
product        -0.320556
money          -0.898031
would          -0.362167
return         -2.109331
dtype: float64

Quiz Question: Consider the coefficients of simple_model. How many of the 20 coefficients (corresponding to the 20 significant_words and excluding the intercept term) are positive for the simple_model?


In [124]:
print len(simple_model.coef_[simple_model.coef_>0])


10

Quiz Question: Are the positive words in the simple_model (let us call them positive_significant_words) also positive words in the sentiment_model?

Yes, see weights below for the significant words for the sentiment model


In [125]:
ws_sent_mod_ser


Out[125]:
love            1.574856
great           1.228180
easy            1.358323
old             0.052743
little          0.637940
perfect         1.862897
loves           1.518146
well            0.540171
able            0.390223
car             0.124075
broke          -1.392661
less           -0.277272
even           -0.464528
waste          -1.992954
disappointed   -2.195897
work           -0.460691
product        -0.191460
money          -0.784845
would          -0.288619
return         -1.654747
dtype: float64

Comparing models

We will now compare the accuracy of the sentiment_model and the simple_model using the get_classification_accuracy method you implemented above.

First, compute the classification accuracy of the sentiment_model on the train_data:


In [126]:
acc_sent_mod_train


Out[126]:
0.967897403609762

Now, compute the classification accuracy of the simple_model on the train_data:


In [127]:
preds_simp_mod_train = simple_model.predict(train_matrix_word_subset)
n_cor_preds_simp_mod_train = float(np.sum(preds_simp_mod_train == train_data['sentiment'].values))
n_tol_preds_simp_mod_train = float(len(preds_simp_mod_train))
acc_simp_mod_train = n_cor_preds_simp_mod_train/n_tol_preds_simp_mod_train
print acc_simp_mod_train


0.866822570007

Quiz Question: Which model (sentiment_model or simple_model) has higher accuracy on the TRAINING set?


In [128]:
if acc_sent_mod_train>acc_simp_mod_train:
    print "sentiment_model"
else:
    print "simple_model"


sentiment_model

Now, we will repeat this excercise on the test_data. Start by computing the classification accuracy of the sentiment_model on the test_data:


In [129]:
acc_sent_mod_test


Out[129]:
0.9321454283657308

Next, we will compute the classification accuracy of the simple_model on the test_data:


In [130]:
preds_simp_mod_test = simple_model.predict(test_matrix_word_subset)
n_cor_preds_simp_mod_test = float(np.sum(preds_simp_mod_test == test_data['sentiment'].values))
n_tol_preds_simp_mod_test = float(len(preds_simp_mod_test))
acc_simp_mod_test = n_cor_preds_simp_mod_test/n_tol_preds_simp_mod_test
print acc_simp_mod_test


0.869360451164

Quiz Question: Which model (sentiment_model or simple_model) has higher accuracy on the TEST set?


In [131]:
if acc_sent_mod_test>acc_simp_mod_test:
    print "sentiment_model"
else:
    print "simple_model"


sentiment_model

Baseline: Majority class prediction

It is quite common to use the majority class classifier as the a baseline (or reference) model for comparison with your classifier model. The majority classifier model predicts the majority class for all data points. At the very least, you should healthily beat the majority class classifier, otherwise, the model is (usually) pointless.

What is the majority class in the train_data?


In [132]:
num_positive  = (train_data['sentiment'] == +1).sum()
num_negative = (train_data['sentiment'] == -1).sum()
acc_pos_train = float(num_positive)/float(len(train_data['sentiment']))
acc_neg_train = float(num_negative)/float(len(train_data['sentiment']))
if acc_pos_train>acc_neg_train:
    print "Positive Sentiment is Majority Classifier for Training Data"
else:
    print "Negative Sentiment is Majority Classifier for Training Data"


Positive Sentiment is Majority Classifier for Training Data

Now compute the accuracy of the majority class classifier on test_data.

Quiz Question: Enter the accuracy of the majority class classifier model on the test_data. Round your answer to two decimal places (e.g. 0.76).


In [133]:
num_pos_test = (test_data['sentiment'] == +1).sum()
acc_pos_test = float(num_pos_test)/float(len(test_data['sentiment']))
print "Accuracy of Majority Class Classifier on Test Data: %.2f" %(acc_pos_test)


Accuracy of Majority Class Classifier on Test Data: 0.84

Quiz Question: Is the sentiment_model definitely better than the majority class classifier (the baseline)?


In [134]:
if acc_sent_mod_test>acc_pos_test:
    print "Yes, the sentiment_model is better than majority class classifier"
else:
    print "No, the majority class classifier is better than sentiment_model"


Yes, the sentiment_model is better than majority class classifier

In [ ]: