pd.merge
function, and we'll see few examples of how this can work in practice.display()
functionality from the previous section:import pandas as pd
import numpy as np
class display(object):
"""Display HTML representation of multiple objects"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
</div>"""
def __init__(self, *args):
self.args = args
def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)
def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
pd.merge()
is a subset of what is known as relational algebra, which is a formal set of rules for manipulating relational data, and forms the conceptual foundation of operations available in most databases.
The strength of the relational algebra approach is that it proposes several primitive operations, which become the building blocks of more complicated operations on any dataset.
With this lexicon of fundamental operations implemented efficiently in a database or other program, a wide range of fairly complicated composite operations can be performed.pd.merge()
function and the related join()
method of Series
and Dataframe
s.
As we will see, these let you efficiently link data from different sources.pd.merge()
function implements a number of types of joins: the one-to-one, many-to-one, and many-to-many joins.
All three types of joins are accessed via an identical call to the pd.merge()
interface; the type of join performed depends on the form of the input data.
Here we will show simple examples of the three types of merges, and discuss detailed options further below.DataFrames
which contain information on several employees in a company:df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],
'group': ['Accounting', 'Engineering', 'Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],
'hire_date': [2004, 2008, 2012, 2014]})
display('df1', 'df2')
DataFrame
, we can use the pd.merge()
function:df3 = pd.merge(df1, df2)
df3
pd.merge()
function recognizes that each DataFrame
has an "employee" column, and automatically joins using this column as a key.
The result of the merge is a new DataFrame
that combines the information from the two inputs.
Notice that the order of entries in each column is not necessarily maintained: in this case, the order of the "employee" column differs between df1
and df2
, and the pd.merge()
function correctly accounts for this.
Additionally, keep in mind that the merge in general discards the index, except in the special case of merges by index (see the left_index
and right_index
keywords, discussed momentarily).DataFrame
will preserve those duplicate entries as appropriate.
Consider the following example of a many-to-one join:df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'],
'supervisor': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')
DataFrame
has an aditional column with the "supervisor" information, where the information is repeated in one or more locations as required by the inputs.DataFrame
showing one or more skills associated with a particular group.
By performing a many-to-many join, we can recover the skills associated with any individual person:df5 = pd.DataFrame({'group': ['Accounting', 'Accounting',
'Engineering', 'Engineering', 'HR', 'HR'],
'skills': ['math', 'spreadsheets', 'coding', 'linux',
'spreadsheets', 'organization']})
display('df1', 'df5', "pd.merge(df1, df5)")
pd.merge()
that enable you to tune how the join operations work.pd.merge()
: it looks for one or more matching column names between the two inputs, and uses this as the key.
However, often the column names will not match so nicely, and pd.merge()
provides a variety of options for handling this.on
keywordon
keyword, which takes a column name or a list of column names:display('df1', 'df2', "pd.merge(df1, df2, on='employee')")
DataFrame
s have the specified column name.left_on
and right_on
keywordsleft_on
and right_on
keywords to specify the two column names:df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'salary': [70000, 80000, 120000, 90000]})
display('df1', 'df3', 'pd.merge(df1, df3, left_on="employee", right_on="name")')
drop()
method of DataFrame
s:pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)
left_index
and right_index
keywordsdf1a = df1.set_index('employee')
df2a = df2.set_index('employee')
display('df1a', 'df2a')
left_index
and/or right_index
flags in pd.merge()
:display('df1a', 'df2a',
"pd.merge(df1a, df2a, left_index=True, right_index=True)")
DataFrame
s implement the join()
method, which performs a merge that defaults to joining on indices:display('df1a', 'df2a', 'df1a.join(df2a)')
left_index
with right_on
or left_on
with right_index
to get the desired behavior:display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='name')")
df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],
'food': ['fish', 'beans', 'bread']},
columns=['name', 'food'])
df7 = pd.DataFrame({'name': ['Mary', 'Joseph'],
'drink': ['wine', 'beer']},
columns=['name', 'drink'])
display('df6', 'df7', 'pd.merge(df6, df7)')
how
keyword, which defaults to "inner"
:pd.merge(df6, df7, how='inner')
how
keyword are 'outer'
, 'left'
, and 'right'
.
An outer join returns a join over the union of the input columns, and fills in all missing values with NAs:display('df6', 'df7', "pd.merge(df6, df7, how='outer')")
display('df6', 'df7', "pd.merge(df6, df7, how='left')")
how='right'
works in a similar manner.suffixes
KeywordDataFrame
s have conflicting column names.
Consider this example:df8 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'rank': [1, 2, 3, 4]})
df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'rank': [3, 1, 4, 2]})
display('df8', 'df9', 'pd.merge(df8, df9, on="name")')
_x
or _y
to make the output columns unique.
If these defaults are inappropriate, it is possible to specify a custom suffix using the suffixes
keyword:display('df8', 'df9', 'pd.merge(df8, df9, on="name", suffixes=["_L", "_R"])')
# Following are shell commands to download the data
# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-population.csv
# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-areas.csv
# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-abbrevs.csv
read_csv()
function:pop = pd.read_csv('data/state-population.csv')
areas = pd.read_csv('data/state-areas.csv')
abbrevs = pd.read_csv('data/state-abbrevs.csv')
display('pop.head()', 'areas.head()', 'abbrevs.head()')
DataFrame
.
We want to merge based on the state/region
column of pop
, and the abbreviation
column of abbrevs
.
We'll use how='outer'
to make sure no data is thrown away due to mismatched labels.merged = pd.merge(pop, abbrevs, how='outer',
left_on='state/region', right_on='abbreviation')
merged = merged.drop('abbreviation', 1) # drop duplicate info
merged.head()
merged.isnull().any()
population
info is null; let's figure out which these are!merged[merged['population'].isnull()].head()
state
entries are also null, which means that there was no corresponding entry in the abbrevs
key!
Let's figure out which regions lack this match:merged.loc[merged['state'].isnull(), 'state/region'].unique()
merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'
merged.isnull().any()
state
column: we're all set!state
column in both:final = pd.merge(merged, areas, on='state', how='left')
final.head()
final.isnull().any()
area
column; we can take a look to see which regions were ignored here:final['state'][final['area (sq. mi)'].isnull()].unique()
areas
DataFrame
does not contain the area of the United States as a whole.
We could insert the appropriate value (using the sum of all state areas, for instance), but in this case we'll just drop the null values because the population density of the entire United States is not relevant to our current discussion:final.dropna(inplace=True)
final.head()
query()
function to do this quickly (this requires the numexpr
package to be installed; see High-Performance Pandas: eval()
and query()
):data2010 = final.query("year == 2010 & ages == 'total'")
data2010.head()
data2010.set_index('state', inplace=True)
density = data2010['population'] / data2010['area (sq. mi)']
density.sort_values(ascending=False, inplace=True)
density.head()
density.tail()