The Index#
Prerequisites
Outcomes
Understand how the index is used to align data
Know how to set and reset the index
Understand how to select subsets of data by slicing on index and columns
Understand that for DataFrames, the column names also align data
# Uncomment following line to install on colab
#! pip install
import pandas as pd
import numpy as np
So What is this Index?#
Every Series or DataFrame has an index.
We told you that the index was the “row labels” for the data.
This is true, but an index in pandas does much more than label the rows.
The purpose of this lecture is to understand the importance of the index.
The pandas documentation says
Data alignment is intrinsic. The link between labels and data will not be broken unless done so explicitly by you.
In practice, the index and column names are used to make sure the data is properly aligned when operating on multiple DataFrames.
This is a somewhat abstract concept that is best understood by example…
Let’s begin by loading some data on GDP components that we collected from the World Bank’s World Development Indicators Dataset.
url = "https://datascience.quantecon.org/assets/data/wdi_data.csv"
df = pd.read_csv(url)
df.info()
df.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 72 entries, 0 to 71
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 country 72 non-null object
1 year 72 non-null int64
2 GovExpend 72 non-null float64
3 Consumption 72 non-null float64
4 Exports 72 non-null float64
5 Imports 72 non-null float64
6 GDP 72 non-null float64
dtypes: float64(5), int64(1), object(1)
memory usage: 4.1+ KB
country | year | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|---|
0 | Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
1 | Canada | 2016 | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 |
2 | Canada | 2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
3 | Canada | 2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
4 | Canada | 2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
We’ll also extract a couple smaller DataFrames we can use in examples.
df_small = df.head(5)
df_small
country | year | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|---|
0 | Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
1 | Canada | 2016 | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 |
2 | Canada | 2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
3 | Canada | 2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
4 | Canada | 2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
df_tiny = df.iloc[[0, 3, 2, 4], :]
df_tiny
country | year | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|---|
0 | Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
3 | Canada | 2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
2 | Canada | 2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
4 | Canada | 2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
im_ex = df_small[["Imports", "Exports"]]
im_ex_copy = im_ex.copy()
im_ex_copy
Imports | Exports | |
---|---|---|
0 | 0.600031 | 0.582831 |
1 | 0.575775 | 0.576394 |
2 | 0.575793 | 0.568859 |
3 | 0.572344 | 0.550323 |
4 | 0.558636 | 0.518040 |
Observe what happens when we evaluate im_ex + im_ex_copy
.
im_ex + im_ex_copy
Imports | Exports | |
---|---|---|
0 | 1.200063 | 1.165661 |
1 | 1.151550 | 1.152787 |
2 | 1.151585 | 1.137718 |
3 | 1.144688 | 1.100646 |
4 | 1.117272 | 1.036081 |
Notice that this operated elementwise, meaning that the +
operation was applied to each element of im_ex
and the corresponding
element of im_ex_copy
.
Let’s take a closer look at df_tiny
:
df_tiny
country | year | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|---|
0 | Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
3 | Canada | 2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
2 | Canada | 2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
4 | Canada | 2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
Relative to im_ex
notice a few things:
The row labeled
1
appears inim_ex
but notdf_tiny
.For row labels that appear in both, they are not in the same position within each DataFrame.
Certain columns appear only in
df_tiny
.The
Imports
andExports
columns are the 6th and 5th columns ofdf_tiny
and the 1st and 2nd ofim_ex
, respectively.
Now, let’s see what happens when we try df_tiny + im_ex
.
im_ex_tiny = df_tiny + im_ex
im_ex_tiny
Consumption | Exports | GDP | GovExpend | Imports | country | year | |
---|---|---|---|---|---|---|---|
0 | NaN | 1.165661 | NaN | NaN | 1.200063 | NaN | NaN |
1 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | NaN | 1.137718 | NaN | NaN | 1.151585 | NaN | NaN |
3 | NaN | 1.100646 | NaN | NaN | 1.144688 | NaN | NaN |
4 | NaN | 1.036081 | NaN | NaN | 1.117272 | NaN | NaN |
Whoa, a lot happened! Let’s break it down.
Automatic Alignment#
For all (row, column) combinations that appear in both DataFrames (e.g.
rows [1, 3]
and columns [Imports, Exports]
), the value of im_ex_tiny
is equal to df_tiny.loc[row, col] + im_ex.loc[row, col]
.
This happened even though the rows and columns were not in the same order.
We refer to this as pandas aligning the data for us.
To see how awesome this is, think about how to do something similar in Excel:
df_tiny
andim_ex
would be in different sheets.The index and column names would be the first column and row in each sheet.
We would have a third sheet to hold the sum.
For each label in the first row and column of either the
df_tiny
sheet or theim_ex
sheet we would have to do aIFELSE
to check if the label exists in the other sheet and then aVLOOKUP
to extract the value.
In pandas, this happens automatically, behind the scenes, and very quickly.
Handling Missing Data#
For all elements in row 1
or columns
["country", "year", "GovExpend", "Consumption", "GDP"]
,
the value in im_ex_tiny
is NaN
.
This is how pandas represents missing data.
So, when pandas was trying to look up the values in df_tiny
and im_ex
, it could
only find a value in one DataFrame: the other value was missing.
When pandas tries to add a number to something that is missing, it says
that the result is missing (spelled NaN
).
Exercise
See exercise 1 in the exercise list.
Setting the Index#
For a DataFrame df
, the df.set_index
method allows us to use one
(or more) of the DataFrame’s columns as the index.
Here’s an example.
# first, create the DataFrame
df_year = df.set_index(["year"])
df_year.head()
country | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|
year | ||||||
2017 | Canada | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
2016 | Canada | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 |
2015 | Canada | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
2014 | Canada | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
2013 | Canada | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
Now that the year is on the index, we can use .loc
to extract all the
data for a specific year.
df_year.loc[2010]
country | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|
year | ||||||
2010 | Canada | 0.347332 | 0.921952 | 0.469949 | 0.500341 | 1.613543 |
2010 | Germany | 0.653386 | 1.915481 | 1.443735 | 1.266126 | 3.417095 |
2010 | United Kingdom | 0.521146 | 1.598563 | 0.690824 | 0.745065 | 2.452900 |
2010 | United States | 2.510143 | 10.185836 | 1.846280 | 2.360183 | 14.992053 |
This would be helpful, for example, if we wanted to compute the difference in the average of all our variables from one year to the next.
df_year.loc[2009].mean(numeric_only=True) - df_year.loc[2008].mean(numeric_only=True)
GovExpend 0.033317
Consumption -0.042998
Exports -0.121425
Imports -0.140042
GDP -0.182610
dtype: float64
Notice that pandas did a few things for us.
After computing
.mean()
, the row labels (index) were the former column names.These column names were used to align data when we wanted asked pandas to compute the difference.
Suppose that someone asked you, “What was the GDP in the US in 2010?”
To compute that using df_year
you might do something like this:
df_year.loc[df_year["country"] == "United States", "GDP"].loc[2010]
14.992052727
That was a lot of work!
Now, suppose that after seeing you extract that data, your friend asks you “What about GDP in Germany and the UK in 2010?”
To answer that question, you might write.
df_year.loc[df_year["country"].isin(["United Kingdom", "Germany"]), "GDP"].loc[2010]
year
2010 3.417095
2010 2.452900
Name: GDP, dtype: float64
Notice that this code is similar to the code above, but now provides a result that is ambiguous.
The two elements in the series both have with label 2010.
How do we know which is which?
We might think that the first value corresponds to the United Kingdom because
that is what we listed first in the call to isin
, but we would be wrong!
Let’s check.
df_year.loc[2010]
country | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|
year | ||||||
2010 | Canada | 0.347332 | 0.921952 | 0.469949 | 0.500341 | 1.613543 |
2010 | Germany | 0.653386 | 1.915481 | 1.443735 | 1.266126 | 3.417095 |
2010 | United Kingdom | 0.521146 | 1.598563 | 0.690824 | 0.745065 | 2.452900 |
2010 | United States | 2.510143 | 10.185836 | 1.846280 | 2.360183 | 14.992053 |
Setting just the year as index has one more potential issue: we will get data alignment only on the year, which may not be sufficient.
To demonstrate this point, suppose now you are asked to use our WDI dataset to compute an approximation for net exports and investment in 2009.
As a seasoned economist, you would remember the expenditure formula for GDP is written
which we can rearrange to compute investment as a function of the variables in our DataFrame…
Note that we can compute NetExports as Exports - Imports
.
nx = df_year["Exports"] - df_year["Imports"]
nx.head(19)
year
2017 -0.017201
2016 0.000619
2015 -0.006934
2014 -0.022021
2013 -0.040596
2012 -0.041787
2011 -0.035878
2010 -0.030393
2009 0.000896
2008 0.004068
2007 0.032451
2006 0.053530
2005 0.072729
2004 0.091902
2003 0.097794
2002 0.121850
2001 0.122673
2000 0.118702
2017 0.264214
dtype: float64
Now, suppose that we accidentally had a bug in our code that swapped the data for Canada and Germany’s net exports in 2017.
Note
This example is contrived, but if you were getting unclean data from some resource or doing more complicated operations, this type of mistake becomes increasingly likely.
ca17 = nx.iloc[[0]]
g17 = nx.iloc[[18]]
nx.iloc[[0]] = g17
nx.iloc[[18]] = ca17
nx.head(19)
year
2017 0.264214
2016 0.000619
2015 -0.006934
2014 -0.022021
2013 -0.040596
2012 -0.041787
2011 -0.035878
2010 -0.030393
2009 0.000896
2008 0.004068
2007 0.032451
2006 0.053530
2005 0.072729
2004 0.091902
2003 0.097794
2002 0.121850
2001 0.122673
2000 0.118702
2017 -0.017201
dtype: float64
Notice that if we now add nx
to the DataFrame and compute investment
pandas doesn’t complain.
df_year["NetExports"] = nx
df_year["Investment"] = df_year.eval("GDP - Consumption - GovExpend - NetExports")
df_year.head(19)
country | GovExpend | Consumption | Exports | Imports | GDP | NetExports | Investment | |
---|---|---|---|---|---|---|---|---|
year | ||||||||
2017 | Canada | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 | 0.264214 | 0.135811 |
2016 | Canada | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 | 0.000619 | 0.390072 |
2015 | Canada | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 | -0.006934 | 0.407692 |
2014 | Canada | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 | -0.022021 | 0.438800 |
2013 | Canada | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 | -0.040596 | 0.435369 |
2012 | Canada | 0.354342 | 0.961226 | 0.505969 | 0.547756 | 1.693428 | -0.041787 | 0.419647 |
2011 | Canada | 0.351887 | 0.943145 | 0.492349 | 0.528227 | 1.664240 | -0.035878 | 0.405086 |
2010 | Canada | 0.347332 | 0.921952 | 0.469949 | 0.500341 | 1.613543 | -0.030393 | 0.374652 |
2009 | Canada | 0.339686 | 0.890078 | 0.440692 | 0.439796 | 1.565291 | 0.000896 | 0.334631 |
2008 | Canada | 0.330766 | 0.889602 | 0.506350 | 0.502281 | 1.612862 | 0.004068 | 0.388425 |
2007 | Canada | 0.318777 | 0.864012 | 0.530453 | 0.498002 | 1.596876 | 0.032451 | 0.381636 |
2006 | Canada | 0.311382 | 0.827643 | 0.524461 | 0.470931 | 1.564608 | 0.053530 | 0.372053 |
2005 | Canada | 0.303043 | 0.794390 | 0.519950 | 0.447222 | 1.524608 | 0.072729 | 0.354447 |
2004 | Canada | 0.299854 | 0.764357 | 0.508657 | 0.416754 | 1.477317 | 0.091902 | 0.321203 |
2003 | Canada | 0.294335 | 0.741796 | 0.481993 | 0.384199 | 1.433089 | 0.097794 | 0.299164 |
2002 | Canada | 0.286094 | 0.721974 | 0.490465 | 0.368615 | 1.407725 | 0.121850 | 0.277806 |
2001 | Canada | 0.279767 | 0.694230 | 0.484696 | 0.362023 | 1.366590 | 0.122673 | 0.269921 |
2000 | Canada | 0.270553 | 0.677713 | 0.499526 | 0.380823 | 1.342805 | 0.118702 | 0.275837 |
2017 | Germany | 0.745579 | 2.112009 | 1.930563 | 1.666348 | 3.883870 | -0.017201 | 1.043482 |
Because we didn’t also have data alignment on the country, we would have overstated Canada’s investment by 281 billion USD and understated Germany’s by the same amount.
To make these types operation easier, we need to include both the year and country in the index…
Setting a Hierarchical Index#
Include multiple columns in the index is advantageous in some situations.
These situations might include:
When we need more than one piece of information (column) to identify an observation (as in the Germany and UK GDP example above)
When we need data-alignment by more than one column
To achieve multiple columns in the index, we pass a list of multiple column
names to set_index
.
wdi = df.set_index(["country", "year"])
wdi.head(20)
GovExpend | Consumption | Exports | Imports | GDP | ||
---|---|---|---|---|---|---|
country | year | |||||
Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
2016 | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 | |
2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 | |
2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 | |
2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 | |
2012 | 0.354342 | 0.961226 | 0.505969 | 0.547756 | 1.693428 | |
2011 | 0.351887 | 0.943145 | 0.492349 | 0.528227 | 1.664240 | |
2010 | 0.347332 | 0.921952 | 0.469949 | 0.500341 | 1.613543 | |
2009 | 0.339686 | 0.890078 | 0.440692 | 0.439796 | 1.565291 | |
2008 | 0.330766 | 0.889602 | 0.506350 | 0.502281 | 1.612862 | |
2007 | 0.318777 | 0.864012 | 0.530453 | 0.498002 | 1.596876 | |
2006 | 0.311382 | 0.827643 | 0.524461 | 0.470931 | 1.564608 | |
2005 | 0.303043 | 0.794390 | 0.519950 | 0.447222 | 1.524608 | |
2004 | 0.299854 | 0.764357 | 0.508657 | 0.416754 | 1.477317 | |
2003 | 0.294335 | 0.741796 | 0.481993 | 0.384199 | 1.433089 | |
2002 | 0.286094 | 0.721974 | 0.490465 | 0.368615 | 1.407725 | |
2001 | 0.279767 | 0.694230 | 0.484696 | 0.362023 | 1.366590 | |
2000 | 0.270553 | 0.677713 | 0.499526 | 0.380823 | 1.342805 | |
Germany | 2017 | 0.745579 | 2.112009 | 1.930563 | 1.666348 | 3.883870 |
2016 | 0.734014 | 2.075615 | 1.844949 | 1.589495 | 3.801859 |
Notice that in the display above, the row labels seem to have two levels now.
The outer (or left-most) level is named country
and the inner (or
right-most) level is named year
.
When a DataFrame’s index has multiple levels, we (and the pandas documentation) refer to the DataFrame as having a hierarchical index.
Slicing a Hierarchical Index#
Now, we can answer our friend’s questions in a much more straightforward way.
wdi.loc[("United States", 2010), "GDP"]
14.992052727
wdi.loc[(["United Kingdom", "Germany"], 2010), "GDP"]
country year
United Kingdom 2010 2.452900
Germany 2010 3.417095
Name: GDP, dtype: float64
As shown above, we can use wdi.loc
to extract different slices of our
national accounts data.
The rules for using .loc
with a hierarchically-indexed DataFrame are
similar to the ones we’ve learned for standard DataFrames, but they are a bit
more elaborate as we now have more structure to our data.
We will summarize the main rules, and then work through an exercise that demonstrates each of them.
Slicing rules
pandas slicing reacts differently to list
s and tuple
s.
It does this to provide more flexibility to select the data you want.
list
in row slicing will be an “or” operation, where it chooses rows
based on whether the index value corresponds to any element of the list.
tuple
in row slicing will be used to denote a single hierarchical
index and must include a value for each level.
Row slicing examples
wdi.loc["United States"]
: all rows where the outer most index value is equal toUnited States
wdi.loc[("United States", 2010)]
: all rows where the outer-most index value is equal to"United States
and the second level is equal to2010
wdi.loc[["United States", "Canada"]]
: all rows where the outer-most index is either"United States"
or"Canada"
wdi.loc[(["United States", "Canada"], [2010, 2011]), :]
: all rows where the outer-most index is either"United States
or"Canada"
AND where the second level index is either2010
or2011
wdi.loc[[("United States", 2010), ("Canada", 2011)], :]
: all rows where the the two hierarchical indices are either("United States", 2010)
or("Canada", 2011)
We can also restrict .loc
to extract certain columns by doing:
wdi.loc[rows, GDP]
: return the rows specified by rows (see rules above) and only column namedGDP
(returned object will be a Series)df.loc[rows, ["GDP", "Consumption"]]
: return the rows specified by rows (see rules above) and only columnsGDP
andConsumption
Exercise
See exercise 2 in the exercise list.
Alignment with MultiIndex
#
The data alignment features we talked about above also apply to a
MultiIndex
DataFrame.
The exercise below gives you a chance to experiment with this.
Exercise
See exercise 3 in the exercise list.
pd.IndexSlice
#
When we want to extract rows for a few values of the outer index and all
values for an inner index level, we can use the convenient
df.loc[[id11, id22]]
shorthand.
We can use this notation to extract all the data for the United States and Canada.
wdi.loc[["United States", "Canada"]]
GovExpend | Consumption | Exports | Imports | GDP | ||
---|---|---|---|---|---|---|
country | year | |||||
United States | 2017 | 2.405743 | 12.019266 | 2.287071 | 3.069954 | 17.348627 |
2016 | 2.407981 | 11.722133 | 2.219937 | 2.936004 | 16.972348 | |
2015 | 2.373130 | 11.409800 | 2.222228 | 2.881337 | 16.710459 | |
2014 | 2.334071 | 11.000619 | 2.209555 | 2.732228 | 16.242526 | |
2013 | 2.353381 | 10.687214 | 2.118639 | 2.600198 | 15.853796 | |
2012 | 2.398873 | 10.534042 | 2.045509 | 2.560677 | 15.567038 | |
2011 | 2.434378 | 10.378060 | 1.978083 | 2.493194 | 15.224555 | |
2010 | 2.510143 | 10.185836 | 1.846280 | 2.360183 | 14.992053 | |
2009 | 2.507390 | 10.010687 | 1.646432 | 2.086299 | 14.617299 | |
2008 | 2.407771 | 10.137847 | 1.797347 | 2.400349 | 14.997756 | |
2007 | 2.351987 | 10.159387 | 1.701096 | 2.455016 | 15.018268 | |
2006 | 2.314957 | 9.938503 | 1.564920 | 2.395189 | 14.741688 | |
2005 | 2.287022 | 9.643098 | 1.431205 | 2.246246 | 14.332500 | |
2004 | 2.267999 | 9.311431 | 1.335978 | 2.108585 | 13.846058 | |
2003 | 2.233519 | 8.974708 | 1.218199 | 1.892825 | 13.339312 | |
2002 | 2.193188 | 8.698306 | 1.192180 | 1.804105 | 12.968263 | |
2001 | 2.112038 | 8.480461 | 1.213253 | 1.740797 | 12.746262 | |
2000 | 2.040500 | 8.272097 | 1.287739 | 1.790995 | 12.620268 | |
Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
2016 | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 | |
2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 | |
2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 | |
2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 | |
2012 | 0.354342 | 0.961226 | 0.505969 | 0.547756 | 1.693428 | |
2011 | 0.351887 | 0.943145 | 0.492349 | 0.528227 | 1.664240 | |
2010 | 0.347332 | 0.921952 | 0.469949 | 0.500341 | 1.613543 | |
2009 | 0.339686 | 0.890078 | 0.440692 | 0.439796 | 1.565291 | |
2008 | 0.330766 | 0.889602 | 0.506350 | 0.502281 | 1.612862 | |
2007 | 0.318777 | 0.864012 | 0.530453 | 0.498002 | 1.596876 | |
2006 | 0.311382 | 0.827643 | 0.524461 | 0.470931 | 1.564608 | |
2005 | 0.303043 | 0.794390 | 0.519950 | 0.447222 | 1.524608 | |
2004 | 0.299854 | 0.764357 | 0.508657 | 0.416754 | 1.477317 | |
2003 | 0.294335 | 0.741796 | 0.481993 | 0.384199 | 1.433089 | |
2002 | 0.286094 | 0.721974 | 0.490465 | 0.368615 | 1.407725 | |
2001 | 0.279767 | 0.694230 | 0.484696 | 0.362023 | 1.366590 | |
2000 | 0.270553 | 0.677713 | 0.499526 | 0.380823 | 1.342805 |
However, suppose we wanted to extract the data for all countries, but only the years 2005, 2007, and 2009.
We cannot do this using wdi.loc
because the year is on the second level,
not outer-most level of our index.
To get around this limitation, we can use the pd.IndexSlice
helper.
Here’s an example.
wdi.loc[pd.IndexSlice[:, [2005, 2007, 2009]], :]
GovExpend | Consumption | Exports | Imports | GDP | ||
---|---|---|---|---|---|---|
country | year | |||||
Canada | 2005 | 0.303043 | 0.794390 | 0.519950 | 0.447222 | 1.524608 |
Germany | 2005 | 0.591184 | 1.866253 | 1.175200 | 1.028094 | 3.213777 |
United Kingdom | 2005 | 0.490806 | 1.578914 | 0.640088 | 0.715951 | 2.403352 |
United States | 2005 | 2.287022 | 9.643098 | 1.431205 | 2.246246 | 14.332500 |
Canada | 2007 | 0.318777 | 0.864012 | 0.530453 | 0.498002 | 1.596876 |
Germany | 2007 | 0.605624 | 1.894219 | 1.442436 | 1.213835 | 3.441356 |
United Kingdom | 2007 | 0.504549 | 1.644789 | 0.710200 | 0.767699 | 2.527327 |
United States | 2007 | 2.351987 | 10.159387 | 1.701096 | 2.455016 | 15.018268 |
Canada | 2009 | 0.339686 | 0.890078 | 0.440692 | 0.439796 | 1.565291 |
Germany | 2009 | 0.645023 | 1.908393 | 1.260525 | 1.121914 | 3.283144 |
United Kingdom | 2009 | 0.519716 | 1.587152 | 0.653830 | 0.689011 | 2.411632 |
United States | 2009 | 2.507390 | 10.010687 | 1.646432 | 2.086299 | 14.617299 |
Notice that the :
in the first part of [:, ["A", "D"]]
instructed pandas to give us rows for all values of the outer most index
level and that the :
just before ]
said grab all the columns.
Exercise
See exercise 4 in the exercise list.
Multi-index Columns#
The functionality of MultiIndex
also applies to the column names.
Let’s see how it works.
wdiT = wdi.T # .T means "transpose" or "swap rows and columns"
wdiT
country | Canada | ... | United States | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
year | 2017 | 2016 | 2015 | 2014 | 2013 | 2012 | 2011 | 2010 | 2009 | 2008 | ... | 2009 | 2008 | 2007 | 2006 | 2005 | 2004 | 2003 | 2002 | 2001 | 2000 |
GovExpend | 0.372665 | 0.364899 | 0.358303 | 0.353485 | 0.351541 | 0.354342 | 0.351887 | 0.347332 | 0.339686 | 0.330766 | ... | 2.507390 | 2.407771 | 2.351987 | 2.314957 | 2.287022 | 2.267999 | 2.233519 | 2.193188 | 2.112038 | 2.040500 |
Consumption | 1.095475 | 1.058426 | 1.035208 | 1.011988 | 0.986400 | 0.961226 | 0.943145 | 0.921952 | 0.890078 | 0.889602 | ... | 10.010687 | 10.137847 | 10.159387 | 9.938503 | 9.643098 | 9.311431 | 8.974708 | 8.698306 | 8.480461 | 8.272097 |
Exports | 0.582831 | 0.576394 | 0.568859 | 0.550323 | 0.518040 | 0.505969 | 0.492349 | 0.469949 | 0.440692 | 0.506350 | ... | 1.646432 | 1.797347 | 1.701096 | 1.564920 | 1.431205 | 1.335978 | 1.218199 | 1.192180 | 1.213253 | 1.287739 |
Imports | 0.600031 | 0.575775 | 0.575793 | 0.572344 | 0.558636 | 0.547756 | 0.528227 | 0.500341 | 0.439796 | 0.502281 | ... | 2.086299 | 2.400349 | 2.455016 | 2.395189 | 2.246246 | 2.108585 | 1.892825 | 1.804105 | 1.740797 | 1.790995 |
GDP | 1.868164 | 1.814016 | 1.794270 | 1.782252 | 1.732714 | 1.693428 | 1.664240 | 1.613543 | 1.565291 | 1.612862 | ... | 14.617299 | 14.997756 | 15.018268 | 14.741688 | 14.332500 | 13.846058 | 13.339312 | 12.968263 | 12.746262 | 12.620268 |
5 rows × 72 columns
Notice that wdiT
seems to have two levels of names for the columns.
The same logic laid out in the above row slicing rules applies when we have a hierarchical index for column names.
wdiT.loc[:, "United States"]
year | 2017 | 2016 | 2015 | 2014 | 2013 | 2012 | 2011 | 2010 | 2009 | 2008 | 2007 | 2006 | 2005 | 2004 | 2003 | 2002 | 2001 | 2000 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
GovExpend | 2.405743 | 2.407981 | 2.373130 | 2.334071 | 2.353381 | 2.398873 | 2.434378 | 2.510143 | 2.507390 | 2.407771 | 2.351987 | 2.314957 | 2.287022 | 2.267999 | 2.233519 | 2.193188 | 2.112038 | 2.040500 |
Consumption | 12.019266 | 11.722133 | 11.409800 | 11.000619 | 10.687214 | 10.534042 | 10.378060 | 10.185836 | 10.010687 | 10.137847 | 10.159387 | 9.938503 | 9.643098 | 9.311431 | 8.974708 | 8.698306 | 8.480461 | 8.272097 |
Exports | 2.287071 | 2.219937 | 2.222228 | 2.209555 | 2.118639 | 2.045509 | 1.978083 | 1.846280 | 1.646432 | 1.797347 | 1.701096 | 1.564920 | 1.431205 | 1.335978 | 1.218199 | 1.192180 | 1.213253 | 1.287739 |
Imports | 3.069954 | 2.936004 | 2.881337 | 2.732228 | 2.600198 | 2.560677 | 2.493194 | 2.360183 | 2.086299 | 2.400349 | 2.455016 | 2.395189 | 2.246246 | 2.108585 | 1.892825 | 1.804105 | 1.740797 | 1.790995 |
GDP | 17.348627 | 16.972348 | 16.710459 | 16.242526 | 15.853796 | 15.567038 | 15.224555 | 14.992053 | 14.617299 | 14.997756 | 15.018268 | 14.741688 | 14.332500 | 13.846058 | 13.339312 | 12.968263 | 12.746262 | 12.620268 |
wdiT.loc[:, ["United States", "Canada"]]
country | United States | ... | Canada | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
year | 2017 | 2016 | 2015 | 2014 | 2013 | 2012 | 2011 | 2010 | 2009 | 2008 | ... | 2009 | 2008 | 2007 | 2006 | 2005 | 2004 | 2003 | 2002 | 2001 | 2000 |
GovExpend | 2.405743 | 2.407981 | 2.373130 | 2.334071 | 2.353381 | 2.398873 | 2.434378 | 2.510143 | 2.507390 | 2.407771 | ... | 0.339686 | 0.330766 | 0.318777 | 0.311382 | 0.303043 | 0.299854 | 0.294335 | 0.286094 | 0.279767 | 0.270553 |
Consumption | 12.019266 | 11.722133 | 11.409800 | 11.000619 | 10.687214 | 10.534042 | 10.378060 | 10.185836 | 10.010687 | 10.137847 | ... | 0.890078 | 0.889602 | 0.864012 | 0.827643 | 0.794390 | 0.764357 | 0.741796 | 0.721974 | 0.694230 | 0.677713 |
Exports | 2.287071 | 2.219937 | 2.222228 | 2.209555 | 2.118639 | 2.045509 | 1.978083 | 1.846280 | 1.646432 | 1.797347 | ... | 0.440692 | 0.506350 | 0.530453 | 0.524461 | 0.519950 | 0.508657 | 0.481993 | 0.490465 | 0.484696 | 0.499526 |
Imports | 3.069954 | 2.936004 | 2.881337 | 2.732228 | 2.600198 | 2.560677 | 2.493194 | 2.360183 | 2.086299 | 2.400349 | ... | 0.439796 | 0.502281 | 0.498002 | 0.470931 | 0.447222 | 0.416754 | 0.384199 | 0.368615 | 0.362023 | 0.380823 |
GDP | 17.348627 | 16.972348 | 16.710459 | 16.242526 | 15.853796 | 15.567038 | 15.224555 | 14.992053 | 14.617299 | 14.997756 | ... | 1.565291 | 1.612862 | 1.596876 | 1.564608 | 1.524608 | 1.477317 | 1.433089 | 1.407725 | 1.366590 | 1.342805 |
5 rows × 36 columns
wdiT.loc[:, (["United States", "Canada"], 2010)]
country | United States | Canada |
---|---|---|
year | 2010 | 2010 |
GovExpend | 2.510143 | 0.347332 |
Consumption | 10.185836 | 0.921952 |
Exports | 1.846280 | 0.469949 |
Imports | 2.360183 | 0.500341 |
GDP | 14.992053 | 1.613543 |
Exercise
See exercise 5 in the exercise list.
Re-setting the Index#
The df.reset_index
method will move one or more level of the index
back into the DataFrame as a normal column.
With no additional arguments, it moves all levels out of the index and
sets the index of the returned DataFrame to the default of
range(df.shape[0])
.
wdi.reset_index()
country | year | GovExpend | Consumption | Exports | Imports | GDP | |
---|---|---|---|---|---|---|---|
0 | Canada | 2017 | 0.372665 | 1.095475 | 0.582831 | 0.600031 | 1.868164 |
1 | Canada | 2016 | 0.364899 | 1.058426 | 0.576394 | 0.575775 | 1.814016 |
2 | Canada | 2015 | 0.358303 | 1.035208 | 0.568859 | 0.575793 | 1.794270 |
3 | Canada | 2014 | 0.353485 | 1.011988 | 0.550323 | 0.572344 | 1.782252 |
4 | Canada | 2013 | 0.351541 | 0.986400 | 0.518040 | 0.558636 | 1.732714 |
... | ... | ... | ... | ... | ... | ... | ... |
67 | United States | 2004 | 2.267999 | 9.311431 | 1.335978 | 2.108585 | 13.846058 |
68 | United States | 2003 | 2.233519 | 8.974708 | 1.218199 | 1.892825 | 13.339312 |
69 | United States | 2002 | 2.193188 | 8.698306 | 1.192180 | 1.804105 | 12.968263 |
70 | United States | 2001 | 2.112038 | 8.480461 | 1.213253 | 1.740797 | 12.746262 |
71 | United States | 2000 | 2.040500 | 8.272097 | 1.287739 | 1.790995 | 12.620268 |
72 rows × 7 columns
Exercise
See exercise 6 in the exercise list.
Choose the Index Carefully#
So, now that we know that we use index and column names for aligning data, “how should we pick the index?” is a natural question to ask.
To guide us to the right answer, we will list the first two components to Hadley Wickham’s description of tidy data:
Each column should each have one variable.
Each row should each have one observation.
If we strive to have our data in a tidy form (we should), then when choosing the index, we should set:
the row labels (index) to be a unique identifier for an observation of data
the column names to identify one variable
For example, suppose we are looking data on interest rates.
Each column might represent one bond or asset and each row might represent the date.
Using hierarchical row and column indices allows us to store higher dimensional data in our (inherently) two dimensional DataFrame.
Know Your Goal#
The correct column(s) to choose for the index often depends on the context of your analysis.
For example, if I were studying how GDP and consumption evolved over time for various countries, I would want time (year) and country name on the index
On the other hand, if I were trying to look at the differences across countries and variables within a particular year, I may opt to put the country and variable on the index and have years be columns.
Following the tidy data rules above and thinking about how you intend to use the data – and a little practice – will enable you to consistently select the correct index.
Exercises#
Exercise 1#
What happens when you apply the mean
method to im_ex_tiny
?
In particular, what happens to columns that have missing data?
Hint
Also looking at the output of the sum
method might help.
Exercise 2#
For each of the examples below do the following:
Determine which of the rules above applies.
Identify the
type
of the returned value.Explain why the slicing operation returned the data it did.
Write your answers.
wdi.loc[["United States", "Canada"]]
wdi.loc[(["United States", "Canada"], [2010, 2011, 2012]), :]
wdi.loc["United States"]
wdi.loc[("United States", 2010), ["GDP", "Exports"]]
wdi.loc[("United States", 2010)]
wdi.loc[[("United States", 2010), ("Canada", 2015)]]
wdi.loc[["United States", "Canada"], "GDP"]
wdi.loc["United States", "GDP"]
Exercise 3#
Try setting my_df
to some subset of the rows in wdi
(use one of the
.loc
variations above).
Then see what happens when you do wdi / my_df
or my_df ** wdi
.
Try changing the subset of rows in my_df
and repeat until you
understand what is happening.
Exercise 4#
Below, we create wdi2
, which is the same as df4
except that the
levels of the index are swapped.
In the cells after df6
is defined, we have commented out
a few of the slicing examples from the previous exercise.
For each of these examples, use pd.IndexSlice
to extract the same
data from df6
.
Hint
You will need to swap the order of the row slicing arguments
within the pd.IndexSlice
.
wdi2 = df.set_index(["year", "country"])
# wdi.loc["United States"]
# wdi.loc[(["United States", "Canada"], [2010, 2011, 2012]), :]
# wdi.loc[["United States", "Canada"], "GDP"]
Exercise 5#
Use pd.IndexSlice
to extract all data from wdiT
where the year
level of the column names (the second level) is one of 2010, 2012, and 2014
Exercise 6#
Look up the documentation for the reset_index
method and study it to
learn how to do the following:
Move just the
year
level of the index back as a column.Completely throw away all levels of the index.
Remove the
country
of the index and do not keep it as a column.
# remove just year level and add as column
# throw away all levels of index
# Remove country from the index -- don't keep it as a column