16) Data Manipulation with Pandas¶
Related references:
First, let’s check that we have Pandas installed¶
Previous lectures had instructions to install Pandas. Let’s check if we are good to go!
In [1]:
# Just as NumPy is commonly abbreviated as 'np', Pandas is abbreviated as 'pd'
import pandas as pd
pd.__version__
Out[1]:
'0.21.0'
Pandas extends features of NumPy¶
Pandas is a new library built on NumPy. Ever wish you could have column
and row labels on a NumPy ndarray
? The Pandas DataFrame
answers
that wish, and can also handle heterogeneous types and/or missing data!
The three main data types in Pandas are built on the Numpy ndarray
:
Series
DataFrame
Index
We’ll also be using NumPy later, so we’ll import that now.
In [2]:
import numpy as np
The Pandas Series
Object¶
A Series
is a one-dimensional array-like object containing a
sequence of values (of similar types to NumPy types) and an associated
array of data labels, called its index. The simplest Series is formed
from only an array of data:
In [3]:
obj = pd.Series([4, 7, -5, 3])
obj
Out[3]:
0 4
1 7
2 -5
3 3
dtype: int64
The string representation of a Series displayed interactively shows the index on the left and the values on the right. Since we did not specify an index for the data, a default one consisting of the integers 0 through N - 1 (where N is the length of the data) is created. You can get the array representation and index object of the Series via its values and index attributes, respectively:
In [4]:
obj.values
Out[4]:
array([ 4, 7, -5, 3])
In [5]:
obj.index # like range(4)
Out[5]:
RangeIndex(start=0, stop=4, step=1)
Often it will be desirable to create a Series with an index identifying each data point with a label:
In [6]:
obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
obj2
Out[6]:
d 4
b 7
a -5
c 3
dtype: int64
In [7]:
obj2.index
Out[7]:
Index(['d', 'b', 'a', 'c'], dtype='object')
Like with a NumPy array, data can be accessed by the associated index via the familiar Python square-bracket notation:
In [8]:
obj2[1:3]
Out[8]:
b 7
a -5
dtype: int64
Compared with NumPy arrays, you can use labels in the index when selecting single values or a set of values:
In [9]:
obj2['a']
Out[9]:
-5
In [10]:
obj2['d'] = 6
obj2[['c', 'a', 'd']]
Out[10]:
c 3
a -5
d 6
dtype: int64
Here [‘c’, ‘a’, ‘d’] is interpreted as a list of indices, even though it contains strings instead of integers.
Using NumPy functions or NumPy-like operations, such as filtering with a boolean array, scalar multiplication, or applying math functions, will preserve the index-value link:
In [11]:
obj2[obj2 > 0]
Out[11]:
d 6
b 7
c 3
dtype: int64
In [12]:
obj2 * 2
Out[12]:
d 12
b 14
a -10
c 6
dtype: int64
In [13]:
np.exp(obj2)
Out[13]:
d 403.428793
b 1096.633158
a 0.006738
c 20.085537
dtype: float64
Another way to think about a Series is as a fixed-length, ordered dict, as it is a mapping of index values to data values. It can be used in many contexts where you might use a dict:
In [14]:
'b' in obj2
Out[14]:
True
In [15]:
'e' in obj2
Out[15]:
False
Should you have data contained in a Python dict, you can create a Series from it by passing the dict:
In [16]:
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3 = pd.Series(sdata)
obj3
Out[16]:
Ohio 35000
Oregon 16000
Texas 71000
Utah 5000
dtype: int64
When you are only passing a dict, the index in the resulting Series will have the dict’s keys in sorted order. You can override this by passing the dict keys in the order you want them to appear in the resulting Series:
In [17]:
states = ['Texas', 'California', 'Ohio', 'Oregon']
obj4 = pd.Series(sdata, index=states)
obj4
Out[17]:
Texas 71000.0
California NaN
Ohio 35000.0
Oregon 16000.0
dtype: float64
In [18]:
obj4.values
Out[18]:
array([ 71000., nan, 35000., 16000.])
Here, three values found in sdata were placed in the appropriate
locations, but since no value for California
was found, it appears
as NaN (not a number), which is considered in pandas to mark missing or
NA values. Since ‘Utah’ was not included in states, it is excluded from
the resulting object.
The isnull and notnull functions in pandas should be used to detect missing data:
In [19]:
pd.isnull(obj4)
Out[19]:
Texas False
California True
Ohio False
Oregon False
dtype: bool
In [20]:
pd.notnull(obj4)
Out[20]:
Texas True
California False
Ohio True
Oregon True
dtype: bool
Series also has these as instance methods:
In [21]:
obj4.isnull()
Out[21]:
Texas False
California True
Ohio False
Oregon False
dtype: bool
A useful Series feature for many applications is that it automatically aligns by index label in arithmetic operations:
In [22]:
obj3
Out[22]:
Ohio 35000
Oregon 16000
Texas 71000
Utah 5000
dtype: int64
In [23]:
obj4
Out[23]:
Texas 71000.0
California NaN
Ohio 35000.0
Oregon 16000.0
dtype: float64
In [24]:
obj3 + obj4
Out[24]:
California NaN
Ohio 70000.0
Oregon 32000.0
Texas 142000.0
Utah NaN
dtype: float64
Both the Series object itself and its index have a name attribute, which integrates with other key areas of pandas functionality:
In [25]:
obj4.name = 'population'
obj4.index.name = 'state'
obj4
Out[25]:
state
Texas 71000.0
California NaN
Ohio 35000.0
Oregon 16000.0
Name: population, dtype: float64
A Series’s index can be altered in-place by assignment:
In [26]:
obj
Out[26]:
0 4
1 7
2 -5
3 3
dtype: int64
In [27]:
obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
obj
Out[27]:
Bob 4
Steve 7
Jeff -5
Ryan 3
dtype: int64
The Pandas DataFrame
Object¶
A DataFrame represents a rectangular table of data and contains an ordered collection of columns, each of which can be a different value type (numeric, string, boolean, etc.). The DataFrame has both a row and column index; it can be thought of as a dict of Series all sharing the same index. Under the hood, the data is stored as one or more two-dimensional blocks rather than a list, dict, or some other collection of one-dimensional arrays.
FYI: While a DataFrame is physically two-dimensional, you can use it to represent higher dimensional data in a tabular format using hierarchical indexing, which we’ll come back to later.
There are many ways to construct a DataFrame
, though one of the most
common is from a dict of equal-length lists or NumPy arrays:
In [28]:
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
The resulting DataFrame will have its index assigned automatically as with Series, and the columns are placed in sorted order:
In [29]:
frame
Out[29]:
pop | state | year | |
---|---|---|---|
0 | 1.5 | Ohio | 2000 |
1 | 1.7 | Ohio | 2001 |
2 | 3.6 | Ohio | 2002 |
3 | 2.4 | Nevada | 2001 |
4 | 2.9 | Nevada | 2002 |
5 | 3.2 | Nevada | 2003 |
FYI: Thank Jupyter notebooks for the nice DataFrame output format.
For large DataFrames, the head method selects only the first five rows:
In [30]:
frame.head()
Out[30]:
pop | state | year | |
---|---|---|---|
0 | 1.5 | Ohio | 2000 |
1 | 1.7 | Ohio | 2001 |
2 | 3.6 | Ohio | 2002 |
3 | 2.4 | Nevada | 2001 |
4 | 2.9 | Nevada | 2002 |
If you pass a column that isn’t contained in the dict, it will appear with missing values in the result:
In [31]:
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
index=['one', 'two', 'three', 'four',
'five', 'six'])
frame2
Out[31]:
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | NaN |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | NaN |
five | 2002 | Nevada | 2.9 | NaN |
six | 2003 | Nevada | 3.2 | NaN |
A column in a DataFrame can be retrieved as a Series either by dict-like notation or by attribute:
In [32]:
frame2['state']
Out[32]:
one Ohio
two Ohio
three Ohio
four Nevada
five Nevada
six Nevada
Name: state, dtype: object
Rows can also be retrieved by position or name with the special loc
attribute:
In [33]:
frame2.loc['three']
Out[33]:
year 2002
state Ohio
pop 3.6
debt NaN
Name: three, dtype: object
Columns can be modified by assignment. For example, the empty ‘debt’ column could be assigned a scalar value or an array of values:
In [34]:
frame2['debt'] = 16.5
frame2
Out[34]:
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | 16.5 |
two | 2001 | Ohio | 1.7 | 16.5 |
three | 2002 | Ohio | 3.6 | 16.5 |
four | 2001 | Nevada | 2.4 | 16.5 |
five | 2002 | Nevada | 2.9 | 16.5 |
six | 2003 | Nevada | 3.2 | 16.5 |
In [35]:
frame2['debt'] = np.arange(6.)
frame2
Out[35]:
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | 0.0 |
two | 2001 | Ohio | 1.7 | 1.0 |
three | 2002 | Ohio | 3.6 | 2.0 |
four | 2001 | Nevada | 2.4 | 3.0 |
five | 2002 | Nevada | 2.9 | 4.0 |
six | 2003 | Nevada | 3.2 | 5.0 |
When you are assigning lists or arrays to a column, the value’s length must match the length of the DataFrame. If you assign a Series, its labels will be realigned exactly to the DataFrame’s index, inserting missing values in any holes:
In [36]:
val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
frame2['debt'] = val
frame2
Out[36]:
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | -1.2 |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | -1.5 |
five | 2002 | Nevada | 2.9 | -1.7 |
six | 2003 | Nevada | 3.2 | NaN |
In [37]:
val2 = pd.Series([-1.2, -1.5, -1.7], index=['one', 'three', 'six'])
frame2['debt'] = val
frame2
Out[37]:
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | -1.2 |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | -1.5 |
five | 2002 | Nevada | 2.9 | -1.7 |
six | 2003 | Nevada | 3.2 | NaN |
Assigning a column that doesn’t exist will create a new column. The
del
keyword will delete columns as with a dict.
As an example of del
, let’s add a new column of boolean values where
the state column equals ‘Ohio’
In [38]:
frame2['eastern'] = frame2['state'] == 'Ohio'
frame2
Out[38]:
year | state | pop | debt | eastern | |
---|---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN | True |
two | 2001 | Ohio | 1.7 | -1.2 | True |
three | 2002 | Ohio | 3.6 | NaN | True |
four | 2001 | Nevada | 2.4 | -1.5 | False |
five | 2002 | Nevada | 2.9 | -1.7 | False |
six | 2003 | Nevada | 3.2 | NaN | False |
The del
method can then be used to remove this column:
In [39]:
del frame2['eastern']
frame2.columns
Out[39]:
Index(['year', 'state', 'pop', 'debt'], dtype='object')
Caution: The column returned from indexing a DataFrame is a view on
the underlying data, not a copy. Thus, any in-place modifications to the
Series will be reflected in the DataFrame. The column can be explicitly
copied with the Series’s copy
method.
Another common form of data is a nested dict of dicts:
In [40]:
pop = {'Nevada': {2001: 2.4, 2002: 2.9},
'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
If the nested dict is passed to the DataFrame, pandas will interpret the outer dict keys as the columns and the inner keys as the row indices:
In [41]:
frame3 = pd.DataFrame(pop)
frame3
Out[41]:
Nevada | Ohio | |
---|---|---|
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
Given a two-dimensional array of data, we can create a DataFrame
with any specified column and index names. If omitted, an integer index
will be used for each:
In [42]:
pd.DataFrame(np.random.rand(3, 2),
columns=['foo', 'bar'],
index=['a', 'b', 'c'])
Out[42]:
foo | bar | |
---|---|---|
a | 0.480958 | 0.226559 |
b | 0.760850 | 0.898359 |
c | 0.503980 | 0.902335 |
You can transpose the DataFrame (swap rows and columns) with similar syntax to a NumPy array:
In [43]:
frame3.T
Out[43]:
2000 | 2001 | 2002 | |
---|---|---|---|
Nevada | NaN | 2.4 | 2.9 |
Ohio | 1.5 | 1.7 | 3.6 |
The keys in the inner dicts are combined and sorted to form the index in the result. This isn’t true if an explicit index is specified:
In [44]:
pd.DataFrame(pop, index=[2001, 2002, 2003])
Out[44]:
Nevada | Ohio | |
---|---|---|
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
2003 | NaN | NaN |
Dicts of Series are treated in much the same way:
In [45]:
pdata = {'Ohio': frame3['Ohio'][:-1],
'Nevada': frame3['Nevada'][:2]}
pd.DataFrame(pdata)
Out[45]:
Nevada | Ohio | |
---|---|---|
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
If a DataFrame’s index and columns have their name attributes set, these will also be displayed:
In [46]:
frame3.index.name = 'year'
frame3.columns.name = 'state'
frame3
Out[46]:
state | Nevada | Ohio |
---|---|---|
year | ||
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
As with Series, the values attribute returns the data contained in the DataFrame as a two-dimensional ndarray:
In [47]:
frame3.values
Out[47]:
array([[ nan, 1.5],
[ 2.4, 1.7],
[ 2.9, 3.6]])
In [48]:
type(frame3.values)
Out[48]:
numpy.ndarray
If the DataFrame’s columns are different dtypes, the dtype of the values array will be chosen to accommodate all of the columns:
In [49]:
frame2.values
Out[49]:
array([[2000, 'Ohio', 1.5, nan],
[2001, 'Ohio', 1.7, -1.2],
[2002, 'Ohio', 3.6, nan],
[2001, 'Nevada', 2.4, -1.5],
[2002, 'Nevada', 2.9, -1.7],
[2003, 'Nevada', 3.2, nan]], dtype=object)
In [50]:
type(frame2.values)
Out[50]:
numpy.ndarray
The Pandas Index
Object¶
Index objects are responsible for holding the axis labels and other metadata (like the axis name or names). Any array or other sequence of labels you use when constructing a Series or DataFrame is internally converted to an Index:
In [51]:
obj = pd.Series(range(3), index=['a', 'b', 'c'])
index = obj.index
index
Out[51]:
Index(['a', 'b', 'c'], dtype='object')
In [52]:
index[1:]
Out[52]:
Index(['b', 'c'], dtype='object')
Index objects are immutable and thus can’t be modified by the user:
In [53]:
index[1] = 'd' # TypeError
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-53-c25f1f037188> in <module>()
----> 1 index[1] = 'd' # TypeError
~/.local/lib/python3.6/site-packages/pandas/core/indexes/base.py in __setitem__(self, key, value)
1722
1723 def __setitem__(self, key, value):
-> 1724 raise TypeError("Index does not support mutable operations")
1725
1726 def __getitem__(self, key):
TypeError: Index does not support mutable operations
Immutability makes it safer to share Index objects among data structures:
In [54]:
labels = pd.Index(np.arange(3))
labels
Out[54]:
Int64Index([0, 1, 2], dtype='int64')
In [55]:
obj2 = pd.Series([1.5, -2.5, 0], index=labels)
obj2
Out[55]:
0 1.5
1 -2.5
2 0.0
dtype: float64
In addition to being array-like, an Index also behaves like a fixed-size set:
In [56]:
frame3
Out[56]:
state | Nevada | Ohio |
---|---|---|
year | ||
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
In [57]:
frame3.columns
Out[57]:
Index(['Nevada', 'Ohio'], dtype='object', name='state')
In [58]:
'Ohio' in frame3.columns
Out[58]:
True
In [59]:
2003 in frame3.index
Out[59]:
False
Caution: Unlike Python sets, a pandas Index can contain duplicate labels:
In [60]:
dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])
dup_labels
Out[60]:
Index(['foo', 'foo', 'bar', 'bar'], dtype='object')
Selections with duplicate labels will select all occurrences of that label.
Some Index
methods and properties¶
Method | Description |
---|---|
append | Concatenate with additional Index objects, producing a new Index |
difference | Compute set difference as an Index |
intersection | Compute set intersection |
union | Compute set union |
isin | Compute boolean array indicating whether each value is contained in the passed collection |
delete | Compute new Index with element at index i deleted |
drop | Compute new Index by deleting passed values |
insert | Compute new Index by inserting element at index i |
is_monotonic | Returns True if each element is greater than or equal to the previous element |
is_unique | Returns True if the Index has no duplicate values |
unique | Compute the array of unique values in the Index |
For example:
In [61]:
indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
indA.is_monotonic
Out[61]:
True
In [62]:
indA.intersection(indB)
Out[62]:
Int64Index([3, 5, 7], dtype='int64')
Essential Functionality¶
As throughout this class, we’ll focus on the most important features, leaving the less common (i.e., more esoteric) things for you to explore on your own.
Reindexing¶
An important method on pandas objects is reindex
, which means to
create a new object with the data conformed to a new index. Consider
an example:
In [63]:
obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
obj
Out[63]:
d 4.5
b 7.2
a -5.3
c 3.6
dtype: float64
Calling reindex
on this Series rearranges the data according to the
new index, introducing missing values if any index values were not
already present:
In [64]:
obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
obj2
Out[64]:
a -5.3
b 7.2
c 3.6
d 4.5
e NaN
dtype: float64
For ordered data like time series, it may be desirable to do some
interpolation or filling of values when reindexing. The method option
allows us to do this, using a method such as ffill
, which
forward-fills the values:
In [65]:
obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
obj3
Out[65]:
0 blue
2 purple
4 yellow
dtype: object
In [66]:
obj3.reindex(range(6), method='ffill')
Out[66]:
0 blue
1 blue
2 purple
3 purple
4 yellow
5 yellow
dtype: object
In [67]:
# We can also backfill
obj3.reindex(range(6), method='bfill')
Out[67]:
0 blue
1 purple
2 purple
3 yellow
4 yellow
5 NaN
dtype: object
In [68]:
# or fill with a specified value
obj3.reindex(range(6), fill_value='white')
Out[68]:
0 blue
1 white
2 purple
3 white
4 yellow
5 white
dtype: object
With DataFrame, reindex can alter either the (row) index, columns, or both. When passed only a sequence, it reindexes the rows in the result:
In [69]:
frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
index=['a', 'c', 'd'],
columns=['Ohio', 'Texas', 'California'])
frame
Out[69]:
Ohio | Texas | California | |
---|---|---|---|
a | 0 | 1 | 2 |
c | 3 | 4 | 5 |
d | 6 | 7 | 8 |
In [70]:
frame2 = frame.reindex(['a', 'b', 'c', 'd'])
frame2
Out[70]:
Ohio | Texas | California | |
---|---|---|---|
a | 0.0 | 1.0 | 2.0 |
b | NaN | NaN | NaN |
c | 3.0 | 4.0 | 5.0 |
d | 6.0 | 7.0 | 8.0 |
The columns can be reindexed with the columns keyword:
In [71]:
states = ['Texas', 'Utah', 'California']
frame.reindex(columns=states)
Out[71]:
Texas | Utah | California | |
---|---|---|---|
a | 1 | NaN | 2 |
c | 4 | NaN | 5 |
d | 7 | NaN | 8 |
Dropping Entries from an Axis¶
Dropping one or more entries from an axis is easy if you already have an index array or list without those entries. As that can require a bit of munging and set logic, the drop method will return a new object with the indicated value or values deleted from an axis:
In [72]:
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
obj
Out[72]:
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
In [73]:
new_obj = obj.drop('c')
new_obj
Out[73]:
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
In [74]:
obj.drop(['d', 'c'])
Out[74]:
a 0.0
b 1.0
e 4.0
dtype: float64
In [75]:
obj
Out[75]:
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
With DataFrame, index values can be deleted from either axis. To illustrate this, we first create an example DataFrame:
In [76]:
data = pd.DataFrame(np.arange(16).reshape((4, 4)),
index=['Ohio', 'Colorado', 'Utah', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
Out[76]:
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
Calling drop
with a sequence of labels will drop values from the row
labels (axis 0):
In [77]:
data.drop(['Colorado', 'Ohio'])
Out[77]:
one | two | three | four | |
---|---|---|---|---|
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
You can drop values from the columns by passing axis=1 or axis=‘columns’:
In [78]:
data.drop('two', axis=1)
Out[78]:
one | three | four | |
---|---|---|---|
Ohio | 0 | 2 | 3 |
Colorado | 4 | 6 | 7 |
Utah | 8 | 10 | 11 |
New York | 12 | 14 | 15 |
In [79]:
data.drop(['two', 'four'], axis='columns')
Out[79]:
one | three | |
---|---|---|
Ohio | 0 | 2 |
Colorado | 4 | 6 |
Utah | 8 | 10 |
New York | 12 | 14 |
Many functions, like drop, which modify the size or shape of a Series or DataFrame, can manipulate an object in-place without returning a new object:
In [80]:
obj.drop('c', inplace=True)
obj
Out[80]:
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
Be careful with the inplace, as it destroys any data that is dropped.
Indexing, Selection, and Filtering¶
Series indexing (obj[…]) works analogously to NumPy array indexing, except you can use the Series’s index values instead of only integers. Here are some examples of this:
In [81]:
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
obj
Out[81]:
a 0.0
b 1.0
c 2.0
d 3.0
dtype: float64
In [82]:
obj['b']
Out[82]:
1.0
In [83]:
obj[1]
Out[83]:
1.0
In [84]:
obj[2:4]
Out[84]:
c 2.0
d 3.0
dtype: float64
In [85]:
obj[['b', 'a', 'd']]
Out[85]:
b 1.0
a 0.0
d 3.0
dtype: float64
In [86]:
obj[[1, 3]]
Out[86]:
b 1.0
d 3.0
dtype: float64
In [87]:
obj[obj < 2]
Out[87]:
a 0.0
b 1.0
dtype: float64
Caution: Slicing with labels behaves differently than normal Python slicing in that the endpoint is inclusive:
In [88]:
obj['b':'c']
Out[88]:
b 1.0
c 2.0
dtype: float64
Setting using these methods modifies the corresponding section of the Series:
In [89]:
obj['b':'c'] = 5
obj
Out[89]:
a 0.0
b 5.0
c 5.0
d 3.0
dtype: float64
Indexing into a DataFrame is for retrieving one or more columns either with a single value or sequence:
In [90]:
data = pd.DataFrame(np.arange(16).reshape((4, 4)),
index=['Ohio', 'Colorado', 'Utah', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
Out[90]:
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
In [91]:
data['two']
Out[91]:
Ohio 1
Colorado 5
Utah 9
New York 13
Name: two, dtype: int64
In [92]:
data[['three', 'one']]
Out[92]:
three | one | |
---|---|---|
Ohio | 2 | 0 |
Colorado | 6 | 4 |
Utah | 10 | 8 |
New York | 14 | 12 |
Selection with loc
and iloc
¶
These slicing and indexing conventions can be a source of confusion. For
example, if your Series
has an explicit integer index, an indexing
operation such as data[1]
will use the explicit indices, while a
slicing operation like data[1:3]
will use the implicit Python-style
index.
In [93]:
data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
data
Out[93]:
1 a
3 b
5 c
dtype: object
In [94]:
# explicit index when indexing
data[1]
Out[94]:
'a'
In [95]:
# implicit index when slicing
data[1:3]
Out[95]:
3 b
5 c
dtype: object
Because of this potential confusion in the case of integer indexes,
Pandas provides some special indexer attributes that explicitly expose
certain indexing schemes. These are not functional methods, but
attributes that expose a particular slicing interface to the data in the
Series
.
First, the loc
attribute allows indexing and slicing that always
references the explicit index:
In [96]:
data.loc[1]
Out[96]:
'a'
In [97]:
data.loc[1:3]
Out[97]:
1 a
3 b
dtype: object
The iloc
attribute allows indexing and slicing that always
references the implicit Python-style index:
In [98]:
data.iloc[1]
Out[98]:
'b'
In [99]:
data.iloc[1:3]
Out[99]:
3 b
5 c
dtype: object
One guiding principle of Python code is that “explicit is better than
implicit.” The explicit nature of loc
and iloc
make them very
useful in maintaining clean and readable code; especially in the case of
integer indexes, I recommend using these both to make code easier to
read and understand, and to prevent subtle bugs due to the mixed
indexing/slicing convention.
Next up: Data selection in dataframes!