Data wrangling: join, combine, reshape
Table of Contents¶
import pandas as pd
# import pandas_datareader.data as web
print(pd.__version__)
import numpy as np0.25.0
Data wrangling: join, combine, reshape¶
- Data can be spread across files or databases, or in a way that is difficult to analyze
Hierarchical indexing¶
Two or more index levels on an axis
- Allows to work with high dimensional data using a lower dimensional form
Used in:
- group based operations
- pivot tables
- reshaping
INV: A hierarchical index comes when a list of lists is used (instead of a simple list)
INV: Indexing in a hierarchical index is like indexing in a np array
Series hierarchical index¶
# Create a hierarchical index.
np.random.seed(10)
data = pd.Series(
np.random.randn(9),
index=[
["a", "a", "a", "b", "b", "c", "c", "d", "d"],
[1, 2, 3, 1, 3, 1, 2, 2, 3],
],
)
dataa 1 1.331587
2 0.715279
3 -1.545400
b 1 -0.008384
3 0.621336
c 1 -0.720086
2 0.265512
d 2 0.108549
3 0.004291
dtype: float64# There are
# - "levels", i.e., the values that are used as Cartesian product.
# - the subset of the indices (in the "labels" sets).
# E.g.,
# (0, 0) -> (a, 1)
# (0, 1) -> (a, 2)
# ...
data.indexMultiIndex([('a', 1),
('a', 2),
('a', 3),
('b', 1),
('b', 3),
('c', 1),
('c', 2),
('d', 2),
('d', 3)],
)Partial indexing¶
# Partial indexing.
# Extract the values for the first level.
data["b"]1 -0.008384
3 0.621336
dtype: float64# Range of one level of an index.
data["b":"c"]b 1 -0.008384
3 0.621336
c 1 -0.720086
2 0.265512
dtype: float64# Select some indices with .loc[list].
data.loc[["b", "d"]]b 1 -0.008384
3 0.621336
d 2 0.108549
3 0.004291
dtype: float64# Select all the first level and part of the second.
# Indexing works like a numpy array.
data.loc[:, 2]a 0.715279
c 0.265512
d 0.108549
dtype: float64# .unstack() moves one level of indexing into the columns.
data2 = data.unstack()
data2Loading...
# .stack() moves the column index into a hierarchical index.
data2.stack()a 1 1.331587
2 0.715279
3 -1.545400
b 1 -0.008384
3 0.621336
c 1 -0.720086
2 0.265512
d 2 0.108549
3 0.004291
dtype: float64Dataframe hierarchical index¶
- In a df both columns and rows can have a hierarchical index
- Note that indices in pandas have “values” and “names”
frame = pd.DataFrame(np.arange(12).reshape((4, 3)))
frameLoading...
frame = pd.DataFrame(
np.arange(12).reshape((4, 3)),
index=["a a b b".split(), list(map(int, "1 2 1 2".split()))],
columns=["Ohio Ohio Colorado".split(), "Green Red Green".split()],
)
frameLoading...
frame.indexMultiIndex([('a', 1),
('a', 2),
('b', 1),
('b', 2)],
)frame.columnsMultiIndex([( 'Ohio', 'Green'),
( 'Ohio', 'Red'),
('Colorado', 'Green')],
)# The levels have no name.
print(frame.index.names)
print(frame.columns.names)[None, None]
[None, None]
# Assign names to hierarchical levels.
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
frameLoading...
# A multiindex can be created by itself and used.print(frame.columns)MultiIndex([( 'Ohio', 'Green'),
( 'Ohio', 'Red'),
('Colorado', 'Green')],
names=['state', 'color'])
pd.MultiIndex.from_arrays(
["Ohio Ohio Colorado".split(), "Green Red Green".split()],
names=["state", "color"],
)MultiIndex([( 'Ohio', 'Green'),
( 'Ohio', 'Red'),
('Colorado', 'Green')],
names=['state', 'color'])Reordering and sorting levels¶
swaplevel()¶
# Swap (or reorder) the order of the levels on an axis.
display(frame)
frame.swaplevel("key1", "key2")Loading...
Loading...
sort_index¶
# Sort the values in one specific level.
display(frame)
frame2 = frame.sort_index(
# level=1 is key2
# level="key2")
level=1
)
frame2Loading...
Loading...
frame2.sort_index(
# level=0 is key1
level=0
)Loading...
Summary statistics by level¶
frameLoading...
# frame.sum(level='key2')
frame.sum(level=1)Loading...
frame.sum(level="color", axis=1)Loading...
Indexing with df columns¶
- One can create an index from a column
DataFrame.set_index()¶
# Build from dictionary: each (key, value) becomes a column.
# A new index is added.
frame = pd.DataFrame(
{
"a": list(range(7)),
"b": list(range(7, 0, -1)),
"c": ["one", "one", "one", "two", "two", "two", "two"],
"d": [0, 1, 2, 0, 1, 2, 3],
}
)
frameLoading...
# Use two columns as index.
frame2 = frame.set_index(["c", "d"])
frame2Loading...
DataFrame.reset_index()¶
# Move index to columns and reindex with unique integer
frame2.reset_index()Loading...
# Two reset index creates two indices.
frame2.reset_index().reset_index()Loading...
Combining and merging datasets¶
Db-style df joins¶
df1 = pd.DataFrame(
{"key": ["b", "b", "a", "c", "a", "a", "b"], "data1": list(range(7))}
)
df2 = pd.DataFrame({"key": ["a", "b", "d"], "data2": list(range(3))})
print("df1=")
display(df1)
print("\ndf2=")
display(df2)df1=
Loading...
df2=
Loading...
# We want to merge on column "key"
# This is a many-to-one join
# - the data in df1['key'] has multiple rows with 'a', and 'b'
# - df2['key'] has fewer
# so there will be a partial Cartesian product.
# Default values
# - Without specifying the columns to join, the common columns are used
# - inner join
# pd.merge(df1, df2, on='key')
pd.merge(df1, df2)Loading...
# One can specify explicitly different column names
# E.g.,
# pd.merge(df3, df4, left_on='lkey', right_on='rkey')
# - Indexes are discarded
# - Common columns not used for joined are renamed _x, _y# To merge on index
# pd.merge(df1, df2, left_on='key', right_index=True)
# Merge on index with multi-index
# pd.merge(df1, df2, left_on=['key1', 'key2'], right_index=True)
# Merge on both indices
# pd.merge(df1, df2, how='outer', left_index=True, right_index=True)Concatenating along an axis¶
- Aka stacking
Concat in numpy¶
arr = np.arange(12)
arrarray([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])arr = np.arange(12).reshape((3, 4))
arrarray([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])# Concatenate horizontally.
np.concatenate([arr, arr], axis=1)array([[ 0, 1, 2, 3, 0, 1, 2, 3],
[ 4, 5, 6, 7, 4, 5, 6, 7],
[ 8, 9, 10, 11, 8, 9, 10, 11]])# Concatenate vertically.
np.concatenate([arr, arr], axis=0)array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])Concat in pandas Series¶
- Since pandas objects (e.g., Series and DataFrame) have labeled axis
- If indexes are different, should do union or intersection of labels?
- Should we preserve the labels or discard it after concatenation?
s1 = pd.Series([0, 1], index="a b".split())
print("s1=\n%s" % s1)
s2 = pd.Series([2, 3, 4], index="c d e".split())
print("\ns2=\n%s" % s2)
s3 = pd.Series([5, 6], index="f g".split())
print("\ns3=\n%s" % s3)s1=
a 0
b 1
dtype: int64
s2=
c 2
d 3
e 4
dtype: int64
s3=
f 5
g 6
dtype: int64
# Concat series along axis=0 producing another Series with index and values glued together.
pd.concat([s1, s2, s3], axis=0)a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64# Concat series along axis=1, does a union of the indices and then merges into a DataFrame
# (sorted outer join).
pd.concat([s1, s2, s3], axis=1, sort=True)Loading...
# concat() can also do a inner join.
pd.concat([s1, s2, s3], axis=1, join="inner")Loading...
# If you want to identify where the data came from, you can use
# a hierarchical axis.
result = pd.concat([s1, s1, s3], keys="one two three".split())
print(result.index)
resultMultiIndex([( 'one', 'a'),
( 'one', 'b'),
( 'two', 'a'),
( 'two', 'b'),
('three', 'f'),
('three', 'g')],
)
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64# Unstack moves a level of hierarchical indexing into columns.
result.unstack()Loading...
Concat in pandas DataFrame¶
df1 = pd.DataFrame(
np.arange(6).reshape(3, 2), index=["a", "b", "c"], columns=["one", "two"]
)
print("df1=")
display(df1)
df2 = pd.DataFrame(
5 + np.arange(4).reshape(2, 2), index=["a", "c"], columns=["three", "four"]
)
print("\ndf2=")
display(df2)df1=
Loading...
df2=
Loading...
# Merge using hierarchical columns to track where the data is coming from.
# axis=1 means (columns)
pd.concat([df1, df2], axis=1, keys=["level1", "level2"])/Users/saggese/.conda/envs/study/lib/python3.7/site-packages/ipykernel_launcher.py:3: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
This is separate from the ipykernel package so we can avoid doing imports until
Loading...
# "ignore_index=True" is used to ignore the indices and create a new
# numeric index.
pd.concat([df1, df2], axis=0, sort=True)Loading...
pd.concat([df1, df2], axis=0, ignore_index=True, sort=True)Loading...
# Check if there are duplicated indices.
# pd.concat([df1, df2], axis=0, verify_integrity=True)Combining data with overlap¶
# np.where() performs a vectorized if-then-else operation.
a = pd.Series(
[np.nan, 2.5, np.nan, 3.5, 4.5, np.nan], index="f e d c b a".split()
)
print(a)f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
b = pd.Series(np.arange(len(a), dtype=np.float64), index="f e d c b a".split())
b[-1] = np.nan
display(b)f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64# We can combine the two series by using the values from a and
# using values from b to fill the nan.
# Compute the null values
np.where(pd.isnull(a), b, a)array([0. , 2.5, 2. , 3.5, 4.5, nan])a.combine_first(b)f 0.0
e 2.5
d 2.0
c 3.5
b 4.5
a NaN
dtype: float64Reshaping and pivoting¶
Reshaping with hierarchical indexing¶
stack: rotates (or pivots) from the columns to the rowsunstack: pivots from the rows into the columns
data = pd.DataFrame(
np.arange(6).reshape((2, 3)),
index=pd.Index(["Ohio", "Colorado"], name="state"),
columns=pd.Index(["one", "two", "three"], name="number"),
)
dataLoading...
# Note that it produces a Series since it has a single column.
result = data.stack()
print(type(result))
print(type(result.index))
result<class 'pandas.core.series.Series'>
<class 'pandas.core.indexes.multi.MultiIndex'>
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int64result.unstack()Loading...
# One can unstack a different level than the innermost.
# Level 0 is the outermost.
result.unstack(0)Loading...
# unstack() can produce nan if there are missing values.
s1 = pd.Series([0, 1, 2, 3], index=["a", "b", "c", "d"])
s2 = pd.Series([4, 5, 6], index=["c", "d", "e"])
# Concat vertically the series.
data2 = pd.concat([s1, s2], keys=["one", "two"])
print("data2=")
print(type(data2))
display(data2)
print("data2.unstack=")
data2.unstack()data2=
<class 'pandas.core.series.Series'>
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64data2.unstack=
Loading...
print("data2=")
# gives the original object.
data2.unstack().stack()data2=
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64Pivoting “long” to “wide” format¶
# git clone https://github.com/wesm/pydata-book
data = pd.read_csv("/Users/saggese/src/pydata-book/examples/macrodata.csv")
display(data.head())
display(data.tail())Loading...
Loading...
# Combine year and quarter to create a kind of time interval type.
periods = pd.PeriodIndex(year=data.year, quarter=data.quarter, name="date")
print(periods)
data2 = data.copy()
data2.index = periods.to_timestamp("D", "end")
# The data is in "wide" format.
data2.head()PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
'1960Q3', '1960Q4', '1961Q1', '1961Q2',
...
'2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
'2008Q4', '2009Q1', '2009Q2', '2009Q3'],
dtype='period[Q-DEC]', name='date', length=203, freq='Q-DEC')
Loading...
# We need to assign a name to the column otherwise when we "melt"
# the column is not going to have a name.
columns = pd.Index(["realgdp", "infl", "unemp"], name="item")
# Use reindex to extract columns.
data2 = data2.reindex(columns=columns)
print("columns=", columns)
data2.head()columns= Index(['realgdp', 'infl', 'unemp'], dtype='object', name='item')
Loading...
# Put data in "long" format by moving the three columns into one.
ldata = data2.stack().reset_index()
# Rename column to value.
ldata = ldata.rename(columns={0: "value"})
ldata.head()Loading...
# pivot() allows to transform from long format to wide format.
pivoted = ldata.pivot("date", "item", "value")
pivoted.head()Loading...
# Add another row: now we have an item and 2 values for each.
ldata2 = ldata.copy()
ldata2["value2"] = np.random.randn(len(ldata))
display(ldata2.head())Loading...
# Pivoting when there are multiple values creates a hierarchical
# data frame.
pivoted = ldata2.pivot("date", "item")
pivoted.head()Loading...
Pivoting “wide” to “long” format¶
# The inverse operation to "pivot" is "melt".
df = pd.DataFrame(
{
"key": ["foo", "bar", "baz"],
"A": [1, 2, 3],
"B": [4, 5, 6],
"C": [7, 8, 9],
}
)
dfLoading...
# We need to decide which column is:
# - the variable
# - the value
melted = pd.melt(df, ["key"])
meltedLoading...