{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction to Dataset Processing\n", "#### by Carl Shan and Jen Selby" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This Jupyter Notebook will share more details about how to process your data. \n", "\n", "Data processing is like preparing the ingredients before cooking; if you prepare them poorly (e.g., leave things half-peeled and dirty) , the meal will taste poor no matter how skillful a chef you are. \n", "\n", "It's similarly true in machine learning. Dataset processing can be one of the most important things you can do to get your model to perform well.\n", "\n", "You can read more about dataset processing on the [course notes here](https://jennselby.github.io/MachineLearningCourseNotes/#data-processing).\n", "\n", "If you haven't already, follow [the setup instructions here](https://jennselby.github.io/MachineLearningCourseNotes/#setting-up-python3) to get all necessary software installed.\n", "\n", "## Table of Contents\n", " * [Setup](#Setup)\n", " * [Section 1: Converting Categorical Values to Numerical Ones](#Section-1:-Converting-Categorical-Values-to-Numerical-Ones)\n", " * [Section 2: Dealing with Text Data](#Section-2:-Dealing-with-Text-Data)\n", " * [Section 3: Dealing with Null Values](#Section-3:-Dealing-with-Null-Values)\n", " * [Section 4: Now Let's Learn How to Standardize Data](#Section-4:-Now-Let's-Learn-How-to-Standardize-Data)\n", " * [Bonus: What if we would like to split up columns?](#Bonus:-What-if-we-would-like-to-split-up-columns?)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hint: Use the `?` symbol\n", "\n", "As you go through this notebook, as well as learn more about processing data in iPython, it will be helpful to know the `?` symbol.\n", "\n", "E.g., You can try to type the following into Python\n", "\n", "```python\n", "import sklearn\n", "\n", "sklearn?\n", "```\n", "\n", "Typing the `?` symbol after a function, module or variable will bring up the documentation of that bit of code, assuming it exists. It'll tell you more about the variable, function or module.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from sklearn import preprocessing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Download the [student performance data](http://archive.ics.uci.edu/ml/machine-learning-databases/00320/) and change the path below to wherever you put the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "student_data = pd.read_csv('../data/student/student-mat.csv', sep=';')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "student_data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Section 1: Converting Categorical Values to Numerical Ones\n", "\n", "Looking at the data above, we want to convert a number of the columns from categorical to numerical. Most machine learning models deal with numbers and don't know how to model data that is in text form. As a result we need to learn how to do things such as e.g., convert the values in the `school` column to numbers." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### First, let's see what values there are in the `school` column" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This shows a list of unique values and how many times they appear\n", "student_data['school'].value_counts()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Converting values in the school column to text\n", "# We are going to define a function that takes a single value and apply it to all the values\n", "def convert_school(row):\n", " if row == 'GP':\n", " return 0\n", " elif row == 'MS':\n", " return 1\n", " else:\n", " return None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Avoid for loops\n", "Normally, we might write a for loop like the one below. But this is really slow when using Pandas.\n", "\n", "### _Don't write loops like this_." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Here's a slow way of using the above function\n", "%time\n", "converted_school = []\n", "\n", "for row in student_data['school']:\n", " new_value = convert_school(row)\n", " converted_school.append(new_value)\n", "converted_school\n", "\n", "\n", "# Don't do this! It's very slow." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Instead, Use `.apply`\n", "This will do the same thing as the for loop above, but _much_ faster. It'll apply a function to all the rows of a `DataFrame`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%time\n", "converted_school = student_data['school'].apply(convert_school)\n", "converted_school" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Look how much faster that took!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Or, you can use `.map()`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use the `.map()` function to map certain values to other data.\n", "\n", "For example, imagine you had a column named `'colors'` that contained the values `\"red\"` and `\"blue\"` and you wanted to convert these to the numbers `1` and `2`.\n", "\n", "```python\n", "\n", "mappings = {\n", " 'red': 1,\n", " 'blue': 2\n", "}\n", "\n", "data['colors_mapped'] = data['colors'].map(mappings)\n", "\n", "```\n", "\n", "The above will create a new column called `colors_mapped` that now has the values `1` and `2`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using sklearn's built-in preprocessing module, we can do the same thing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "enc_school = preprocessing.LabelEncoder()\n", "transformed_school = enc_school.fit_transform(student_data['school'])\n", "transformed_school" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### We can also use one-hot encoding if we have more than two values. We still need to encode it first, as we did above.\n", "See example at https://stackoverflow.com/a/43589167/2159992" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "enc_mjob = preprocessing.LabelEncoder()\n", "encoded_mjob = enc_mjob.fit_transform(student_data['Mjob'])\n", "encoded_mjob" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "onehot_mjob = preprocessing.OneHotEncoder(sparse=False)\n", "transformed_mjob = onehot_mjob.fit_transform(encoded_mjob.reshape(-1,1))\n", "transformed_mjob" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once we've fitted the label encoder and one-hot encoder, we can use them to transform more values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "onehot_mjob.transform(enc_mjob.transform(['other', 'health']).reshape(-1,1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What if we want to apply a transform that looks at multiple values in the row?\n", "For instance, what if we want to create a new column with a 1 if both parents have the highest level of education measured?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "medu_index = student_data.columns.get_loc('Medu')\n", "fedu_index = student_data.columns.get_loc('Fedu')\n", "\n", "def both_parents_edu(row):\n", " if row[medu_index] > 3 and row[fedu_index] >= 4:\n", " return 1\n", " else:\n", " return 0\n", " \n", "# axis 1 means that we will apply the function to each row\n", "student_data['parents_high_edu'] = student_data.apply(both_parents_edu, axis=1)\n", "student_data.head(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Section 2: Dealing with Text Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`pandas` has a lot of built-in modules that work with text-based data. \n", "\n", "`sklearn` similarly has a lot of modules for this as well.\n", "\n", "This section gives a brief outline of the things you can try.\n", "\n", "If you want to see a fuller list, with examples, of how `pandas` deals with text data, you can look at [the documentation here](https://pandas.pydata.org/pandas-docs/stable/text.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#### First, I'm going to make some fake data that we can work with for the rest of this section\n", "\n", "data = pd.DataFrame(data={'text': ['apple', '%badly,formatted,data%', 'pear']})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Removing or replacing data\n", "\n", "Okay, we want to remove the `','` and `'%'` symbols from the data. How do we do so?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data['text_removed'] = data['text'].str.replace(',', '')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nice. Now try and replace the `'%'` symbols." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#### Your code here\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Checking to see if a string contains a certain value\n", "\n", "Now, we want to see if a text contains certain values, and only get the rows that contains those values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### Again, I have to make some fake data\n", "\n", "data = pd.DataFrame(data={'text': ['Nueva Maverick', 'San Francisco Maverick', 'Vikings']})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cool, what if we only wanted to get the rows that contained the word `'Maverick'`?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data['text'].str.contains('Maverick')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can use this `Series` of `boolean` `True` and `False` values to index into our data!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "condition = data['text'].str.contains('Maverick')\n", "\n", "filtered_data = data[condition]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filtered_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### There is a whole list of other things you can do with your text data. \n", "\n", "**Some are listed below:**\n", "\n", "* `str.startswith()` and `str.endswith()` - checks to see if a string starts or ends with a given argument\n", "* `str.count()` - counts the number of appearances of a certain pattern\n", "* `str.numeric()` - checks to see if the string is numeric (e.g., `23123` is a digit whereas `213123abc` is not)\n", "* `str.split()` - splits the string on some deliminter and returns a dataframe of the string, split on the characters.\n", "\n", "There's plenty more and you can see the [documentation here for more](https://pandas.pydata.org/pandas-docs/stable/text.html).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What about transforming text data into a DataFrame?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "raw_text = [\"\"\"This is a giant series of sentences that you want to convert into a DataFrame containing \n", "the raw counts for each word. There are some abbr. and some punctuations here and there that make things more complicated.\n", "So how in the world do we turn this into something that we can build a machine learning model off of?\n", "\"\"\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Okay, so we want to turn the above into a DataFrame where every column is a different word, and each entry stores the number of times that word came up.\n", "\n", "We're going to use the `CountVectorizer` class in `sklearn`.\n", "\n", "A more [in-depth tutorial on how to use it, and more, can be found here](https://machinelearningmastery.com/prepare-text-data-machine-learning-scikit-learn/)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.feature_extraction.text import CountVectorizer" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initializing an empty CountVectorizer object\n", "count_vect = CountVectorizer()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Now we fit the object to our actual data\n", "counts = count_vect.fit_transform(raw_text)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This is a `sparse matrix` class. It saves our computer space.\n", "counts" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's use the `.todense()` function to turn this sparse matrix into something that can be transformed into a DataFrame\n", "word_counts_df = pd.DataFrame(data=counts.todense())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "word_counts_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great, but what do each of the columns mean?\n", "\n", "We can inspect the `count_vect.vocabulary_` attribute to find out." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "count_vect.vocabulary_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great. Now we know the words for each of the datasets\n", "\n", "**Your challenge**: Your challenge is to write some code so that you end converting the columns in `word_counts_df` to each of the words in `count_vect.vocabulary_`." ] }, { "cell_type": "code", "execution_count": 11, "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", "
abbrandarebuildcancomplicatedcontainingconvertcountsdataframe...therethingsthistoturnwantwewordworldyou
01211111111...2121112111
\n", "

1 rows × 45 columns

\n", "
" ], "text/plain": [ " abbr and are build can complicated containing convert counts \\\n", "0 1 2 1 1 1 1 1 1 1 \n", "\n", " dataframe ... there things this to turn want we word world you \n", "0 1 ... 2 1 2 1 1 1 2 1 1 1 \n", "\n", "[1 rows x 45 columns]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# If you're successful it should look like the output below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### Your code here\n", "\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Section 3: Dealing with Null Values\n", "To show you how to deal with null values, I'm going to make some simulated data of students." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "grades = np.random.choice(range(1, 13), 100) # chooses 100 random numbers between 1 - 12\n", "num_friends_or_none = list(range(0, 20)) + [None] * 5\n", "num_friends = np.random.choice(num_friends_or_none, 100)\n", "new_data = pd.DataFrame(data={'Grade': grades, '# Friends': num_friends})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_data.head(n=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### One way to deal with null values is to drop them" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_data['# Friends'].dropna()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### We can also drop any rows with nulls from the entire table." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_data.dropna()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Or we can replace the null values with an average" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "average_friends = new_data['# Friends'].mean()\n", "new_data['# Friends'].fillna(average_friends)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_data['# Friends'] = new_data['# Friends'].fillna(average_friends)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### What if instead of null values, there is something else that stands for missing values?\n", "Try the replace function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grades = np.random.choice(range(1, 13), 100) # chooses 100 random numbers between 1 - 12\n", "num_friends_or_none = list(range(0, 20)) + [\"Unknown\"] * 5\n", "num_friends = np.random.choice(num_friends_or_none, 100)\n", "unknown_data = pd.DataFrame(data={'Grade': grades, '# Friends': num_friends})\n", "unknown_data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "unknown_data.replace(\"Unknown\", 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Section 4: Now Let's Learn How to Standardize Data\n", "By that I mean to transform our data so that it has a mean of 0 and a standard deviation of 1.\n", "\n", "Why would we want to do this?\n", "\n", "\n", "Well often we will have strange parameter estimates on many models models if different bits of our data are in wildly different ranges.\n", "\n", "\n", "> Many researchers have noted the importance of standardizing variables for multivariate analysis. \n", ">\n", "> Otherwise, variables measured at different scales do not contribute equally to the analysis. \n", "\n", ">For example, in boundary detection, a variable that ranges between 0 and 100 will outweigh a variable that ranges between 0 and 1. Using these variables without standardization in effect gives the variable with the larger range a weight of 100 in the analysis. \n", "\n", ">Transforming the data to comparable scales can prevent this problem. Typical data standardization procedures equalize the range and/or data variability.\n", "\n", "[Source](https://www.biomedware.com/files/documentation/Preparing_data/Why_standardize_variables.htm)\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.preprocessing import StandardScaler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scaler = StandardScaler()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scaler.fit_transform(new_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above will transform the data so that all the columns have an average of 0 and a standard deviation of 1.\n", "\n", "You can read the full documentatio for the `StandardScaler` [here](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html).\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bonus: What if we would like to split up columns?\n", "\n", "Maybe you have data in a column that's a mashup between multiple values.\n", "\n", "For example, imagine if you have a column that stores values like:\n", "\n", "`'8th Grade - 13 years old'` and `'12th grade - 17 years old'` and you want to create two columns: `grade` and `age` to store the two separate bits of data.\n", "\n", "How do you do so?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# I'm going to generate some fake data here. Ignore the below>\n", "grades = np.random.choice(range(1, 13), 100) # chooses 100 random numbers between 1 - 12\n", "grades_and_ages = ['Grade {grade} - {age} years old'.format(grade=grade, age=grade+6) for grade in grades]\n", "num_friends_or_none = list(range(0, 20)) + [\"Unknown\"] * 5\n", "num_friends = np.random.choice(num_friends_or_none, 100)\n", "combined_data = pd.DataFrame(data={'Grade and Age': grades_and_ages, '# Friends': num_friends})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "combined_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Awesome, now let's split things up. We'll use the built in `.str.split()` function with the extra input `expand=True`.\n", "\n", "The `expand=True` will convert the splitted data into a `DataFrame` instead of keeping a list of values. \n", "\n", "(Try taking out `expand=True` and seeing what happens.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "combined_data['Grade and Age'].str.split(' - ', expand=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Your challenge**: Write some code that does the following:\n", "\n", "1. Removes the words 'Grade' and 'years old' from the data\n", "2. Takes these two columns and puts them back into the original `combined_data` DataFrame." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### Your code here\n", "\n", "\n", "\n", "\n" ] } ], "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.7.6" } }, "nbformat": 4, "nbformat_minor": 2 }