Mathew K Analytics

Lesson 8 · Pandas Full Course

Pandas loc vs iloc: Select Exactly the Rows and Columns You Need | Pandas Course #8

In this lesson you'll be able to: Pick one column or several with square brackets Select rows and columns by position with iloc Select rows and columns by…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Pandas 08: Selecting Rows, Columns, and Subsets of Data#

In this lesson you'll be able to:

  • Pick one column or several with square brackets
  • Select rows and columns by position with iloc
  • Select rows and columns by label with loc
  • Read and change single values with at and loc
  • Grab handy subsets: samples, top rows and column types Dataset: the Titanic passenger list (data/pandas/titanic.csv)

Warm-up: load and check#

Lesson 07 read files into pandas. Load today's file the same way.

import pandas as pd
df = pd.read_csv("data/pandas/titanic.csv")
print(df.shape)
print(df.columns.tolist())
(891, 12)
['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked']
df.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S

Pick columns with square brackets#

Problem: work with just the columns a question needs.

ages = df["Age"]
print(type(ages))
ages.head()
<class 'pandas.Series'>
0    22.0
1    38.0
2    26.0
3    35.0
4    35.0
Name: Age, dtype: float64
cols = ["Name", "Age", "Fare"]
people = df[cols]
print(type(people))
people.head()
<class 'pandas.DataFrame'>
Name Age Fare
0 Braund, Mr. Owen Harris 22.0 7.2500
1 Cumings, Mrs. John Bradley (Florence Briggs Th... 38.0 71.2833
2 Heikkinen, Miss. Laina 26.0 7.9250
3 Futrelle, Mrs. Jacques Heath (Lily May Peel) 35.0 53.1000
4 Allen, Mr. William Henry 35.0 8.0500

Quick check: df["Age"] and df[["Age"]] look alike. What's the difference?

print(df["Age"].shape)
print(df[["Age"]].shape)
(891,)
(891, 1)

Common mistake: listing two columns without the inner brackets

df["Name", "Age"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\base.py:3641, in Index.get_loc(self, key)
   3640 try:
-> 3641     return self._engine.get_loc(casted_key)
   3642 except KeyError as err:

File pandas/_libs/index.pyx:168, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/index.pyx:176, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/index.pyx:583, in pandas._libs.index.StringObjectEngine._check_type()

KeyError: ('Name', 'Age')

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[7], line 1
----> 1 df["Name", "Age"]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\frame.py:4378, in DataFrame.__getitem__(self, key)
   4376 if self.columns.nlevels > 1:
   4377     return self._getitem_multilevel(key)
-> 4378 indexer = self.columns.get_loc(key)
   4379 if is_integer(indexer):
   4380     indexer = [indexer]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\base.py:3648, in Index.get_loc(self, key)
   3643     if isinstance(casted_key, slice) or (
   3644         isinstance(casted_key, abc.Iterable)
   3645         and any(isinstance(x, slice) for x in casted_key)
   3646     ):
   3647         raise InvalidIndexError(key) from err
-> 3648     raise KeyError(key) from err
   3649 except TypeError:
   3650     # If we have a listlike key, _check_indexing_error will raise
   3651     #  InvalidIndexError. Otherwise we fall through and re-raise
   3652     #  the TypeError.
   3653     self._check_indexing_error(key)

KeyError: ('Name', 'Age')
df[["Name", "Age"]].head(3)
Name Age
0 Braund, Mr. Owen Harris 22.0
1 Cumings, Mrs. John Bradley (Florence Briggs Th... 38.0
2 Heikkinen, Miss. Laina 26.0
numbers = df.select_dtypes(include="number")
print(numbers.columns.tolist())
['PassengerId', 'Survived', 'Pclass', 'Age', 'SibSp', 'Parch', 'Fare']
print(df.filter(like="Sib").columns.tolist())
print(df.filter(regex="^S").columns.tolist())
['SibSp']
['Survived', 'Sex', 'SibSp']

Select by position with iloc#

Problem: grab rows and columns by where they sit, like row 0.

df.iloc[0]
PassengerId                          1
Survived                             0
Pclass                               3
Name           Braund, Mr. Owen Harris
Sex                               male
Age                               22.0
SibSp                                1
Parch                                0
Ticket                       A/5 21171
Fare                              7.25
Cabin                              NaN
Embarked                             S
Name: 0, dtype: object
df.iloc[-2:]
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.00 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.75 NaN Q
df.iloc[0:3, [3, 4, 5]]
Name Sex Age
0 Braund, Mr. Owen Harris male 22.0
1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0
2 Heikkinen, Miss. Laina female 26.0

Quick check: how many rows does df.iloc[10:20] return?

chunk = df.iloc[10:20]
print(len(chunk))
print(chunk.index[0], chunk.index[-1])
10
10 19

Common mistake: asking for a position past the end

df.iloc[891]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[15], line 1
----> 1 df.iloc[891]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexing.py:1207, in _LocationIndexer.__getitem__(self, key)
   1205 maybe_callable = com.apply_if_callable(key, self.obj)
   1206 maybe_callable = self._raise_callable_usage(key, maybe_callable)
-> 1207 return self._getitem_axis(maybe_callable, axis=axis)

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexing.py:1773, in _iLocIndexer._getitem_axis(self, key, axis)
   1770     raise TypeError("Cannot index by location index with a non-integer key")
   1772 # validate the location
-> 1773 self._validate_integer(key, axis)
   1775 return self.obj._ixs(key, axis=axis)

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexing.py:1706, in _iLocIndexer._validate_integer(self, key, axis)
   1704 len_axis = len(self.obj._get_axis(axis))
   1705 if key >= len_axis or key < -len_axis:
-> 1706     raise IndexError("single positional indexer is out-of-bounds")

IndexError: single positional indexer is out-of-bounds
print(len(df))
df.iloc[len(df) - 1, [0, 3]]
891
PassengerId                    891
Name           Dooley, Mr. Patrick
Name: 890, dtype: object

Your turn#

Use iloc to show rows 100 to 104 and the last three columns.

df.iloc[100:105, -3:]
Fare Cabin Embarked
100 7.8958 NaN S
101 7.8958 NaN S
102 77.2875 D26 S
103 8.6542 NaN S
104 7.9250 NaN S

Select by label with loc#

Problem: ask for rows and columns by their names, not positions.

df.loc[2:5, ["Name", "Pclass"]]
Name Pclass
2 Heikkinen, Miss. Laina 3
3 Futrelle, Mrs. Jacques Heath (Lily May Peel) 1
4 Allen, Mr. William Henry 3
5 Moran, Mr. James 3

Quick check: iloc[2:5] gave 3 rows. Why did loc[2:5] give 4?

print(len(df.iloc[2:5]), len(df.loc[2:5]))
df.loc[0, "Name":"Age"]
3 4
Name    Braund, Mr. Owen Harris
Sex                        male
Age                        22.0
Name: 0, dtype: object
pax = df.set_index("PassengerId")
pax.loc[[1, 2, 3], ["Name", "Age", "Fare"]]
Name Age Fare
PassengerId
1 Braund, Mr. Owen Harris 22.0 7.2500
2 Cumings, Mrs. John Bradley (Florence Briggs Th... 38.0 71.2833
3 Heikkinen, Miss. Laina 26.0 7.9250

Common mistake: using a position where loc expects a label

pax.loc[0, "Name"]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\range.py:531, in RangeIndex.get_loc(self, key)
    530 try:
--> 531     return self._range.index(new_key)
    532 except ValueError as err:

ValueError: 0 is not in range

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[21], line 1
----> 1 pax.loc[0, "Name"]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexing.py:1199, in _LocationIndexer.__getitem__(self, key)
   1197     key = tuple(com.apply_if_callable(x, self.obj) for x in key)
   1198     if self._is_scalar_access(key):
-> 1199         return self.obj._get_value(*key, takeable=self._takeable)
   1200     return self._getitem_tuple(key)
   1201 else:
   1202     # we by definition only have the 0th axis

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\frame.py:4495, in DataFrame._get_value(self, index, col, takeable)
   4489 series = self._get_item(col)
   4491 if not isinstance(self.index, MultiIndex):
   4492     # CategoricalIndex: Trying to use the engine fastpath may give incorrect
   4493     #  results if our categories are integers that dont match our codes
   4494     # IntervalIndex: IntervalTree has no get_loc
-> 4495     row = self.index.get_loc(index)
   4496     return series._values[row]
   4498 # For MultiIndex going through engine effectively restricts us to
   4499 #  same-length tuples; see test_get_set_value_no_partial_indexing

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\range.py:533, in RangeIndex.get_loc(self, key)
    531         return self._range.index(new_key)
    532     except ValueError as err:
--> 533         raise KeyError(key) from err
    534 if isinstance(key, Hashable):
    535     raise KeyError(key)

KeyError: 0
print(pax.iloc[0]["Name"])
print(pax.loc[1, "Name"])
Braund, Mr. Owen Harris
Braund, Mr. Owen Harris
by_name = df.set_index("Name")
by_name.loc["Andersson, Mr. Anders Johan", ["Age", "Pclass"]]
Age       39.0
Pclass     3.0
Name: Andersson, Mr. Anders Johan, dtype: float64

Your turn#

Using pax, show the Name, Sex and Survived of passengers 10 to 14.

pax.loc[10:14, ["Name", "Sex", "Survived"]]
Name Sex Survived
PassengerId
10 Nasser, Mrs. Nicholas (Adele Achem) female 1
11 Sandstrom, Miss. Marguerite Rut female 1
12 Bonnell, Miss. Elizabeth female 1
13 Saundercock, Mr. William Henry male 0
14 Andersson, Mr. Anders Johan male 0

Select inside a single column#

Problem: pick values out of a Series, not a whole table.

fares = pax["Fare"]
print(fares.loc[1])
print(fares.iloc[-1])
fares.loc[[62, 830]]
7.25
7.75
PassengerId
62     80.0
830    80.0
Name: Fare, dtype: float64

Common mistake: plain square brackets with a number on a Series

fares[0]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\range.py:531, in RangeIndex.get_loc(self, key)
    530 try:
--> 531     return self._range.index(new_key)
    532 except ValueError as err:

ValueError: 0 is not in range

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[26], line 1
----> 1 fares[0]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\series.py:959, in Series.__getitem__(self, key)
    954     key = unpack_1tuple(key)
    956 elif key_is_scalar:
    957     # Note: GH#50617 in 3.0 we changed int key to always be treated as
    958     #  a label, matching DataFrame behavior.
--> 959     return self._get_value(key)
    961 # Convert generator to list before going through hashable part
    962 # (We will iterate through the generator there to check for slices)
    963 if is_iterator(key):

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\series.py:1046, in Series._get_value(self, label, takeable)
   1043     return self._values[label]
   1045 # Similar to Index.get_value, but we do not fall back to positional
-> 1046 loc = self.index.get_loc(label)
   1048 if is_integer(loc):
   1049     return self._values[loc]

File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\indexes\range.py:533, in RangeIndex.get_loc(self, key)
    531         return self._range.index(new_key)
    532     except ValueError as err:
--> 533         raise KeyError(key) from err
    534 if isinstance(key, Hashable):
    535     raise KeyError(key)

KeyError: 0
print(fares.iloc[0])
print(fares.loc[1])
7.25
7.25

Read and change single values#

Problem: look up one cell quickly, and correct it safely.

print(pax.at[1, "Fare"])
print(pax.iat[0, 8])
7.25
7.25
missing = [62, 830]
pax.loc[missing, ["Name", "Embarked"]]
Name Embarked
PassengerId
62 Icard, Miss. Amelie NaN
830 Stone, Mrs. George Nelson (Martha Evelyn) NaN
pax.loc[missing, "Embarked"] = "S"
pax.loc[missing, ["Name", "Embarked"]]
Name Embarked
PassengerId
62 Icard, Miss. Amelie S
830 Stone, Mrs. George Nelson (Martha Evelyn) S

Quick check: did that change touch the original df too?

print(df["Embarked"].isna().sum())
print(pax["Embarked"].isna().sum())
2
0

Your turn#

Passenger 259 paid one of the top fares. Use at to print her name, and whether she survived.

print(pax.at[259, "Name"])
print(pax.at[259, "Survived"])
Ward, Miss. Anna
1

Grab handy subsets#

Problem: get a quick, fair look at a big table.

df.sample(n=4, random_state=42)[["Name", "Pclass", "Age"]]
Name Pclass Age
709 Moubarek, Master. Halim Gonios ("William George") 3 NaN
439 Kvillner, Mr. Johan Henrik Johannesson 2 31.0
840 Alhomaki, Mr. Ilmari Rudolf 3 20.0
720 Harper, Miss. Annie Jessie "Nina" 2 6.0
tiny = df.sample(frac=0.01, random_state=42)
print(tiny.shape)
(9, 12)
df.nlargest(3, "Fare")[["Name", "Pclass", "Fare"]]
Name Pclass Fare
258 Ward, Miss. Anna 1 512.3292
679 Cardeza, Mr. Thomas Drake Martinez 1 512.3292
737 Lesurer, Mr. Gustave J 1 512.3292
df.nsmallest(3, "Age")[["Name", "Age", "Survived"]]
Name Age Survived
803 Thomas, Master. Assad Alexander 0.42 1
755 Hamalainen, Master. Viljo 0.67 1
469 Baclini, Miss. Helene Barbara 0.75 1
every_100 = df.iloc[::100, [3, 9]]
every_100
Name Fare
0 Braund, Mr. Owen Harris 7.2500
100 Petranec, Miss. Matilda 7.8958
200 Vande Walle, Mr. Nestor Cyriel 9.5000
300 Kelly, Miss. Anna Katherine "Annie Kate" 7.7500
400 Niskanen, Mr. Juha 7.9250
500 Calic, Mr. Petar 8.6625
600 Jacobsohn, Mrs. Sidney Samuel (Amy Frances Chr... 27.0000
700 Astor, Mrs. John Jacob (Madeleine Talmadge Force) 227.5250
800 Ponesell, Mr. Martin 13.0000

Your turn#

Show the Name, Age and Fare of the 5 oldest passengers.

oldest = df.nlargest(5, "Age")
oldest[["Name", "Age", "Fare"]]
Name Age Fare
630 Barkworth, Mr. Algernon Henry Wilson 80.0 30.0000
851 Svensson, Mr. Johan 74.0 7.7750
96 Goldschmidt, Mr. George B 71.0 34.6542
493 Artagaveytia, Mr. Ramon 71.0 49.5042
116 Connors, Mr. Patrick 70.5 7.7500

Mini-project: richest versus cheapest tickets#

Question: did the 50 priciest tickets survive more than the 50 cheapest?

Plan: select both groups, compare survival and class, then chart.

rich = df.nlargest(50, "Fare")
cheap = df.nsmallest(50, "Fare")
print(rich["Fare"].min(), cheap["Fare"].max())
108.9 7.225
rich_rate = rich["Survived"].mean()
cheap_rate = cheap["Survived"].mean()
print(rich_rate, cheap_rate)
0.76 0.1
print(rich["Pclass"].value_counts().to_dict())
print(cheap["Pclass"].value_counts().to_dict())
{1: 50}
{3: 38, 1: 6, 2: 6}
odd = cheap.loc[cheap["Pclass"] == 1, ["Name", "Fare", "Survived"]]
odd
Name Fare Survived
263 Harrison, Mr. William 0.0 0
633 Parr, Mr. William Henry Marsh 0.0 0
806 Andrews, Mr. Thomas Jr 0.0 0
815 Fry, Mr. Richard 0.0 0
822 Reuchlin, Jonkheer. John George 0.0 0
872 Carlsson, Mr. Frans Olof 5.0 0
print(rich["Age"].mean().round(1), cheap["Age"].mean().round(1))
print(rich["Sex"].value_counts().to_dict())
print(cheap["Sex"].value_counts().to_dict())
31.9 31.4
{'female': 33, 'male': 17}
{'male': 48, 'female': 2}
import matplotlib.pyplot as plt
rates = pd.Series({"Top 50 fares": rich_rate,
                   "Bottom 50 fares": cheap_rate})
ax = rates.plot(kind="bar", rot=0, figsize=(6, 4))
ax.set_title("Titanic survival: priciest vs cheapest tickets")
ax.set_xlabel("Ticket group")
ax.set_ylabel("Share who survived")
plt.show()
No description has been provided for this image
gap = rich_rate / cheap_rate
print(f"Top 50: {rich_rate:.0%}, bottom 50: {cheap_rate:.0%}")
print(f"About {gap:.1f} times the survival rate")
Top 50: 76%, bottom 50: 10%
About 7.6 times the survival rate

Recap#

  • df["col"] gives a Series; df[["a", "b"]] gives a DataFrame
  • iloc uses positions from 0, and its slices exclude the end
  • loc uses labels, and its slices include both ends
  • On a Series, prefer .loc and .iloc to plain [] with numbers
  • at and iat read one value; loc[rows, col] = value changes data
  • sample, nlargest and nsmallest give quick, useful subsets

Challenges#

  1. Use iloc to show the last 5 rows and the first 4 columns.
  2. Index by Name and look up the Fare and Cabin of two passengers.
  3. Compare the mean Age of the 100 priciest and 100 cheapest tickets.

What's next#

Lesson 09: Conditional Selection and Boolean Indexing Keep only the rows that meet a rule, like Age < 18 and Pclass == 1.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.