{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Natural Language Processing (NLP)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "\n", "*Adapted from [NLP Crash Course](http://files.meetup.com/7616132/DC-NLP-2013-09%20Charlie%20Greenbacker.pdf) by Charlie Greenbacker and [Introduction to NLP](http://spark-public.s3.amazonaws.com/nlp/slides/intro.pdf) by Dan Jurafsky*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What is NLP?\n", "\n", "- Using computers to process (analyze, understand, generate) natural human languages\n", "- Most knowledge created by humans is unstructured text, and we need a way to make sense of it\n", "- Build probabilistic model using data about a language" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What are some of the higher level task areas?\n", "\n", "- **Information retrieval**: Find relevant results and similar results\n", " - [Google](https://www.google.com/)\n", "- **Information extraction**: Structured information from unstructured documents\n", " - [Events from Gmail](https://support.google.com/calendar/answer/6084018?hl=en)\n", "- **Machine translation**: One language to another\n", " - [Google Translate](https://translate.google.com/)\n", "- **Text simplification**: Preserve the meaning of text, but simplify the grammar and vocabulary\n", " - [Rewordify](https://rewordify.com/)\n", " - [Simple English Wikipedia](https://simple.wikipedia.org/wiki/Main_Page)\n", "- **Predictive text input**: Faster or easier typing\n", " - [My application](https://justmarkham.shinyapps.io/textprediction/)\n", " - [A much better application](https://farsite.shinyapps.io/swiftkey-cap/)\n", "- **Sentiment analysis**: Attitude of speaker\n", " - [Hater News](http://haternews.herokuapp.com/)\n", "- **Automatic summarization**: Extractive or abstractive summarization\n", " - [autotldr](https://www.reddit.com/r/technology/comments/35brc8/21_million_people_still_use_aol_dialup/cr2zzj0)\n", "- **Natural Language Generation**: Generate text from data\n", " - [How a computer describes a sports match](http://www.bbc.com/news/technology-34204052)\n", " - [Publishers withdraw more than 120 gibberish papers](http://www.nature.com/news/publishers-withdraw-more-than-120-gibberish-papers-1.14763)\n", "- **Speech recognition and generation**: Speech-to-text, text-to-speech\n", " - [Google's Web Speech API demo](https://www.google.com/intl/en/chrome/demos/speech.html)\n", " - [Vocalware Text-to-Speech demo](https://www.vocalware.com/index/demo)\n", "- **Question answering**: Determine the intent of the question, match query with knowledge base, evaluate hypotheses\n", " - [How did supercomputer Watson beat Jeopardy champion Ken Jennings?](http://blog.ted.com/how-did-supercomputer-watson-beat-jeopardy-champion-ken-jennings-experts-discuss/)\n", " - [IBM's Watson Trivia Challenge](http://www.nytimes.com/interactive/2010/06/16/magazine/watson-trivia-game.html)\n", " - [The AI Behind Watson](http://www.aaai.org/Magazine/Watson/watson.php)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What are some of the lower level components?\n", "\n", "- **Tokenization**: breaking text into tokens (words, sentences, n-grams)\n", "- **Stopword removal**: a/an/the\n", "- **Stemming and lemmatization**: root word\n", "- **TF-IDF**: word importance\n", "- **Part-of-speech tagging**: noun/verb/adjective\n", "- **Named entity recognition**: person/organization/location\n", "- **Spelling correction**: \"New Yrok City\"\n", "- **Word sense disambiguation**: \"buy a mouse\"\n", "- **Segmentation**: \"New York City subway\"\n", "- **Language detection**: \"translate this page\"\n", "- **Machine learning**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Why is NLP hard?\n", "\n", "- **Ambiguity**:\n", " - Hospitals are Sued by 7 Foot Doctors\n", " - Juvenile Court to Try Shooting Defendant\n", " - Local High School Dropouts Cut in Half\n", "- **Non-standard English**: text messages\n", "- **Idioms**: \"throw in the towel\"\n", "- **Newly coined words**: \"retweet\"\n", "- **Tricky entity names**: \"Where is A Bug's Life playing?\"\n", "- **World knowledge**: \"Mary and Sue are sisters\", \"Mary and Sue are mothers\"\n", "\n", "NLP requires an understanding of the **language** and the **world**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 1: Reading in the Yelp Reviews" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- \"corpus\" = collection of documents\n", "- \"corpora\" = plural form of corpus" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import scipy as sp\n", "from sklearn.cross_validation import train_test_split\n", "from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer\n", "from sklearn.naive_bayes import MultinomialNB\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn import metrics\n", "from textblob import TextBlob, Word\n", "from nltk.stem.snowball import SnowballStemmer\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# read yelp.csv into a DataFrame\n", "url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/yelp.csv'\n", "yelp = pd.read_csv(url)\n", "\n", "# create a new DataFrame that only contains the 5-star and 1-star reviews\n", "yelp_best_worst = yelp[(yelp.stars==5) | (yelp.stars==1)]\n", "\n", "# define X and y\n", "X = yelp_best_worst.text\n", "y = yelp_best_worst.stars\n", "\n", "# split the new DataFrame into training and testing sets\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 2: Tokenization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **What:** Separate text into units such as sentences or words\n", "- **Why:** Gives structure to previously unstructured text\n", "- **Notes:** Relatively easy with English language text, not easy with some languages" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# use CountVectorizer to create document-term matrices from X_train and X_test\n", "vect = CountVectorizer()\n", "X_train_dtm = vect.fit_transform(X_train)\n", "X_test_dtm = vect.transform(X_test)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 16825)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# rows are documents, columns are terms (aka \"tokens\" or \"features\")\n", "X_train_dtm.shape" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[u'yyyyy', u'z11', u'za', u'zabba', u'zach', u'zam', u'zanella', u'zankou', u'zappos', u'zatsiki', u'zen', u'zero', u'zest', u'zexperience', u'zha', u'zhou', u'zia', u'zihuatenejo', u'zilch', u'zin', u'zinburger', u'zinburgergeist', u'zinc', u'zinfandel', u'zing', u'zip', u'zipcar', u'zipper', u'zippers', u'zipps', u'ziti', u'zoe', u'zombi', u'zombies', u'zone', u'zones', u'zoning', u'zoo', u'zoyo', u'zucca', u'zucchini', u'zuchinni', u'zumba', u'zupa', u'zuzu', u'zwiebel', u'zzed', u'\\xe9clairs', u'\\xe9cole', u'\\xe9m']\n" ] } ], "source": [ "# last 50 features\n", "print vect.get_feature_names()[-50:]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "CountVectorizer(analyzer=u'word', binary=False, decode_error=u'strict',\n", " dtype=, encoding=u'utf-8', input=u'content',\n", " lowercase=True, max_df=1.0, max_features=None, min_df=1,\n", " ngram_range=(1, 1), preprocessor=None, stop_words=None,\n", " strip_accents=None, token_pattern=u'(?u)\\\\b\\\\w\\\\w+\\\\b',\n", " tokenizer=None, vocabulary=None)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# show vectorizer options\n", "vect" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[CountVectorizer documentation](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **lowercase:** boolean, True by default\n", "- Convert all characters to lowercase before tokenizing." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 20838)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# don't convert to lowercase\n", "vect = CountVectorizer(lowercase=False)\n", "X_train_dtm = vect.fit_transform(X_train)\n", "X_train_dtm.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **ngram_range:** tuple (min_n, max_n)\n", "- The lower and upper boundary of the range of n-values for different n-grams to be extracted. All values of n such that min_n <= n <= max_n will be used." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 169847)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# include 1-grams and 2-grams\n", "vect = CountVectorizer(ngram_range=(1, 2))\n", "X_train_dtm = vect.fit_transform(X_train)\n", "X_train_dtm.shape" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[u'zone out', u'zone when', u'zones', u'zones dolls', u'zoning', u'zoning issues', u'zoo', u'zoo and', u'zoo is', u'zoo not', u'zoo the', u'zoo ve', u'zoyo', u'zoyo for', u'zucca', u'zucca appetizer', u'zucchini', u'zucchini and', u'zucchini bread', u'zucchini broccoli', u'zucchini carrots', u'zucchini fries', u'zucchini pieces', u'zucchini strips', u'zucchini veal', u'zucchini very', u'zucchini with', u'zuchinni', u'zuchinni again', u'zuchinni the', u'zumba', u'zumba class', u'zumba or', u'zumba yogalates', u'zupa', u'zupa flavors', u'zuzu', u'zuzu in', u'zuzu is', u'zuzu the', u'zwiebel', u'zwiebel kr\\xe4uter', u'zzed', u'zzed in', u'\\xe9clairs', u'\\xe9clairs napoleons', u'\\xe9cole', u'\\xe9cole len\\xf4tre', u'\\xe9m', u'\\xe9m all']\n" ] } ], "source": [ "# last 50 features\n", "print vect.get_feature_names()[-50:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Predicting the star rating:**" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.918786692759\n" ] } ], "source": [ "# use default options for CountVectorizer\n", "vect = CountVectorizer()\n", "\n", "# create document-term matrices\n", "X_train_dtm = vect.fit_transform(X_train)\n", "X_test_dtm = vect.transform(X_test)\n", "\n", "# use Naive Bayes to predict the star rating\n", "nb = MultinomialNB()\n", "nb.fit(X_train_dtm, y_train)\n", "y_pred_class = nb.predict(X_test_dtm)\n", "\n", "# calculate accuracy\n", "print metrics.accuracy_score(y_test, y_pred_class)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0.81996086105675148" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# calculate null accuracy\n", "y_test_binary = np.where(y_test==5, 1, 0)\n", "max(y_test_binary.mean(), 1 - y_test_binary.mean())" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# define a function that accepts a vectorizer and calculates the accuracy\n", "def tokenize_test(vect):\n", " X_train_dtm = vect.fit_transform(X_train)\n", " print 'Features: ', X_train_dtm.shape[1]\n", " X_test_dtm = vect.transform(X_test)\n", " nb = MultinomialNB()\n", " nb.fit(X_train_dtm, y_train)\n", " y_pred_class = nb.predict(X_test_dtm)\n", " print 'Accuracy: ', metrics.accuracy_score(y_test, y_pred_class)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 169847\n", "Accuracy: 0.854207436399\n" ] } ], "source": [ "# include 1-grams and 2-grams\n", "vect = CountVectorizer(ngram_range=(1, 2))\n", "tokenize_test(vect)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 3: Stopword Removal" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **What:** Remove common words that will likely appear in any text\n", "- **Why:** They don't tell you much about your text" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "CountVectorizer(analyzer=u'word', binary=False, decode_error=u'strict',\n", " dtype=, encoding=u'utf-8', input=u'content',\n", " lowercase=True, max_df=1.0, max_features=None, min_df=1,\n", " ngram_range=(1, 2), preprocessor=None, stop_words=None,\n", " strip_accents=None, token_pattern=u'(?u)\\\\b\\\\w\\\\w+\\\\b',\n", " tokenizer=None, vocabulary=None)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# show vectorizer options\n", "vect" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **stop_words:** string {'english'}, list, or None (default)\n", "- If 'english', a built-in stop word list for English is used.\n", "- If a list, that list is assumed to contain stop words, all of which will be removed from the resulting tokens.\n", "- If None, no stop words will be used. max_df can be set to a value in the range [0.7, 1.0) to automatically detect and filter stop words based on intra corpus document frequency of terms." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 16528\n", "Accuracy: 0.915851272016\n" ] } ], "source": [ "# remove English stop words\n", "vect = CountVectorizer(stop_words='english')\n", "tokenize_test(vect)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "frozenset(['all', 'six', 'less', 'being', 'indeed', 'over', 'move', 'anyway', 'four', 'not', 'own', 'through', 'yourselves', 'fify', 'where', 'mill', 'only', 'find', 'before', 'one', 'whose', 'system', 'how', 'somewhere', 'with', 'thick', 'show', 'had', 'enough', 'should', 'to', 'must', 'whom', 'seeming', 'under', 'ours', 'has', 'might', 'thereafter', 'latterly', 'do', 'them', 'his', 'around', 'than', 'get', 'very', 'de', 'none', 'cannot', 'every', 'whether', 'they', 'front', 'during', 'thus', 'now', 'him', 'nor', 'name', 'several', 'hereafter', 'always', 'who', 'cry', 'whither', 'this', 'someone', 'either', 'each', 'become', 'thereupon', 'sometime', 'side', 'two', 'therein', 'twelve', 'because', 'often', 'ten', 'our', 'eg', 'some', 'back', 'up', 'go', 'namely', 'towards', 'are', 'further', 'beyond', 'ourselves', 'yet', 'out', 'even', 'will', 'what', 'still', 'for', 'bottom', 'mine', 'since', 'please', 'forty', 'per', 'its', 'everything', 'behind', 'un', 'above', 'between', 'it', 'neither', 'seemed', 'ever', 'across', 'she', 'somehow', 'be', 'we', 'full', 'never', 'sixty', 'however', 'here', 'otherwise', 'were', 'whereupon', 'nowhere', 'although', 'found', 'alone', 're', 'along', 'fifteen', 'by', 'both', 'about', 'last', 'would', 'anything', 'via', 'many', 'could', 'thence', 'put', 'against', 'keep', 'etc', 'amount', 'became', 'ltd', 'hence', 'onto', 'or', 'con', 'among', 'already', 'co', 'afterwards', 'formerly', 'within', 'seems', 'into', 'others', 'while', 'whatever', 'except', 'down', 'hers', 'everyone', 'done', 'least', 'another', 'whoever', 'moreover', 'couldnt', 'throughout', 'anyhow', 'yourself', 'three', 'from', 'her', 'few', 'together', 'top', 'there', 'due', 'been', 'next', 'anyone', 'eleven', 'much', 'call', 'therefore', 'interest', 'then', 'thru', 'themselves', 'hundred', 'was', 'sincere', 'empty', 'more', 'himself', 'elsewhere', 'mostly', 'on', 'fire', 'am', 'becoming', 'hereby', 'amongst', 'else', 'part', 'everywhere', 'too', 'herself', 'former', 'those', 'he', 'me', 'myself', 'made', 'twenty', 'these', 'bill', 'cant', 'us', 'until', 'besides', 'nevertheless', 'below', 'anywhere', 'nine', 'can', 'of', 'toward', 'my', 'something', 'and', 'whereafter', 'whenever', 'give', 'almost', 'wherever', 'is', 'describe', 'beforehand', 'herein', 'an', 'as', 'itself', 'at', 'have', 'in', 'seem', 'whence', 'ie', 'any', 'fill', 'again', 'hasnt', 'inc', 'thereby', 'thin', 'no', 'perhaps', 'latter', 'meanwhile', 'when', 'detail', 'same', 'wherein', 'beside', 'also', 'that', 'other', 'take', 'which', 'becomes', 'you', 'if', 'nobody', 'see', 'though', 'may', 'after', 'upon', 'most', 'hereupon', 'eight', 'but', 'serious', 'nothing', 'such', 'your', 'why', 'a', 'off', 'whereby', 'third', 'i', 'whole', 'noone', 'sometimes', 'well', 'amoungst', 'yours', 'their', 'rather', 'without', 'so', 'five', 'the', 'first', 'whereas', 'once'])\n" ] } ], "source": [ "# set of stop words\n", "print vect.get_stop_words()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 4: Other CountVectorizer Options" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **max_features:** int or None, default=None\n", "- If not None, build a vocabulary that only consider the top max_features ordered by term frequency across the corpus." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 100\n", "Accuracy: 0.869863013699\n" ] } ], "source": [ "# remove English stop words and only keep 100 features\n", "vect = CountVectorizer(stop_words='english', max_features=100)\n", "tokenize_test(vect)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[u'amazing', u'area', u'atmosphere', u'awesome', u'bad', u'bar', u'best', u'better', u'big', u'came', u'cheese', u'chicken', u'clean', u'coffee', u'come', u'day', u'definitely', u'delicious', u'did', u'didn', u'dinner', u'don', u'eat', u'excellent', u'experience', u'favorite', u'feel', u'food', u'free', u'fresh', u'friendly', u'friends', u'going', u'good', u'got', u'great', u'happy', u'home', u'hot', u'hour', u'just', u'know', u'like', u'little', u'll', u'location', u'long', u'looking', u'lot', u'love', u'lunch', u'make', u'meal', u'menu', u'minutes', u'need', u'new', u'nice', u'night', u'order', u'ordered', u'people', u'perfect', u'phoenix', u'pizza', u'place', u'pretty', u'prices', u'really', u'recommend', u'restaurant', u'right', u'said', u'salad', u'sandwich', u'sauce', u'say', u'service', u'staff', u'store', u'sure', u'table', u'thing', u'things', u'think', u'time', u'times', u'took', u'town', u'tried', u'try', u've', u'wait', u'want', u'way', u'went', u'wine', u'work', u'worth', u'years']\n" ] } ], "source": [ "# all 100 features\n", "print vect.get_feature_names()" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 100000\n", "Accuracy: 0.885518590998\n" ] } ], "source": [ "# include 1-grams and 2-grams, and limit the number of features\n", "vect = CountVectorizer(ngram_range=(1, 2), max_features=100000)\n", "tokenize_test(vect)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **min_df:** float in range [0.0, 1.0] or int, default=1\n", "- When building the vocabulary ignore terms that have a document frequency strictly lower than the given threshold. This value is also called cut-off in the literature. If float, the parameter represents a proportion of documents, integer absolute counts." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 43957\n", "Accuracy: 0.932485322896\n" ] } ], "source": [ "# include 1-grams and 2-grams, and only include terms that appear at least 2 times\n", "vect = CountVectorizer(ngram_range=(1, 2), min_df=2)\n", "tokenize_test(vect)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 5: Introduction to TextBlob" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "TextBlob: \"Simplified Text Processing\"" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My wife took me here on my birthday for breakfast and it was excellent. The weather was perfect which made sitting outside overlooking their grounds an absolute pleasure. Our waitress was excellent and our food arrived quickly on the semi-busy Saturday morning. It looked like the place fills up pretty quickly so the earlier you get here the better.\n", "\n", "Do yourself a favor and get their Bloody Mary. It was phenomenal and simply the best I've ever had. I'm pretty sure they only use ingredients from their garden and blend them fresh when you order it. It was amazing.\n", "\n", "While EVERYTHING on the menu looks excellent, I had the white truffle scrambled eggs vegetable skillet and it was tasty and delicious. It came with 2 pieces of their griddled bread with was amazing and it absolutely made the meal complete. It was the best \"toast\" I've ever had.\n", "\n", "Anyway, I can't wait to go back!\n" ] } ], "source": [ "# print the first review\n", "print yelp_best_worst.text[0]" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# save it as a TextBlob object\n", "review = TextBlob(yelp_best_worst.text[0])" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "WordList(['My', 'wife', 'took', 'me', 'here', 'on', 'my', 'birthday', 'for', 'breakfast', 'and', 'it', 'was', 'excellent', 'The', 'weather', 'was', 'perfect', 'which', 'made', 'sitting', 'outside', 'overlooking', 'their', 'grounds', 'an', 'absolute', 'pleasure', 'Our', 'waitress', 'was', 'excellent', 'and', 'our', 'food', 'arrived', 'quickly', 'on', 'the', 'semi-busy', 'Saturday', 'morning', 'It', 'looked', 'like', 'the', 'place', 'fills', 'up', 'pretty', 'quickly', 'so', 'the', 'earlier', 'you', 'get', 'here', 'the', 'better', 'Do', 'yourself', 'a', 'favor', 'and', 'get', 'their', 'Bloody', 'Mary', 'It', 'was', 'phenomenal', 'and', 'simply', 'the', 'best', 'I', \"'ve\", 'ever', 'had', 'I', \"'m\", 'pretty', 'sure', 'they', 'only', 'use', 'ingredients', 'from', 'their', 'garden', 'and', 'blend', 'them', 'fresh', 'when', 'you', 'order', 'it', 'It', 'was', 'amazing', 'While', 'EVERYTHING', 'on', 'the', 'menu', 'looks', 'excellent', 'I', 'had', 'the', 'white', 'truffle', 'scrambled', 'eggs', 'vegetable', 'skillet', 'and', 'it', 'was', 'tasty', 'and', 'delicious', 'It', 'came', 'with', '2', 'pieces', 'of', 'their', 'griddled', 'bread', 'with', 'was', 'amazing', 'and', 'it', 'absolutely', 'made', 'the', 'meal', 'complete', 'It', 'was', 'the', 'best', 'toast', 'I', \"'ve\", 'ever', 'had', 'Anyway', 'I', 'ca', \"n't\", 'wait', 'to', 'go', 'back'])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# list the words\n", "review.words" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[Sentence(\"My wife took me here on my birthday for breakfast and it was excellent.\"),\n", " Sentence(\"The weather was perfect which made sitting outside overlooking their grounds an absolute pleasure.\"),\n", " Sentence(\"Our waitress was excellent and our food arrived quickly on the semi-busy Saturday morning.\"),\n", " Sentence(\"It looked like the place fills up pretty quickly so the earlier you get here the better.\"),\n", " Sentence(\"Do yourself a favor and get their Bloody Mary.\"),\n", " Sentence(\"It was phenomenal and simply the best I've ever had.\"),\n", " Sentence(\"I'm pretty sure they only use ingredients from their garden and blend them fresh when you order it.\"),\n", " Sentence(\"It was amazing.\"),\n", " Sentence(\"While EVERYTHING on the menu looks excellent, I had the white truffle scrambled eggs vegetable skillet and it was tasty and delicious.\"),\n", " Sentence(\"It came with 2 pieces of their griddled bread with was amazing and it absolutely made the meal complete.\"),\n", " Sentence(\"It was the best \"toast\" I've ever had.\"),\n", " Sentence(\"Anyway, I can't wait to go back!\")]" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# list the sentences\n", "review.sentences" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "TextBlob(\"my wife took me here on my birthday for breakfast and it was excellent. the weather was perfect which made sitting outside overlooking their grounds an absolute pleasure. our waitress was excellent and our food arrived quickly on the semi-busy saturday morning. it looked like the place fills up pretty quickly so the earlier you get here the better.\n", "\n", "do yourself a favor and get their bloody mary. it was phenomenal and simply the best i've ever had. i'm pretty sure they only use ingredients from their garden and blend them fresh when you order it. it was amazing.\n", "\n", "while everything on the menu looks excellent, i had the white truffle scrambled eggs vegetable skillet and it was tasty and delicious. it came with 2 pieces of their griddled bread with was amazing and it absolutely made the meal complete. it was the best \"toast\" i've ever had.\n", "\n", "anyway, i can't wait to go back!\")" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# some string methods are available\n", "review.lower()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 6: Stemming and Lemmatization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Stemming:**\n", "\n", "- **What:** Reduce a word to its base/stem/root form\n", "- **Why:** Often makes sense to treat related words the same way\n", "- **Notes:**\n", " - Uses a \"simple\" and fast rule-based approach\n", " - Stemmed words are usually not shown to users (used for analysis/indexing)\n", " - Some search engines treat words with the same stem as synonyms" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[u'my', u'wife', u'took', u'me', u'here', u'on', u'my', u'birthday', u'for', u'breakfast', u'and', u'it', u'was', u'excel', u'the', u'weather', u'was', u'perfect', u'which', u'made', u'sit', u'outsid', u'overlook', u'their', u'ground', u'an', u'absolut', u'pleasur', u'our', u'waitress', u'was', u'excel', u'and', u'our', u'food', u'arriv', u'quick', u'on', u'the', u'semi-busi', u'saturday', u'morn', u'it', u'look', u'like', u'the', u'place', u'fill', u'up', u'pretti', u'quick', u'so', u'the', u'earlier', u'you', u'get', u'here', u'the', u'better', u'do', u'yourself', u'a', u'favor', u'and', u'get', u'their', u'bloodi', u'mari', u'it', u'was', u'phenomen', u'and', u'simpli', u'the', u'best', u'i', u've', u'ever', u'had', u'i', u\"'m\", u'pretti', u'sure', u'they', u'onli', u'use', u'ingredi', u'from', u'their', u'garden', u'and', u'blend', u'them', u'fresh', u'when', u'you', u'order', u'it', u'it', u'was', u'amaz', u'while', u'everyth', u'on', u'the', u'menu', u'look', u'excel', u'i', u'had', u'the', u'white', u'truffl', u'scrambl', u'egg', u'veget', u'skillet', u'and', u'it', u'was', u'tasti', u'and', u'delici', u'it', u'came', u'with', u'2', u'piec', u'of', u'their', u'griddl', u'bread', u'with', u'was', u'amaz', u'and', u'it', u'absolut', u'made', u'the', u'meal', u'complet', u'it', u'was', u'the', u'best', u'toast', u'i', u've', u'ever', u'had', u'anyway', u'i', u'ca', u\"n't\", u'wait', u'to', u'go', u'back']\n" ] } ], "source": [ "# initialize stemmer\n", "stemmer = SnowballStemmer('english')\n", "\n", "# stem each word\n", "print [stemmer.stem(word) for word in review.words]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Lemmatization**\n", "\n", "- **What:** Derive the canonical form ('lemma') of a word\n", "- **Why:** Can be better than stemming\n", "- **Notes:** Uses a dictionary-based approach (slower than stemming)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['My', 'wife', 'took', 'me', 'here', 'on', 'my', 'birthday', 'for', 'breakfast', 'and', 'it', u'wa', 'excellent', 'The', 'weather', u'wa', 'perfect', 'which', 'made', 'sitting', 'outside', 'overlooking', 'their', u'ground', 'an', 'absolute', 'pleasure', 'Our', 'waitress', u'wa', 'excellent', 'and', 'our', 'food', 'arrived', 'quickly', 'on', 'the', 'semi-busy', 'Saturday', 'morning', 'It', 'looked', 'like', 'the', 'place', u'fill', 'up', 'pretty', 'quickly', 'so', 'the', 'earlier', 'you', 'get', 'here', 'the', 'better', 'Do', 'yourself', 'a', 'favor', 'and', 'get', 'their', 'Bloody', 'Mary', 'It', u'wa', 'phenomenal', 'and', 'simply', 'the', 'best', 'I', \"'ve\", 'ever', 'had', 'I', \"'m\", 'pretty', 'sure', 'they', 'only', 'use', u'ingredient', 'from', 'their', 'garden', 'and', 'blend', 'them', 'fresh', 'when', 'you', 'order', 'it', 'It', u'wa', 'amazing', 'While', 'EVERYTHING', 'on', 'the', 'menu', u'look', 'excellent', 'I', 'had', 'the', 'white', 'truffle', 'scrambled', u'egg', 'vegetable', 'skillet', 'and', 'it', u'wa', 'tasty', 'and', 'delicious', 'It', 'came', 'with', '2', u'piece', 'of', 'their', 'griddled', 'bread', 'with', u'wa', 'amazing', 'and', 'it', 'absolutely', 'made', 'the', 'meal', 'complete', 'It', u'wa', 'the', 'best', 'toast', 'I', \"'ve\", 'ever', 'had', 'Anyway', 'I', 'ca', \"n't\", 'wait', 'to', 'go', 'back']\n" ] } ], "source": [ "# assume every word is a noun\n", "print [word.lemmatize() for word in review.words]" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['My', 'wife', u'take', 'me', 'here', 'on', 'my', 'birthday', 'for', 'breakfast', 'and', 'it', u'be', 'excellent', 'The', 'weather', u'be', 'perfect', 'which', u'make', u'sit', 'outside', u'overlook', 'their', u'ground', 'an', 'absolute', 'pleasure', 'Our', 'waitress', u'be', 'excellent', 'and', 'our', 'food', u'arrive', 'quickly', 'on', 'the', 'semi-busy', 'Saturday', 'morning', 'It', u'look', 'like', 'the', 'place', u'fill', 'up', 'pretty', 'quickly', 'so', 'the', 'earlier', 'you', 'get', 'here', 'the', 'better', 'Do', 'yourself', 'a', 'favor', 'and', 'get', 'their', 'Bloody', 'Mary', 'It', u'be', 'phenomenal', 'and', 'simply', 'the', 'best', 'I', \"'ve\", 'ever', u'have', 'I', \"'m\", 'pretty', 'sure', 'they', 'only', 'use', 'ingredients', 'from', 'their', 'garden', 'and', 'blend', 'them', 'fresh', 'when', 'you', 'order', 'it', 'It', u'be', u'amaze', 'While', 'EVERYTHING', 'on', 'the', 'menu', u'look', 'excellent', 'I', u'have', 'the', 'white', 'truffle', u'scramble', u'egg', 'vegetable', 'skillet', 'and', 'it', u'be', 'tasty', 'and', 'delicious', 'It', u'come', 'with', '2', u'piece', 'of', 'their', u'griddle', 'bread', 'with', u'be', u'amaze', 'and', 'it', 'absolutely', u'make', 'the', 'meal', 'complete', 'It', u'be', 'the', 'best', 'toast', 'I', \"'ve\", 'ever', u'have', 'Anyway', 'I', 'ca', \"n't\", 'wait', 'to', 'go', 'back']\n" ] } ], "source": [ "# assume every word is a verb\n", "print [word.lemmatize(pos='v') for word in review.words]" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# define a function that accepts text and returns a list of lemmas\n", "def split_into_lemmas(text):\n", " text = unicode(text, 'utf-8').lower()\n", " words = TextBlob(text).words\n", " return [word.lemmatize() for word in words]" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features: 16452\n", "Accuracy: 0.920743639922\n" ] } ], "source": [ "# use split_into_lemmas as the feature extraction function (WARNING: SLOW!)\n", "vect = CountVectorizer(analyzer=split_into_lemmas)\n", "tokenize_test(vect)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[u'yuyuyummy', u'yuzu', u'z', u'z-grill', u'z11', u'zach', u'zam', u'zanella', u'zankou', u'zappos', u'zatsiki', u'zen', u'zen-like', u'zero', u'zero-star', u'zest', u'zexperience', u'zha', u'zhou', u'zia', u'zilch', u'zin', u'zinburger', u'zinburgergeist', u'zinc', u'zinfandel', u'zing', u'zip', u'zipcar', u'zipper', u'zipps', u'ziti', u'zoe', u'zombi', u'zombie', u'zone', u'zoning', u'zoo', u'zoyo', u'zucca', u'zucchini', u'zuchinni', u'zumba', u'zupa', u'zuzu', u'zwiebel-kr\\xe4uter', u'zzed', u'\\xe9clairs', u'\\xe9cole', u'\\xe9m']\n" ] } ], "source": [ "# last 50 features\n", "print vect.get_feature_names()[-50:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 7: Term Frequency-Inverse Document Frequency (TF-IDF)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **What:** Computes \"relative frequency\" that a word appears in a document compared to its frequency across all documents\n", "- **Why:** More useful than \"term frequency\" for identifying \"important\" words in each document (high frequency in that document, low frequency in other documents)\n", "- **Notes:** Used for search engine scoring, text summarization, document clustering" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# example documents\n", "simple_train = ['call you tonight', 'Call me a cab', 'please call me... PLEASE!']" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cabcallmepleasetonightyou
0010011
1111000
2011200
\n", "
" ], "text/plain": [ " cab call me please tonight you\n", "0 0 1 0 0 1 1\n", "1 1 1 1 0 0 0\n", "2 0 1 1 2 0 0" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Term Frequency\n", "vect = CountVectorizer()\n", "tf = pd.DataFrame(vect.fit_transform(simple_train).toarray(), columns=vect.get_feature_names())\n", "tf" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cabcallmepleasetonightyou
0132111
\n", "
" ], "text/plain": [ " cab call me please tonight you\n", "0 1 3 2 1 1 1" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Document Frequency\n", "vect = CountVectorizer(binary=True)\n", "df = vect.fit_transform(simple_train).toarray().sum(axis=0)\n", "pd.DataFrame(df.reshape(1, 6), columns=vect.get_feature_names())" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cabcallmepleasetonightyou
000.3333330.0011
110.3333330.5000
200.3333330.5200
\n", "
" ], "text/plain": [ " cab call me please tonight you\n", "0 0 0.333333 0.0 0 1 1\n", "1 1 0.333333 0.5 0 0 0\n", "2 0 0.333333 0.5 2 0 0" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Term Frequency-Inverse Document Frequency (simple version)\n", "tf/df" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cabcallmepleasetonightyou
00.0000000.3853720.0000000.0000000.6524910.652491
10.7203330.4254410.5478320.0000000.0000000.000000
20.0000000.2660750.3426200.9010080.0000000.000000
\n", "
" ], "text/plain": [ " cab call me please tonight you\n", "0 0.000000 0.385372 0.000000 0.000000 0.652491 0.652491\n", "1 0.720333 0.425441 0.547832 0.000000 0.000000 0.000000\n", "2 0.000000 0.266075 0.342620 0.901008 0.000000 0.000000" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# TfidfVectorizer\n", "vect = TfidfVectorizer()\n", "pd.DataFrame(vect.fit_transform(simple_train).toarray(), columns=vect.get_feature_names())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**More details:** [TF-IDF is about what matters](http://planspace.org/20150524-tfidf_is_about_what_matters/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 8: Using TF-IDF to Summarize a Yelp Review\n", "\n", "Reddit's autotldr uses the [SMMRY](http://smmry.com/about) algorithm, which is based on TF-IDF!" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(10000, 28881)" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create a document-term matrix using TF-IDF\n", "vect = TfidfVectorizer(stop_words='english')\n", "dtm = vect.fit_transform(yelp.text)\n", "features = vect.get_feature_names()\n", "dtm.shape" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def summarize():\n", " \n", " # choose a random review that is at least 300 characters\n", " review_length = 0\n", " while review_length < 300:\n", " review_id = np.random.randint(0, len(yelp))\n", " review_text = unicode(yelp.text[review_id], 'utf-8')\n", " review_length = len(review_text)\n", " \n", " # create a dictionary of words and their TF-IDF scores\n", " word_scores = {}\n", " for word in TextBlob(review_text).words:\n", " word = word.lower()\n", " if word in features:\n", " word_scores[word] = dtm[review_id, features.index(word)]\n", " \n", " # print words with the top 5 TF-IDF scores\n", " print 'TOP SCORING WORDS:'\n", " top_scores = sorted(word_scores.items(), key=lambda x: x[1], reverse=True)[:5]\n", " for word, score in top_scores:\n", " print word\n", " \n", " # print 5 random words\n", " print '\\n' + 'RANDOM WORDS:'\n", " random_words = np.random.choice(word_scores.keys(), size=5, replace=False)\n", " for word in random_words:\n", " print word\n", " \n", " # print the review\n", " print '\\n' + review_text" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TOP SCORING WORDS:\n", "restaurant\n", "machaca\n", "cactus\n", "pickup\n", "rental\n", "\n", "RANDOM WORDS:\n", "did\n", "pickup\n", "hole\n", "burrito\n", "arrived\n", "\n", "The place is a hole in the wall right behind the PHX car rental center. Arrived at dinner time, and had the best Mexican food! I had the #5 Machaca Plate w/rice, beans, & one of their homemade tortilla's burrito style. It was just amazing, and so cheap ($5.65). The only issue I have with this place is that its very well used, and looks very dirty, I did find it a bit sticky. Maybe this is a better place for pickup. If you want a clean restaurant, their North restaurant on Cactus Road restaurant is much cleaner.\n" ] } ], "source": [ "summarize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 9: Sentiment Analysis" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My wife took me here on my birthday for breakfast and it was excellent. The weather was perfect which made sitting outside overlooking their grounds an absolute pleasure. Our waitress was excellent and our food arrived quickly on the semi-busy Saturday morning. It looked like the place fills up pretty quickly so the earlier you get here the better.\n", "\n", "Do yourself a favor and get their Bloody Mary. It was phenomenal and simply the best I've ever had. I'm pretty sure they only use ingredients from their garden and blend them fresh when you order it. It was amazing.\n", "\n", "While EVERYTHING on the menu looks excellent, I had the white truffle scrambled eggs vegetable skillet and it was tasty and delicious. It came with 2 pieces of their griddled bread with was amazing and it absolutely made the meal complete. It was the best \"toast\" I've ever had.\n", "\n", "Anyway, I can't wait to go back!\n" ] } ], "source": [ "print review" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0.40246913580246907" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# polarity ranges from -1 (most negative) to 1 (most positive)\n", "review.sentiment.polarity" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
business_iddatereview_idstarstexttypeuser_idcoolusefulfunnylength
09yKzy9PApeiPPOUJEtnvkg2011-01-26fWKvX83p0-ka4JS3dc6E5A5My wife took me here on my birthday for breakf...reviewrLtl8ZkDX5vH5nAx9C3q5Q250889
\n", "
" ], "text/plain": [ " business_id date review_id stars \\\n", "0 9yKzy9PApeiPPOUJEtnvkg 2011-01-26 fWKvX83p0-ka4JS3dc6E5A 5 \n", "\n", " text type \\\n", "0 My wife took me here on my birthday for breakf... review \n", "\n", " user_id cool useful funny length \n", "0 rLtl8ZkDX5vH5nAx9C3q5Q 2 5 0 889 " ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# understanding the apply method\n", "yelp['length'] = yelp.text.apply(len)\n", "yelp.head(1)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# define a function that accepts text and returns the polarity\n", "def detect_sentiment(text):\n", " return TextBlob(text.decode('utf-8')).sentiment.polarity" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# create a new DataFrame column for sentiment (WARNING: SLOW!)\n", "yelp['sentiment'] = yelp.text.apply(detect_sentiment)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# box plot of sentiment grouped by stars\n", "yelp.boxplot(column='sentiment', by='stars')" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "254 Our server Gary was awesome. Food was amazing....\n", "347 3 syllables for this place. \\nA-MAZ-ING!\\n\\nTh...\n", "420 LOVE the food!!!!\n", "459 Love it!!! Wish we still lived in Arizona as C...\n", "679 Excellent burger\n", "Name: text, dtype: object" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# reviews with most positive sentiment\n", "yelp[yelp.sentiment == 1].text.head()" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "773 This was absolutely horrible. I got the suprem...\n", "1517 Nasty workers and over priced trash\n", "3266 Absolutely awful... these guys have NO idea wh...\n", "4766 Very bad food!\n", "5812 I wouldn't send my worst enemy to this place.\n", "Name: text, dtype: object" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# reviews with most negative sentiment\n", "yelp[yelp.sentiment == -1].text.head()" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# widen the column display\n", "pd.set_option('max_colwidth', 500)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
business_iddatereview_idstarstexttypeuser_idcoolusefulfunnylengthsentiment
390106JT5p8e8Chtd0CZpcARw2009-08-06KowGVoP_gygzdSu6Mt3zKQ5RIP AZ Coffee Connection. :( I stopped by two days ago unaware that they had closed. I am severely bummed. This place is irreplaceable! Damn you, Starbucks and McDonalds!reviewjKeaOrPyJ-dI9SNeVqrbww100175-0.302083
\n", "
" ], "text/plain": [ " business_id date review_id stars \\\n", "390 106JT5p8e8Chtd0CZpcARw 2009-08-06 KowGVoP_gygzdSu6Mt3zKQ 5 \n", "\n", " text \\\n", "390 RIP AZ Coffee Connection. :( I stopped by two days ago unaware that they had closed. I am severely bummed. This place is irreplaceable! Damn you, Starbucks and McDonalds! \n", "\n", " type user_id cool useful funny length sentiment \n", "390 review jKeaOrPyJ-dI9SNeVqrbww 1 0 0 175 -0.302083 " ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# negative sentiment in a 5-star review\n", "yelp[(yelp.stars == 5) & (yelp.sentiment < -0.3)].head(1)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
business_iddatereview_idstarstexttypeuser_idcoolusefulfunnylengthsentiment
178153YGfwmbW73JhFiemNeyzQ2012-06-22Gi-4O3EhE175vujbFGDIew1If you like the stuck up Scottsdale vibe this is a good place for you. The food isn't impressive. Nice outdoor seating.reviewHqgx3IdJAAaoQjvrUnbNvw0121190.766667
\n", "
" ], "text/plain": [ " business_id date review_id stars \\\n", "1781 53YGfwmbW73JhFiemNeyzQ 2012-06-22 Gi-4O3EhE175vujbFGDIew 1 \n", "\n", " text \\\n", "1781 If you like the stuck up Scottsdale vibe this is a good place for you. The food isn't impressive. Nice outdoor seating. \n", "\n", " type user_id cool useful funny length sentiment \n", "1781 review Hqgx3IdJAAaoQjvrUnbNvw 0 1 2 119 0.766667 " ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# positive sentiment in a 1-star review\n", "yelp[(yelp.stars == 1) & (yelp.sentiment > 0.5)].head(1)" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# reset the column display width\n", "pd.reset_option('max_colwidth')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bonus: Adding Features to a Document-Term Matrix" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# create a DataFrame that only contains the 5-star and 1-star reviews\n", "yelp_best_worst = yelp[(yelp.stars==5) | (yelp.stars==1)]\n", "\n", "# define X and y\n", "feature_cols = ['text', 'sentiment', 'cool', 'useful', 'funny']\n", "X = yelp_best_worst[feature_cols]\n", "y = yelp_best_worst.stars\n", "\n", "# split into training and testing sets\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(3064, 16825)\n", "(1022, 16825)\n" ] } ], "source": [ "# use CountVectorizer with text column only\n", "vect = CountVectorizer()\n", "X_train_dtm = vect.fit_transform(X_train.text)\n", "X_test_dtm = vect.transform(X_test.text)\n", "print X_train_dtm.shape\n", "print X_test_dtm.shape" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 4)" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# shape of other four feature columns\n", "X_train.drop('text', axis=1).shape" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 4)" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# cast other feature columns to float and convert to a sparse matrix\n", "extra = sp.sparse.csr_matrix(X_train.drop('text', axis=1).astype(float))\n", "extra.shape" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(3064, 16829)" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# combine sparse matrices\n", "X_train_dtm_extra = sp.sparse.hstack((X_train_dtm, extra))\n", "X_train_dtm_extra.shape" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(1022, 16829)" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# repeat for testing set\n", "extra = sp.sparse.csr_matrix(X_test.drop('text', axis=1).astype(float))\n", "X_test_dtm_extra = sp.sparse.hstack((X_test_dtm, extra))\n", "X_test_dtm_extra.shape" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.917808219178\n" ] } ], "source": [ "# use logistic regression with text column only\n", "logreg = LogisticRegression(C=1e9)\n", "logreg.fit(X_train_dtm, y_train)\n", "y_pred_class = logreg.predict(X_test_dtm)\n", "print metrics.accuracy_score(y_test, y_pred_class)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.922700587084\n" ] } ], "source": [ "# use logistic regression with all features\n", "logreg = LogisticRegression(C=1e9)\n", "logreg.fit(X_train_dtm_extra, y_train)\n", "y_pred_class = logreg.predict(X_test_dtm_extra)\n", "print metrics.accuracy_score(y_test, y_pred_class)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bonus: Fun TextBlob Features" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "TextBlob(\"15 minutes late\")" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# spelling correction\n", "TextBlob('15 minuets late').correct()" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[('part', 0.9929478138222849), (u'parrot', 0.007052186177715092)]" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# spellcheck\n", "Word('parot').spellcheck()" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[u'tip laterally',\n", " u'enclose with a bank',\n", " u'do business with a bank or keep an account at a bank',\n", " u'act as the banker in a game or in gambling',\n", " u'be in the banking business',\n", " u'put into a bank account',\n", " u'cover with ashes so to control the rate of burning',\n", " u'have confidence or faith in']" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# definitions\n", "Word('bank').define('v')" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "u'es'" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# language identification\n", "TextBlob('Hola amigos').detect_language()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "- NLP is a gigantic field\n", "- Understanding the basics broadens the types of data you can work with\n", "- Simple techniques go a long way\n", "- Use scikit-learn for NLP whenever possible" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 }