{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pandas\n", "\n", "[Pandas](http://pandas.pydata.org/) is a an open source library providing high-performance, easy-to-use data structures and data analysis tools. Pandas is particularly suited to the analysis of _tabular_ data, i.e. data that can can go into a table. In other words, if you can imagine the data in an Excel spreadsheet, then Pandas is the tool for the job.\n", "\n", "A [recent analysis](https://stackoverflow.blog/2017/09/06/incredible-growth-python/) of questions from Stack Overflow showed that python is the fastest growing and most widely used programming language in the world (in developed countries).\n", "\n", "![python growth](https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2017/09/growth_major_languages-1-1024x878.png)\n", "\n", "A [follow-up analysis](https://stackoverflow.blog/2017/09/14/python-growing-quickly/) showed that this growth is driven by the data science packages such as numpy, matplotlib, and especially pandas.\n", "\n", "![pandas growth](https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2017/09/related_tags_over_time-1-1024x1024.png)\n", "\n", "The exponential growth of pandas is due to the fact that it _just works_. It saves you time and helps you do science more efficiently and effictively.\n", "\n", "### Pandas capabilities (from the Pandas website):\n", "\n", "* A fast and efficient DataFrame object for data manipulation with integrated indexing;\n", "* Tools for reading and writing data between in-memory data structures and different formats: CSV and text files, Microsoft Excel, SQL databases, and the fast HDF5 format;\n", "* Intelligent data alignment and integrated handling of missing data: gain automatic label-based alignment in computations and easily manipulate messy data into an orderly form;\n", "* Flexible reshaping and pivoting of data sets;\n", "* Intelligent label-based slicing, fancy indexing, and subsetting of large data sets;\n", "* Columns can be inserted and deleted from data structures for size mutability;\n", "* Aggregating or transforming data with a powerful group by engine allowing split-apply-combine operations on data sets;\n", "* High performance merging and joining of data sets;\n", "* Hierarchical axis indexing provides an intuitive way of working with high-dimensional data in a lower-dimensional data structure;\n", "* Time series-functionality: date range generation and frequency conversion, moving window statistics, moving window linear regressions, date shifting and lagging. Even create domain-specific time offsets and join time series without losing data;\n", "* Highly optimized for performance, with critical code paths written in Cython or C.\n", "* Python with pandas is in use in a wide variety of academic and commercial domains, including Finance, Neuroscience, Economics, Statistics, Advertising, Web Analytics, and more.\n", "\n", "In this lecture, we will go over the basic capabilities of Pandas. It is a very deep library, and you will need to dig into the [documentation](http://pandas.pydata.org/pandas-docs/stable/) for more advanced usage.\n", "\n", "Pandas was created by [Wes McKinney](http://wesmckinney.com/). Many of the examples here are drawn from Wes McKinney's book [Python for Data Analysis](http://shop.oreilly.com/product/0636920023784.do), which includes a github repo of [code samples](https://github.com/wesm/pydata-book)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pandas Data Structures: Series\n", "\n", "A Series represents a one-dimensional array of data. The main difference between a Series and numpy array is that a Series has an _index_. The index contains the labels that we use to access the data.\n", "\n", "There are many ways to [create a Series](https://pandas.pydata.org/pandas-docs/stable/dsintro.html#series). We will just show a few." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names = ['Ryan', 'Chiara', 'Johnny']\n", "values = [35, 36, 1.8]\n", "ages = pd.Series(values, index=names)\n", "ages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Series have built in plotting methods." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.plot(kind='bar')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arithmetic operations and most numpy function can be applied to Series.\n", "An important point is that the Series keep their index during such operations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.log(ages) / ages**2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can access the underlying index object if we need to:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.index" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can get values back out using the index via the `.loc` attribute" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.loc['Johnny']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or by raw position using `.iloc`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.iloc[2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we need to, we can always get the raw data back out as well" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.values" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ages.index" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pandas Data Structures: DataFrame\n", "\n", "There is a lot more to Series, but they are limit to a single \"column\". A more useful Pandas data structure is the DataFrame. A DataFrame is basically a bunch of series that share the same index. It's a lot like a table in a spreadsheet.\n", "\n", "Below we create a DataFrame." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# first we create a dictionary\n", "data = {'age': [35, 36, 1.8],\n", " 'height': [180, 155, 83],\n", " 'weight': [72.5, np.nan, 11.3]}\n", "df = pd.DataFrame(data, index=['Ryan', 'Chiara', 'Johnny'])\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pandas handles missing data very elegantly, keeping track of it through all calculations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A wide range of statistical functions are available on both Series and DataFrames." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.min()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.mean()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.std()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can get a single column as a Series using python's getitem syntax on the DataFrame object." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['height']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...or using attribute syntax." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.height" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "New columns can easily be added to DataFrames" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['density'] = df.weight / df.height\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Merging Data\n", "\n", "Pandas supports a wide range of methods for merging different datasets. These are described extensively in the [documentation](https://pandas.pydata.org/pandas-docs/stable/merging.html). Here we just give a few examples." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "education = pd.Series(['PhD', 'PhD', None, 'masters'],\n", " index=['Ryan', 'Chiara', 'Johnny', 'Takaya'],\n", " name='education')\n", "# returns a new DataFrame\n", "df.join(education)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# returns a new DataFrame\n", "df.join(education, how='right')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# returns a new DataFrame\n", "df.reindex(['Ryan', 'Chiara', 'Johnny', 'Takaya', 'Kerry'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also index using a boolean series. This is very useful" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adults = df[df.age > 18]\n", "adults" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['is_adult'] = df.age > 18\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting\n", "\n", "DataFrames have all kinds of [useful plotting](https://pandas.pydata.org/pandas-docs/stable/visualization.html) built in." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.plot(kind='scatter', x='age', y='height', grid=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.plot(kind='bar')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Time Indexes\n", "\n", "Indexes are very powerful. They are a big part of why Pandas is so useful. There are different indices for different types of data. Time Indexes are especially great!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "two_years = pd.date_range(start='2014-01-01', end='2016-01-01', freq='D')\n", "timeseries = pd.Series(np.sin(2 *np.pi *two_years.dayofyear / 365),\n", " index=two_years)\n", "timeseries.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use python's slicing notation inside `.loc` to select a date range." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timeseries.loc['2015-01-01':'2015-07-01'].plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Stock Market Data\n", "\n", "* oops - Ryan's links don't work, so we will use static files\n", "\n", "Now we read some stock market data from Google finance. I have created direct links to Google and Apple stock price data." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#!curl -L -o goog.csv http://tinyurl.com/rces-goog\n", "#!curl -L -o aapl.csv http://tinyurl.com/rces-aapl-csv\n", "! cp /home/pangeo/notebooks/GOOGL.csv goog.csv\n", "! cp /home/pangeo/notebooks/AAPL.csv aapl.csv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "! head goog.csv" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that this is well-formated, tidy CSV data, ready for immediate ingestion into Pandas.\n", "We use Pandas' amazing [read_csv](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) function to do this." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "goog = pd.read_csv('goog.csv')\n", "goog.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not bad! But we can do better by giving read_csv some hints." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "goog = pd.read_csv('goog.csv', parse_dates=[0], index_col=0)\n", "goog.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "goog.info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "goog.Close.plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "aapl = pd.read_csv('aapl.csv', parse_dates=[0], index_col=0)\n", "aapl.info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "aapl_close = aapl.Close.rename('aapl')\n", "goog_close = goog.Close.rename('goog')\n", "stocks = pd.concat([aapl_close, goog_close], axis=1)\n", "stocks.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stocks.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pandas knows how to take correlations. And [tons of other computations](https://pandas.pydata.org/pandas-docs/stable/computation.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stocks.corr()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because it understands times, it can do really cool stuff like resampling." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resample by taking the mean over each month\n", "\n", "fig, ax = plt.subplots()\n", "stocks.resample('MS').mean().plot(ax=ax, colors=['r', 'b'])\n", "# and each year\n", "stocks.resample('AS').mean().plot(ax=ax, colors=['r', 'b'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The string `QS` means \"month start. The string `AS` mean \"year start\". There is a long list of possible [frequency aliases](https://pandas.pydata.org/pandas-docs/stable/timeseries.html#timeseries-offset-aliases).\n", "\n", "We can also apply other reduction operations with resample. These are described in the [resample docs](https://pandas.pydata.org/pandas-docs/stable/timeseries.html#resampling)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get resample object\n", "rs = stocks.goog.resample('MS')\n", "# standard deviation of each month\n", "rs.std().plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Temperature Data\n", "\n", "We download some timeseries data from the [Berkeley Earth(http://berkeleyearth.org/) surface temperature dataset. This is timeseries data from various locations around earth. Let's get our local temperatures." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "! curl -o nyc_temp.txt http://berkeleyearth.lbl.gov/auto/Local/TAVG/Text/40.99N-74.56W-TAVG-Trend.txt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we examine this data, we see it is NOT a well formated CSV file. Loading it will be a bit painful, but Pandas makes the job retatively easy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "! head -72 nyc_temp.txt | tail -8" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "##### http://berkeleyearth.lbl.gov/locations/40.99N-74.56W\n", "# http://berkeleyearth.lbl.gov/auto/Local/TAVG/Text/40.99N-74.56W-TAVG-Trend.txt\n", "\n", "\n", "#temp = pd.read_csv('nyc_temp.txt')\n", "\n", "col_names = ['year', 'month', 'monthly_anom'] + 10*[]\n", "temp = pd.read_csv('nyc_temp.txt',\n", " header=None, usecols=[0, 1, 2], names=col_names,\n", " delim_whitespace=True, comment='%')\n", "\n", "temp.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# need a day\n", "date_df = temp.drop('monthly_anom', axis=1)\n", "date_df['day'] = 1\n", "date_index = pd.DatetimeIndex(pd.to_datetime(date_df))\n", "temp = temp.set_index(date_index).drop(['year', 'month'], axis=1)\n", "temp.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temp.plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "temp.plot(ax=ax)\n", "temp.resample('AS').mean().plot(ax=ax)\n", "temp.resample('10AS').mean().plot(ax=ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pandas can do both time-based resampling and operation over fixed-length rolling windows. These are very similar but distinct; see [discussion in Pandas docs](https://pandas.pydata.org/pandas-docs/stable/computation.html#time-aware-rolling-vs-resampling)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# more advanced operation on rolling windows\n", "def difference_max_min(data):\n", " return data.max() - data.min()\n", "\n", "rw = temp.rolling('365D')\n", "rw.apply(difference_max_min).plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To create a \"climatology\" (i.e. the average of all same months), we can use Pandas' [groupby](https://pandas.pydata.org/pandas-docs/stable/groupby.html) functionality." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# diurnal cycle has been removed!\n", "temp.groupby(temp.index.month).mean().plot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# find the hottest years\n", "temp.groupby(temp.index.year).mean().sort_values('monthly_anom', ascending=False).head(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Groupby\n", "\n", "Now we will explore groupby's capabilities more in a public dataset from the City of New York: the [Rat Information Portal](The Rat Information Portal)!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# https://data.cityofnewyork.us/Health/Rats/amyk-xiv9\n", "rats = pd.read_csv('https://data.cityofnewyork.us/api/views/amyk-xiv9/rows.csv',\n", " parse_dates=['APPROVED_DATE', 'INSPECTION_DATE'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats.info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's do some grouping to explore the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats.groupby('INSPECTION_TYPE')['INSPECTION_TYPE'].count()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats.groupby('BORO_CODE')['BORO_CODE'].count().head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats.groupby('STREET_NAME')['STREET_NAME'].count().head(20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This dataset clearly needs some cleaning. We can Pandas' [text features](https://pandas.pydata.org/pandas-docs/stable/text.html) to strip the whitespace out of the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# clean up street name\n", "street_names_cleaned = rats.STREET_NAME.str.strip()\n", "street_names_cleaned.groupby(street_names_cleaned).count().head(20)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "count = street_names_cleaned.groupby(street_names_cleaned).count()\n", "count.sort_values(ascending=False).head(20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get a better idea of the geography, let's plot the locations of the inspections. But first let's look at the statistics." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rats[['LATITUDE', 'LONGITUDE']].describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are clearly some weird outliers in the location data. We need to strip these out before plotting." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "valid_latlon = rats[(rats.LATITUDE > 30) & (rats.LONGITUDE < -70)]\n", "valid_latlon.plot.hexbin('LONGITUDE', 'LATITUDE', C='BORO_CODE', cmap='Set1')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# https://github.com/pandas-dev/pandas/issues/10678\n", "valid_latlon.plot.hexbin('LONGITUDE', 'LATITUDE', sharex=False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "valid_latlon.plot.hexbin('LONGITUDE', 'LATITUDE', sharex=False, bins='log', cmap='magma')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "manhattan_rats = valid_latlon[valid_latlon.BORO_CODE==1]\n", "manhattan_rats.plot.hexbin('LONGITUDE', 'LATITUDE', sharex=False, bins='log', cmap='magma')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inspection_date = pd.DatetimeIndex(rats.INSPECTION_DATE)\n", "\n", "fig, ax = plt.subplots()\n", "rats.groupby(inspection_date.weekday)['JOB_ID'].count().plot(kind='bar', ax=ax)\n", "ax.set_xlabel('weekday');" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "rats.groupby(inspection_date.hour)['JOB_ID'].count().plot(kind='bar', ax=ax)\n", "ax.set_xlabel('hour');" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "rats.groupby(inspection_date.month)['JOB_ID'].count().plot(kind='bar', ax=ax)\n", "ax.set_xlabel('month')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "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.5" } }, "nbformat": 4, "nbformat_minor": 2 }