{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction\n", "\n", "In this notebook, I will extract restaurant ratings and reviews from [Foursquare](https://foursquare.com/) and use distance (one of the main ideas behind recommender systems) to generate recommendations for restaurants in one city that have similar reviews to restaurants in another city.\n", "\n", "### Motivation\n", "\n", "I grew up in Austin, Texas, and moved to Minneapolis, Minnesota for my wife's work a few years ago. My wife and I are people who love food, and loved the food culture in Austin. After our move, we wanted to find new restaurants to replace our favorites from back home. However, most decently rated places in Minneapolis we went to just didn't quite live up to our Austin expectations. These restaurants usually came at the recommendations of locals, acquaintances, or from Google ratings, but the food was often bland and overpriced. It took us one deep dive into the actual reviews to figure it out. \n", "\n", "In order to better illustrate our problem, below are recent reviews from three restaurants that left us disappointed. On the left is a typical American restaurant, the middle is a Vietnamese restaurant, and the right is a pizza shop:\n", "\n", "\n", "\n", "I highlighted the main points to stand out against the small font. Service, atmosphere, and apparently eggrolls were the most common and unifying factors. You see very little discussion on the quality of the actual food, and you can even see an instance where a reviewer rates the pizza place as 5/5 even after saying that it is expensive. I began to notice a disconnect in how I evaluate restaurants versus how the people of Minneapolis evaluate restaurants. If you have previously worked with recommender systems, you already know where I'm going with this. If not, here is a primer:\n", "\n", "### Recommender Systems Overview\n", "\n", "Before getting into the overview of recommender systems, I wanted to point out that I won't actually be building a legitimate recommender system in this notebook. There are some [great](https://turi.com/learn/userguide/recommender/introduction.html) [packages](https://github.com/lyst/lightfm) for doing so, but I'm going to stick with one of the main ideas behind recommender systems. This is for two reasons:\n", "\n", "**1)** Classes have started back up, and my available free time for side projects like this one is almost non-existant.\n", "\n", "**2)** My gastronomic adventures don't need the added benefits that a recommender system provides over what I'll be doing.\n", "\n", "Let's get back into it.\n", "\n", "In the world of recommender systems, there are three broad types:\n", "\n", "- **[Collaborative Filtering (user-user)](https://en.wikipedia.org/wiki/Collaborative_filtering)**: This is the most prevalent type of recommender systems that uses \"wisdom of the crowd\" for popularity among peers. This option is particularly popular because you don't need to know a lot about the item itself, you only need the ratings submitted by reviewers. The two primary restrictions are that it makes the assumption that peoples' tastes do not change over time, and new items run into the \"[cold start problem](https://en.wikipedia.org/wiki/Cold_start)\". This is when either a new item has not yet received any ratings and fails to appear on recommendation lists, or a new user has not reviewed anything so we don't know what their tastes are.\n", " - **E.x.:** People who like item **X** also like item **Y**\n", " - This is how Spotify selects songs for your recommended play list. Specifically, it will take songs from other play lists that contain songs you recently liked.\n", " \n", " \n", "- **[Content-Based (item-item)](https://en.wikipedia.org/wiki/Recommender_system#Content-based_filtering)**: This method recommends items based off of their similarity to other items. This requires reliable information about the items themselves, which makes it difficult to implement in a lot of cases. Additionally, recommendations generated from this will option likely not deviate very far from the item being compared to, but there are tricks available to account for this.\n", " - **E.x.:** Item **X** is similar to item **Y**\n", " - This is how Pandora selects songs for your stations. Specifically, it assigns each song a list of characteristics (assigned through the [Music Genome Project](http://www.pandora.com/corporate/mgp.shtml)), and selects songs with similar characteristics as those that you liked.\n", " \n", "\n", "- **[Hybrid](https://en.wikipedia.org/wiki/Recommender_system#Hybrid_recommender_systems)**: You probably guessed it - this is a combination of the above two types. The idea here is use what you have if you have it. [Here](http://www.math.uci.edu/icamp/courses/math77b/lecture_12w/pdfs/Chapter%2005%20-%20Hybrid%20recommendation%20approaches.pdf) are a few designs related to this that are worth looking into.\n", "\n", "\n", "Those are the three main types, but there is one additional type that you may find if you are diving a little deeper into the subject material:\n", "\n", "\n", "- **[Knowledge-Based](https://en.wikipedia.org/wiki/Knowledge-based_recommender_system)**: This is is the most rare type mainly because it requires explicit domain knowledge. It is often used for products that have a low number of available ratings, such as high luxury goods like hypercars. We won't delve any further into this type, but I recommend reading more about it if you're interested in the concept.\n", "\n", "\n", "### Methodology\n", "\n", "Let's return to our problem. The previous way of selecting restaurants at the recommendation of locals and acquaintances (collaborative filtering) wasn't always successful, so we are going to use the idea behind content-based recommender systems to evaluate our options. However, we don't have a lot of content about the restaurants available, so we are going to primarily use the reviews people left for them. More specifically, we are going to determine similarity between restaurants based off of the similarity of the reviews that people have written for them. \n", "\n", "We're going to use cosine similarity since it's generally accepted as producing better results in item-to-item filtering:\n", "\n", "$\\hspace{8cm}sim(A, B) = \\cos(\\theta) = \\frac{A \\cdot B}{\\|A\\|\\|B\\|}$\n", "\n", "Before calculating this, we need to perform a couple of pre-processing steps on our reviews in order to make the data usable for our cosine similarity calculation. These will be common NLP (**n**atural **l**anguage **p**rocessing) techniques that you should be familiar with if you have worked with text before. These are the steps I took, but I am open to feedback and improvement if you have recommendations on other methods that may yield better results.\n", "\n", "**1) Normalizing**: This step converts our words into lower case so that when we map to our feature space, we don't end up with redundant features for the same words.\n", "\n", " Ex. \"Central Texas barbecue is the best smoked and the only barbecue that matters\"\n", "\n", "$\\hspace{1.75cm}$becomes\n", "\n", "```\n", " \"central texas barbecue is the best smoked and the only barbecue that matters\"\n", "```\n", "\n", "**2) Tokenizing**: This step breaks up a sentence into individual words, essentially turning our reviews into [bags of words](https://en.wikipedia.org/wiki/Bag-of-words_model), which makes it easier to perform other operations. Though we are going to perform many other preprocessing operations, this is more or less the beginning of mapping our reviews into the feature space.\n", "\n", " Ex. 'Central Texas barbecue is the best smoked and the only barbecue that matters'\n", "\n", "$\\hspace{1.75cm}$becomes\n", "\n", "```\n", " ['Central', 'Texas', 'barbecue', 'is', 'the', 'best', 'smoked', 'and', 'the', 'only', 'barbecue', 'that', 'matters']\n", "```\n", "\n", "**3) Removing Stopwords and Punctuation**: This step removes unnecessary words and punctuation often used in language that computers don't need such as *as*, *the*, *and*, and *of*.\n", "\n", " Ex. ['central', 'texas', 'barbecue', 'is', 'the', 'best', 'smoked', 'and', 'the', 'only', 'barbecue', 'that', 'matters']\n", "\n", "$\\hspace{1.75cm}$becomes\n", "\n", "```\n", " ['central', 'texas', 'barbecue', 'best', 'smoked', 'only', 'barbecue', 'matters']\n", "```\n", "\n", "**4) Lemmatizing (Stemming)**: Lemmatizing (which is very similar to stemming) removes variations at the end of a word to revert words to their root word.\n", "\n", " Ex. ['central', 'texas', 'barbecue', 'best', 'smoked', 'only', 'barbecue', 'matters']\n", "\n", "$\\hspace{1.75cm}$becomes\n", "\n", "```\n", " ['central', 'texas', 'barbecue', 'best', 'smoke', 'only', 'barbecue', 'matter']\n", "```\n", "\n", "**5) Term Frequency-Inverse Document Frequency (TF-IDF)**: This technique determines how important a word is to a document (which is a review in this case) within a corpus (the collection documents, or all reviews). This doesn't necessarily help establish context within our reviews themselves (for example, 'this Pad Kee Mao is bad ass' is technically a good thing, which wouldn't be accounted for unless we did [n-grams](https://en.wikipedia.org/wiki/N-gram) (which will give my laptop a much more difficult time)), but it does help with establishing the importance of the word.\n", "\n", "$\\hspace{8cm}TFIDF(t, d) = TF(t, d) \\cdot IDF(t)$\n", "\n", "$\\hspace{8cm}IDF(t) = 1 + \\log\\Big(\\frac{\\#\\ Documents}{\\#\\ Documents\\ Containing\\ t}\\Big)$\n", "\n", "$\\hspace{8cm}t:\\ \\text{Term}$\n", "\n", "$\\hspace{8cm}d:\\ \\text{Document}$\n", "\n", "On a side note, sarcasm, slang, misspellings, emoticons, and context are common problems in NLP, but we will be ignoring these due to time limitations. \n", "\n", "### Assumptions\n", "\n", "It's always important to state your assumptions in any analysis because a violation of them will often impact the reliability of the results. My assumptions in this case are as follows:\n", "\n", "- The reviews are indicative of the characteristics of the restaurant.\n", "- The language used in the reviews does not directly impact the rating a user gives.\n", " - E.g. Reviews contain a description of their experience, but ratings are the result of the user applying weights to specific things they value.\n", " - Ex. \"The food was great, but the service was terrible.\" would be a 2/10 for one user, but a 7/10 for users like myself.\n", "- The restaurants did not undergo significant changes in the time frame for the reviews being pulled.\n", "- Sarcasm, slang, misspellings, and other common NLP problems will not have a significant impact on our results.\n", "\n", "---\n", "# Restaurant Recommender\n", "\n", "\n", "If you're still with us after all of that, let's get started!\n", "\n", "---\n", "\n", "In addition to the library imports, we have to specify our credentials to access the Foursquare API. I'm not keen on sharing mine, but you can get your own by [signing up](https://developer.foursquare.com/). I stored mine in a text file which is being read in as a variable before making the API calls." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:29:38.061295Z", "start_time": "2017-07-02T15:29:22.852342Z" }, "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import nltk # Natural Language Processing\n", "import re # Regex\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "\n", "# Functions for preparing and calculating similarity\n", "from scipy import sparse\n", "from sklearn.metrics.pairwise import cosine_similarity\n", "from sklearn.preprocessing import MinMaxScaler, normalize\n", "from sklearn.feature_extraction import text as sktext\n", "\n", "import foursquare\n", "\n", "# Controls aesthetics for plots\n", "sns.set_context(\"notebook\", font_scale=1.1)\n", "sns.set_style(\"ticks\")\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:29:38.109281Z", "start_time": "2017-07-02T15:29:38.065251Z" }, "collapsed": true }, "outputs": [], "source": [ "# Reading credentials for the API\n", "with open('foursquareCredentials.txt') as f:\n", " lines = [line.split('=')[1] for line in f.readlines()]\n", " credentials = [line.replace(\"'\", '').replace('\\n', '') for line in lines]\n", " f.close()\n", "\n", "# Assigning the credentials to an object\n", "client = foursquare.Foursquare(\n", " client_id=credentials[0], \n", " client_secret=credentials[1], \n", " redirect_uri=credentials[2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Data\n", "\n", "Foursquare works similarly to Yelp where users will review restaurants. They can either leave a rating (1-10), or write a review for the restaurant. The reviews are what we're interested in here since I established above that the rating has less meaning due to the way people rate restaurants differently between the two cities.\n", "\n", "The [documentation](https://developer.foursquare.com/docs/) was fortunately fairly robust. I used the [foursquare categoryID tree](https://developer.foursquare.com/categorytree) in order to grab the venue category ID for the different types of restaurants. The [venue search](https://developer.foursquare.com/docs/venues/search) function grabs the actual restaurants, and the [tips](https://developer.foursquare.com/docs/venues/tips) function returns the reviews (and not what users left for a tip like you'd think).\n", "\n", "We're going to use the individual reviews, restaurant category, price tier, and the number of check-ins, reviews, and users.\n", "\n", "\n", "### Restaurants\n", "\n", "Before pulling our restaurants, we have to first include a few parameters. We'll begin with the cities we want to include and the restaurant types we want to include.\n", "\n", "Since we're not interested in chains or fast food restaurants, we have to specify which venue category IDs we want. There are also a few grocery stores (such as Whole Foods and the magnificent [H-E-B](https://www.heb.com/)) that appear under the bakery and deli categories, so excluding this is easier than manually filtering them out. I left them commented out so there is the full list in case anyone else would like to include them for their own uses." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:29:38.245376Z", "start_time": "2017-07-02T15:29:38.125291Z" }, "collapsed": true }, "outputs": [], "source": [ "cities = ['Austin, TX', # Where I'm from\n", " 'Minneapolis, MN'] # Where I'm going to grad school \n", "\n", "venueCategoryId = ['503288ae91d4c4b30a586d67' # Afghan\n", " , '4bf58dd8d48988d1c8941735' # African\n", " , '4bf58dd8d48988d14e941735' # American\n", " , '4bf58dd8d48988d142941735' # Asian. Includes most Asian countries.\n", " , '4bf58dd8d48988d169941735' # Australian\n", " , '52e81612bcbc57f1066b7a01' # Austrian\n", " , '4bf58dd8d48988d1df931735' # BBQ\n", " , '4bf58dd8d48988d179941735' # Bagel\n", "# , '4bf58dd8d48988d16a941735' # Bakery\n", " , '52e81612bcbc57f1066b7a02' # Belgian\n", " , '52e81612bcbc57f1066b79f1' # Bistro\n", " , '4bf58dd8d48988d143941735' # Breakfast\n", " , '52e81612bcbc57f1066b7a0c' # Bubble Tea\n", " , '4bf58dd8d48988d16c941735' # Burgers. Does not include fast food.\n", " , '4bf58dd8d48988d16d941735' # Cafe\n", " , '4bf58dd8d48988d17a941735' # Cajun/Creole\n", " , '4bf58dd8d48988d144941735' # Caribbean\n", " , '5293a7d53cf9994f4e043a45' # Caucasian\n", " , '4bf58dd8d48988d1e0931735' # Coffee Shop\n", " , '52e81612bcbc57f1066b7a00' # Comfort Food\n", " , '52e81612bcbc57f1066b79f2' # Creperie\n", " , '52f2ae52bcbc57f1066b8b81' # Czech\n", "# , '4bf58dd8d48988d146941735' # Deli\n", " , '4bf58dd8d48988d1d0941735' # Dessert\n", " , '4bf58dd8d48988d147941735' # Diner\n", " , '4bf58dd8d48988d148941735' # Donuts\n", " , '5744ccdfe4b0c0459246b4d0' # Dutch\n", " , '4bf58dd8d48988d109941735' # Eastern Europe\n", " , '52e81612bcbc57f1066b7a05' # English\n", " , '4bf58dd8d48988d10b941735' # Falafel\n", " , '4edd64a0c7ddd24ca188df1a' # Fish & Chips\n", " , '52e81612bcbc57f1066b7a09' # Fondue\n", " , '56aa371be4b08b9a8d57350b' # Food Stand\n", " , '4bf58dd8d48988d1cb941735' # Food Truck\n", " , '4bf58dd8d48988d10c941735' # French\n", " , '4bf58dd8d48988d155941735' # Gastropub\n", " , '4bf58dd8d48988d10d941735' # German\n", " , '4bf58dd8d48988d10e941735' # Greek\n", " , '52e81612bcbc57f1066b79ff' # Halal\n", " , '52e81612bcbc57f1066b79fe' # Hawaiian\n", " , '52e81612bcbc57f1066b79fa' # Hungarian\n", " , '4bf58dd8d48988d10f941735' # Indian\n", " , '52e81612bcbc57f1066b7a06' # Irish Pub\n", " , '4bf58dd8d48988d110941735' # Italian\n", " , '52e81612bcbc57f1066b79fd' # Jewish\n", " , '4bf58dd8d48988d112941735' # Juice Bar\n", " , '5283c7b4e4b094cb91ec88d7' # Kebab\n", " , '4bf58dd8d48988d1be941735' # Latin American\n", " , '4bf58dd8d48988d1c0941735' # Mediterranean\n", " , '4bf58dd8d48988d1c1941735' # Mexican\n", " , '4bf58dd8d48988d115941735' # Middle Eastern\n", " , '52e81612bcbc57f1066b79f9' # Modern European\n", " , '52e81612bcbc57f1066b79f8' # Pakistani\n", " , '56aa371be4b08b9a8d573508' # Pet Cafe\n", " , '4bf58dd8d48988d1ca941735' # Pizza\n", " , '52e81612bcbc57f1066b7a04' # Polish\n", " , '4def73e84765ae376e57713a' # Portuguese\n", " , '5293a7563cf9994f4e043a44' # Russian\n", " , '4bf58dd8d48988d1bd941735' # Salad\n", " , '4bf58dd8d48988d1c6941735' # Scandinavian\n", " , '5744ccdde4b0c0459246b4a3' # Scottish\n", " , '4bf58dd8d48988d1ce941735' # Seafood\n", " , '56aa371be4b08b9a8d57355a' # Slovak\n", " , '4bf58dd8d48988d1dd931735' # Soup\n", " , '4bf58dd8d48988d14f941735' # Southern\n", " , '4bf58dd8d48988d150941735' # Spanish\n", " , '5413605de4b0ae91d18581a9' # Sri Lankan\n", " , '4bf58dd8d48988d1cc941735' # Steakhouse\n", " , '4bf58dd8d48988d158941735' # Swiss\n", " , '4bf58dd8d48988d1dc931735' # Tea Room\n", " , '56aa371be4b08b9a8d573538' # Theme Restaurant\n", " , '4f04af1f2fb6e1c99f3db0bb' # Turkish\n", " , '52e928d0bcbc57f1066b7e96' # Ukranian\n", " , '4bf58dd8d48988d1d3941735' # Vegetarian\n", " ]\n", "venueCategoryIdstr = ','.join(venueCategoryId) # Converts to string for API call" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With those specified, we'll go ahead and pull in the restaurants. This loop will go through each restaurant category, grab the restaurants that meet our criteria, and puts them into a data frame called dfRest (**d**ata **f**rame of **rest**aurants).\n", "\n", "Tasks like these can sometimes take a lot of time, but this fortunately runs in under a minute." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:49.368483Z", "start_time": "2017-07-02T15:29:38.249378Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 1min 11s\n" ] } ], "source": [ "%%time\n", "\n", "# Empty data frame to be filled with restaurant information\n", "dfRest = pd.DataFrame()\n", "\n", "for city in cities:\n", " \n", " # Run the API for each category\n", " for category in venueCategoryId:\n", " apiCall = client.venues.search(params = \n", " {'near': city\n", " , 'intent': 'browse' # Non-user based\n", " , 'limit': '50' # Max is 50\n", " , 'radius': '56372' # In meters. Converts to 35 miles\n", " , 'categoryId': category})['venues']\n", " if len(apiCall) == 0:\n", " pass\n", " else:\n", " for restaurant in np.arange(len(apiCall)):\n", " \n", " # Error handling due to not every restaurant possessing\n", " # a value for city/state\n", " try:\n", " apiCity = apiCall[restaurant]['location']['city']\n", " apiState = apiCall[restaurant]['location']['state']\n", " except:\n", " apiCity = np.NaN\n", " apiState = np.NaN\n", " \n", " # Appending a temporary data frame to dfRest to prevent overwriting\n", " temp = pd.DataFrame({\n", " 'name': apiCall[restaurant]['name'],\n", " 'id': apiCall[restaurant]['id'],\n", " 'category': apiCall[restaurant]['categories'][0]['name'],\n", " 'shortCategory': apiCall[restaurant]['categories'][0]['shortName'],\n", " 'city': apiCity,\n", " 'state': apiState,\n", " 'location': city,\n", " 'checkinsCount': apiCall[restaurant]['stats']['checkinsCount'],\n", " 'commentsCount': apiCall[restaurant]['stats']['tipCount'],\n", " 'usersCount': apiCall[restaurant]['stats']['usersCount']\n", " }, index=[0])\n", " dfRest = pd.concat([dfRest, temp])" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:49.397504Z", "start_time": "2017-07-02T15:30:49.377990Z" } }, "outputs": [ { "data": { "text/plain": [ "(3021, 10)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfRest.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have pulled the restaurants, let's clean it up by removing any restaurants with:\n", "- **Duplicates:** A small handful of these made it through, and we only need one of each.\n", "- **$<$ 10 reviews:** These won't be too useful for our purposes.\n", "- **Categories we don't want:** Some of these also got through, and I have no interest in seeing how the Burger King around the corner compares to a Whataburger back home." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:49.509083Z", "start_time": "2017-07-02T15:30:49.408011Z" }, "collapsed": true }, "outputs": [], "source": [ "# Cleaning the dataset before making more API calls\n", "dfRest = dfRest.dropna().drop_duplicates()\n", "dfRest = dfRest[dfRest['commentsCount'] >= 10]\n", "\n", "# Removing unwanted categories\n", "excludeCategories = ['Fast Food', 'Gas Station', 'Golf Course', 'Bowling Alley']\n", "dfRest = dfRest.loc[~dfRest.category.isin(excludeCategories)]" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:49.535602Z", "start_time": "2017-07-02T15:30:49.515087Z" } }, "outputs": [ { "data": { "text/plain": [ "(1362, 10)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfRest.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While that removed more than half of our records, ~1,300 restaurants is still plenty enough to work with. This will also drastically reduce the number of reviews we need to grab, as well.\n", "\n", "Moving on, let's look at a few charts for exploratory analysis to see what restaurants we ended up with:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:50.344177Z", "start_time": "2017-07-02T15:30:49.543609Z" } }, "outputs": [ { "data": { "image/png": "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\nuU/84pwRAAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Number of reviews per restaurant\n", "plt.figure(figsize=(8, 5))\n", "sns.kdeplot(dfRest['commentsCount'], legend=False)\n", "plt.title('# Comments per Restaurant Distribution')\n", "sns.despine()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:50.984131Z", "start_time": "2017-07-02T15:30:50.354184Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Number of restaurants per city\n", "plt.figure(figsize=(8, 5))\n", "dfRest['id'].groupby(dfRest['location']).count().plot(kind='bar')\n", "plt.title('# Restaurants per City')\n", "sns.despine()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:30:55.364746Z", "start_time": "2017-07-02T15:30:50.997641Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Breakdown of restaurant type by city\n", "plt.figure(figsize=(12, 20))\n", "\n", "# Grouping by the number of restaurants per category and city\n", "categoryPlot = dfRest['id'].groupby(\n", " (dfRest['shortCategory'], dfRest['location'])).count().sort_values(\n", " ascending=False)[:100].reset_index()\n", "\n", "# Plotting \n", "sns.barplot(x=\"id\", y=\"shortCategory\", hue=\"location\", data=categoryPlot)\n", "\n", "# Setting labels and configuring visuals\n", "plt.xlabel('Count')\n", "plt.ylabel('Category')\n", "sns.despine()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So our number of restaurants between each city is basically even, most restaurants have between 10 and 70 reviews, and we see a few interesting things in the breakdown between categories. To summarize the chart:\n", "\n", "**Austin:**\n", "- Significantly more BBQ, tacos, food trucks, donuts, juice bars, and Cajun restaurants (but I could have told you this)\n", "- Seemingly more diversity in the smaller categories\n", "\n", "**Minneapolis:**\n", "- American is king\n", "- Significantly more bars, bakeries, middle eastern, cafés, tea rooms, German, and breweries\n", "\n", "Our initial pull didn't include the ratings, so we'll go ahead and pull those in along with the price tier.\n", "\n", "This loop takes a lot longer (~7 minutes), possibly due to making individual calls for each restaurant (~1,300) instead of just each category (~72)." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:37:42.068359Z", "start_time": "2017-07-02T15:30:55.373251Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 6min 46s\n" ] } ], "source": [ "%%time\n", "\n", "ratings = []\n", "numRatings = []\n", "priceTiers = []\n", "\n", "for restId in dfRest['id']:\n", " apiCall = client.venues(restId)['venue']\n", " \n", " try:\n", " rate = apiCall['rating']\n", " numRates = apiCall['ratingSignals']\n", " tiers = apiCall['attributes']['groups'][0]['items'][0]['priceTier']\n", " except:\n", " rate = np.NaN\n", " numRates = np.NaN\n", " tiers = np.NaN\n", " \n", " ratings.append(rate)\n", " numRatings.append(numRates)\n", " priceTiers.append(tiers)\n", " \n", "dfRest['rating'] = ratings\n", "dfRest['numRatings'] = numRatings\n", "dfRest['priceTier'] = priceTiers" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:37:42.090875Z", "start_time": "2017-07-02T15:37:42.075347Z" }, "collapsed": true }, "outputs": [], "source": [ "# Re-ordering the columns since the loop puts them out of order\n", "dfRest = dfRest.reindex_axis(['id', 'name', 'category', 'shortCategory',\n", " 'checkinsCount', 'city','state', 'location',\n", " 'commentsCount', 'usersCount', 'priceTier',\n", " 'numRatings', 'rating'],\n", " axis=1).reset_index(drop=True)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:37:42.236462Z", "start_time": "2017-07-02T15:37:42.099363Z" } }, "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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamecategoryshortCategorycheckinsCountcitystatelocationcommentsCountusersCountpriceTiernumRatingsrating
04e17b348b0fb8567c665ddafSouper SaladSalad PlaceSalad1769AustinTXAustin, TX176831.041.06.9
14aceefb7f964a52013d220e3Aster's Ethiopian RestaurantEthiopian RestaurantEthiopian1463AustinTXAustin, TX3410182.093.08.0
24b591015f964a520c17a28e3Taste Of EthiopiaEthiopian RestaurantEthiopian1047PflugervilleTXAustin, TX316722.088.08.3
34ead97ba4690615f26a8adfeWasota African CuisineAfrican RestaurantAfrican195AustinTXAustin, TX121402.010.06.2
44c7efeba2042b1f76cd1c1adCazamanceAfrican RestaurantAfrican500AustinTXAustin, TX114351.015.08.0
\n", "
" ], "text/plain": [ " id name \\\n", "0 4e17b348b0fb8567c665ddaf Souper Salad \n", "1 4aceefb7f964a52013d220e3 Aster's Ethiopian Restaurant \n", "2 4b591015f964a520c17a28e3 Taste Of Ethiopia \n", "3 4ead97ba4690615f26a8adfe Wasota African Cuisine \n", "4 4c7efeba2042b1f76cd1c1ad Cazamance \n", "\n", " category shortCategory checkinsCount city state \\\n", "0 Salad Place Salad 1769 Austin TX \n", "1 Ethiopian Restaurant Ethiopian 1463 Austin TX \n", "2 Ethiopian Restaurant Ethiopian 1047 Pflugerville TX \n", "3 African Restaurant African 195 Austin TX \n", "4 African Restaurant African 500 Austin TX \n", "\n", " location commentsCount usersCount priceTier numRatings rating \n", "0 Austin, TX 17 683 1.0 41.0 6.9 \n", "1 Austin, TX 34 1018 2.0 93.0 8.0 \n", "2 Austin, TX 31 672 2.0 88.0 8.3 \n", "3 Austin, TX 12 140 2.0 10.0 6.2 \n", "4 Austin, TX 11 435 1.0 15.0 8.0 " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Cleaning the dataset by dropping duplicates\n", "dfRest = dfRest.dropna().drop_duplicates()\n", "\n", "dfRest.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reviews\n", "\n", "These are the actual comments people write about the restaurants, and they are what we will be comparing for our \"recommender\" system. \n", "comments\n", "This loop grabs the actual comments per restaurant, and puts them into the data frame dfComments. It takes ~10 minutes to run.\n", "\n", "Since I'm using a free developer key, I'm currently limited to 30 comments per restaurant ID. This will impact the quality for some restaurants, but we saw that most of our restaurants had under 30 comments in the earlier chart." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:02.635009Z", "start_time": "2017-07-02T15:37:42.280493Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 9min 20s\n" ] } ], "source": [ "%%time\n", "\n", "dfComments = pd.DataFrame()\n", "\n", "for rest in dfRest['id']:\n", " apiCall = client.venues.tips(rest)['tips']['items']\n", " numComments = len(apiCall)\n", " \n", " for idx in np.arange(numComments):\n", " temp = pd.DataFrame({\n", " 'id': rest,\n", " 'comment': apiCall[idx]['text']\n", " }, index=[0])\n", " dfComments = pd.concat([dfComments, temp])\n", " \n", "dfComments = dfComments.reindex_axis(['id', 'comment'], axis = 1).reset_index(drop=True)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:02.666014Z", "start_time": "2017-07-02T15:47:02.645000Z" } }, "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", "
idcomment
04e17b348b0fb8567c665ddafHealthy fresh salad plus baked potatoes, selec...
14e17b348b0fb8567c665ddafMake sure to print out your coupons. These fol...
24e17b348b0fb8567c665ddafFirst of all, this place uses CANNED chick pea...
34e17b348b0fb8567c665ddafGreat healthy food, good prices. Try the ginge...
44e17b348b0fb8567c665ddafLove coming here. Makes getting lunch so easy ...
\n", "
" ], "text/plain": [ " id comment\n", "0 4e17b348b0fb8567c665ddaf Healthy fresh salad plus baked potatoes, selec...\n", "1 4e17b348b0fb8567c665ddaf Make sure to print out your coupons. These fol...\n", "2 4e17b348b0fb8567c665ddaf First of all, this place uses CANNED chick pea...\n", "3 4e17b348b0fb8567c665ddaf Great healthy food, good prices. Try the ginge...\n", "4 4e17b348b0fb8567c665ddaf Love coming here. Makes getting lunch so easy ..." ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfComments.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we need to group the comments together so we have one set of comments per restaurant before joining everything together." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:03.062796Z", "start_time": "2017-07-02T15:47:02.673520Z" }, "collapsed": true }, "outputs": [], "source": [ "# Grouping individual comments by restaurant ID\n", "groupedComments = dfComments.groupby('id')['comment'].apply(\n", " lambda x: \"{%s}\" % ''.join(x)).reset_index()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is where we merge everything into one data frame, df, which has a row for each restaurant that includes all of the comments. We'll also perform an additional sanitation step here by removing non-ASCII characters from the comments." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:03.245426Z", "start_time": "2017-07-02T15:47:03.066298Z" }, "scrolled": 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", " \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", " \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", "
idnamecategoryshortCategorycheckinsCountcitystatelocationcommentsCountusersCountpriceTiernumRatingsratingcomments
04e17b348b0fb8567c665ddafSouper SaladSalad PlaceSalad1769AustinTXAustin, TX176831.041.06.9{Healthy fresh salad plus baked potatoes, sele...
14aceefb7f964a52013d220e3Aster's Ethiopian RestaurantEthiopian RestaurantEthiopian1463AustinTXAustin, TX3410182.093.08.0{The lunch buffet is wonderful; everything is ...
24b591015f964a520c17a28e3Taste Of EthiopiaEthiopian RestaurantEthiopian1047PflugervilleTXAustin, TX316722.088.08.3{Wonderful! Spicy lovers: Kitfo, was awesome! ...
34ead97ba4690615f26a8adfeWasota African CuisineAfrican RestaurantAfrican195AustinTXAustin, TX121402.010.06.2{Obsessed with this place. One little nugget (...
44c7efeba2042b1f76cd1c1adCazamanceAfrican RestaurantAfrican500AustinTXAustin, TX114351.015.08.0{West African fusion reigns at this darling tr...
\n", "
" ], "text/plain": [ " id name \\\n", "0 4e17b348b0fb8567c665ddaf Souper Salad \n", "1 4aceefb7f964a52013d220e3 Aster's Ethiopian Restaurant \n", "2 4b591015f964a520c17a28e3 Taste Of Ethiopia \n", "3 4ead97ba4690615f26a8adfe Wasota African Cuisine \n", "4 4c7efeba2042b1f76cd1c1ad Cazamance \n", "\n", " category shortCategory checkinsCount city state \\\n", "0 Salad Place Salad 1769 Austin TX \n", "1 Ethiopian Restaurant Ethiopian 1463 Austin TX \n", "2 Ethiopian Restaurant Ethiopian 1047 Pflugerville TX \n", "3 African Restaurant African 195 Austin TX \n", "4 African Restaurant African 500 Austin TX \n", "\n", " location commentsCount usersCount priceTier numRatings rating \\\n", "0 Austin, TX 17 683 1.0 41.0 6.9 \n", "1 Austin, TX 34 1018 2.0 93.0 8.0 \n", "2 Austin, TX 31 672 2.0 88.0 8.3 \n", "3 Austin, TX 12 140 2.0 10.0 6.2 \n", "4 Austin, TX 11 435 1.0 15.0 8.0 \n", "\n", " comments \n", "0 {Healthy fresh salad plus baked potatoes, sele... \n", "1 {The lunch buffet is wonderful; everything is ... \n", "2 {Wonderful! Spicy lovers: Kitfo, was awesome! ... \n", "3 {Obsessed with this place. One little nugget (... \n", "4 {West African fusion reigns at this darling tr... " ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Merging everything together into one data frame\n", "df = pd.merge(dfRest, groupedComments, on='id')\n", "\n", "# Removing non-ASCII characters\n", "df['comments'] = df['comment'].apply(\n", " lambda comment: re.sub(r'[^\\x00-\\x7f]', r'', comment))\n", "\n", "df.drop('comment', axis=1, inplace=True)\n", "\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an example of the comments for one random restaurant to give a better idea of the data we're dealing with:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:03.279450Z", "start_time": "2017-07-02T15:47:03.255935Z" } }, "outputs": [ { "data": { "text/plain": [ "'{Make sure you check your steaks, they don\\'t cook it the way you ask for them. The Outlaw Ribeye is excellent when cooked to order!Kyle is the best waiter here he should be a trainer or head waiter an train everyone at his level of customer service. I will wait to be seated when he\\'s working.Alex is a great waitress very polite and very conscious about refills and service. I give her 4.5 stars. Not quite a 5 yet.Great place awesome staff....but alas....their is a sign banning Cody G. Sangria is awesome!!!They make good food but if you are taking to go it takes awhile. They say 15 minutes but it\\'s like 25-30. Prepare for that.Lunch specials are great & service is always good.If you don\\'t like lemon juice in your water, make sure to ask for it without.The burgers here are awesome! Freshly ground sirloin! Yummy!Try the Wild West Shrimp. Plenty to share. Yum!!!Customer service here is at 110%. Best service of any longhorns that I have been to across Texas.I was enjoying my salad when I bit into something odd. It turned out to be a shard of clear hard plastic. I called the manager over. He replied, \"glad you didn\\'t swallow that\". Not even an apology.I ordered a pork chop dinner and they brought me ribs. Talk about disorganized, plus I had to wait an hour for the pork chops.This isn\\'t Ruth\\'s Chris; understanding that is key to having a good time.The Mula drink it\\'s excellentI can\\'t believe they don\\'t have Dr Pepper here; come on, this Texas, it\\'s a requirement!!!The broccoli cheese soup was good!The passion/pineapple vodka is yummy!!!!Excelente lugarBuen servicio.This place is trash, save ur money ! Go to Texas road house.... YeaaaahhhhhhhCody G. must live here.Casey spends a lot of time here.TERRIBLE!!! Mediocre chain food and RUDE SERVICE!!! NEVER GOING BACK!!!Expensive.}'" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df['comments'][np.random.randint(df.shape[0])] # Selects one restaurant at random" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Processing\n", "\n", "When working with language, we have to process the text into something that a computer can handle more easily. Our end result will be a large number of numerical features for each restaurant that we can use to calculate the cosine similarity.\n", "\n", "The steps here are:\n", "1. Normalizing\n", "2. Tokenizing\n", "3. Removing stopwords\n", "4. Lemmatizing (Stemming)\n", "5. Term Frequency-Inverse Document Frequency (TF-IDF)\n", "\n", "I'll explain a little more on what these are and why we are doing them below in case you aren't familiar with them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1) Normalizing\n", "\n", "This section uses regex scripts that makes cases every word lower cased, removes punctuation, and removes digits.\n", "\n", "For example:\n", "\n", "**Before:**\n", "\n", "$\\hspace{1cm}$\"ThIs Is HoW mIdDlE sChOoLeRs TaLkEd 2 EaCh OtHeR oN AIM!!!!\"\n", "\n", "**After:**\n", "\n", "$\\hspace{1cm}$\"this is how middle schoolers talked each other on aim\"\n", "\n", "The benefit in this is that it vastly reduces our feature space. Our pre-processed example would have created an additional ~10 features from someone who doesn't know how to type like a regular human being." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:04.242945Z", "start_time": "2017-07-02T15:47:03.317477Z" } }, "outputs": [ { "data": { "text/plain": [ "0 {healthy fresh salad plus baked potatoes, sele...\n", "1 {the lunch buffet is wonderful; everything is ...\n", "2 {wonderful! spicy lovers: kitfo, was awesome! ...\n", "3 {obsessed with this place one little nugget (...\n", "4 {west african fusion reigns at this darling tr...\n", "Name: comments, dtype: object" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Converting all words to lower case and removing punctuation\n", "df['comments'] = [re.sub(r'\\d+\\S*', '',\n", " row.lower().replace('.', ' ').replace('_', '').replace('/', ''))\n", " for row in df['comments']]\n", "\n", "df['comments'] = [re.sub(r'(?:^| )\\w(?:$| )', '', row)\n", " for row in df['comments']]\n", "\n", "# Removing numbers\n", "df['comments'] = [re.sub(r'\\d+', '', row) for row in df['comments']]\n", "\n", "df['comments'].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2) Tokenizing\n", "\n", "Tokenizing a sentence is a way to map our words into a feature space. This is achieved by treating every word as an individual object.\n", "\n", "**Before:**\n", "\n", "$\\hspace{1cm}$'central texas barbecue is the best smoked and the only barbecue that matters'\n", "\n", "**After:**\n", "\n", "$\\hspace{1cm}$['central', 'texas', 'barbecue', 'is', 'the', 'best', 'smoked', 'and', 'the', 'only', 'barbecue', 'that', 'matters']\n" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:47:05.222643Z", "start_time": "2017-07-02T15:47:04.504631Z" } }, "outputs": [ { "data": { "text/plain": [ "0 [healthy, fresh, salad, plus, baked, potatoes,...\n", "1 [the, lunch, buffet, is, wonderful, everything...\n", "2 [wonderful, spicy, lovers, kitfo, was, awesome...\n", "3 [obsessed, with, this, place, one, little, nug...\n", "4 [west, african, fusion, reigns, at, this, darl...\n", "Name: tokens, dtype: object" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Tokenizing comments and putting them into a new column\n", "tokenizer = nltk.tokenize.RegexpTokenizer(r'\\w+') # by blank space\n", "df['tokens'] = df['comments'].apply(tokenizer.tokenize)\n", "\n", "df['tokens'].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3) Removing Stopwords & Punctuation\n", "\n", "Stopwords are unnecessary words like *as*, *the*, *and*, and *of* that aren't very useful for our purposes. Since they don't have any intrinsic value, removing them reduces our feature space which will speed up our computations.\n", "\n", "**Before:**\n", "\n", "$\\hspace{1cm}$['central', 'texas', 'barbecue', 'is', 'the', 'best', 'smoked', 'and', 'the', 'only', 'barbecue', 'that', 'matters']\n", "\n", "**After:**\n", "\n", "$\\hspace{1cm}$['central', 'texas', 'barbecue', 'best', 'smoked', 'only', 'barbecue', 'matters']\n", "\n", "This does take a bit longer to run at ~6 minutes" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T15:52:04.881831Z", "start_time": "2017-07-02T15:47:05.230649Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 4min 59s\n" ] } ], "source": [ "%%time\n", "\n", "filtered_words = []\n", "for row in df['tokens']:\n", " filtered_words.append([\n", " word.lower() for word in row\n", " if word.lower() not in nltk.corpus.stopwords.words('english')\n", " ])\n", "\n", "df['tokens'] = filtered_words" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4) Lemmatizing (Stemming)\n", "\n", "Stemming removes variations at the end of a word to revert words to their root in order to reduce our overall feature space (e.x. running $\\rightarrow$ run). This has the possibility to adversely impact our performance when the root word is different (e.x. university $\\rightarrow$ universe), but the net positives typically outweigh the net negatives.\n", "\n", "**Before:**\n", "\n", "$\\hspace{1cm}$['central', 'texas', 'barbecue', 'best', 'smoked', 'only', 'barbecue', 'matters']\n", "\n", "**After:**\n", "\n", "$\\hspace{1cm}$['central', 'texas', 'barbecue', 'best', 'smoke', 'only', 'barbecue', 'matter']\n", "\n", "One very important thing to note here is that we're actually doing something called **[Lemmatization](https://en.wikipedia.org/wiki/Lemmatisation)**, which is similar to [stemming](https://en.wikipedia.org/wiki/Stemming), but is a little different. Both seek to reduce inflectional forms and sometimes derivationally related forms of a word to a common base form, but they go about it in different ways. In order to illustrate the difference, here's a dictionary entry:\n", "\n", "\n", "\n", "Lemmatization seeks to get the *lemma*, or the base dictionary form of the word. In our example above, that would be \"graduate\". It does this by using vocabulary and a morphological analysis of the words, rather than just chopping off the variations (the \"verb forms\" in the example above) like a traditional stemmer would.\n", "\n", "The advantage of lemmatization here is that we don't run into issues like our other example of *university* $\\rightarrow$ *universe* that can happen in conventional stemmers. It is also relatively quick on this data set!\n", "\n", "The disadvantage is that it is not able to infer if the word is a noun/verb/adjective/etc., so we have to specify which type it is. Since we're looking at, well, everything, we're going to lemmatize for nouns, verbs, and adjectives.\n", "\n", "[Here](https://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html) is an excerpt from the Stanford book *Introduction to Information Retrieval* if you wanted to read more stemming and lemmatization." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:15:53.578714Z", "start_time": "2017-07-02T17:11:00.982881Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 4min 52s\n" ] } ], "source": [ "%%time\n", "\n", "# Setting the Lemmatization object\n", "lmtzr = nltk.stem.wordnet.WordNetLemmatizer()\n", "\n", "# Looping through the words and appending the lemmatized version to a list\n", "stemmed_words = []\n", "for row in df['tokens']:\n", " stemmed_words.append([\n", " # Verbs\n", " lmtzr.lemmatize( \n", " # Adjectives\n", " lmtzr.lemmatize( \n", " # Nouns\n", " lmtzr.lemmatize(word.lower()), 'a'), 'v')\n", " for word in row\n", " if word.lower() not in nltk.corpus.stopwords.words('english')])\n", "\n", "# Adding the list as a column in the data frame\n", "df['tokens'] = stemmed_words" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's take a look at how many unique words we now have and a few of the examples:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:38.129948Z", "start_time": "2017-07-02T17:30:36.895071Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of unique words: 36592 \n", "\n", "Previewing sample of unique words:\n", " ['andhitachino' 'andhome' 'andhomemade' 'andhopping' 'andhot' 'andhouse'\n", " 'andhuge' 'andiamo' 'andimmediatly' 'andis']\n" ] } ], "source": [ "# Appends all words to a list in order to find the unique words\n", "allWords = []\n", "for row in stemmed_words:\n", " for word in row:\n", " allWords.append(str(word))\n", " \n", "uniqueWords = np.unique(allWords)\n", "\n", "print('Number of unique words:', len(uniqueWords), '\\n')\n", "print('Previewing sample of unique words:\\n', uniqueWords[1234:1244])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see a few of the challenges from slang or typos that I mentioned in the beginning. These will pose problems for what we're doing, but we'll just have to assume that the vast majority of words are spelled correctly.\n", "\n", "Before doing the TF-IDF transformation, we need to make sure that we have spaces in between each word in the comments:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:42.993287Z", "start_time": "2017-07-02T17:30:42.259265Z" } }, "outputs": [ { "data": { "text/plain": [ "' line ridiculous download app order get bowl min whilst person take place line wait wait hangry townsuper nice staff give extra chicken cost waswink speedy service even onsaturday order fajita sure let know mean pepper bean skimpy portion compare location especially compare portion employee give front pay customer try almost chipotle area one make best steak service really shitty everyone else say third row line least min getbowl andbunch chip best chip dip ever taste use app order advance get comical long line location staff oblivious tell cashier reguerilla getfree bag chip slow chipolte ever seriously come rush anywhere chance go late end withoutdinner get salad dress tangy place crazy busy matter go staff slow expect wait putlime wedge coke good linesridiculous worth wait chicken burrito bowl bestgetbowl healthy great burrito chipsits nice charge guacamolesteak soft taco withside guacamole mexican cuisineorder ahead online great staff line way behind fillingfax order good customer servicei desire earn back mayor ship quality food service takenserious turn since mgr ben sexe leave slow chipotle staff twin citiesthis place go hill lot staff fast mayor unhappyborrito bowlgreat place disfunctional officially bad chipotle go lyndale good service slow chipotle land prepare wait line always terrible management seem carekicks qdoba buttway toooooo slow tonight outta chip guaq areno brainerdamn soooo slow food good service suck'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stemmed_sentences = []\n", "\n", "# Spacing out the words in the reviews for each restaurant\n", "for row in df['tokens']:\n", " stemmed_string = ''\n", " for word in row:\n", " stemmed_string = stemmed_string + ' ' + word\n", " stemmed_sentences.append(stemmed_string)\n", " \n", "df['tokens'] = stemmed_sentences\n", "stemmed_sentences[np.random.randint(len(stemmed_sentences))]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5) Term Frequency-Inverse Document Frequency (TF-IDF)\n", "\n", "This determines how important a word is to a document (which is a review in this case) within a corpus (the collection documents). It is a number resulting from the following formula: \n", "\n", "$\\hspace{8cm}TFIDF(t, d) = TF(t, d) \\cdot IDF(t)$\n", "\n", "\n", "$\\hspace{8cm}IDF(t) = 1 + \\log\\Big(\\frac{\\#\\ Documents}{\\#\\ Documents\\ Containing\\ t}\\Big)$\n", "\n", "\n", "$\\hspace{8cm}t:\\ \\text{Term}$\n", "\n", "$\\hspace{8cm}d:\\ \\text{Document}$\n", "\n", "Scikit-learn has an [excellent function](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html) that is able to transform our processed text into a TF-IDF matrix very quickly. We'll convert it back to a data frame, and join it to our original data frame by the indexes." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:44.482495Z", "start_time": "2017-07-02T17:30:42.999792Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wall time: 1.47 s\n" ] } ], "source": [ "%%time\n", "\n", "# Creating the sklearn object\n", "tfidf = sktext.TfidfVectorizer(smooth_idf=False)\n", "\n", "# Transforming our 'tokens' column into a TF-IDF matrix and then a data frame\n", "tfidf_df = pd.DataFrame(tfidf.fit_transform(df['tokens']).toarray(), \n", " columns=tfidf.get_feature_names())" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:44.711154Z", "start_time": "2017-07-02T17:30:44.489997Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1335, 36571)\n" ] }, { "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", " \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", " \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", " \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", "
aaaaaaaaaaaaaaaaaaamazingaaaaaamaaaaaaazingaaaaalllaaaaandaaaammaazzingaaallllllllaaarrrggghhh...zuccazucchinizuccinizuccottozuchinizukezuppazurzushizzzzzzzzzzzzoops
00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
10.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
20.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
30.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
40.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
\n", "

5 rows × 36571 columns

\n", "
" ], "text/plain": [ " aa aaa aaaaaaa aaaaaaamazing aaaaaamaaaaaaazing aaaaalll aaaaand \\\n", "0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "\n", " aaaammaazzing aaallllllll aaarrrggghhh ... zucca \\\n", "0 0.0 0.0 0.0 ... 0.0 \n", "1 0.0 0.0 0.0 ... 0.0 \n", "2 0.0 0.0 0.0 ... 0.0 \n", "3 0.0 0.0 0.0 ... 0.0 \n", "4 0.0 0.0 0.0 ... 0.0 \n", "\n", " zucchini zuccini zuccotto zuchini zuke zuppa zur zushi \\\n", "0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "\n", " zzzzzzzzzzzzoops \n", "0 0.0 \n", "1 0.0 \n", "2 0.0 \n", "3 0.0 \n", "4 0.0 \n", "\n", "[5 rows x 36571 columns]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(tfidf_df.shape)\n", "tfidf_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we transformed *all* of the words, we have a [sparse matrix](https://en.wikipedia.org/wiki/Sparse_matrix). We don't care about things like typos or words specific to one particular restaurant, so we're going to remove columns that don't have a lot of contents." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.359115Z", "start_time": "2017-07-02T17:30:44.738673Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1335, 976)\n" ] }, { "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", " \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", " \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", " \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", "
absoluteabsolutelyacrossactuallyaddafternoonaheadahialale...wowwrapwrongwwwyearyesyetyorkyumyummy
00.00.0000000.00.0000000.00.00.00.00.00.0...0.00000.0000000.0000000.0746050.00.00.0000000.0000000.0000000.000000
10.00.0000000.00.0347780.00.00.00.00.00.0...0.00000.0000000.0241010.0000000.00.00.0000000.0000000.0233010.023143
20.00.0312680.00.0000000.00.00.00.00.00.0...0.03760.0000000.0261750.0000000.00.00.0000000.0000000.0000000.025135
30.00.0000000.00.0000000.00.00.00.00.00.0...0.00000.0000000.0000000.0000000.00.00.0000000.0000000.0000000.000000
40.00.0000000.00.0000000.00.00.00.00.00.0...0.00000.0604020.0000000.0000000.00.00.0652360.0851970.0000000.000000
\n", "

5 rows × 976 columns

\n", "
" ], "text/plain": [ " absolute absolutely across actually add afternoon ahead ahi al \\\n", "0 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.000000 0.0 0.034778 0.0 0.0 0.0 0.0 0.0 \n", "2 0.0 0.031268 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "3 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "4 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "\n", " ale ... wow wrap wrong www year yes yet \\\n", "0 0.0 ... 0.0000 0.000000 0.000000 0.074605 0.0 0.0 0.000000 \n", "1 0.0 ... 0.0000 0.000000 0.024101 0.000000 0.0 0.0 0.000000 \n", "2 0.0 ... 0.0376 0.000000 0.026175 0.000000 0.0 0.0 0.000000 \n", "3 0.0 ... 0.0000 0.000000 0.000000 0.000000 0.0 0.0 0.000000 \n", "4 0.0 ... 0.0000 0.060402 0.000000 0.000000 0.0 0.0 0.065236 \n", "\n", " york yum yummy \n", "0 0.000000 0.000000 0.000000 \n", "1 0.000000 0.023301 0.023143 \n", "2 0.000000 0.000000 0.025135 \n", "3 0.000000 0.000000 0.000000 \n", "4 0.085197 0.000000 0.000000 \n", "\n", "[5 rows x 976 columns]" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Removing sparse columns\n", "tfidf_df = tfidf_df[tfidf_df.columns[tfidf_df.sum() > 2.5]]\n", "\n", "# Removing any remaining digits\n", "tfidf_df = tfidf_df.filter(regex=r'^((?!\\d).)*$')\n", "\n", "print(tfidf_df.shape)\n", "tfidf_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This drastically reduced the dimensions of our data set, and we now have something usable to calculate similarity." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.485703Z", "start_time": "2017-07-02T17:30:45.366119Z" }, "scrolled": true }, "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", " \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", " \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", " \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", "
idNamecategoryshortCategorycheckinsCountCitystateLocationcommentsCountusersCount...wowwrapwrongwwwyearyesyetyorkyumyummy
04e17b348b0fb8567c665ddafSouper SaladSalad PlaceSalad1769AustinTXAustin, TX17683...0.00000.0000000.0000000.0746050.00.00.0000000.0000000.0000000.000000
14aceefb7f964a52013d220e3Aster's Ethiopian RestaurantEthiopian RestaurantEthiopian1463AustinTXAustin, TX341018...0.00000.0000000.0241010.0000000.00.00.0000000.0000000.0233010.023143
24b591015f964a520c17a28e3Taste Of EthiopiaEthiopian RestaurantEthiopian1047PflugervilleTXAustin, TX31672...0.03760.0000000.0261750.0000000.00.00.0000000.0000000.0000000.025135
34ead97ba4690615f26a8adfeWasota African CuisineAfrican RestaurantAfrican195AustinTXAustin, TX12140...0.00000.0000000.0000000.0000000.00.00.0000000.0000000.0000000.000000
44c7efeba2042b1f76cd1c1adCazamanceAfrican RestaurantAfrican500AustinTXAustin, TX11435...0.00000.0604020.0000000.0000000.00.00.0652360.0851970.0000000.000000
\n", "

5 rows × 991 columns

\n", "
" ], "text/plain": [ " id Name \\\n", "0 4e17b348b0fb8567c665ddaf Souper Salad \n", "1 4aceefb7f964a52013d220e3 Aster's Ethiopian Restaurant \n", "2 4b591015f964a520c17a28e3 Taste Of Ethiopia \n", "3 4ead97ba4690615f26a8adfe Wasota African Cuisine \n", "4 4c7efeba2042b1f76cd1c1ad Cazamance \n", "\n", " category shortCategory checkinsCount City state \\\n", "0 Salad Place Salad 1769 Austin TX \n", "1 Ethiopian Restaurant Ethiopian 1463 Austin TX \n", "2 Ethiopian Restaurant Ethiopian 1047 Pflugerville TX \n", "3 African Restaurant African 195 Austin TX \n", "4 African Restaurant African 500 Austin TX \n", "\n", " Location commentsCount usersCount ... wow wrap \\\n", "0 Austin, TX 17 683 ... 0.0000 0.000000 \n", "1 Austin, TX 34 1018 ... 0.0000 0.000000 \n", "2 Austin, TX 31 672 ... 0.0376 0.000000 \n", "3 Austin, TX 12 140 ... 0.0000 0.000000 \n", "4 Austin, TX 11 435 ... 0.0000 0.060402 \n", "\n", " wrong www year yes yet york yum yummy \n", "0 0.000000 0.074605 0.0 0.0 0.000000 0.000000 0.000000 0.000000 \n", "1 0.024101 0.000000 0.0 0.0 0.000000 0.000000 0.023301 0.023143 \n", "2 0.026175 0.000000 0.0 0.0 0.000000 0.000000 0.000000 0.025135 \n", "3 0.000000 0.000000 0.0 0.0 0.000000 0.000000 0.000000 0.000000 \n", "4 0.000000 0.000000 0.0 0.0 0.065236 0.085197 0.000000 0.000000 \n", "\n", "[5 rows x 991 columns]" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Storing the original data frame before the merge in case any changes are needed\n", "df_orig = df.copy()\n", "\n", "# Renaming columns that conflict with column names in tfidfCore\n", "df.rename(columns={'name': 'Name', \n", " 'city': 'City', \n", " 'location': 'Location'}, inplace=True)\n", "\n", "# Merging the data frames by index\n", "df = pd.merge(df, tfidf_df, how='inner', left_index=True, right_index=True)\n", "\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lastly, we're going to add additional features for the category. This just puts a heavier weight on those with the same type, so for example a Mexican restaurant will be more likely to have Mexican restaurants show up as most similar instead of Brazilian restaurants." ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.622801Z", "start_time": "2017-07-02T17:30:45.490707Z" } }, "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", " \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", " \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", " \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", "
idNamecategoryshortCategorycheckinsCountCitystateLocationcommentsCountusersCount...TapasTea RoomTex-MexThaiTheme RestaurantTurkishVegetarian / VeganVietnameseWine BarYogurt
04e17b348b0fb8567c665ddafSouper SaladSalad PlaceSalad1769AustinTXAustin, TX17683...0000000000
14aceefb7f964a52013d220e3Aster's Ethiopian RestaurantEthiopian RestaurantEthiopian1463AustinTXAustin, TX341018...0000000000
24b591015f964a520c17a28e3Taste Of EthiopiaEthiopian RestaurantEthiopian1047PflugervilleTXAustin, TX31672...0000000000
34ead97ba4690615f26a8adfeWasota African CuisineAfrican RestaurantAfrican195AustinTXAustin, TX12140...0000000000
44c7efeba2042b1f76cd1c1adCazamanceAfrican RestaurantAfrican500AustinTXAustin, TX11435...0000000000
\n", "

5 rows × 1090 columns

\n", "
" ], "text/plain": [ " id Name \\\n", "0 4e17b348b0fb8567c665ddaf Souper Salad \n", "1 4aceefb7f964a52013d220e3 Aster's Ethiopian Restaurant \n", "2 4b591015f964a520c17a28e3 Taste Of Ethiopia \n", "3 4ead97ba4690615f26a8adfe Wasota African Cuisine \n", "4 4c7efeba2042b1f76cd1c1ad Cazamance \n", "\n", " category shortCategory checkinsCount City state \\\n", "0 Salad Place Salad 1769 Austin TX \n", "1 Ethiopian Restaurant Ethiopian 1463 Austin TX \n", "2 Ethiopian Restaurant Ethiopian 1047 Pflugerville TX \n", "3 African Restaurant African 195 Austin TX \n", "4 African Restaurant African 500 Austin TX \n", "\n", " Location commentsCount usersCount ... Tapas Tea Room Tex-Mex \\\n", "0 Austin, TX 17 683 ... 0 0 0 \n", "1 Austin, TX 34 1018 ... 0 0 0 \n", "2 Austin, TX 31 672 ... 0 0 0 \n", "3 Austin, TX 12 140 ... 0 0 0 \n", "4 Austin, TX 11 435 ... 0 0 0 \n", "\n", " Thai Theme Restaurant Turkish Vegetarian / Vegan Vietnamese Wine Bar \\\n", "0 0 0 0 0 0 0 \n", "1 0 0 0 0 0 0 \n", "2 0 0 0 0 0 0 \n", "3 0 0 0 0 0 0 \n", "4 0 0 0 0 0 0 \n", "\n", " Yogurt \n", "0 0 \n", "1 0 \n", "2 0 \n", "3 0 \n", "4 0 \n", "\n", "[5 rows x 1090 columns]" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Creates dummy variables out of the restaurant category\n", "df = pd.concat([df, pd.get_dummies(df['shortCategory'])], axis=1)\n", "\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because we introduced an additional type of feature, we'll have to check it's weight in comparison to the TF-IDF features:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.709363Z", "start_time": "2017-07-02T17:30:45.629806Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Max: 0.889350911658 \n", " Mean: 0.005518928565159508 \n", " Standard Deviation: 0.010229821082730768\n" ] } ], "source": [ "# Summary stats of TF-IDF\n", "print('Max:', np.max(tfidf_df.max()), '\\n',\n", " 'Mean:', np.mean(tfidf_df.mean()), '\\n',\n", " 'Standard Deviation:', np.std(tfidf_df.std()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dummy variables for the restaurant type are quite a bit higher than the average word, but I'm comfortable with this since I think it has a benefit." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# \"Recommender System\"\n", "\n", "As a reminder, we are not using a conventional recommender system. Instead, we are using recommender system theory by calculating the cosine distance between comments in order to find restaurants with the most similar comments. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Loading in personal ratings\n", "\n", "In order to recommend restaurants with this approach, we have to identify the restaurants to which we want to find the most similarities. I took the data frame and assigned my own ratings to some of my favorites." ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.769906Z", "start_time": "2017-07-02T17:30:45.716869Z" } }, "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", "
idselfRating
043c968a2f964a5209c2d1fe310.0
1574481f8498e2cd16a0911a6NaN
24cb5e045e262b60c46cb6ae09.0
349be75ccf964a520ad541fe39.0
44d8d295fc1b1721e798b1246NaN
\n", "
" ], "text/plain": [ " id selfRating\n", "0 43c968a2f964a5209c2d1fe3 10.0\n", "1 574481f8498e2cd16a0911a6 NaN\n", "2 4cb5e045e262b60c46cb6ae0 9.0\n", "3 49be75ccf964a520ad541fe3 9.0\n", "4 4d8d295fc1b1721e798b1246 NaN" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Loading in self-ratings for restaurants in the data set\n", "selfRatings = pd.read_csv('selfRatings.csv', usecols=[0, 4])\n", "selfRatings.head()" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:45.873479Z", "start_time": "2017-07-02T17:30:45.775911Z" } }, "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", " \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", " \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", " \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", "
idNamecategoryshortCategorycheckinsCountCitystateLocationcommentsCountusersCount...Tea RoomTex-MexThaiTheme RestaurantTurkishVegetarian / VeganVietnameseWine BarYogurtselfRating
04e17b348b0fb8567c665ddafSouper SaladSalad PlaceSalad1769AustinTXAustin, TX17683...000000000NaN
14aceefb7f964a52013d220e3Aster's Ethiopian RestaurantEthiopian RestaurantEthiopian1463AustinTXAustin, TX341018...000000000NaN
24b591015f964a520c17a28e3Taste Of EthiopiaEthiopian RestaurantEthiopian1047PflugervilleTXAustin, TX31672...000000000NaN
34ead97ba4690615f26a8adfeWasota African CuisineAfrican RestaurantAfrican195AustinTXAustin, TX12140...000000000NaN
44c7efeba2042b1f76cd1c1adCazamanceAfrican RestaurantAfrican500AustinTXAustin, TX11435...000000000NaN
\n", "

5 rows × 1091 columns

\n", "
" ], "text/plain": [ " id Name \\\n", "0 4e17b348b0fb8567c665ddaf Souper Salad \n", "1 4aceefb7f964a52013d220e3 Aster's Ethiopian Restaurant \n", "2 4b591015f964a520c17a28e3 Taste Of Ethiopia \n", "3 4ead97ba4690615f26a8adfe Wasota African Cuisine \n", "4 4c7efeba2042b1f76cd1c1ad Cazamance \n", "\n", " category shortCategory checkinsCount City state \\\n", "0 Salad Place Salad 1769 Austin TX \n", "1 Ethiopian Restaurant Ethiopian 1463 Austin TX \n", "2 Ethiopian Restaurant Ethiopian 1047 Pflugerville TX \n", "3 African Restaurant African 195 Austin TX \n", "4 African Restaurant African 500 Austin TX \n", "\n", " Location commentsCount usersCount ... Tea Room Tex-Mex Thai \\\n", "0 Austin, TX 17 683 ... 0 0 0 \n", "1 Austin, TX 34 1018 ... 0 0 0 \n", "2 Austin, TX 31 672 ... 0 0 0 \n", "3 Austin, TX 12 140 ... 0 0 0 \n", "4 Austin, TX 11 435 ... 0 0 0 \n", "\n", " Theme Restaurant Turkish Vegetarian / Vegan Vietnamese Wine Bar Yogurt \\\n", "0 0 0 0 0 0 0 \n", "1 0 0 0 0 0 0 \n", "2 0 0 0 0 0 0 \n", "3 0 0 0 0 0 0 \n", "4 0 0 0 0 0 0 \n", "\n", " selfRating \n", "0 NaN \n", "1 NaN \n", "2 NaN \n", "3 NaN \n", "4 NaN \n", "\n", "[5 rows x 1091 columns]" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Merging into df to add the column 'selfRating'\n", "df = pd.merge(df, selfRatings)\n", "\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Additional features & min-max scaling \n", "\n", "We're going to include a few additional features from the original data set to capture information that the comments may not have. Specifically:\n", "\n", "- **Popularity:** checkinsCount, commentsCount, usersCount, numRatings\n", "- **Price:** priceTier\n", "\n", "We're also going to scale these down so they don't carry a huge advantage over everything else. I'm going to scale the popularity attributes to be between 0 and 0.5, and the price attribute to be between 0 and 1. I'll do this by first min-max scaling everything (to put it between 0 and 1), and then dividing the popularity features in half." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.042100Z", "start_time": "2017-07-02T17:30:45.881485Z" } }, "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", " \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", " \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", " \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", "
absoluteabsolutelyacrossactuallyaddafternoonaheadahialale...TurkishVegetarian / VeganVietnameseWine BarYogurtcheckinsCountcommentsCountusersCountpriceTiernumRatings
00.00.0000000.00.0000000.00.00.00.00.00.0...000000.0306820.0175440.0204290.0000000.017494
10.00.0000000.00.0347780.00.00.00.00.00.0...000000.0248440.0601500.0316480.3333330.046840
20.00.0312680.00.0000000.00.00.00.00.00.0...000000.0169060.0526320.0200600.3333330.044018
30.00.0000000.00.0000000.00.00.00.00.00.0...000000.0006490.0050130.0022440.3333330.000000
40.00.0000000.00.0000000.00.00.00.00.00.0...000000.0064680.0025060.0121230.0000000.002822
\n", "

5 rows × 1080 columns

\n", "
" ], "text/plain": [ " absolute absolutely across actually add afternoon ahead ahi al \\\n", "0 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.000000 0.0 0.034778 0.0 0.0 0.0 0.0 0.0 \n", "2 0.0 0.031268 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "3 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "4 0.0 0.000000 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 \n", "\n", " ale ... Turkish Vegetarian / Vegan Vietnamese Wine Bar Yogurt \\\n", "0 0.0 ... 0 0 0 0 0 \n", "1 0.0 ... 0 0 0 0 0 \n", "2 0.0 ... 0 0 0 0 0 \n", "3 0.0 ... 0 0 0 0 0 \n", "4 0.0 ... 0 0 0 0 0 \n", "\n", " checkinsCount commentsCount usersCount priceTier numRatings \n", "0 0.030682 0.017544 0.020429 0.000000 0.017494 \n", "1 0.024844 0.060150 0.031648 0.333333 0.046840 \n", "2 0.016906 0.052632 0.020060 0.333333 0.044018 \n", "3 0.000649 0.005013 0.002244 0.333333 0.000000 \n", "4 0.006468 0.002506 0.012123 0.000000 0.002822 \n", "\n", "[5 rows x 1080 columns]" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Removing everything that won't be used in the similarity calculation\n", "df_item = df.drop(['id', 'category', 'Name', 'shortCategory', 'City', 'tokens',\n", " 'comments', 'state', 'Location', 'selfRating', 'rating'],\n", " axis=1)\n", "\n", "# Copying into a separate data frame to be normalized\n", "df_item_norm = df_item.copy()\n", "\n", "columns_to_scale = ['checkinsCount', 'commentsCount',\n", " 'usersCount', 'priceTier', 'numRatings']\n", "\n", "# Split\n", "df_item_split = df_item[columns_to_scale]\n", "df_item_norm.drop(columns_to_scale, axis=1, inplace=True)\n", "\n", "# Apply\n", "df_item_split = pd.DataFrame(MinMaxScaler().fit_transform(df_item_split),\n", " columns=df_item_split.columns)\n", "df_item_split_half = df_item_split.drop('priceTier', axis=1)\n", "df_item_split_half = df_item_split_half / 2\n", "df_item_split_half['priceTier'] = df_item_split['priceTier']\n", "\n", "# Combine\n", "df_item_norm = df_item_norm.merge(df_item_split,\n", " left_index=True, right_index=True)\n", "\n", "df_item_norm.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculating cosine similarities\n", "\n", "Here's the moment that we've spent all of this time getting to: the similarity. \n", "\n", "This section calculates the cosine similarity and puts it into a matrix with the pairwise similarity:\n", "\n", "| | 0 | 1 | ... | n |\n", "|------|------|------|------|------|\n", "| 0 | 1.00 | 0.03 | ... | 0.15 |\n", "| 1 | 0.31 | 1.00 | ... | 0.89 |\n", "| ... | ... | ... | ... | ... |\n", "| n | 0.05 | 0.13 | ... | 1.00 |\n", "\n", "As a reminder, we're using cosine similarity because it's generally accepted as producing better results in item-to-item filtering. For all you math folk, here's the formula again:\n", "\n", "$\\hspace{8cm}sim(A, B) = \\cos(\\theta) = \\frac{A \\cdot B}{\\|A\\|\\|B\\|}$" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.466401Z", "start_time": "2017-07-02T17:30:46.048604Z" } }, "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", " \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", " \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", " \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", "
0123456789...1113111411151116111711181119112011211122
01.0000000.0562520.0672310.0262300.0249960.0591790.0515030.0679220.0798870.073811...0.0196770.0517690.0669440.0475000.0554410.0596090.0320870.0456750.0640430.047003
10.0562521.0000000.8816400.1129500.0367020.1645340.1956390.1440180.1583200.150945...0.0830640.1259480.1444520.0892930.1282210.0786540.1134360.1299430.1466700.128873
20.0672310.8816401.0000000.1182540.0451150.1390960.1794900.1322800.1532440.145920...0.0947440.1200790.1453570.0833240.1267030.0821190.1021280.1225370.1519890.132252
30.0262300.1129500.1182541.0000000.7672800.0780280.1514360.0907130.0867040.102522...0.0941470.0713280.1031300.0800020.1001530.0497300.0939610.1110840.1094740.096562
40.0249960.0367020.0451150.7672801.0000000.0326170.0308230.0309060.0417410.031845...0.0061930.0262840.0383490.0422550.0466790.0389770.0119200.0276610.0447960.021876
\n", "

5 rows × 1123 columns

\n", "
" ], "text/plain": [ " 0 1 2 3 4 5 6 \\\n", "0 1.000000 0.056252 0.067231 0.026230 0.024996 0.059179 0.051503 \n", "1 0.056252 1.000000 0.881640 0.112950 0.036702 0.164534 0.195639 \n", "2 0.067231 0.881640 1.000000 0.118254 0.045115 0.139096 0.179490 \n", "3 0.026230 0.112950 0.118254 1.000000 0.767280 0.078028 0.151436 \n", "4 0.024996 0.036702 0.045115 0.767280 1.000000 0.032617 0.030823 \n", "\n", " 7 8 9 ... 1113 1114 1115 \\\n", "0 0.067922 0.079887 0.073811 ... 0.019677 0.051769 0.066944 \n", "1 0.144018 0.158320 0.150945 ... 0.083064 0.125948 0.144452 \n", "2 0.132280 0.153244 0.145920 ... 0.094744 0.120079 0.145357 \n", "3 0.090713 0.086704 0.102522 ... 0.094147 0.071328 0.103130 \n", "4 0.030906 0.041741 0.031845 ... 0.006193 0.026284 0.038349 \n", "\n", " 1116 1117 1118 1119 1120 1121 1122 \n", "0 0.047500 0.055441 0.059609 0.032087 0.045675 0.064043 0.047003 \n", "1 0.089293 0.128221 0.078654 0.113436 0.129943 0.146670 0.128873 \n", "2 0.083324 0.126703 0.082119 0.102128 0.122537 0.151989 0.132252 \n", "3 0.080002 0.100153 0.049730 0.093961 0.111084 0.109474 0.096562 \n", "4 0.042255 0.046679 0.038977 0.011920 0.027661 0.044796 0.021876 \n", "\n", "[5 rows x 1123 columns]" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Calculating cosine similarity\n", "df_item_norm_sparse = sparse.csr_matrix(df_item_norm)\n", "similarities = cosine_similarity(df_item_norm_sparse)\n", "\n", "# Putting into a data frame\n", "dfCos = pd.DataFrame(similarities)\n", "\n", "dfCos.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are the some of the restaurants I rated very highly, and I'm pulling these up so we can use the index number in order to compare it to the others in our data set:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.536451Z", "start_time": "2017-07-02T17:30:46.474907Z" } }, "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", " \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", " \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", " \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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NamecategoryLocationselfRating
14Jack Allen's KitchenAmerican RestaurantAustin, TX10.0
88Black's BarbecueBBQ JointAustin, TX10.0
447Cabo Bob'sBurrito PlaceAustin, TX10.0
420TacodeliTaco PlaceAustin, TX10.0
228Round Rock DonutsDonut ShopAustin, TX10.0
27Jack Allens on Capital of TXAmerican RestaurantAustin, TX10.0
124Blue Dahlia BistroCaféAustin, TX10.0
967ChimborazoLatin American RestaurantMinneapolis, MN10.0
83Black's BBQ, The OriginalBBQ JointAustin, TX10.0
66The Salt LickBBQ JointAustin, TX10.0
109Torchy's TacosTaco PlaceAustin, TX9.0
338Clay Pit Contemporary Indian CuisineIndian RestaurantAustin, TX9.0
637Brasa Premium RotisserieBBQ JointMinneapolis, MN9.0
576Afro DeliAfrican RestaurantMinneapolis, MN9.0
441Trudy's Texas StarMexican RestaurantAustin, TX9.0
426Trudy's North StarMexican RestaurantAustin, TX9.0
421Chuy'sMexican RestaurantAustin, TX9.0
360Mandola's Italian MarketItalian RestaurantAustin, TX9.0
72La Barbecue Cuisine TexicanaBBQ JointAustin, TX9.0
74Franklin BarbecueBBQ JointAustin, TX9.0
154Mighty Fine BurgersBurger JointAustin, TX9.0
151Hopdoddy Burger BarBurger JointAustin, TX9.0
171P. Terry's Burger StandBurger JointAustin, TX9.0
116Juan in a MillionMexican RestaurantAustin, TX8.0
216Mozart's CoffeeCoffee ShopAustin, TX8.0
50UchiJapanese RestaurantAustin, TX8.0
558The Coffee Bean & Tea LeafCoffee ShopAustin, TX8.0
120Texas Honey Ham CompanySandwich PlaceAustin, TX8.0
832Kramarczuk's East European DeliEastern European RestaurantMinneapolis, MN8.0
\n", "
" ], "text/plain": [ " Name category \\\n", "14 Jack Allen's Kitchen American Restaurant \n", "88 Black's Barbecue BBQ Joint \n", "447 Cabo Bob's Burrito Place \n", "420 Tacodeli Taco Place \n", "228 Round Rock Donuts Donut Shop \n", "27 Jack Allens on Capital of TX American Restaurant \n", "124 Blue Dahlia Bistro Café \n", "967 Chimborazo Latin American Restaurant \n", "83 Black's BBQ, The Original BBQ Joint \n", "66 The Salt Lick BBQ Joint \n", "109 Torchy's Tacos Taco Place \n", "338 Clay Pit Contemporary Indian Cuisine Indian Restaurant \n", "637 Brasa Premium Rotisserie BBQ Joint \n", "576 Afro Deli African Restaurant \n", "441 Trudy's Texas Star Mexican Restaurant \n", "426 Trudy's North Star Mexican Restaurant \n", "421 Chuy's Mexican Restaurant \n", "360 Mandola's Italian Market Italian Restaurant \n", "72 La Barbecue Cuisine Texicana BBQ Joint \n", "74 Franklin Barbecue BBQ Joint \n", "154 Mighty Fine Burgers Burger Joint \n", "151 Hopdoddy Burger Bar Burger Joint \n", "171 P. Terry's Burger Stand Burger Joint \n", "116 Juan in a Million Mexican Restaurant \n", "216 Mozart's Coffee Coffee Shop \n", "50 Uchi Japanese Restaurant \n", "558 The Coffee Bean & Tea Leaf Coffee Shop \n", "120 Texas Honey Ham Company Sandwich Place \n", "832 Kramarczuk's East European Deli Eastern European Restaurant \n", "\n", " Location selfRating \n", "14 Austin, TX 10.0 \n", "88 Austin, TX 10.0 \n", "447 Austin, TX 10.0 \n", "420 Austin, TX 10.0 \n", "228 Austin, TX 10.0 \n", "27 Austin, TX 10.0 \n", "124 Austin, TX 10.0 \n", "967 Minneapolis, MN 10.0 \n", "83 Austin, TX 10.0 \n", "66 Austin, TX 10.0 \n", "109 Austin, TX 9.0 \n", "338 Austin, TX 9.0 \n", "637 Minneapolis, MN 9.0 \n", "576 Minneapolis, MN 9.0 \n", "441 Austin, TX 9.0 \n", "426 Austin, TX 9.0 \n", "421 Austin, TX 9.0 \n", "360 Austin, TX 9.0 \n", "72 Austin, TX 9.0 \n", "74 Austin, TX 9.0 \n", "154 Austin, TX 9.0 \n", "151 Austin, TX 9.0 \n", "171 Austin, TX 9.0 \n", "116 Austin, TX 8.0 \n", "216 Austin, TX 8.0 \n", "50 Austin, TX 8.0 \n", "558 Austin, TX 8.0 \n", "120 Austin, TX 8.0 \n", "832 Minneapolis, MN 8.0 " ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Filtering to those from my list with the highest ratings\n", "topRated = df[df['selfRating'] >= 8].drop_duplicates('Name')\n", "\n", "# Preparing for display\n", "topRated[['Name', 'category', 'Location', 'selfRating']].sort_values(\n", " 'selfRating', ascending=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to speed things up, we'll make a function that formats the cosine similarity data frame and retrieves the top $n$ most similar restaurants for the given restaurant:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.596994Z", "start_time": "2017-07-02T17:30:46.542454Z" }, "collapsed": true }, "outputs": [], "source": [ "def retrieve_recommendations(restaurant_index, num_recommendations=5):\n", " \"\"\"\n", " Retrieves the most similar restaurants for the index of a given restaurant \n", " \n", " Outputs a data frame showing similarity, name, location, category, and rating\n", " \"\"\"\n", " # Formatting the cosine similarity data frame for merging\n", " similarity = pd.melt(dfCos[dfCos.index == restaurant_index])\n", " similarity.columns = (['restIndex', 'cosineSimilarity'])\n", "\n", " # Merging the cosine similarity data frame to the original data frame\n", " similarity = similarity.merge(\n", " df[['Name', 'City', 'state', 'Location',\n", " 'category', 'rating', 'selfRating']],\n", " left_on=similarity['restIndex'],\n", " right_index=True)\n", " similarity.drop(['restIndex'], axis=1, inplace=True)\n", "\n", " # Ensuring that retrieved recommendations are for Minneapolis\n", " similarity = similarity[(similarity['Location'] == 'Minneapolis, MN') | (\n", " similarity.index == restaurant_index)]\n", "\n", " # Sorting by similarity\n", " similarity = similarity.sort_values(\n", " 'cosineSimilarity', ascending=False)[:num_recommendations + 1]\n", " \n", " return similarity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alright, let's test it out!\n", "\n", "### Barbecue\n", "\n", "Let's start with the [Salt Lick](http://saltlickbbq.com/). This is a popular central Texas barbecue place featured on [various food shows](https://www.youtube.com/watch?v=vLnsXechOWc). They are well-known for their open smoke pit:\n", "\n", "\n", "\n", "In case you're not familiar with [central Texas barbecue](https://en.wikipedia.org/wiki/Barbecue_in_Texas), it primarily features smoked meats (especially brisket) with white bread, onions, pickles, potato salad, beans and cornbread on the side. Sweet tea is usually the drink of choice if you're not drinking a Shiner or a Lonestar." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.682054Z", "start_time": "2017-07-02T17:30:46.601496Z" } }, "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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
661.000000The Salt LickDriftwoodTXAustin, TXBBQ Joint9.510.0
6370.696079Brasa Premium RotisserieMinneapolisMNMinneapolis, MNBBQ Joint9.39.0
6460.604501Famous Dave'sMinneapolisMNMinneapolis, MNBBQ Joint7.4NaN
11140.590742Psycho Suzi's Motor Lounge & Tiki GardenMinneapolisMNMinneapolis, MNTheme Restaurant8.52.0
6540.572947Rack Shack BBQBurnsvilleMNMinneapolis, MNBBQ Joint8.0NaN
8380.567213Brit's Pub & Eating EstablishmentMinneapolisMNMinneapolis, MNEnglish Restaurant8.8NaN
\n", "
" ], "text/plain": [ " cosineSimilarity Name City \\\n", "66 1.000000 The Salt Lick Driftwood \n", "637 0.696079 Brasa Premium Rotisserie Minneapolis \n", "646 0.604501 Famous Dave's Minneapolis \n", "1114 0.590742 Psycho Suzi's Motor Lounge & Tiki Garden Minneapolis \n", "654 0.572947 Rack Shack BBQ Burnsville \n", "838 0.567213 Brit's Pub & Eating Establishment Minneapolis \n", "\n", " state Location category rating selfRating \n", "66 TX Austin, TX BBQ Joint 9.5 10.0 \n", "637 MN Minneapolis, MN BBQ Joint 9.3 9.0 \n", "646 MN Minneapolis, MN BBQ Joint 7.4 NaN \n", "1114 MN Minneapolis, MN Theme Restaurant 8.5 2.0 \n", "654 MN Minneapolis, MN BBQ Joint 8.0 NaN \n", "838 MN Minneapolis, MN English Restaurant 8.8 NaN " ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Salt Lick\n", "retrieve_recommendations(66)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Surprisingly, our top recommendation is one of my favorite restaurants I've found in Minneapolis - Brasa! They're actually a [Creole](https://en.wikipedia.org/wiki/Louisiana_Creole_cuisine) restaurant, but they have a lot of smoked meats, beans, and corn bread, and they're probably the only restaurant I've found so far that lists sweet tea on the menu:\n", "\n", "\n", "\n", "Funny enough, Brasa was also in [Man vs Food](https://www.youtube.com/watch?v=gZmGAi5DKE4) with Andrew Zimmerman as a guest.\n", "\n", "Famous Dave's is a Midwestern barbecue chain that focuses more on ribs, which isn't generally considered a Texan specialty. Psycho Suzi's (a theme restaurant that serves pizza and cocktails) and Brit's Pub (an English pub with a lawn bowling field) don't seem very similar, but their cosine similarity scores reflect that.\n", "\n", "### Donuts\n", "\n", "Moving on, let's find some donuts. Before maple-bacon-cereal-whatever donuts become the craze (thanks for nothing, Portland), my home town was also famous for [Round Rock Donuts](http://roundrockdonuts.com/), a simple and delicious no-nonsense donut shop. And yes, Man vs. Food also did a segment here.\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.746601Z", "start_time": "2017-07-02T17:30:46.689059Z" } }, "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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
2221.000000Summer Moon Wood-Fired CoffeeAustinTXAustin, TXCoffee Shop8.9NaN
7490.865335Spyhouse CoffeeMinneapolisMNMinneapolis, MNCoffee Shop8.1NaN
7420.857070Five Watt CoffeeMinneapolisMNMinneapolis, MNCoffee Shop8.9NaN
7450.848061Spyhouse CoffeeMinneapolisMNMinneapolis, MNCoffee Shop8.3NaN
7470.843742Dunn Bros CoffeeMinneapolisMNMinneapolis, MNCoffee Shop7.9NaN
7400.834707Spyhouse CoffeeMinneapolisMNMinneapolis, MNCoffee Shop9.16.0
\n", "
" ], "text/plain": [ " cosineSimilarity Name City state \\\n", "222 1.000000 Summer Moon Wood-Fired Coffee Austin TX \n", "749 0.865335 Spyhouse Coffee Minneapolis MN \n", "742 0.857070 Five Watt Coffee Minneapolis MN \n", "745 0.848061 Spyhouse Coffee Minneapolis MN \n", "747 0.843742 Dunn Bros Coffee Minneapolis MN \n", "740 0.834707 Spyhouse Coffee Minneapolis MN \n", "\n", " Location category rating selfRating \n", "222 Austin, TX Coffee Shop 8.9 NaN \n", "749 Minneapolis, MN Coffee Shop 8.1 NaN \n", "742 Minneapolis, MN Coffee Shop 8.9 NaN \n", "745 Minneapolis, MN Coffee Shop 8.3 NaN \n", "747 Minneapolis, MN Coffee Shop 7.9 NaN \n", "740 Minneapolis, MN Coffee Shop 9.1 6.0 " ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Round Rock Donuts\n", "retrieve_recommendations(222)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sadly, a lot of the most similar places our results returned were places I've tried and didn't like. For some reason, the donuts at most places up here are usually cold, cake-based, and covered in kitschy stuff like bacon. However, Granny donuts looks like it could be promising, as does Bogart's:\n", "\n", "\n", "\n", "### Tacos\n", "\n", "This is another Austin specialty that likely won't give promising results, but let's try it anyway.\n", "\n", "[Tacodeli](http://www.tacodeli.com/) is my personal favorite taco place in Austin (yes, it's better than [Torchy's](http://torchystacos.com/)), and they're a little different than the traditional taco (corn tortilla, meat, onions, cilantro, and lime that you might find at traditional Mexican taquerias). They're typically on flour tortillas, and diversify their flavor profiles and toppings:\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.800639Z", "start_time": "2017-07-02T17:30:46.752103Z" } }, "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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
4201.000000TacodeliAustinTXAustin, TXTaco Place9.210.0
9860.869268Rusty TacoMinneapolisMNMinneapolis, MNTaco Place8.14.0
8490.826003Taco TaxiMinneapolisMNMinneapolis, MNTaco Place8.4NaN
11140.270148Psycho Suzi's Motor Lounge & Tiki GardenMinneapolisMNMinneapolis, MNTheme Restaurant8.52.0
5790.259052Hell's KitchenMinneapolisMNMinneapolis, MNAmerican Restaurant8.94.0
8380.256581Brit's Pub & Eating EstablishmentMinneapolisMNMinneapolis, MNEnglish Restaurant8.8NaN
\n", "
" ], "text/plain": [ " cosineSimilarity Name City \\\n", "420 1.000000 Tacodeli Austin \n", "986 0.869268 Rusty Taco Minneapolis \n", "849 0.826003 Taco Taxi Minneapolis \n", "1114 0.270148 Psycho Suzi's Motor Lounge & Tiki Garden Minneapolis \n", "579 0.259052 Hell's Kitchen Minneapolis \n", "838 0.256581 Brit's Pub & Eating Establishment Minneapolis \n", "\n", " state Location category rating selfRating \n", "420 TX Austin, TX Taco Place 9.2 10.0 \n", "986 MN Minneapolis, MN Taco Place 8.1 4.0 \n", "849 MN Minneapolis, MN Taco Place 8.4 NaN \n", "1114 MN Minneapolis, MN Theme Restaurant 8.5 2.0 \n", "579 MN Minneapolis, MN American Restaurant 8.9 4.0 \n", "838 MN Minneapolis, MN English Restaurant 8.8 NaN " ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Tacodeli\n", "retrieve_recommendations(420)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It looks like there's a pretty sharp drop-off in cosine similarity after our second recommendation (which makes sense when you look at the ratio of taco places in Austin vs. Minneapolis from when we pulled our data), so I'm going to discard the bottom three. I'm surprised again that Psycho Suzi's and Brit's Pub made a second appearance since neither of them serve tacos, but I won't into that too much since their cosine similarity is really low.\n", "\n", "I have tried Rusty Taco, and it does seem a lot like Tacodeli. They even sell breakfast tacos, which is a very Texan thing that can be rare in the rest of the country. The primary difference is in the diversity and freshness of ingredients, and subsequently for me, taste:\n", "\n", "\n", "\n", "Taco Taxi looks like it could be promising, but they appear to be more of a traditional taqueria (delicious but dissimilar). To be fair, taquerias have had the best tacos I've found up here (though most of them aren't included in this list because they were outside of the search range).\n", "\n", "### Burritos\n", "\n", "I'm not actually going to run our similarity function for this part because the burrito place back home actually disappeared from our data pulling query in between me originally running this and finally having time to annotate everything and write this post. However, I wanted to include it because it was one of my other field tests.\n", "\n", "[Cabo Bob's](http://cabobobs.com/) is my favorite burrito place back home, and they made it to the semi-finals in the [538 best burrito in America search](https://fivethirtyeight.com/burrito/#brackets-view) losing to the overall champion. To anyone not familiar with non-chain burrito restaurants, they are similar to Chipotle, but are usually higher quality.\n", "\n", "\n", "\n", "[El Burrito Mercado](http://elburritomercado.com/) returned as highly similar, so we tried it. It's tucked in the back of a mercado, and has both a sit-down section as well as a lunch line similar to Cabo Bob's. We decided to go for the sit-down section since we had come from the opposite side of the metropolitan area, so the experience was a little different. My burrito was more of a traditional Mexican burrito (as opposed to Tex-Mex), but it was still pretty darn good.\n", "\n", "\n", "\n", "### Indian\n", "\n", "Next up is the the [Clay Pit](https://www.claypit.com/), a contemporary Indian restaurant in Austin. They focus mostly on curry dishes with naan, though some of my friends from grad school can tell you that India has way more cuisine diversity than curry dishes.\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.856178Z", "start_time": "2017-07-02T17:30:46.806642Z" } }, "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", " \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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
3381.000000Clay Pit Contemporary Indian CuisineAustinTXAustin, TXIndian Restaurant8.99.0
9040.874870India PalaceSaint PaulMNMinneapolis, MNIndian Restaurant7.9NaN
9090.853726Darbar India GrillMinneapolisMNMinneapolis, MNIndian Restaurant7.7NaN
9050.848635Dancing GaneshaMinneapolisMNMinneapolis, MNIndian Restaurant7.3NaN
9160.847020Gandhi MahalMinneapolisMNMinneapolis, MNIndian Restaurant8.0NaN
9170.845153Best of India Indian RestaurantSaint Louis ParkMNMinneapolis, MNIndian Restaurant7.6NaN
9060.837536Gorkha PalaceMinneapolisMNMinneapolis, MNIndian Restaurant9.0NaN
9120.834911India HouseSaint PaulMNMinneapolis, MNIndian Restaurant7.9NaN
9100.821674Copper Pot Indian GrillMinneapolisMNMinneapolis, MNIndian Restaurant7.3NaN
\n", "
" ], "text/plain": [ " cosineSimilarity Name City \\\n", "338 1.000000 Clay Pit Contemporary Indian Cuisine Austin \n", "904 0.874870 India Palace Saint Paul \n", "909 0.853726 Darbar India Grill Minneapolis \n", "905 0.848635 Dancing Ganesha Minneapolis \n", "916 0.847020 Gandhi Mahal Minneapolis \n", "917 0.845153 Best of India Indian Restaurant Saint Louis Park \n", "906 0.837536 Gorkha Palace Minneapolis \n", "912 0.834911 India House Saint Paul \n", "910 0.821674 Copper Pot Indian Grill Minneapolis \n", "\n", " state Location category rating selfRating \n", "338 TX Austin, TX Indian Restaurant 8.9 9.0 \n", "904 MN Minneapolis, MN Indian Restaurant 7.9 NaN \n", "909 MN Minneapolis, MN Indian Restaurant 7.7 NaN \n", "905 MN Minneapolis, MN Indian Restaurant 7.3 NaN \n", "916 MN Minneapolis, MN Indian Restaurant 8.0 NaN \n", "917 MN Minneapolis, MN Indian Restaurant 7.6 NaN \n", "906 MN Minneapolis, MN Indian Restaurant 9.0 NaN \n", "912 MN Minneapolis, MN Indian Restaurant 7.9 NaN \n", "910 MN Minneapolis, MN Indian Restaurant 7.3 NaN " ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Clay Pit\n", "retrieve_recommendations(338, 8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This was actually the first place I did a field test on. When I originally looked this up, we ended up trying [Gorkha Palace](http://gorkhapalace.com/) since it was the closest one to our house with the best reviews. It has a more expanded offering including Nepali and Tibetan food (though I wasn't complaining because I love [momos](https://en.wikipedia.org/wiki/Momo_(food)). It was delicious, and was very similar to the Clay Pit. We'll be going back.\n", "\n", "\n", "\n", "### French/Bistro\n", "\n", "One of our favorite places back home is [Blue Dahlia Bistro](http://www.bluedahliabistro.com/), a European-style bistro specializing in French fusion. They use a lot of fresh and simple ingredients, and it's a great place for a date night due to its cozy interior and decorated back patio.\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:46.926729Z", "start_time": "2017-07-02T17:30:46.863183Z" } }, "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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
1241.000000Blue Dahlia BistroAustinTXAustin, TXCafé9.110.0
5840.827561Wilde Roast CafeMinneapolisMNMinneapolis, MNCafé8.6NaN
7620.785928Jensen's CafeBurnsvilleMNMinneapolis, MNCafé8.2NaN
7410.781564Cafe LatteSaint PaulMNMinneapolis, MNCafé9.3NaN
7590.777269Peoples OrganicEdinaMNMinneapolis, MNCafé8.1NaN
6860.772962Black Dog, LowertownSaint PaulMNMinneapolis, MNCafé8.6NaN
\n", "
" ], "text/plain": [ " cosineSimilarity Name City state \\\n", "124 1.000000 Blue Dahlia Bistro Austin TX \n", "584 0.827561 Wilde Roast Cafe Minneapolis MN \n", "762 0.785928 Jensen's Cafe Burnsville MN \n", "741 0.781564 Cafe Latte Saint Paul MN \n", "759 0.777269 Peoples Organic Edina MN \n", "686 0.772962 Black Dog, Lowertown Saint Paul MN \n", "\n", " Location category rating selfRating \n", "124 Austin, TX Café 9.1 10.0 \n", "584 Minneapolis, MN Café 8.6 NaN \n", "762 Minneapolis, MN Café 8.2 NaN \n", "741 Minneapolis, MN Café 9.3 NaN \n", "759 Minneapolis, MN Café 8.1 NaN \n", "686 Minneapolis, MN Café 8.6 NaN " ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Blue Dahlia\n", "retrieve_recommendations(124)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I think our heavier category weighting is hurting us here since Blue Dahlia is classified as a café. Most of the recommendations focus on American food (remember, American food is king in Minneapolis), but I'm guessing the [Wilde Roast Cafe](http://wildecafe.com/) was listed as the most similar restaurant due to the similarly cozy interior and various espresso drinks they offer. I've been to the Wilde Roast before beginning this project, and I can tell you that the food is completely different.\n", "\n", "\n", "\n", "\n", "### Coffee\n", "\n", "Speaking of coffee, let's wrap this up with coffee recommendations. I still have a lot of places to find matches for, but since I did this project as a poor graduate student, most of them would be \"that looks promising, but I haven't tried it yet\". \n", "\n", "Sadly, a lot of my favorite coffee shops from in and around Austin didn't show up since Starbucks took up most of the space when searching for coffee places (remember, we're limited to 50 results per category). We ended up with [Mozart's](http://www.mozartscoffee.com/):\n", "\n", "\n", "\n", "and the [Coffee Bean & Tea Leaf](https://www.coffeebean.com/):\n", "\n", "\n", "\n", "I ran the results for Mozart's and didn't get anything too similar back. To be fair, there aren't any coffee shops on a river up here, and I'm sure most comments for Mozart's are about the view. \n", "\n", "Let's go with The Coffee Bean & Tea Leaf instead. It's actually a small chain out of California that is, in my opinion, tastier than Starbucks." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "ExecuteTime": { "end_time": "2017-07-02T17:30:47.037307Z", "start_time": "2017-07-02T17:30:46.935735Z" } }, "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", " \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", "
cosineSimilarityNameCitystateLocationcategoryratingselfRating
5581.000000The Coffee Bean & Tea LeafAustinTXAustin, TXCoffee Shop8.18.0
7420.811790Five Watt CoffeeMinneapolisMNMinneapolis, MNCoffee Shop8.9NaN
7470.805479Dunn Bros CoffeeMinneapolisMNMinneapolis, MNCoffee Shop7.9NaN
7570.797197Caribou CoffeeSavageMNMinneapolis, MNCoffee Shop7.7NaN
7600.791427Dunn Bros CoffeeMinneapolisMNMinneapolis, MNCoffee Shop7.1NaN
7780.790609StarbucksBloomingtonMNMinneapolis, MNCoffee Shop8.0NaN
\n", "
" ], "text/plain": [ " cosineSimilarity Name City state \\\n", "558 1.000000 The Coffee Bean & Tea Leaf Austin TX \n", "742 0.811790 Five Watt Coffee Minneapolis MN \n", "747 0.805479 Dunn Bros Coffee Minneapolis MN \n", "757 0.797197 Caribou Coffee Savage MN \n", "760 0.791427 Dunn Bros Coffee Minneapolis MN \n", "778 0.790609 Starbucks Bloomington MN \n", "\n", " Location category rating selfRating \n", "558 Austin, TX Coffee Shop 8.1 8.0 \n", "742 Minneapolis, MN Coffee Shop 8.9 NaN \n", "747 Minneapolis, MN Coffee Shop 7.9 NaN \n", "757 Minneapolis, MN Coffee Shop 7.7 NaN \n", "760 Minneapolis, MN Coffee Shop 7.1 NaN \n", "778 Minneapolis, MN Coffee Shop 8.0 NaN " ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Coffee Bean & Tea Leaf\n", "retrieve_recommendations(558)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are perfectly logical results. [Caribou](https://www.cariboucoffee.com/) is a chain popular in the Midwest (I also describe it as 'like Starbucks but better' to friends back home), and [Dunn Bros](https://dunnbrothers.com/) is similar, but specific to Minnesota.\n", "\n", "This chart from [an article on FlowingData](http://flowingdata.com/2014/03/18/coffee-place-geography/) helps describe why I think these results make so much sense:\n", "\n", "\n", "\n", "As for my verdict on Caribou, I actually like it better than the Coffee Bean and Tea Leaf. In fact, I actually have a gift card for them in my wallet right now. There also used to be a location for the Coffee Bean and Tea Leaf up here, but they closed it down shortly after I moved here (just like all of the Minnesotan [Schlotzsky's](https://www.schlotzskys.com/) locations...I'm still mad about that)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Summary\n", "\n", "Like any other tool, this method isn't perfect for every application, but it can work if we use it effectively. While there is room for improvement, I am pleased with how it has been working for me so far.\n", "\n", "I'm going to continue to use this when we can't decide on a restaurant and feeling homesick, and will likely update this post in the future with either more places I try out or when I move to a new city in the future. In the meantime, I hope you enjoyed reading, and feel free to use my code ([here is the github link](https://github.com/JeffMacaluso/Blog/blob/master/Restaurant%20Recommender.ipynb)) to try it out for your own purposes.\n", "\n", "Happy eating!" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.3" }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }