{ "cells": [ { "cell_type": "markdown", "id": "43f31fc5", "metadata": {}, "source": [ "\n", "\n", "
\n", " \n", " \"QuantEcon\"\n", " \n", "
" ] }, { "cell_type": "markdown", "id": "a6dec173", "metadata": {}, "source": [ "# Pandas\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "id": "97a6d543", "metadata": {}, "source": [ "## Contents\n", "\n", "- [Pandas](#Pandas) \n", " - [Overview](#Overview) \n", " - [Series](#Series) \n", " - [DataFrames](#DataFrames) \n", " - [On-Line Data Sources](#On-Line-Data-Sources) \n", " - [Exercises](#Exercises) " ] }, { "cell_type": "markdown", "id": "00995dbc", "metadata": {}, "source": [ "In addition to what’s in Anaconda, this lecture will need the following libraries:" ] }, { "cell_type": "code", "execution_count": null, "id": "6850a629", "metadata": { "hide-output": false }, "outputs": [], "source": [ "!pip install --upgrade pandas-datareader\n", "!pip install --upgrade yfinance" ] }, { "cell_type": "markdown", "id": "81b74b64", "metadata": {}, "source": [ "## Overview\n", "\n", "[Pandas](http://pandas.pydata.org/) is a package of fast, efficient data analysis tools for Python.\n", "\n", "Its popularity has surged in recent years, coincident with the rise\n", "of fields such as data science and machine learning.\n", "\n", "Here’s a popularity comparison over time against Matlab and STATA courtesy of Stack Overflow Trends\n", "\n", "![https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_vs_rest.png](https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_vs_rest.png)\n", "\n", " \n", "Just as [NumPy](http://www.numpy.org/) provides the basic array data type plus core array operations, pandas\n", "\n", "1. defines fundamental structures for working with data and \n", "1. endows them with methods that facilitate operations such as \n", " - reading in data \n", " - adjusting indices \n", " - working with dates and time series \n", " - sorting, grouping, re-ordering and general data munging [1] \n", " - dealing with missing values, etc., etc. \n", "\n", "\n", "More sophisticated statistical functionality is left to other packages, such\n", "as [statsmodels](http://www.statsmodels.org/) and [scikit-learn](http://scikit-learn.org/), which are built on top of pandas.\n", "\n", "This lecture will provide a basic introduction to pandas.\n", "\n", "Throughout the lecture, we will assume that the following imports have taken\n", "place" ] }, { "cell_type": "code", "execution_count": null, "id": "acd6e721", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%matplotlib inline\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "plt.rcParams[\"figure.figsize\"] = [10,8] # Set default figure size\n", "import requests" ] }, { "cell_type": "markdown", "id": "381dcfe8", "metadata": {}, "source": [ "Two important data types defined by pandas are `Series` and `DataFrame`.\n", "\n", "You can think of a `Series` as a “column” of data, such as a collection of observations on a single variable.\n", "\n", "A `DataFrame` is a two-dimensional object for storing related columns of data." ] }, { "cell_type": "markdown", "id": "1f699b29", "metadata": {}, "source": [ "## Series\n", "\n", "\n", "\n", "Let’s start with Series.\n", "\n", "We begin by creating a series of four random observations" ] }, { "cell_type": "code", "execution_count": null, "id": "893c56a9", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s = pd.Series(np.random.randn(4), name='daily returns')\n", "s" ] }, { "cell_type": "markdown", "id": "d2b85317", "metadata": {}, "source": [ "Here you can imagine the indices `0, 1, 2, 3` as indexing four listed\n", "companies, and the values being daily returns on their shares.\n", "\n", "Pandas `Series` are built on top of NumPy arrays and support many similar\n", "operations" ] }, { "cell_type": "code", "execution_count": null, "id": "ea048f2d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s * 100" ] }, { "cell_type": "code", "execution_count": null, "id": "224beb30", "metadata": { "hide-output": false }, "outputs": [], "source": [ "np.abs(s)" ] }, { "cell_type": "markdown", "id": "767cb44c", "metadata": {}, "source": [ "But `Series` provide more than NumPy arrays.\n", "\n", "Not only do they have some additional (statistically oriented) methods" ] }, { "cell_type": "code", "execution_count": null, "id": "e16dd04f", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s.describe()" ] }, { "cell_type": "markdown", "id": "83d9fb09", "metadata": {}, "source": [ "But their indices are more flexible" ] }, { "cell_type": "code", "execution_count": null, "id": "c548f672", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s.index = ['AMZN', 'AAPL', 'MSFT', 'GOOG']\n", "s" ] }, { "cell_type": "markdown", "id": "dadc406c", "metadata": {}, "source": [ "Viewed in this way, `Series` are like fast, efficient Python dictionaries\n", "(with the restriction that the items in the dictionary all have the same\n", "type—in this case, floats).\n", "\n", "In fact, you can use much of the same syntax as Python dictionaries" ] }, { "cell_type": "code", "execution_count": null, "id": "04ecb3e8", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s['AMZN']" ] }, { "cell_type": "code", "execution_count": null, "id": "2d4d2717", "metadata": { "hide-output": false }, "outputs": [], "source": [ "s['AMZN'] = 0\n", "s" ] }, { "cell_type": "code", "execution_count": null, "id": "8d864b64", "metadata": { "hide-output": false }, "outputs": [], "source": [ "'AAPL' in s" ] }, { "cell_type": "markdown", "id": "80a36089", "metadata": {}, "source": [ "## DataFrames\n", "\n", "\n", "\n", "While a `Series` is a single column of data, a `DataFrame` is several columns, one for each variable.\n", "\n", "In essence, a `DataFrame` in pandas is analogous to a (highly optimized) Excel spreadsheet.\n", "\n", "Thus, it is a powerful tool for representing and analyzing data that are naturally organized into rows and columns, often with descriptive indexes for individual rows and individual columns.\n", "\n", "Let’s look at an example that reads data from the CSV file `pandas/data/test_pwt.csv`, which is taken from the [Penn World Tables](https://www.rug.nl/ggdc/productivity/pwt/pwt-releases/pwt-7.0).\n", "\n", "The dataset contains the following indicators\n", "\n", "|Variable Name|Description|\n", "|:------------------------------------------------:|:------------------------------------------------:|\n", "|POP|Population (in thousands)|\n", "|XRAT|Exchange Rate to US Dollar|\n", "|tcgdp|Total PPP Converted GDP (in million international dollar)|\n", "|cc|Consumption Share of PPP Converted GDP Per Capita (%)|\n", "|cg|Government Consumption Share of PPP Converted GDP Per Capita (%)|\n", "We’ll read this in from a URL using the `pandas` function `read_csv`." ] }, { "cell_type": "code", "execution_count": null, "id": "1971bd29", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df = pd.read_csv('https://raw.githubusercontent.com/QuantEcon/lecture-python-programming/master/source/_static/lecture_specific/pandas/data/test_pwt.csv')\n", "type(df)" ] }, { "cell_type": "markdown", "id": "3f374fd2", "metadata": {}, "source": [ "Here’s the content of `test_pwt.csv`" ] }, { "cell_type": "code", "execution_count": null, "id": "c76d7697", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df" ] }, { "cell_type": "markdown", "id": "be132465", "metadata": {}, "source": [ "### Select Data by Position\n", "\n", "In practice, one thing that we do all the time is to find, select and work with a subset of the data of our interests.\n", "\n", "We can select particular rows using standard Python array slicing notation" ] }, { "cell_type": "code", "execution_count": null, "id": "5c21f0fc", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[2:5]" ] }, { "cell_type": "markdown", "id": "ec2abf9e", "metadata": {}, "source": [ "To select columns, we can pass a list containing the names of the desired columns represented as strings" ] }, { "cell_type": "code", "execution_count": null, "id": "213dadbb", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[['country', 'tcgdp']]" ] }, { "cell_type": "markdown", "id": "e665457c", "metadata": {}, "source": [ "To select both rows and columns using integers, the `iloc` attribute should be used with the format `.iloc[rows, columns]`." ] }, { "cell_type": "code", "execution_count": null, "id": "28f375e4", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.iloc[2:5, 0:4]" ] }, { "cell_type": "markdown", "id": "9a7a489e", "metadata": {}, "source": [ "To select rows and columns using a mixture of integers and labels, the `loc` attribute can be used in a similar way" ] }, { "cell_type": "code", "execution_count": null, "id": "f746bec5", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.loc[df.index[2:5], ['country', 'tcgdp']]" ] }, { "cell_type": "markdown", "id": "80d7ac48", "metadata": {}, "source": [ "### Select Data by Conditions\n", "\n", "Instead of indexing rows and columns using integers and names, we can also obtain a sub-dataframe of our interests that satisfies certain (potentially complicated) conditions.\n", "\n", "This section demonstrates various ways to do that.\n", "\n", "The most straightforward way is with the `[]` operator." ] }, { "cell_type": "code", "execution_count": null, "id": "d3b25427", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[df.POP >= 20000]" ] }, { "cell_type": "markdown", "id": "c289d0f8", "metadata": {}, "source": [ "To understand what is going on here, notice that `df.POP >= 20000` returns a series of boolean values." ] }, { "cell_type": "code", "execution_count": null, "id": "2534a347", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.POP >= 20000" ] }, { "cell_type": "markdown", "id": "4f717f55", "metadata": {}, "source": [ "In this case, `df[___]` takes a series of boolean values and only returns rows with the `True` values.\n", "\n", "Take one more example," ] }, { "cell_type": "code", "execution_count": null, "id": "04827532", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[(df.country.isin(['Argentina', 'India', 'South Africa'])) & (df.POP > 40000)]" ] }, { "cell_type": "markdown", "id": "5617e444", "metadata": {}, "source": [ "However, there is another way of doing the same thing, which can be slightly faster for large dataframes, with more natural syntax." ] }, { "cell_type": "code", "execution_count": null, "id": "3650280c", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# the above is equivalent to \n", "df.query(\"POP >= 20000\")" ] }, { "cell_type": "code", "execution_count": null, "id": "2568417a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.query(\"country in ['Argentina', 'India', 'South Africa'] and POP > 40000\")" ] }, { "cell_type": "markdown", "id": "4bc520ad", "metadata": {}, "source": [ "We can also allow arithmetic operations between different columns." ] }, { "cell_type": "code", "execution_count": null, "id": "979847fb", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[(df.cc + df.cg >= 80) & (df.POP <= 20000)]" ] }, { "cell_type": "code", "execution_count": null, "id": "88959bc3", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# the above is equivalent to \n", "df.query(\"cc + cg >= 80 & POP <= 20000\")" ] }, { "cell_type": "markdown", "id": "4994e6e9", "metadata": {}, "source": [ "For example, we can use the conditioning to select the country with the largest household consumption - gdp share `cc`." ] }, { "cell_type": "code", "execution_count": null, "id": "c19af218", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.loc[df.cc == max(df.cc)]" ] }, { "cell_type": "markdown", "id": "bb045d47", "metadata": {}, "source": [ "When we only want to look at certain columns of a selected sub-dataframe, we can use the above conditions with the `.loc[__ , __]` command.\n", "\n", "The first argument takes the condition, while the second argument takes a list of columns we want to return." ] }, { "cell_type": "code", "execution_count": null, "id": "d045b98a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.loc[(df.cc + df.cg >= 80) & (df.POP <= 20000), ['country', 'year', 'POP']]" ] }, { "cell_type": "markdown", "id": "cefdea16", "metadata": {}, "source": [ "**Application: Subsetting Dataframe**\n", "\n", "Real-world datasets can be [enormous](https://developers.google.com/machine-learning/data-prep/construct/collect/data-size-quality).\n", "\n", "It is sometimes desirable to work with a subset of data to enhance computational efficiency and reduce redundancy.\n", "\n", "Let’s imagine that we’re only interested in the population (`POP`) and total GDP (`tcgdp`).\n", "\n", "One way to strip the data frame `df` down to only these variables is to overwrite the dataframe using the selection method described above" ] }, { "cell_type": "code", "execution_count": null, "id": "e53ba06f", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df_subset = df[['country', 'POP', 'tcgdp']]\n", "df_subset" ] }, { "cell_type": "markdown", "id": "d5bf1fb2", "metadata": {}, "source": [ "We can then save the smaller dataset for further analysis." ] }, { "cell_type": "markdown", "id": "fcc573d0", "metadata": { "hide-output": false }, "source": [ "```python3\n", "df_subset.to_csv('pwt_subset.csv', index=False)\n", "```\n" ] }, { "cell_type": "markdown", "id": "bc163c05", "metadata": {}, "source": [ "### Apply Method\n", "\n", "Another widely used Pandas method is `df.apply()`.\n", "\n", "It applies a function to each row/column and returns a series.\n", "\n", "This function can be some built-in functions like the `max` function, a `lambda` function, or a user-defined function.\n", "\n", "Here is an example using the `max` function" ] }, { "cell_type": "code", "execution_count": null, "id": "d965c507", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df[['year', 'POP', 'XRAT', 'tcgdp', 'cc', 'cg']].apply(max)" ] }, { "cell_type": "markdown", "id": "d776854b", "metadata": {}, "source": [ "This line of code applies the `max` function to all selected columns.\n", "\n", "`lambda` function is often used with `df.apply()` method\n", "\n", "A trivial example is to return itself for each row in the dataframe" ] }, { "cell_type": "code", "execution_count": null, "id": "2882f786", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.apply(lambda row: row, axis=1)" ] }, { "cell_type": "markdown", "id": "9406f36a", "metadata": {}, "source": [ ">**Note**\n", ">\n", ">For the `.apply()` method\n", "\n", "- axis = 0 – apply function to each column (variables) \n", "- axis = 1 – apply function to each row (observations) \n", "- axis = 0 is the default parameter \n", "\n", "\n", "We can use it together with `.loc[]` to do some more advanced selection." ] }, { "cell_type": "code", "execution_count": null, "id": "8bd1c2da", "metadata": { "hide-output": false }, "outputs": [], "source": [ "complexCondition = df.apply(\n", " lambda row: row.POP > 40000 if row.country in ['Argentina', 'India', 'South Africa'] else row.POP < 20000, \n", " axis=1), ['country', 'year', 'POP', 'XRAT', 'tcgdp']" ] }, { "cell_type": "markdown", "id": "68090cc0", "metadata": {}, "source": [ "`df.apply()` here returns a series of boolean values rows that satisfies the condition specified in the if-else statement.\n", "\n", "In addition, it also defines a subset of variables of interest." ] }, { "cell_type": "code", "execution_count": null, "id": "cb6fff99", "metadata": { "hide-output": false }, "outputs": [], "source": [ "complexCondition" ] }, { "cell_type": "markdown", "id": "9c3a936b", "metadata": {}, "source": [ "When we apply this condition to the dataframe, the result will be" ] }, { "cell_type": "code", "execution_count": null, "id": "a69db826", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.loc[complexCondition]" ] }, { "cell_type": "markdown", "id": "313b34f6", "metadata": {}, "source": [ "### Make Changes in DataFrames\n", "\n", "The ability to make changes in dataframes is important to generate a clean dataset for future analysis.\n", "\n", "**1.** We can use `df.where()` conveniently to “keep” the rows we have selected and replace the rest rows with any other values" ] }, { "cell_type": "code", "execution_count": null, "id": "d57b22b0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.where(df.POP >= 20000, False)" ] }, { "cell_type": "markdown", "id": "5a47c04e", "metadata": {}, "source": [ "**2.** We can simply use `.loc[]` to specify the column that we want to modify, and assign values" ] }, { "cell_type": "code", "execution_count": null, "id": "684dde2a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.loc[df.cg == max(df.cg), 'cg'] = np.nan\n", "df" ] }, { "cell_type": "markdown", "id": "647ce30f", "metadata": {}, "source": [ "**3.** We can use the `.apply()` method to modify *rows/columns as a whole*" ] }, { "cell_type": "code", "execution_count": null, "id": "bfdc63a2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def update_row(row):\n", " # modify POP\n", " row.POP = np.nan if row.POP<= 10000 else row.POP\n", "\n", " # modify XRAT\n", " row.XRAT = row.XRAT / 10\n", " return row\n", "\n", "df.apply(update_row, axis=1)" ] }, { "cell_type": "markdown", "id": "fa0b7441", "metadata": {}, "source": [ "**4.** We can use the `.applymap()` method to modify all *individual entries* in the dataframe altogether." ] }, { "cell_type": "code", "execution_count": null, "id": "49ce3d77", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Round all decimal numbers to 2 decimal places\n", "df.applymap(lambda x : round(x,2) if type(x)!=str else x)" ] }, { "cell_type": "markdown", "id": "e047fc0e", "metadata": {}, "source": [ "**Application: Missing Value Imputation**\n", "\n", "Replacing missing values is an important step in data munging.\n", "\n", "Let’s randomly insert some NaN values" ] }, { "cell_type": "code", "execution_count": null, "id": "cc541396", "metadata": { "hide-output": false }, "outputs": [], "source": [ "for idx in list(zip([0, 3, 5, 6], [3, 4, 6, 2])):\n", " df.iloc[idx] = np.nan\n", "\n", "df" ] }, { "cell_type": "markdown", "id": "30aa935c", "metadata": {}, "source": [ "The `zip()` function here creates pairs of values from the two lists (i.e. [0,3], [3,4] …)\n", "\n", "We can use the `.applymap()` method again to replace all missing values with 0" ] }, { "cell_type": "code", "execution_count": null, "id": "c14b5148", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# replace all NaN values by 0\n", "def replace_nan(x):\n", " if type(x)!=str:\n", " return 0 if np.isnan(x) else x\n", " else:\n", " return x\n", "\n", "df.applymap(replace_nan)" ] }, { "cell_type": "markdown", "id": "567fc8f9", "metadata": {}, "source": [ "Pandas also provides us with convenient methods to replace missing values.\n", "\n", "For example, single imputation using variable means can be easily done in pandas" ] }, { "cell_type": "code", "execution_count": null, "id": "dd3a96cd", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df = df.fillna(df.iloc[:,2:8].mean())\n", "df" ] }, { "cell_type": "markdown", "id": "e30bd7ba", "metadata": {}, "source": [ "Missing value imputation is a big area in data science involving various machine learning techniques.\n", "\n", "There are also more [advanced tools](https://scikit-learn.org/stable/modules/impute.html) in python to impute missing values." ] }, { "cell_type": "markdown", "id": "e19e9e6d", "metadata": {}, "source": [ "### Standardization and Visualization\n", "\n", "Let’s imagine that we’re only interested in the population (`POP`) and total GDP (`tcgdp`).\n", "\n", "One way to strip the data frame `df` down to only these variables is to overwrite the dataframe using the selection method described above" ] }, { "cell_type": "code", "execution_count": null, "id": "2a621a24", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df = df[['country', 'POP', 'tcgdp']]\n", "df" ] }, { "cell_type": "markdown", "id": "2ccf0a08", "metadata": {}, "source": [ "Here the index `0, 1,..., 7` is redundant because we can use the country names as an index.\n", "\n", "To do this, we set the index to be the `country` variable in the dataframe" ] }, { "cell_type": "code", "execution_count": null, "id": "e715c1d3", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df = df.set_index('country')\n", "df" ] }, { "cell_type": "markdown", "id": "32c4f721", "metadata": {}, "source": [ "Let’s give the columns slightly better names" ] }, { "cell_type": "code", "execution_count": null, "id": "ed73ad8f", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df.columns = 'population', 'total GDP'\n", "df" ] }, { "cell_type": "markdown", "id": "7d5f513c", "metadata": {}, "source": [ "The `population` variable is in thousands, let’s revert to single units" ] }, { "cell_type": "code", "execution_count": null, "id": "64f2b724", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df['population'] = df['population'] * 1e3\n", "df" ] }, { "cell_type": "markdown", "id": "ddb138e8", "metadata": {}, "source": [ "Next, we’re going to add a column showing real GDP per capita, multiplying by 1,000,000 as we go because total GDP is in millions" ] }, { "cell_type": "code", "execution_count": null, "id": "8a4a7641", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df['GDP percap'] = df['total GDP'] * 1e6 / df['population']\n", "df" ] }, { "cell_type": "markdown", "id": "46322942", "metadata": {}, "source": [ "One of the nice things about pandas `DataFrame` and `Series` objects is that they have methods for plotting and visualization that work through Matplotlib.\n", "\n", "For example, we can easily generate a bar plot of GDP per capita" ] }, { "cell_type": "code", "execution_count": null, "id": "3565a0ab", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ax = df['GDP percap'].plot(kind='bar')\n", "ax.set_xlabel('country', fontsize=12)\n", "ax.set_ylabel('GDP per capita', fontsize=12)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "643d8978", "metadata": {}, "source": [ "At the moment the data frame is ordered alphabetically on the countries—let’s change it to GDP per capita" ] }, { "cell_type": "code", "execution_count": null, "id": "559f7c47", "metadata": { "hide-output": false }, "outputs": [], "source": [ "df = df.sort_values(by='GDP percap', ascending=False)\n", "df" ] }, { "cell_type": "markdown", "id": "e7280805", "metadata": {}, "source": [ "Plotting as before now yields" ] }, { "cell_type": "code", "execution_count": null, "id": "2d9a1df9", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ax = df['GDP percap'].plot(kind='bar')\n", "ax.set_xlabel('country', fontsize=12)\n", "ax.set_ylabel('GDP per capita', fontsize=12)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "0177c5bd", "metadata": {}, "source": [ "## On-Line Data Sources\n", "\n", "\n", "\n", "Python makes it straightforward to query online databases programmatically.\n", "\n", "An important database for economists is [FRED](https://research.stlouisfed.org/fred2/) — a vast collection of time series data maintained by the St. Louis Fed.\n", "\n", "For example, suppose that we are interested in the [unemployment rate](https://research.stlouisfed.org/fred2/series/UNRATE).\n", "\n", "Via FRED, the entire series for the US civilian unemployment rate can be downloaded directly by entering\n", "this URL into your browser (note that this requires an internet connection)" ] }, { "cell_type": "markdown", "id": "2350ba88", "metadata": { "hide-output": false }, "source": [ "```text\n", "https://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv\n", "```\n" ] }, { "cell_type": "markdown", "id": "1ebd36f8", "metadata": {}, "source": [ "(Equivalently, click here: [https://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv](https://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv))\n", "\n", "This request returns a CSV file, which will be handled by your default application for this class of files.\n", "\n", "Alternatively, we can access the CSV file from within a Python program.\n", "\n", "This can be done with a variety of methods.\n", "\n", "We start with a relatively low-level method and then return to pandas." ] }, { "cell_type": "markdown", "id": "07318dd0", "metadata": {}, "source": [ "### Accessing Data with requests\n", "\n", "\n", "\n", "One option is to use [requests](https://requests.readthedocs.io/en/master/), a standard Python library for requesting data over the Internet.\n", "\n", "To begin, try the following code on your computer" ] }, { "cell_type": "code", "execution_count": null, "id": "d0f563ed", "metadata": { "hide-output": false }, "outputs": [], "source": [ "r = requests.get('http://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv')" ] }, { "cell_type": "markdown", "id": "b29b8dc0", "metadata": {}, "source": [ "If there’s no error message, then the call has succeeded.\n", "\n", "If you do get an error, then there are two likely causes\n", "\n", "1. You are not connected to the Internet — hopefully, this isn’t the case. \n", "1. Your machine is accessing the Internet through a proxy server, and Python isn’t aware of this. \n", "\n", "\n", "In the second case, you can either\n", "\n", "- switch to another machine \n", "- solve your proxy problem by reading [the documentation](https://requests.readthedocs.io/en/master/) \n", "\n", "\n", "Assuming that all is working, you can now proceed to use the `source` object returned by the call `requests.get('http://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv')`" ] }, { "cell_type": "code", "execution_count": null, "id": "77e671c3", "metadata": { "hide-output": false }, "outputs": [], "source": [ "url = 'http://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv'\n", "source = requests.get(url).content.decode().split(\"\\n\")\n", "source[0]" ] }, { "cell_type": "code", "execution_count": null, "id": "56855877", "metadata": { "hide-output": false }, "outputs": [], "source": [ "source[1]" ] }, { "cell_type": "code", "execution_count": null, "id": "2aa887ca", "metadata": { "hide-output": false }, "outputs": [], "source": [ "source[2]" ] }, { "cell_type": "markdown", "id": "4021ca63", "metadata": {}, "source": [ "We could now write some additional code to parse this text and store it as an array.\n", "\n", "But this is unnecessary — pandas’ `read_csv` function can handle the task for us.\n", "\n", "We use `parse_dates=True` so that pandas recognizes our dates column, allowing for simple date filtering" ] }, { "cell_type": "code", "execution_count": null, "id": "32edf617", "metadata": { "hide-output": false }, "outputs": [], "source": [ "data = pd.read_csv(url, index_col=0, parse_dates=True)" ] }, { "cell_type": "markdown", "id": "147a93bf", "metadata": {}, "source": [ "The data has been read into a pandas DataFrame called `data` that we can now manipulate in the usual way" ] }, { "cell_type": "code", "execution_count": null, "id": "01432f5a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "type(data)" ] }, { "cell_type": "code", "execution_count": null, "id": "ef6aed1e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "data.head() # A useful method to get a quick look at a data frame" ] }, { "cell_type": "code", "execution_count": null, "id": "014c4598", "metadata": { "hide-output": false }, "outputs": [], "source": [ "pd.set_option('display.precision', 1)\n", "data.describe() # Your output might differ slightly" ] }, { "cell_type": "markdown", "id": "072e5336", "metadata": {}, "source": [ "We can also plot the unemployment rate from 2006 to 2012 as follows" ] }, { "cell_type": "code", "execution_count": null, "id": "0ea08d76", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ax = data['2006':'2012'].plot(title='US Unemployment Rate', legend=False)\n", "ax.set_xlabel('year', fontsize=12)\n", "ax.set_ylabel('%', fontsize=12)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "42aeb863", "metadata": {}, "source": [ "Note that pandas offers many other file type alternatives.\n", "\n", "Pandas has [a wide variety](https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html) of top-level methods that we can use to read, excel, json, parquet or plug straight into a database server." ] }, { "cell_type": "markdown", "id": "7d8884ef", "metadata": {}, "source": [ "### Using pandas_datareader and yfinance to Access Data\n", "\n", "\n", "\n", "The maker of pandas has also authored a library called\n", "[pandas_datareader](https://pandas-datareader.readthedocs.io/en/latest/) that\n", "gives programmatic access to many data sources straight from the Jupyter notebook.\n", "\n", "While some sources require an access key, many of the most important (e.g., FRED, [OECD](https://data.oecd.org/), [EUROSTAT](https://ec.europa.eu/eurostat/data/database) and the World Bank) are free to use.\n", "\n", "We will also use [yfinance](https://pypi.org/project/yfinance/) to fetch data from Yahoo finance\n", "in the exercises.\n", "\n", "For now let’s work through one example of downloading and plotting data — this\n", "time from the World Bank.\n", "\n", ">**Note**\n", ">\n", ">There are also other [python libraries](https://data.worldbank.org/products/third-party-apps)\n", "available for working with world bank data such as [wbgapi](https://pypi.org/project/wbgapi/)\n", "\n", "The World Bank [collects and organizes data](http://data.worldbank.org/indicator) on a huge range of indicators.\n", "\n", "For example, [here’s](http://data.worldbank.org/indicator/GC.DOD.TOTL.GD.ZS/countries) some data on government debt as a ratio to GDP.\n", "\n", "The next code example fetches the data for you and plots time series for the US and Australia" ] }, { "cell_type": "code", "execution_count": null, "id": "4ff03cda", "metadata": { "hide-output": false }, "outputs": [], "source": [ "from pandas_datareader import wb\n", "\n", "govt_debt = wb.download(indicator='GC.DOD.TOTL.GD.ZS', country=['US', 'AU'], start=2005, end=2016).stack().unstack(0)\n", "ind = govt_debt.index.droplevel(-1)\n", "govt_debt.index = ind\n", "ax = govt_debt.plot(lw=2)\n", "ax.set_xlabel('year', fontsize=12)\n", "plt.title(\"Government Debt to GDP (%)\")\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "75f6e60b", "metadata": {}, "source": [ "The [documentation](https://pandas-datareader.readthedocs.io/en/latest/index.html) provides more details on how to access various data sources." ] }, { "cell_type": "markdown", "id": "011e033a", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "id": "6f6cb5e9", "metadata": {}, "source": [ "## Exercise 14.1\n", "\n", "With these imports:" ] }, { "cell_type": "code", "execution_count": null, "id": "ec5dc715", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import datetime as dt\n", "import yfinance as yf" ] }, { "cell_type": "markdown", "id": "8331e05b", "metadata": {}, "source": [ "Write a program to calculate the percentage price change over 2021 for the following shares:" ] }, { "cell_type": "code", "execution_count": null, "id": "10d8718a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ticker_list = {'INTC': 'Intel',\n", " 'MSFT': 'Microsoft',\n", " 'IBM': 'IBM',\n", " 'BHP': 'BHP',\n", " 'TM': 'Toyota',\n", " 'AAPL': 'Apple',\n", " 'AMZN': 'Amazon',\n", " 'C': 'Citigroup',\n", " 'QCOM': 'Qualcomm',\n", " 'KO': 'Coca-Cola',\n", " 'GOOG': 'Google'}" ] }, { "cell_type": "markdown", "id": "4c1fff52", "metadata": {}, "source": [ "Here’s the first part of the program" ] }, { "cell_type": "code", "execution_count": null, "id": "9eae4001", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def read_data(ticker_list,\n", " start=dt.datetime(2021, 1, 1),\n", " end=dt.datetime(2021, 12, 31)):\n", " \"\"\"\n", " This function reads in closing price data from Yahoo\n", " for each tick in the ticker_list.\n", " \"\"\"\n", " ticker = pd.DataFrame()\n", "\n", " for tick in ticker_list:\n", " stock = yf.Ticker(tick)\n", " prices = stock.history(start=start, end=end)\n", "\n", " # Change the index to date-only\n", " prices.index = pd.to_datetime(prices.index.date)\n", " \n", " closing_prices = prices['Close']\n", " ticker[tick] = closing_prices\n", "\n", " return ticker\n", "\n", "ticker = read_data(ticker_list)" ] }, { "cell_type": "markdown", "id": "e30d1813", "metadata": {}, "source": [ "Complete the program to plot the result as a bar graph like this one:\n", "\n", "![https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_share_prices.png](https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_share_prices.png)" ] }, { "cell_type": "markdown", "id": "5e93ed60", "metadata": {}, "source": [ "## Solution to[ Exercise 14.1](https://python-programming.quantecon.org/#pd_ex1)\n", "\n", "There are a few ways to approach this problem using Pandas to calculate\n", "the percentage change.\n", "\n", "First, you can extract the data and perform the calculation such as:" ] }, { "cell_type": "code", "execution_count": null, "id": "03c80881", "metadata": { "hide-output": false }, "outputs": [], "source": [ "p1 = ticker.iloc[0] #Get the first set of prices as a Series\n", "p2 = ticker.iloc[-1] #Get the last set of prices as a Series\n", "price_change = (p2 - p1) / p1 * 100\n", "price_change" ] }, { "cell_type": "markdown", "id": "5312d495", "metadata": {}, "source": [ "Alternatively you can use an inbuilt method `pct_change` and configure it to\n", "perform the correct calculation using `periods` argument." ] }, { "cell_type": "code", "execution_count": null, "id": "b633b361", "metadata": { "hide-output": false }, "outputs": [], "source": [ "change = ticker.pct_change(periods=len(ticker)-1, axis='rows')*100\n", "price_change = change.iloc[-1]\n", "price_change" ] }, { "cell_type": "markdown", "id": "4436970d", "metadata": {}, "source": [ "Then to plot the chart" ] }, { "cell_type": "code", "execution_count": null, "id": "2cb9d62a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "price_change.sort_values(inplace=True)\n", "price_change = price_change.rename(index=ticker_list)\n", "fig, ax = plt.subplots(figsize=(10,8))\n", "ax.set_xlabel('stock', fontsize=12)\n", "ax.set_ylabel('percentage change in price', fontsize=12)\n", "price_change.plot(kind='bar', ax=ax)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "4d26ff38", "metadata": {}, "source": [ "## Exercise 14.2\n", "\n", "Using the method `read_data` introduced in Exercise 14.1, write a program to obtain year-on-year percentage change for the following indices:" ] }, { "cell_type": "code", "execution_count": null, "id": "3894e8c5", "metadata": { "hide-output": false }, "outputs": [], "source": [ "indices_list = {'^GSPC': 'S&P 500',\n", " '^IXIC': 'NASDAQ',\n", " '^DJI': 'Dow Jones',\n", " '^N225': 'Nikkei'}" ] }, { "cell_type": "markdown", "id": "bfd49196", "metadata": {}, "source": [ "Complete the program to show summary statistics and plot the result as a time series graph like this one:\n", "\n", "![https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_indices_pctchange.png](https://python-programming.quantecon.org/_static/lecture_specific/pandas/pandas_indices_pctchange.png)" ] }, { "cell_type": "markdown", "id": "10f57d09", "metadata": {}, "source": [ "## Solution to[ Exercise 14.2](https://python-programming.quantecon.org/#pd_ex2)\n", "\n", "Following the work you did in Exercise 14.1, you can query the data using `read_data` by updating the start and end dates accordingly." ] }, { "cell_type": "code", "execution_count": null, "id": "2a3dd150", "metadata": { "hide-output": false }, "outputs": [], "source": [ "indices_data = read_data(\n", " indices_list,\n", " start=dt.datetime(1971, 1, 1), #Common Start Date\n", " end=dt.datetime(2021, 12, 31)\n", ")" ] }, { "cell_type": "markdown", "id": "4bff4b6d", "metadata": {}, "source": [ "Then, extract the first and last set of prices per year as DataFrames and calculate the yearly returns such as:" ] }, { "cell_type": "code", "execution_count": null, "id": "6ff31a99", "metadata": { "hide-output": false }, "outputs": [], "source": [ "yearly_returns = pd.DataFrame()\n", "\n", "for index, name in indices_list.items():\n", " p1 = indices_data.groupby(indices_data.index.year)[index].first() # Get the first set of returns as a DataFrame\n", " p2 = indices_data.groupby(indices_data.index.year)[index].last() # Get the last set of returns as a DataFrame\n", " returns = (p2 - p1) / p1\n", " yearly_returns[name] = returns\n", "\n", "yearly_returns" ] }, { "cell_type": "markdown", "id": "56d92929", "metadata": {}, "source": [ "Next, you can obtain summary statistics by using the method `describe`." ] }, { "cell_type": "code", "execution_count": null, "id": "7f2186f0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "yearly_returns.describe()" ] }, { "cell_type": "markdown", "id": "4e10f6c0", "metadata": {}, "source": [ "Then, to plot the chart" ] }, { "cell_type": "code", "execution_count": null, "id": "41b88c52", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(2, 2, figsize=(10, 8))\n", "\n", "for iter_, ax in enumerate(axes.flatten()): # Flatten 2-D array to 1-D array\n", " index_name = yearly_returns.columns[iter_] # Get index name per iteration\n", " ax.plot(yearly_returns[index_name]) # Plot pct change of yearly returns per index\n", " ax.set_ylabel(\"percent change\", fontsize = 12)\n", " ax.set_title(index_name)\n", "\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "id": "3faf532f", "metadata": {}, "source": [ "

[1] Wikipedia defines munging as cleaning data from one raw form into a structured, purged one." ] } ], "metadata": { "date": 1710455932.404865, "filename": "pandas.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "Pandas" }, "nbformat": 4, "nbformat_minor": 5 }