diff --git a/your-code/building-features.jpg b/your-code/building-features.jpg new file mode 100644 index 0000000..0dd24d2 Binary files /dev/null and b/your-code/building-features.jpg differ diff --git a/your-code/challenge-1.ipynb b/your-code/challenge-1.ipynb new file mode 100644 index 0000000..e292086 --- /dev/null +++ b/your-code/challenge-1.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Challenge 1: Prepare Textual Data for Analysis\n", + "\n", + "In this challenge, we will walk you through how to prepare raw text data for NLP analysis. Due to time limitation, we will cover **text cleaning, tokenization, stemming, lemmatization, and stop words removal** but skip POS tags, named entity recognition, and trunking. The latter 3 steps are more advanced and not required for our next challenge on sentiment analysis. \n", + "\n", + "## Objectives\n", + "\n", + "* Learn how to prepare text data for NLP analysis in Python\n", + "* Write the functions you will use in Challenge 3 for cleaning, tokenizing, stemming, and lemmatizing data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Text Cleaning\n", + "\n", + "Text cleaning is also called text cleansing. The goal is to clean up the messy real-world textual data in order to improve the text analysis accuracy at later steps. For generic textual data sources, we usually need to fix the following problems:\n", + "\n", + "* Missing values\n", + "* Special characters\n", + "* Numbers\n", + "\n", + "For web data, we need to additinally fix:\n", + "\n", + "* HTML tags\n", + "* JavaScripts\n", + "* CSS\n", + "* URLs\n", + "\n", + "Case by case, there may also be special problems we need to fix for certain types of data. For instance, for Twitter tweets data we need to fix hashtags and the Twitter handler including a *@* sign and Twitter usernames.\n", + "\n", + "In addition, we also need to convert the texts to lower cases so that when we anaylize the words later, NLTK will not think *Ironhack* and *ironhack* mean different things.\n", + "\n", + "Note that the above are the general steps to clean up data for NLP analysis. In specific cases, not all those steps apply. For example, if you are analyzing textual data on history, you probably don't want to remove numbers because numbers (such as years and dates) are important in history. Besides, if you are doing something like network analysis on web data, you may want to retain hyperlinks so that you will be able to extract the outbounding links in the next steps. Sometimes you may also need to do some cleaning first, then extract some features, then do more cleaning, then extract more features. You'll have to make these judgments by yourself case by case. \n", + "\n", + "In this challenge we are keeping things relatively simple so **you only need to clean up special characters, numbers, and URLs**. Let's say you have the following messy string to clean up:\n", + "\n", + "```\n", + "@Ironhack's-#Q website 776-is http://ironhack.com [(2018)]\")\n", + "```\n", + "\n", + "You will write a function, which will be part of you NLP analysis pipeline in the next challenge, to clean up strings like above and output:\n", + "\n", + "```\n", + "ironhack s q website is\n", + "```\n", + "\n", + "**In the cell below, write a function called `clean_up`**. Test your function with the above string and make sure you receive the expected output.\n", + "\n", + "*Notes:*\n", + "\n", + "* Use regular expressions to identify URL patterns and remove URLs.\n", + "\n", + "* You don't want to replace special characters/numbers with an empty string. Because that will join words that shouldn't be joined. For instance, if you replace the `'` in `you're`, you will get `youre` which is undesirable. So instead, replace special characters and numbers with a whitespace.\n", + "\n", + "* The order matters in terms of what to clean before others. For example, if you clean special characters before URLs, it will be difficult to identify the URLs patterns.\n", + "\n", + "* Don't worry about single letters and multiple whitespaces in your returned string. In our next steps those issues will be fixed." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "import nltk" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ironhack s q website is'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def clean_up(s):\n", + " \"\"\"\n", + " Cleans up numbers, URLs, and special characters from a string.\n", + "\n", + " Args:\n", + " s: The string to be cleaned up.\n", + "\n", + " Returns:\n", + " A string that has been cleaned up.\n", + " \"\"\"\n", + " string = re.sub(r'http\\S+', '', s)\n", + " return re.sub('[^A-Za-z]+', ' ', string).lower().strip()\n", + " \n", + "test = \"@Ironhack's-#Q website 776-is http://ironhack.com [(2018)]\"\n", + "\n", + "test_string = clean_up(test)\n", + "test_string" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tokenization\n", + "\n", + "We have actually discussed the concept of tokenization in the Bag of Words lab before. In that lab, we did both tokenization and calculated the [matrix of document-term frequency](https://en.wikipedia.org/wiki/Document-term_matrix). In this lab, we only need tokenization.\n", + "\n", + "In the cell below, write a function called **`tokenize`** to convert a string to a list of words. We'll use the string we received in the previous step *`ironhack s q website is`* to test your function. Your function shoud return:\n", + "\n", + "```python\n", + "['ironhack', 's', 'q', 'website', 'is']\n", + "```\n", + "\n", + "*Hint: use the `word_tokenize` function in NLTK.*" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ironhack', 's', 'q', 'website', 'is']" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def tokenize(s):\n", + " \"\"\"\n", + " Tokenize a string.\n", + "\n", + " Args:\n", + " s: String to be tokenized.\n", + "\n", + " Returns:\n", + " A list of words as the result of tokenization.\n", + " \"\"\"\n", + " return nltk.word_tokenize(s)\n", + "\n", + "test_string = tokenize(test_string)\n", + "test_string" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stemming and Lemmatization\n", + "\n", + "We will do stemming and lemmatization in the same step because otherwise we'll have to loop each token lists twice. You have learned in the previous challenge that stemming and lemmatization are similar but have different purposes for text normalization:\n", + "\n", + "**Stemming reduces words to their root forms (stems) even if the stem itself is not a valid word**. For instance, *token*, *tokenize*, and *tokenization* will be reduced to the same stem - *token*. And *change*, *changed*, *changing* will be reduced to *chang*.\n", + "\n", + "In NLTK, there are three stemming libraries: [*Porter*](https://www.nltk.org/_modules/nltk/stem/porter.html), [*Snowball*](https://www.nltk.org/_modules/nltk/stem/snowball.html), and [*Lancaster*](https://www.nltk.org/_modules/nltk/stem/lancaster.html). The difference among the three is the agressiveness with which they perform stemming. Porter is the most gentle stemmer that preserves the word's original form if it has doubts. In contrast, Lancaster is the most aggressive one that sometimes produces wrong outputs. And Snowball is in between. **In most cases you will use either Porter or Snowball**.\n", + "\n", + "**Lemmatization differs from stemming in that lemmatization cares about whether the reduced form belongs to the target language and it often requires the context (i.e. POS or parts-of-speech) in order to perform the correct transformation**. For example, the [*Word Net lemmatizer* in NLTK](https://www.nltk.org/_modules/nltk/stem/wordnet.html) yields different results with and without being told that *was* is a verb:\n", + "\n", + "```python\n", + ">>> from nltk.stem import WordNetLemmatizer\n", + ">>> lemmatizer = WordNetLemmatizer()\n", + ">>> lemmatizer.lemmatize('was')\n", + "'wa'\n", + ">>> lemmatizer.lemmatize('runs', pos='v')\n", + "'be'\n", + "```\n", + "\n", + "In the cell below, import the necessary libraries and define a function called `stem_and_lemmatize` that performs both stemming and lemmatization on a list of words. Don't worry about the POS part of lemmatization for now." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['ironhack', 's', 'q', 'websit', 'is']\n" + ] + } + ], + "source": [ + "def stem_and_lemmatize(l):\n", + " \n", + " \"\"\"\n", + " Perform stemming and lemmatization on a list of words.\n", + "\n", + " Args:\n", + " l: A list of strings.\n", + "\n", + " Returns:\n", + " A list of strings after being stemmed and lemmatized.\n", + " \"\"\"\n", + " ps = nltk.PorterStemmer()\n", + " lemmatizer = nltk.WordNetLemmatizer()\n", + " l2 = []\n", + " \n", + " for w in l:\n", + " s = ps.stem(w)\n", + " s = lemmatizer.lemmatize(s)\n", + " l2 += [s]\n", + " \n", + " return l2\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stop Words Removal\n", + "\n", + "Stop Words are the most commonly used words in a language that don't contribute to the main meaning of the texts. Examples of English stop words are `i`, `me`, `is`, `and`, `the`, `but`, and `here`. We want to remove stop words from analysis because otherwise stop words will take the overwhelming portion in our tokenized word list and the NLP algorithms will have problems in identifying the truely important words.\n", + "\n", + "NLTK has a `stopwords` package that allows us to import the most common stop words in over a dozen langauges including English, Spanish, French, German, Dutch, Portuguese, Italian, etc. These are the bare minimum stop words (100-150 words in each language) that can get beginners started. Some other NLP packages such as [*stop-words*](https://pypi.org/project/stop-words/) and [*wordcloud*](https://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html) provide bigger lists of stop words.\n", + "\n", + "Now in the cell below, create a function called `remove_stopwords` that loop through a list of words that have been stemmed and lemmatized to check and remove stop words. Return a new list where stop words have been removed." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ironhack q website\n" + ] + } + ], + "source": [ + "from nltk.corpus import stopwords\n", + "def remove_stopwords(l):\n", + " \"\"\"\n", + " Remove English stopwords from a list of strings.\n", + "\n", + " Args:\n", + " l: A list of strings.\n", + "\n", + " Returns:\n", + " A list of strings after stop words are removed.\n", + " \"\"\"\n", + " stop_words = stopwords.words('english')\n", + "\n", + " return ' '.join([w for w in l if w not in stop_words])\n", + "\n", + "print(remove_stopwords(test_string))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this challenge you have learned several text preparation techniques in more depths including text cleaning, tokenization, stemming, lemmatization, and stopwords removal. You have also written the functions you will be using in the next challenge to prepare texts for NLP analysis. Now we are ready to move on to the next challenge - Sentiment Analysis." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/your-code/challenge-2.ipynb b/your-code/challenge-2.ipynb new file mode 100644 index 0000000..6c11cb8 --- /dev/null +++ b/your-code/challenge-2.ipynb @@ -0,0 +1,1655 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Challenge 2: Sentiment Analysis\n", + "\n", + "In this challenge we will learn sentiment analysis and practice performing sentiment analysis on Twitter tweets. \n", + "\n", + "## Introduction\n", + "\n", + "Sentiment analysis is to *systematically identify, extract, quantify, and study affective states and subjective information* based on texts ([reference](https://en.wikipedia.org/wiki/Sentiment_analysis)). In simple words, it's to understand whether a person is happy or unhappy in producing the piece of text. Why we (or rather, companies) care about sentiment in texts? It's because by understanding the sentiments in texts, we will be able to know if our customers are happy or unhappy about our products and services. If they are unhappy, the subsequent action is to figure out what have caused the unhappiness and make improvements.\n", + "\n", + "Basic sentiment analysis only understands the *positive* or *negative* (sometimes *neutral* too) polarities of the sentiment. More advanced sentiment analysis will also consider dimensions such as agreement, subjectivity, confidence, irony, and so on. In this challenge we will conduct the basic positive vs negative sentiment analysis based on real Twitter tweets.\n", + "\n", + "NLTK comes with a [sentiment analysis package](https://www.nltk.org/api/nltk.sentiment.html). This package is great for dummies to perform sentiment analysis because it requires only the textual data to make predictions. For example:\n", + "\n", + "```python\n", + ">>> from nltk.sentiment.vader import SentimentIntensityAnalyzer\n", + ">>> txt = \"Ironhack is a Global Tech School ranked num 2 worldwide. 
", + "
", + "Our mission is to help people transform their careers and join a thriving community of tech professionals that love what they do.\"\n", + ">>> analyzer = SentimentIntensityAnalyzer()\n", + ">>> analyzer.polarity_scores(txt)\n", + "{'neg': 0.0, 'neu': 0.741, 'pos': 0.259, 'compound': 0.8442}\n", + "```\n", + "\n", + "In this challenge, however, you will not use NLTK's sentiment analysis package because in your Machine Learning training in the past 2 weeks you have learned how to make predictions more accurate than that. The [tweets data](https://www.kaggle.com/kazanova/sentiment140) we will be using today are already coded for the positive/negative sentiment. You will be able to use the Naïve Bayes classifier you learned in the lesson to predict the sentiment of tweets based on the labels." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conducting Sentiment Analysis\n", + "\n", + "### Loading and Exploring Data\n", + "\n", + "The dataset we'll be using today is located in the lab directory named `Sentiment140.csv.zip`. You need to unzip it into a `.csv` file. Then in the cell below, load and explore the data.\n", + "\n", + "*Notes:* \n", + "\n", + "* The dataset was downloaded from [Kaggle](https://www.kaggle.com/kazanova/sentiment140). We made a slight change on the original data so that each column has a label.\n", + "\n", + "* The dataset is huuuuge (1.6m tweets). When you develop your data analysis codes, you can sample a subset of the data (e.g. 20k records) so that you will save a lot of time when you test your codes." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "from nltk.corpus import stopwords\n", + "import re\n", + "import nltk\n", + "from sklearn.feature_extraction.text import CountVectorizer\n", + "from sklearn.feature_extraction.text import TfidfVectorizer\n", + "from nltk.probability import ConditionalFreqDist" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def clean_up(s):\n", + " \"\"\"\n", + " Cleans up numbers, URLs, and special characters from a string.\n", + "\n", + " Args:\n", + " s: The string to be cleaned up.\n", + "\n", + " Returns:\n", + " A string that has been cleaned up.\n", + " \"\"\"\n", + " string = re.sub(r'http\\S+', '', s)\n", + " return re.sub('[^A-Za-z]+', ' ', string).lower().strip()\n", + "\n", + "def tokenize(s):\n", + " \"\"\"\n", + " Tokenize a string.\n", + "\n", + " Args:\n", + " s: String to be tokenized.\n", + "\n", + " Returns:\n", + " A list of words as the result of tokenization.\n", + " \"\"\"\n", + " return nltk.word_tokenize(s)\n", + "\n", + "def stem_and_lemmatize(l):\n", + " \n", + " \"\"\"\n", + " Perform stemming and lemmatization on a list of words.\n", + "\n", + " Args:\n", + " l: A list of strings.\n", + "\n", + " Returns:\n", + " A list of strings after being stemmed and lemmatized.\n", + " \"\"\"\n", + " ps = nltk.PorterStemmer()\n", + " lemmatizer = nltk.WordNetLemmatizer()\n", + " l2 = []\n", + " \n", + " for w in l:\n", + " s = ps.stem(w)\n", + " s = lemmatizer.lemmatize(s)\n", + " l2 += [s]\n", + " \n", + " return l2\n", + "\n", + "\n", + "def remove_stopwords(l):\n", + " \"\"\"\n", + " Remove English stopwords from a list of strings.\n", + "\n", + " Args:\n", + " l: A list of strings.\n", + "\n", + " Returns:\n", + " A list of strings after stop words are removed.\n", + " \"\"\"\n", + " stop_words = stopwords.words('english')\n", + "\n", + " return [w for w in l if w not in stop_words]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "tweets = pd.read_csv('C:/Users/Zaca/Documents/Datasets/sentiment140.csv')\n", + "sample = tweets.sample(20000)\n", + "sample['target'] = sample['target'].replace(4, 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare Textual Data for Sentiment Analysis\n", + "\n", + "Now, apply the functions you have written in Challenge 1 to your whole data set. These functions include:\n", + "\n", + "* `clean_up()`\n", + "\n", + "* `tokenize()`\n", + "\n", + "* `stem_and_lemmatize()`\n", + "\n", + "* `remove_stopwords()`\n", + "\n", + "Create a new column called `text_processed` in the dataframe to contain the processed data. At the end, your `text_processed` column should contain lists of word tokens that are cleaned up. Your data should look like below:\n", + "\n", + "![Processed Data](data-cleaning-results.png)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "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", + "
targetiddateflagusertexttext_processed
47168602176677449Mon Jun 15 04:29:23 PDT 2009NO_QUERYJessicaShireeLast day of classes with my Day 1 kids[last, day, class, day, kid]
156875412188268260Mon Jun 15 21:35:26 PDT 2009NO_QUERYlcmelodyRant over. Now it's time for me to actually ge...[rant, time, actual, get]
151417012175446548Mon Jun 15 01:00:40 PDT 2009NO_QUERYtamzinaki@Tsaksonakis love that song.don't remember it ...[tsaksonaki, love, song, rememb, bit, f, amp, ...
101326911881296235Fri May 22 04:14:15 PDT 2009NO_QUERYEghie_DyI'm so happy[happi]
44361302067603654Sun Jun 07 12:38:14 PDT 2009NO_QUERYicysun23@decorus I DONT KNOW!!![decoru, dont, know]
........................
111039111972049147Sat May 30 08:27:15 PDT 2009NO_QUERYiCasandy@SoulGlowActivtr dat song is da bomb!!![soulglowactivtr, dat, song, da, bomb]
64930602237251942Fri Jun 19 05:28:19 PDT 2009NO_QUERYkakaxoI go to the hairdresser and then to friends ...[go, hairdress, friend, night, lt, ugli, weath...
89419511692177930Sun May 03 19:26:28 PDT 2009NO_QUERYewindsor@aLINEofCOCJIN Ah awesome. Good to hear from ...[alineofcocjin, ah, awesom, good, hear]
5769801685723636Sun May 03 01:45:08 PDT 2009NO_QUERYacaigirl@Avie89 sorry to hear that...[avi, sorri, hear]
131418812013877292Tue Jun 02 23:49:36 PDT 2009NO_QUERYJellie1981waiting for the train! http://yfrog.com/eha8sj[wait, train]
\n", + "

20000 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " target id date flag \\\n", + "471686 0 2176677449 Mon Jun 15 04:29:23 PDT 2009 NO_QUERY \n", + "1568754 1 2188268260 Mon Jun 15 21:35:26 PDT 2009 NO_QUERY \n", + "1514170 1 2175446548 Mon Jun 15 01:00:40 PDT 2009 NO_QUERY \n", + "1013269 1 1881296235 Fri May 22 04:14:15 PDT 2009 NO_QUERY \n", + "443613 0 2067603654 Sun Jun 07 12:38:14 PDT 2009 NO_QUERY \n", + "... ... ... ... ... \n", + "1110391 1 1972049147 Sat May 30 08:27:15 PDT 2009 NO_QUERY \n", + "649306 0 2237251942 Fri Jun 19 05:28:19 PDT 2009 NO_QUERY \n", + "894195 1 1692177930 Sun May 03 19:26:28 PDT 2009 NO_QUERY \n", + "57698 0 1685723636 Sun May 03 01:45:08 PDT 2009 NO_QUERY \n", + "1314188 1 2013877292 Tue Jun 02 23:49:36 PDT 2009 NO_QUERY \n", + "\n", + " user text \\\n", + "471686 JessicaShiree Last day of classes with my Day 1 kids \n", + "1568754 lcmelody Rant over. Now it's time for me to actually ge... \n", + "1514170 tamzinaki @Tsaksonakis love that song.don't remember it ... \n", + "1013269 Eghie_Dy I'm so happy \n", + "443613 icysun23 @decorus I DONT KNOW!!! \n", + "... ... ... \n", + "1110391 iCasandy @SoulGlowActivtr dat song is da bomb!!! \n", + "649306 kakaxo I go to the hairdresser and then to friends ... \n", + "894195 ewindsor @aLINEofCOCJIN Ah awesome. Good to hear from ... \n", + "57698 acaigirl @Avie89 sorry to hear that... \n", + "1314188 Jellie1981 waiting for the train! http://yfrog.com/eha8sj \n", + "\n", + " text_processed \n", + "471686 [last, day, class, day, kid] \n", + "1568754 [rant, time, actual, get] \n", + "1514170 [tsaksonaki, love, song, rememb, bit, f, amp, ... \n", + "1013269 [happi] \n", + "443613 [decoru, dont, know] \n", + "... ... \n", + "1110391 [soulglowactivtr, dat, song, da, bomb] \n", + "649306 [go, hairdress, friend, night, lt, ugli, weath... \n", + "894195 [alineofcocjin, ah, awesom, good, hear] \n", + "57698 [avi, sorri, hear] \n", + "1314188 [wait, train] \n", + "\n", + "[20000 rows x 7 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample['text_processed'] = sample['text'].apply(clean_up).apply(tokenize).apply(stem_and_lemmatize).apply(remove_stopwords)\n", + "sample" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating Bag of Words\n", + "\n", + "The purpose of this step is to create a [bag of words](https://en.wikipedia.org/wiki/Bag-of-words_model) from the processed data. The bag of words contains all the unique words in your whole text body (a.k.a. *corpus*) with the number of occurrence of each word. It will allow you to understand which words are the most important features across the whole corpus.\n", + "\n", + "Also, you can imagine you will have a massive set of words. The less important words (i.e. those of very low number of occurrence) do not contribute much to the sentiment. Therefore, you only need to use the most important words to build your feature set in the next step. In our case, we will use the top 5,000 words with the highest frequency to build the features.\n", + "\n", + "In the cell below, combine all the words in `text_processed` and calculate the frequency distribution of all words. A convenient library to calculate the term frequency distribution is NLTK's `FreqDist` class ([documentation](https://www.nltk.org/api/nltk.html#module-nltk.probability)). Then select the top 5,000 words from the frequency distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['last',\n", + " 'day',\n", + " 'class',\n", + " 'kid',\n", + " 'rant',\n", + " 'time',\n", + " 'actual',\n", + " 'get',\n", + " 'tsaksonaki',\n", + " 'love',\n", + " 'song',\n", + " 'rememb',\n", + " 'bit',\n", + " 'f',\n", + " 'amp',\n", + " 'l',\n", + " 'saw',\n", + " 'actor',\n", + " 'studio',\n", + " 'happi',\n", + " 'decoru',\n", + " 'dont',\n", + " 'know',\n", + " 'rebeccamezzino',\n", + " 'hello',\n", + " 'bec',\n", + " 'long',\n", + " 'melbourn',\n", + " 'abl',\n", + " 'make',\n", + " 'tweetup',\n", + " 'ugh',\n", + " 'im',\n", + " 'tire',\n", + " 'hardli',\n", + " 'anyth',\n", + " 'play',\n", + " 'niec',\n", + " 'goin',\n", + " 'bed',\n", + " 'goodnight',\n", + " 'eveyon',\n", + " 'lt',\n", + " 'ha',\n", + " 'reject',\n", + " 'back',\n", + " 'amaz',\n", + " 'two',\n", + " 'never',\n", + " 'forget',\n", + " 'feel',\n", + " 'break',\n", + " 'pool',\n", + " 'swim',\n", + " 'middl',\n", + " 'lightn',\n", + " 'storm',\n", + " 'sunni',\n", + " 'xtineismyhero',\n", + " 'happen',\n", + " 'darl',\n", + " 'laffit',\n", + " 'still',\n", + " 'salon',\n", + " 'send',\n", + " 'pic',\n", + " 'eaten',\n", + " 'aliv',\n", + " 'mosquito',\n", + " 'post',\n", + " 'waaaaay',\n", + " 'much',\n", + " 'forgot',\n", + " 'wa',\n", + " 'gon',\n", + " 'na',\n", + " 'say',\n", + " 'mom',\n", + " 'invit',\n", + " 'parti',\n", + " 'go',\n", + " 'though',\n", + " 'veilin',\n", + " 'miss',\n", + " 'see',\n", + " 'like',\n", + " 'month',\n", + " 'alreadi',\n", + " 'think',\n", + " 'look',\n", + " 'xd',\n", + " 'chemistri',\n", + " 'revison',\n", + " 'well',\n", + " 'bore',\n", + " 'hate',\n", + " 'peopl',\n", + " 'finish',\n", + " 'exam',\n", + " 'festivalfan',\n", + " 'u',\n", + " 'start',\n", + " 'eat',\n", + " 'carb',\n", + " 'ur',\n", + " 'bodi',\n", + " 'wont',\n", + " 'weight',\n", + " 'come',\n", + " 'fast',\n", + " 'ive',\n", + " 'clean',\n", + " 'deserv',\n", + " 'recognit',\n", + " 'mother',\n", + " 'caus',\n", + " 'problem',\n", + " 'instead',\n", + " 'montyrul',\n", + " 'pearllow',\n", + " 'andi',\n", + " 'wish',\n", + " 'luck',\n", + " 'gig',\n", + " 'glad',\n", + " 'went',\n", + " 'glasto',\n", + " 'xxxxxx',\n", + " 'got',\n", + " 'ta',\n", + " 'pack',\n", + " 'trip',\n", + " 'daddi',\n", + " 'granni',\n", + " 'poo',\n", + " 'wait',\n", + " 'midnight',\n", + " 'jona',\n", + " 'brother',\n", + " 'new',\n", + " 'album',\n", + " 'tomorrow',\n", + " 'dead',\n", + " 'zoran',\n", + " 'lost',\n", + " 'croatian',\n", + " 'idol',\n", + " 'differ',\n", + " 'le',\n", + " 'vote',\n", + " 'prepar',\n", + " 'univers',\n", + " 'great',\n", + " 'hook',\n", + " 'learn',\n", + " 'read',\n", + " 'yesterday',\n", + " 'hope',\n", + " 'sloan',\n", + " 'download',\n", + " 'trvsdjam',\n", + " 'mixtap',\n", + " 'unzip',\n", + " 'invalid',\n", + " 'corrupt',\n", + " 'krishnakum',\n", + " 'told',\n", + " 'ya',\n", + " 'uber',\n", + " 'cool',\n", + " 'treat',\n", + " 'omen',\n", + " 'yoo',\n", + " 'sittin',\n", + " 'car',\n", + " 'crazi',\n", + " 'ish',\n", + " 'rite',\n", + " 'man',\n", + " 'ima',\n", + " 'nitemar',\n", + " 'tonit',\n", + " 'bryci',\n", + " 'seen',\n", + " 'ure',\n", + " 'stpatrick',\n", + " 'show',\n", + " 'amazin',\n", + " 'person',\n", + " 'take',\n", + " 'earlier',\n", + " 'comment',\n", + " 'b',\n", + " 'superstar',\n", + " 'synwpn',\n", + " 'whi',\n", + " 'wan',\n", + " 'stalk',\n", + " 'anyway',\n", + " 'chelseamoss',\n", + " 'everyth',\n", + " 'summer',\n", + " 'far',\n", + " 'good',\n", + " 'ashleigharsen',\n", + " 'also',\n", + " 'lemonhead',\n", + " 'delici',\n", + " 'plea',\n", + " 'compar',\n", + " 'one',\n", + " 'latinegro',\n", + " 'unfortun',\n", + " 'red',\n", + " 'robin',\n", + " 'famili',\n", + " 'friend',\n", + " 'perfect',\n", + " 'church',\n", + " 'lunch',\n", + " 'lora',\n", + " 'text',\n", + " 'around',\n", + " 'watch',\n", + " 'oc',\n", + " 'ye',\n", + " 'knockin',\n", + " 'night',\n", + " 'pointforwardpro',\n", + " 'omg',\n", + " 'thnk',\n", + " 'remind',\n", + " 'colleg',\n", + " 'right',\n", + " 'everytim',\n", + " 'spend',\n", + " 'front',\n", + " 'comput',\n", + " 'hurt',\n", + " 'home',\n", + " 'put',\n", + " 'bedtim',\n", + " 'becaus',\n", + " 'want',\n", + " 'sleep',\n", + " 'alon',\n", + " 'feliz',\n", + " 'de',\n", + " 'la',\n", + " 'madr',\n", + " 'godmommi',\n", + " 'sign',\n", + " 'contract',\n", + " 'apart',\n", + " 'need',\n", + " 'email',\n", + " 'contact',\n", + " 'check',\n", + " 'emailunlimit',\n", + " 'final',\n", + " 'broke',\n", + " 'hi',\n", + " 'casino',\n", + " 'habit',\n", + " 'lose',\n", + " 'next',\n", + " 'week',\n", + " 'afraid',\n", + " 'nightmar',\n", + " 'chees',\n", + " 'befor',\n", + " 'effect',\n", + " 'ravioli',\n", + " 'grill',\n", + " 'twitter',\n", + " 'product',\n", + " 'velvet',\n", + " 'cake',\n", + " 'speak',\n", + " 'sofiedevil',\n", + " 'left',\n", + " 'phone',\n", + " 'lie',\n", + " 'cough',\n", + " 'wonder',\n", + " 'anyon',\n", + " 'die',\n", + " 'seedi',\n", + " 'motel',\n", + " 'zombi',\n", + " 'shark',\n", + " 'tuesday',\n", + " 'woke',\n", + " 'pm',\n", + " 'fall',\n", + " 'asleep',\n", + " 'suck',\n", + " 'hour',\n", + " 'ie',\n", + " 'greek',\n", + " 'easter',\n", + " 'asian',\n", + " 'twist',\n", + " 'ashalale',\n", + " 'soo',\n", + " 'creativ',\n", + " 'gt',\n", + " 'thi',\n", + " 'must',\n", + " 'find',\n", + " 'someth',\n", + " 'fun',\n", + " 'blkpanther',\n", + " 'way',\n", + " 'seem',\n", + " 'somali',\n", + " 'civil',\n", + " 'war',\n", + " 'roto',\n", + " 'sadli',\n", + " 'espn',\n", + " 'gener',\n", + " 'medium',\n", + " 'kind',\n", + " 'thing',\n", + " 'amournoir',\n", + " 'work',\n", + " 'casualcottag',\n", + " 'big',\n", + " 'smiley',\n", + " 'face',\n", + " 'mybigg',\n", + " 'lolz',\n", + " 'wnba',\n", + " 'wrong',\n", + " 'live',\n", + " 'access',\n", + " 'graphic',\n", + " 'blog',\n", + " 'oprah',\n", + " 'thank',\n", + " 'share',\n", + " 'alexrk',\n", + " 'haha',\n", + " 'true',\n", + " 'alex',\n", + " 'ahhh',\n", + " 'anoth',\n", + " 'soon',\n", + " 'hmmm',\n", + " 'white',\n", + " 'tri',\n", + " 'chase',\n", + " 'couch',\n", + " 'yell',\n", + " 'rm',\n", + " 'poorer',\n", + " 'accident',\n", + " 'gave',\n", + " 'discount',\n", + " 'magazin',\n", + " 'sob',\n", + " 'twit',\n", + " 'juli',\n", + " 'nope',\n", + " 'yet',\n", + " 'scienc',\n", + " 'summ',\n", + " 'today',\n", + " 'jame',\n", + " 'buckley',\n", + " 'would',\n", + " 'end',\n", + " 'till',\n", + " 'weekend',\n", + " 'reschedul',\n", + " 'shower',\n", + " 'water',\n", + " 'frozen',\n", + " 'readi',\n", + " 'alway',\n", + " 'room',\n", + " 'veri',\n", + " 'hot',\n", + " 'sazp',\n", + " 'lush',\n", + " 'realiz',\n", + " 'tune',\n", + " 'thesixtyon',\n", + " 'com',\n", + " 'coupl',\n", + " 'nice',\n", + " 'daniboo',\n", + " 'hous',\n", + " 'woah',\n", + " 'rock',\n", + " 'life',\n", + " 'jesu',\n", + " 'takin',\n", + " 'step',\n", + " 'stuff',\n", + " 'food',\n", + " 'montanaon',\n", + " 'could',\n", + " 'flight',\n", + " 'agentpatgillen',\n", + " 'sure',\n", + " 'feelin',\n", + " 'trulli',\n", + " 'stune',\n", + " 'mr',\n", + " 'david',\n", + " 'carradin',\n", + " 'kungfu',\n", + " 'movi',\n", + " 'lushi',\n", + " 'dread',\n", + " 'deni',\n", + " 'danimarzillo',\n", + " 'ouch',\n", + " 'slice',\n", + " 'top',\n", + " 'finger',\n", + " 'gloriou',\n", + " 'sunshin',\n", + " 'bake',\n", + " 'browni',\n", + " 'sun',\n", + " 'yay',\n", + " 'boyl',\n", + " 'didnt',\n", + " 'win',\n", + " 'listen',\n", + " 'britney',\n", + " 'loveeess',\n", + " 'xoxo',\n", + " 'crush',\n", + " 'hardcor',\n", + " 'busi',\n", + " 'sore',\n", + " 'realli',\n", + " 'school',\n", + " 'ughh',\n", + " 'quot',\n", + " 'bbq',\n", + " 'outsid',\n", + " 'sweat',\n", + " 'smell',\n", + " 'yummi',\n", + " 'follwr',\n", + " 'drop',\n", + " 'hard',\n", + " 'tell',\n", + " 'spambot',\n", + " 'record',\n", + " 'stock',\n", + " 'nake',\n", + " 'ladi',\n", + " 'opportun',\n", + " 'dianhadinoto',\n", + " 'sweet',\n", + " 'sharlynnx',\n", + " 'aww',\n", + " 'naah',\n", + " 'favourit',\n", + " 'nighti',\n", + " 'cours',\n", + " 'delet',\n", + " 'dawson',\n", + " 'creek',\n", + " 'ol',\n", + " 'ruben',\n", + " 'spanish',\n", + " 'gp',\n", + " 'disappoint',\n", + " 'soft',\n", + " 'spot',\n", + " 'jcookonlin',\n", + " 'yeahhh',\n", + " 'mean',\n", + " 'someon',\n", + " 'fairli',\n", + " 'close',\n", + " 'knew',\n", + " 'sad',\n", + " 'camera',\n", + " 'nsenze',\n", + " 'onli',\n", + " 'wors',\n", + " 'suppos',\n", + " 'repli',\n", + " 'lalaitsmaria',\n", + " 'oh',\n", + " 'carliecarrcrash',\n", + " 'panaera',\n", + " 'four',\n", + " 'tonight',\n", + " 'best',\n", + " 'orlando',\n", + " 'girli',\n", + " 'backyard',\n", + " 'later',\n", + " 'afternoon',\n", + " 'real',\n", + " 'forev',\n", + " 'innoc',\n", + " 'word',\n", + " 'yr',\n", + " 'old',\n", + " 'hold',\n", + " 'even',\n", + " 'millionair',\n", + " 'driver',\n", + " 'spin',\n", + " 'crash',\n", + " 'super',\n", + " 'race',\n", + " 'machin',\n", + " 'raini',\n", + " 'shanghai',\n", + " 'simpli',\n", + " 'made',\n", + " 'fail',\n", + " 'darlingnickieb',\n", + " 'daaannnnggg',\n", + " 'porki',\n", + " 'xo',\n", + " 'handli',\n", + " 'mayb',\n", + " 'charlii',\n", + " 'yeah',\n", + " 'felt',\n", + " 'bad',\n", + " 'um',\n", + " 'ate',\n", + " 'rainbow',\n", + " 'paddl',\n", + " 'pop',\n", + " 'xx',\n", + " 'sethu',\n", + " 'j',\n", + " 'thought',\n", + " 'exactli',\n", + " 'fuck',\n", + " 'er',\n", + " 'girl',\n", + " 'enjoy',\n", + " 'wknd',\n", + " 'might',\n", + " 'landd',\n", + " 'foca',\n", + " 'nesslle',\n", + " 'x',\n", + " 'men',\n", + " 'fan',\n", + " 'paola',\n", + " 'total',\n", + " 'hugh',\n", + " 'gif',\n", + " 'cute',\n", + " 'norm',\n", + " 'cant',\n", + " 'tom',\n", + " 'isnt',\n", + " 'n',\n", + " 'fave',\n", + " 'guess',\n", + " 'hahahha',\n", + " 'pretti',\n", + " 'tea',\n", + " 'thigh',\n", + " 'interfac',\n", + " 'nrwi',\n", + " 'throw',\n", + " 'heavi',\n", + " 'object',\n", + " 'snore',\n", + " 'hezmcfli',\n", + " 'definit',\n", + " 'damn',\n", + " 'sick',\n", + " 'studi',\n", + " 'law',\n", + " 'leav',\n", + " 'stra',\n", + " 'doubl',\n", + " 'ea',\n", + " 'bradford',\n", + " 'aw',\n", + " 'shut',\n", + " 'lol',\n", + " 'superbad',\n", + " 'ashkiiwil',\n", + " 'sportsgirlsplay',\n", + " 'coach',\n", + " 'forc',\n", + " 'retir',\n", + " 'year',\n", + " 'ago',\n", + " 'due',\n", + " 'injuri',\n", + " 'terrinixon',\n", + " 'laugh',\n", + " 'oral',\n", + " 'present',\n", + " 'monday',\n", + " 'whole',\n", + " 'johnherman',\n", + " 'congrat',\n", + " 'deliveri',\n", + " 'trust',\n", + " 'theoshu',\n", + " 'oishi',\n", + " 'cheap',\n", + " 'satisfi',\n", + " 'sushi',\n", + " 'soup',\n", + " 'excel',\n", + " 'edward',\n", + " 'dumb',\n", + " 'twin',\n", + " 'rabbitport',\n", + " 'charact',\n", + " 'tuna',\n", + " 'sandwich',\n", + " 'done',\n", + " 'mayson',\n", + " 'youu',\n", + " 'eu',\n", + " 'queria',\n", + " 'que',\n", + " 'era',\n", + " 'rewind',\n", + " 'fo',\n", + " 'pr',\n", + " 'ximo',\n", + " 'singl',\n", + " 'ou',\n", + " 'without',\n", + " 'worst',\n", + " 'ever',\n", + " 'fml',\n", + " 'fashion',\n", + " 'statement',\n", + " 'head',\n", + " 'scarf',\n", + " 'style',\n", + " 'doin',\n", + " 'mama',\n", + " 'yoyoemma',\n", + " 'ceekaigax',\n", + " 'away',\n", + " 'fulli',\n", + " 'woken',\n", + " 'winterchick',\n", + " 'nah',\n", + " 'write',\n", + " 'wrote',\n", + " 'basic',\n", + " 'idea',\n", + " 'breakfast',\n", + " 'sat',\n", + " 'zach',\n", + " 'ashleeeyyyyy',\n", + " 'tushsharma',\n", + " 'uh',\n", + " 'huh',\n", + " 'fellow',\n", + " 'unit',\n", + " 'cooki',\n", + " 'mood',\n", + " 'bos',\n", + " 'bitch',\n", + " 'endlessli',\n", + " 'cre',\n", + " 'tvdirektr',\n", + " 'wow',\n", + " 'nurseju',\n", + " 'earli',\n", + " 'taxi',\n", + " 'worth',\n", + " 'give',\n", + " 'beamer',\n", + " 'washhhh',\n", + " 'mamzellef',\n", + " 'dad',\n", + " 'tallk',\n", + " 'fix',\n", + " 'weareleet',\n", + " 'hungov',\n", + " 'greasi',\n", + " 'noth',\n", + " 'help',\n", + " 'keep',\n", + " 'run',\n", + " 'truth',\n", + " 'hw',\n", + " 'may',\n", + " 'bother',\n", + " 'meraki',\n", + " 'blanket',\n", + " 'san',\n", + " 'fran',\n", + " 'free',\n", + " 'wi',\n", + " 'fi',\n", + " 'news',\n", + " 'sheilafightseb',\n", + " 'sheila',\n", + " 'riddl',\n", + " 'horni',\n", + " 'kitti',\n", + " 'funni',\n", + " 'accur',\n", + " 'pest',\n", + " 'whiteplum',\n", + " 'inde',\n", + " 'bet',\n", + " 'dog',\n", + " 'bun',\n", + " 'sea',\n", + " 'garbag',\n", + " 'truck',\n", + " 'guy',\n", + " 'plenti',\n", + " 'flashbelt',\n", + " 'tue',\n", + " 'wen',\n", + " 'beacus',\n", + " 'ex',\n", + " 'girlfrend',\n", + " 'plussizemommi',\n", + " 'reason',\n", + " 'open',\n", + " 'found',\n", + " 'whew',\n", + " 'collabor',\n", + " 'admit',\n", + " 'aka',\n", + " 'kristin',\n", + " 'daynaroselli',\n", + " 'elev',\n", + " 'favorit',\n", + " 'danddncgirl',\n", + " 'sorri',\n", + " 'hear',\n", + " 'part',\n", + " 'phx',\n", + " 'rel',\n", + " 'dmosley',\n", + " 'goingbto',\n", + " 'ohsailor',\n", + " 'sri',\n", + " 'bb',\n", + " 'chrissyjohnson',\n", + " 'jacki',\n", + " 'fair',\n", + " 'tomm',\n", + " 'sunday',\n", + " 'rachaelxxo',\n", + " 'oooh',\n", + " 'question',\n", + " 'mark',\n", + " 'quit',\n", + " 'pick',\n", + " 'aah',\n", + " 'annoy',\n", + " 'throat',\n", + " 'seandonaho',\n", + " 'ad',\n", + " 'sergverdi',\n", + " 'lesli',\n", + " 'vfcst',\n", + " 'ouchh',\n", + " 'yea',\n", + " 'aim',\n", + " 'talk',\n", + " 'mee',\n", + " 'laptop',\n", + " 'freak',\n", + " 'piss',\n", + " 'jerk',\n", + " 'heart',\n", + " 'yank',\n", + " 'bryanlyt',\n", + " 'certain',\n", + " 'area',\n", + " 'utama',\n", + " 'kota',\n", + " 'dsara',\n", + " 'mayhemmil',\n", + " 'elli',\n", + " 'defin',\n", + " 'faction',\n", + " 'hell',\n", + " 'son',\n", + " 'cat',\n", + " 'unpack',\n", + " 'miklo',\n", + " 'victori',\n", + " 'park',\n", + " 'twilight',\n", + " 'lineup',\n", + " 'bummer',\n", + " 'proud',\n", + " 'kill',\n", + " 'thursday',\n", + " 'aye',\n", + " 'xcspeed',\n", + " 'south',\n", + " 'padr',\n", + " 'island',\n", + " 'tommorow',\n", + " 'schlitterban',\n", + " 'ali',\n", + " 'davi',\n", + " 'lucki',\n", + " 'student',\n", + " 'teach',\n", + " 'point',\n", + " 'ncheck',\n", + " 'servic',\n", + " 'number',\n", + " 'gb',\n", + " 'gorgeou',\n", + " 'weather',\n", + " 'bike',\n", + " 'london',\n", + " 'babi',\n", + " 'refil',\n", + " 'bttle',\n", + " 'gym',\n", + " 'fountain',\n", + " 'spilt',\n", + " 'evrywhr',\n", + " 'embarress',\n", + " 'controversi',\n", + " 'link',\n", + " 'privat',\n", + " 'wahhh',\n", + " 'restart',\n", + " 'heheh',\n", + " 'jonasbroth',\n", + " 'bf',\n", + " 'weird',\n", + " 'let',\n", + " 'updat',\n", + " 'follow',\n", + " 'deathli',\n", + " 'hangov',\n", + " 'morn',\n", + " 'puke',\n", + " 'zorb',\n", + " 'buffet',\n", + " 'swag',\n", + " 'bag',\n", + " 'panten',\n", + " 'shoppen',\n", + " 'toll',\n", + " 'un',\n", + " 'tina',\n", + " 'getroffen',\n", + " 'yez',\n", + " 'pc',\n", + " 'gammeln',\n", + " 'chakatsunstreak',\n", + " 'small',\n", + " 'math',\n", + " 'yes',\n", + " 'horribl',\n", + " 'excit',\n", + " 'microsoft',\n", + " 'confer',\n", + " 'late',\n", + " 'awesom',\n", + " 'afro',\n", + " 'ffxiii',\n", + " 'w',\n", + " 'shelley',\n", + " 'airport',\n", + " 'nephew',\n", + " 'kat',\n", + " 'dentist',\n", + " 'brace',\n", + " 'monicaa',\n", + " 'sengupta',\n", + " 'krist',\n", + " 'ph',\n", + " 'r',\n", + " 'repeat',\n", + " 'parentstud',\n", + " 'marcolaureano',\n", + " 'welcom',\n", + " 'non',\n", + " 'stop',\n", + " 'tweet',\n", + " 'johnkuan',\n", + " 'pronaz',\n", + " 'raj',\n", + " 'lebron',\n", + " 'v',\n", + " 'kobe',\n", + " 'least',\n", + " 'yanke',\n", + " 'gah',\n", + " 'mimic',\n", + " 'ubisoft',\n", + " 'littl',\n", + " 'orient',\n", + " 'eminem',\n", + " 'track',\n", + " 'formula',\n", + " 'seriou',\n", + " 'arf',\n", + " 'guinea',\n", + " 'pig',\n", + " 'dine',\n", + " 'noisi',\n", + " 'doggi',\n", + " 'ryke',\n", + " 'whether',\n", + " 'includ',\n", + " 'straight',\n", + " 'latest',\n", + " 'report',\n", + " 'surviv',\n", + " 'recess',\n", + " 'ask',\n", + " 'www',\n", + " 'bgacceler',\n", + " 'episod',\n", + " 'simpson',\n", + " 'pollinatewildli',\n", + " 'catalyt',\n", + " 'convert',\n", + " 'broken',\n", + " 'cost',\n", + " 'sold',\n", + " 'max',\n", + " 'settin',\n", + " 'websit',\n", + " 'whoa',\n", + " 'ethansuple',\n", + " 'boy',\n", + " 'freakin',\n", + " 'strong',\n", + " 'weakest',\n", + " 'rorzshach',\n", + " 'devon',\n", + " 'hahaha',\n", + " 'freed',\n", + " 'prison',\n", + " 'ah',\n", + " 'freedom',\n", + " 'princesssuperc',\n", + " 'shoot',\n", + " 'delboy',\n", + " 'promot',\n", + " 'doe',\n", + " 'lindasmith',\n", + " 'pharmaci',\n", + " 'dalla',\n", + " 'hawaii',\n", + " 'friday',\n", + " 'cowboyhazel',\n", + " 'ok',\n", + " 'tip',\n", + " 'plurk',\n", + " 'hellasia',\n", + " 'stu',\n", + " 'gg',\n", + " 'young',\n", + " 'world',\n", + " 'foot',\n", + " 'killin',\n", + " 'name',\n", + " 'better',\n", + " 'ahead',\n", + " 'ashleebiscuit',\n", + " 'fell',\n", + " 'fring',\n", + " 'hang',\n", + " 'puppi',\n", + " 'pakc',\n", + " 'texa',\n", + " 'mission',\n", + " 'jessemccartney',\n", + " 'ughhh',\n", + " 'c',\n", + " 'sooo',\n", + " 'longer',\n", + " 'term',\n", + " 'trick',\n", + " 'jennybdesign',\n", + " 'correct',\n", + " 'goe',\n", + " 'public',\n", + " 'lottaburg',\n", + " 'jam',\n", + " 'fireflight',\n", + " ...]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cfdist = nltk.FreqDist()\n", + "\n", + "for tweet in sample['text_processed']:\n", + " for word in tweet:\n", + " cfdist[word] += 1\n", + "\n", + "top_words = list(cfdist.keys())[:5000]\n", + "top_words" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building Features\n", + "\n", + "Now let's build the features. Using the top 5,000 words, create a 2-dimensional matrix to record whether each of those words is contained in each document (tweet). Then you also have an output column to indicate whether the sentiment in each tweet is positive. For example, assuming your bag of words has 5 items (`['one', 'two', 'three', 'four', 'five']`) out of 4 documents (`['A', 'B', 'C', 'D']`), your feature set is essentially:\n", + "\n", + "| Doc | one | two | three | four | five | is_positive |\n", + "|---|---|---|---|---|---|---|\n", + "| A | True | False | False | True | False | True |\n", + "| B | False | False | False | True | True | False |\n", + "| C | False | True | False | False | False | True |\n", + "| D | True | False | False | False | True | False|\n", + "\n", + "However, because the `nltk.NaiveBayesClassifier.train` class we will use in the next step does not work with Pandas dataframe, the structure of your feature set should be converted to the Python list looking like below:\n", + "\n", + "```python\n", + "[\n", + "\t({\n", + "\t\t'one': True,\n", + "\t\t'two': False,\n", + "\t\t'three': False,\n", + "\t\t'four': True,\n", + "\t\t'five': False\n", + "\t}, True),\n", + "\t({\n", + "\t\t'one': False,\n", + "\t\t'two': False,\n", + "\t\t'three': False,\n", + "\t\t'four': True,\n", + "\t\t'five': True\n", + "\t}, False),\n", + "\t({\n", + "\t\t'one': False,\n", + "\t\t'two': True,\n", + "\t\t'three': False,\n", + "\t\t'four': False,\n", + "\t\t'five': False\n", + "\t}, True),\n", + "\t({\n", + "\t\t'one': True,\n", + "\t\t'two': False,\n", + "\t\t'three': False,\n", + "\t\t'four': False,\n", + "\t\t'five': True\n", + "\t}, False)\n", + "]\n", + "```\n", + "\n", + "To help you in this step, watch the [following video](https://www.youtube.com/watch?v=-vVskDsHcVc) to learn how to build the feature set with Python and NLTK. The source code in this video can be found [here](https://pythonprogramming.net/words-as-features-nltk-tutorial/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![Building Features](building-features.jpg)](https://www.youtube.com/watch?v=-vVskDsHcVc)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20000\n" + ] + } + ], + "source": [ + "def find_features(document):\n", + " words = set(document)\n", + " features = {}\n", + " for w in top_words:\n", + " features[w] = (w in words)\n", + " \n", + " return features\n", + " \n", + "feature_sets = [(find_features(tweet), target) for (tweet, target) in list(zip(sample['text_processed'], sample['target']))]\n", + "print(len(feature_sets))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building and Traininng Naive Bayes Model\n", + "\n", + "In this step you will split your feature set into a training and a test set. Then you will create a Bayes classifier instance using `nltk.NaiveBayesClassifier.train` ([example](https://www.nltk.org/book/ch06.html)) to train with the training dataset.\n", + "\n", + "After training the model, call `classifier.show_most_informative_features()` to inspect the most important features. The output will look like:\n", + "\n", + "```\n", + "Most Informative Features\n", + "\t snow = True False : True = 34.3 : 1.0\n", + "\t easter = True False : True = 26.2 : 1.0\n", + "\t headach = True False : True = 20.9 : 1.0\n", + "\t argh = True False : True = 17.6 : 1.0\n", + "\tunfortun = True False : True = 16.9 : 1.0\n", + "\t jona = True True : False = 16.2 : 1.0\n", + "\t ach = True False : True = 14.9 : 1.0\n", + "\t sad = True False : True = 13.0 : 1.0\n", + "\t parent = True False : True = 12.9 : 1.0\n", + "\t spring = True False : True = 12.7 : 1.0\n", + "```\n", + "\n", + "The [following video](https://www.youtube.com/watch?v=rISOsUaTrO4) will help you complete this step. The source code in this video can be found [here](https://pythonprogramming.net/naive-bayes-classifier-nltk-tutorial/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![Building and Training NB](nb-model-building.jpg)](https://www.youtube.com/watch?v=rISOsUaTrO4)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "train_set, test_set = feature_sets[:10000], feature_sets[10000:]\n", + "classifier = nltk.NaiveBayesClassifier.train(train_set)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Testing Naive Bayes Model\n", + "\n", + "Now we'll test our classifier with the test dataset. This is done by calling `nltk.classify.accuracy(classifier, test)`.\n", + "\n", + "As mentioned in one of the tutorial videos, a Naive Bayes model is considered OK if your accuracy score is over 0.6. If your accuracy score is over 0.7, you've done a great job!" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7162\n", + "Most Informative Features\n", + " sad = True 0 : 1 = 22.1 : 1.0\n", + " sick = True 0 : 1 = 16.5 : 1.0\n", + " headach = True 0 : 1 = 15.8 : 1.0\n", + " bum = True 0 : 1 = 13.3 : 1.0\n", + " hospit = True 0 : 1 = 12.7 : 1.0\n" + ] + } + ], + "source": [ + "print(nltk.classify.accuracy(classifier, test_set))\n", + "classifier.show_most_informative_features(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bonus Question 1: Improve Model Performance\n", + "\n", + "If you are still not exhausted so far and want to dig deeper, try to improve your classifier performance. There are many aspects you can dig into, for example:\n", + "\n", + "* Improve stemming and lemmatization. Inspect your bag of words and the most important features. Are there any words you should furuther remove from analysis? You can append these words to further remove to the stop words list.\n", + "\n", + "* Remember we only used the top 5,000 features to build model? Try using different numbers of top features. The bottom line is to use as few features as you can without compromising your model performance. The fewer features you select into your model, the faster your model is trained. Then you can use a larger sample size to improve your model accuracy score." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# your code here" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bonus Question 2: Machine Learning Pipeline\n", + "\n", + "In a new Jupyter Notebook, combine all your codes into a function (or a class). Your new function will execute the complete machine learning pipeline job by receiving the dataset location and output the classifier. This will allow you to use your function to predict the sentiment of any tweet in real time. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# your code here" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bonus Question 3: Apache Spark\n", + "\n", + "If you have completed the Apache Spark advanced topic lab, what you can do is to migrate your pipeline from local to a Databricks Notebook. Share your notebook with your instructor and classmates to show off your achievements!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# your code here" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/your-code/data-cleaning-results.png b/your-code/data-cleaning-results.png new file mode 100644 index 0000000..59f91c3 Binary files /dev/null and b/your-code/data-cleaning-results.png differ diff --git a/your-code/lab_boston_housing.ipynb b/your-code/lab_boston_housing.ipynb deleted file mode 100644 index 3176602..0000000 --- a/your-code/lab_boston_housing.ipynb +++ /dev/null @@ -1,298 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Understanding Over & Underfitting\n", - "## Predicting Boston Housing Prices" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Getting Started\n", - "In this project, you will use the Boston Housing Prices dataset to build several models to predict the prices of homes with particular qualities from the suburbs of Boston, MA.\n", - "We will build models with several different parameters, which will change the goodness of fit for each. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## Data Exploration\n", - "Since we want to predict the value of houses, the **target variable**, `'MEDV'`, will be the variable we seek to predict." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Import and explore the data. Clean the data for outliers and missing values. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Next, we want to explore the data. Pick several varibables you think will be ost correlated with the prices of homes in Boston, and create plots that show the data dispersion as well as the regression line of best fit." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your plots here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### What do these plots tell you about the relationships between these variables and the prices of homes in Boston? Are these the relationships you expected to see in these variables?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Make a heatmap of the remaining variables. Are there any variables that you did not consider that have very high correlations? What are they?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate Statistics\n", - "Calculate descriptive statistics for housing price. Include the minimum, maximum, mean, median, and standard deviation. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "----\n", - "\n", - "## Developing a Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Implementation: Define a Performance Metric\n", - "What is the performance meteric with which you will determine the performance of your model? Create a function that calculates this performance metric, and then returns the score. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.metrics import r2_score\n", - "\n", - "def performance_metric(y_true, y_predict):\n", - " \"\"\" Calculates and returns the performance score between \n", - " true and predicted values based on the metric chosen. \"\"\"\n", - " # Your code here:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Implementation: Shuffle and Split Data\n", - "Split the data into the testing and training datasets. Shuffle the data as well to remove any bias in selecting the traing and test. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "----\n", - "\n", - "## Analyzing Model Performance\n", - "Next, we are going to build a Random Forest Regressor, and test its performance with several different parameter settings." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Learning Curves\n", - "Lets build the different models. Set the max_depth parameter to 2, 4, 6, 8, and 10 respectively. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Five separate RFR here with the given max depths" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, plot the score for each tree on the training set and on the testing set." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "# Produce a plot with the score for the testing and training for the different max depths" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What do these results tell you about the effect of the depth of the trees on the performance of the model?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Bias-Variance Tradeoff\n", - "When the model is trained with a maximum depth of 1, does the model suffer from high bias or from high variance? How about when the model is trained with a maximum depth of 10? Check out this article before answering: https://towardsdatascience.com/understanding-the-bias-variance-tradeoff-165e6942b229" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Best-Guess Optimal Model\n", - "What is the max_depth parameter that you think would optimize the model? Run your model and explain its performance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Applicability\n", - "*In a few sentences, discuss whether the constructed model should or should not be used in a real-world setting.* \n", - "**Hint:** Some questions to answering:\n", - "- *How relevant today is data that was collected from 1978?*\n", - "- *Are the features present in the data sufficient to describe a home?*\n", - "- *Is the model robust enough to make consistent predictions?*\n", - "- *Would data collected in an urban city like Boston be applicable in a rural city?*" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "kernelspec": { - "display_name": "Python 3", - "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.7.2" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/your-code/lab_overfitting.ipynb b/your-code/lab_overfitting.ipynb deleted file mode 100644 index 3776411..0000000 --- a/your-code/lab_overfitting.ipynb +++ /dev/null @@ -1,226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Overfitting\n", - "## In this lab, we are going to explore how thoughtfully choosing a model and using test set are important parts of avoiding overfitting. \n", - "\n", - "We are going to apply these concepts to a classification model. We want to develop a decision boundary, on one side of which we have class A and on the other we have class B.\n", - "\n", - "Like we disussed in class, when we have noisy data, if we are not careful, we can end up fitting our model to the noise in the data and not the 'signal'-- the factors that actually determine the outcome. This is called overfitting, and results in good results in training, and in bad results when the model is applied to real data. Similarly, we could have a model that is too simplistic to accurately model the signal. This produces a model that doesnt work well (ever), and sucks only slightly less than overfitting. At least your model performs consistently bad :)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### First, we are going to generate some synthetic data. To make these concepts visual, we are going to generate this data to be concentric circles. Run the code below to do so. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from IPython.display import display\n", - "\n", - "# Makin' some data\n", - "from sklearn.datasets import make_circles\n", - "X, y = make_circles(noise=0.2, factor=0.5, random_state=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### As our classification algorithm, we are going to use a type of SVM with a radial basis function. This basically works by mapping each point into a higher dimensional space that can be split by the SVM (gross oversimplificaiton). That looks something like this:\n", - "![RBFSVM.png](../images/RBFSVM.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### We can change thecomplexity of the decision boundaries applied by the SVM by changignt the size of the radial basis function, through the parameter 'gamma'.\n", - "\n", - "Instantiate a list of three SVM classifiers with three different gamma parameters, (.001, 1, and 20)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Now, we are going to plot the boundaries created by each of these classifiers with the points we generated. Run the following code to make three graphs of each SVM classifier." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib.colors import ListedColormap\n", - "\n", - "figure = plt.figure(figsize=(12, 6))\n", - "i = 1\n", - "h = .02 # step size in the mesh\n", - "x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5\n", - "y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5\n", - "xx, yy = np.meshgrid(np.arange(x_min, x_max, h),\n", - " np.arange(y_min, y_max, h))\n", - "cm = plt.cm.RdBu\n", - "cm_bright = ListedColormap(['#FF0000', '#0000FF'])\n", - "\n", - "names = ['gamma = 0.001', 'gamma = 1', 'gamma = 20']\n", - "\n", - "# iterate over classifiers\n", - "for name, clf in zip(names, classifiers):\n", - " ax = plt.subplot(1, len(classifiers), i)\n", - " clf.fit(X, y)\n", - "\n", - " # Plot the decision boundary. For that, we will assign a color to each\n", - " # point in the mesh [x_min, x_max]x[y_min, y_max].\n", - " if hasattr(clf, \"decision_function\"):\n", - " Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])\n", - " else:\n", - " Z = clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]\n", - "\n", - " # Put the result into a color plot\n", - " Z = Z.reshape(xx.shape)\n", - " ax.contourf(xx, yy, Z, cmap=cm, alpha=.8)\n", - "\n", - " ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright)\n", - "\n", - " ax.set_xlim(xx.min(), xx.max())\n", - " ax.set_ylim(yy.min(), yy.max())\n", - " ax.set_xticks(())\n", - " ax.set_yticks(())\n", - " ax.set_title(name)\n", - " i += 1\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Where the contour is red, we will predict red, and same for blue; white means there is a 50/50 chance of either class\n", - "Explain what you see in the plots above.\n", - "\n", - "Which gamma do you think fits the data best? \n", - "\n", - "What would you select as the opitmal gamma?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Without having used a training and testing set, lets see what our accuracy score would be for, for example, a gamma of .7. Use the .score() method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Wow thats good accuracy! But is it generalizable? Make a test/train split and see how the model performs on the SVM with the gamma of 20. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Try with some of the other gammas to see how the accuracy score changes after implementing a test/train split. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your code here" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Was your previous model overfitting? If so, how would you try to improve this model to prevent this?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Your response here" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/your-code/nb-model-building.jpg b/your-code/nb-model-building.jpg new file mode 100644 index 0000000..f42bbe2 Binary files /dev/null and b/your-code/nb-model-building.jpg differ