{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "
\n", "\"Unidata\n", "
\n", "\n", "

Introduction to Pandas

\n", "

Unidata Python Workshop

\n", "\n", "
\n", "
\n", "\n", "
\n", "\n", "\n", "## Overview:\n", "\n", "* **Teaching:** 35 minutes\n", "* **Exercises:** 40 minutes\n", "\n", "### Questions\n", "1. What is Pandas?\n", "1. What are the basic Pandas data structures?\n", "1. How can I read data into Pandas?\n", "1. What are some of the data operations available in Pandas?\n", "\n", "### Objectives\n", "1. Data Series\n", "1. Data Frames\n", "1. Loading Data in Pandas\n", "1. Missing Data\n", "1. Manipulating Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Data Series\n", "Data series are one of the fundamental data structures in Pandas. You can think of them like a dictionary; they have a key (index) and value (data/values) like a dictionary, but also have some handy functionality attached to them.\n", "\n", "To start out, let's create a series from scratch. We'll imagine these are temperature observations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "temperatures = pd.Series([23, 20, 25, 18])\n", "temperatures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The values on the left are the index (zero based integers by default) and on the right are the values. Notice that the data type is an integer. Any NumPy datatype is acceptable in a series.\n", "\n", "That's great, but it'd be more useful if the station were associated with those values. In fact you could say we want the values *indexed* by station name." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures = pd.Series([23, 20, 25, 18], index=['TOP', 'OUN', 'DAL', 'DEN'])\n", "temperatures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, very similar to a dictionary, we can use the index to access and modify elements." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures['DAL']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures[['DAL', 'OUN']]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also do basic filtering, math, etc." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures[temperatures > 20]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures + 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember how I said that series are like dictionaries? We can create a series straight from a dictionary." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dps = {'TOP': 14,\n", " 'OUN': 18,\n", " 'DEN': 9,\n", " 'PHX': 11,\n", " 'DAL': 23}\n", "\n", "dewpoints = pd.Series(dps)\n", "dewpoints" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's also easy to check and see if an index exists in a given series:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'PHX' in dewpoints" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'PHX' in temperatures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Series have a name attribute and their index has a name attribute." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures.name = 'temperature'\n", "temperatures.index.name = 'station'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperatures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "* Create a series of pressures for stations TOP, OUN, DEN, and DAL (assign any values you like).\n", "* Set the series name and series index name.\n", "* Print the pressures for all stations which have a dewpoint below 15." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Your code goes here\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# %load solutions/make_series.py\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Data Frames\n", "Series are great, but what about a bunch of related series? Something like a table or a spreadsheet? Enter the data frame. A data frame can be thought of as a dictionary of data series. They have indexes for their rows and their columns. Each data series can be of a different type, but they will all share a common index.\n", "\n", "The easiest way to create a data frame by hand is to use a dictionary." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = {'station': ['TOP', 'OUN', 'DEN', 'DAL'],\n", " 'temperature': [23, 20, 25, 18],\n", " 'dewpoint': [14, 18, 9, 23]}\n", "\n", "df = pd.DataFrame(data)\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can access columns (data series) using dictionary type notation or attribute type notation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['temperature']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.dewpoint" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the index is shared and that the name of the column is attached as the series name.\n", "\n", "You can also create a new column and assign values. If I only pass a scalar it is duplicated." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['wspeed'] = 0.\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's set the index to be the station." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.index = df.station\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Well, that's close, but we now have a redundant column, so let's get rid of it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = df.drop('station', axis='columns')\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also add data and order it by providing index values. Note that the next cell contains data that's \"out of order\" compared to the dataframe shown above. However, by providing the index that corresponds to each value, the data is organized correctly into the dataframe." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['pressure'] = pd.Series([1010,1000,998,1018], index=['DEN','TOP','DAL','OUN'])\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's get a row from the dataframe instead of a column." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.loc['DEN']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can even transpose the data easily if we needed that do make things easier to merge/munge later." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Look at the `values` attribute to access the data as a 1D or 2D array for series and data frames recpectively." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.values" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.temperature.values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "* Add a series of rain observations to the existing data frame.\n", "* Apply an instrument correction of -2 to the dewpoint observations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Your code goes here\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# %load solutions/rain_obs.py\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Loading Data in Pandas\n", "The real power of pandas is in manupulating and summarizing large sets of tabular data. To do that, we'll need a large set of tabular data. We've included a file in this directory called `JAN17_CO_ASOS.txt` that has all of the ASOS observations for several stations in Colorado for January of 2017. It's a few hundred thousand rows of data in a tab delimited format. Let's load it into Pandas." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv('Jan17_CO_ASOS.txt', sep='\\t')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv('Jan17_CO_ASOS.txt', sep='\\t', parse_dates=['valid'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv('Jan17_CO_ASOS.txt', sep='\\t', parse_dates=['valid'], na_values='M')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look in detail at those column names. Turns out we need to do some cleaning of this file. Welcome to real world data analysis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.columns" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.columns = ['station', 'time', 'temperature', 'dewpoint', 'pressure']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For other formats of data CSV, fixed width, etc. that are tools to read it as well. You can even read excel files straight into Pandas." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Missing Data\n", "We've already dealt with some missing data by turning the 'M' string into actual NaN's while reading the file in. We can do one better though and delete any rows that have all values missing. There are similar operations that could be performed for columns. You can even drop if any values are missing, all are missing, or just those you specify are missing." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(df)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = df.dropna(axis='rows', how='all', subset=['temperature', 'dewpoint', 'pressure'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(df)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "Our dataframe `df` has data in which we dropped any entries that were missing all of the temperature, dewpoint and pressure observations. Let's modify our command some and create a new dataframe `df2` that only keeps observations that have all three variables (i.e. if a pressure is missing, the whole entry is dropped). This is useful if you were doing some computation that requires a complete observation to work." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Your code goes here\n", "# df2 = " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# %load solutions/drop_obs.py\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lastly, we still have the original index values. Let's reindex to a new zero-based index for only the rows that have valid data in them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.reset_index(drop=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Manipulating Data\n", "We can now take our data and do some intersting things with it. Let's start with a simple min/max." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(f'Min: {df.temperature.min()}\\nMax: {df.temperature.max()}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also do some useful statistics on data with attached methods like corr for correlation coefficient." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.temperature.corr(df.dewpoint)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also call a `groupby` on the data frame to start getting some summary information for each station." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby('station').mean()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "Calculate the min, max, and standard deviation of the temperature field grouped by each station." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Calculate min\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Calculate max\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Calculate standard deviation\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# %load solutions/calc_stats.py\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let me show you how to do all of that and more in a single call." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby('station').describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's suppose we're going to make a meteogram or similar and want to get all of the data for a single station." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby('station').get_group('0CO').head().reset_index(drop=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "* Round the temperature column to whole degrees.\n", "* Group the observations by temperature and use the count method to see how many instances of the rounded temperatures there are in the dataset." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Your code goes here\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# %load solutions/temperature_count.py\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\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.3" } }, "nbformat": 4, "nbformat_minor": 2 }